Download.php 23.2 KB
Newer Older
Carsten  Rose's avatar
Carsten Rose committed
1
2
3
4
5
6
<?php
/**
 * Created by PhpStorm.
 * User: crose
 * Date: 4/17/17
 * Time: 11:32 AM
Carsten  Rose's avatar
Carsten Rose committed
7
8
 *
 * Check: CODING.md > Download
Carsten  Rose's avatar
Carsten Rose committed
9
10
 */

Marc Egger's avatar
Marc Egger committed
11
12
namespace IMATHUZH\Qfq\Core\Report;

13
14
15
use IMATHUZH\Qfq\Core\Database\Database;
use IMATHUZH\Qfq\Core\Helper\DownloadPage;
use IMATHUZH\Qfq\Core\Helper\HelperFile;
Marc Egger's avatar
Marc Egger committed
16
use IMATHUZH\Qfq\Core\Helper\KeyValueStringParser;
17
use IMATHUZH\Qfq\Core\Helper\Logger;
Marc Egger's avatar
Marc Egger committed
18
19
20
21
use IMATHUZH\Qfq\Core\Helper\OnArray;
use IMATHUZH\Qfq\Core\Helper\OnString;
use IMATHUZH\Qfq\Core\Helper\Sanitize;
use IMATHUZH\Qfq\Core\Helper\Support;
22
23
24
25
use IMATHUZH\Qfq\Core\QuickFormQuery;
use IMATHUZH\Qfq\Core\Store\Session;
use IMATHUZH\Qfq\Core\Store\Sip;
use IMATHUZH\Qfq\Core\Store\Store;
Carsten  Rose's avatar
Carsten Rose committed
26
27
28
29

/**
 * Class Download
 *
30
31
 * Documentation: PROTOCOL.md >> Download
 *
Carsten  Rose's avatar
Carsten Rose committed
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
 * Param: i=1..n
 *   <i>_mode=direct | html2pdf
 *   <i>_id=<pageId>
 *   <i>_<key i>=<value i>
 *
 * @package qfq
 */
class Download {

    /**
     * @var Store
     */
    private $store = null;

    /**
     * @var Session
     */
    private $session = null;

    /**
     * @var Database
     */
    private $db = null;

    /**
     * @var Html2Pdf
     */
    private $html2pdf = null;

61
62
63
64
65
    /**
     * @var string Filename where to write download Information
     */
    private $downloadDebugLog = '';

66
67
68
69
70
    /**
     * @var string DOWNLOAD_OUTPUT_FORMAT_RAW | DOWNLOAD_OUTPUT_FORMAT_JSON
     */
    private $outputFormat = DOWNLOAD_OUTPUT_FORMAT_RAW;

Carsten  Rose's avatar
Carsten Rose committed
71
72
    /**
     * @param bool|false $phpUnit
Marc Egger's avatar
Marc Egger committed
73
74
75
76
     * @throws \CodeException
     * @throws \DbException
     * @throws \UserFormException
     * @throws \UserReportException
Carsten  Rose's avatar
Carsten Rose committed
77
78
79
     */
    public function __construct($phpUnit = false) {

80
        #TODO: rewrite $phpUnit to: "if (!defined('PHPUNIT_QFQ')) {...}"
Carsten  Rose's avatar
Carsten Rose committed
81
82
83
        $this->session = Session::getInstance($phpUnit);
        $this->store = Store::getInstance('', $phpUnit);
        $this->db = new Database();
84
        $this->html2pdf = new Html2Pdf($this->store->getStore(STORE_SYSTEM), $phpUnit);
85

86
        if (Support::findInSet(SYSTEM_SHOW_DEBUG_INFO_DOWNLOAD, $this->store->getVar(SYSTEM_SHOW_DEBUG_INFO, STORE_SYSTEM))) {
87
88
            $this->downloadDebugLog = $this->store->getVar(SYSTEM_SQL_LOG, STORE_SYSTEM);
        }
Carsten  Rose's avatar
Carsten Rose committed
89
90
91
92
93
94
    }

    /**
     * Concatenate all named files to one PDF file. Return name of new full PDF.
     *
     * @param array $files
Carsten  Rose's avatar
Carsten Rose committed
95
     *
Carsten  Rose's avatar
Carsten Rose committed
96
     * @return string  - fileName of concatenated file
Marc Egger's avatar
Marc Egger committed
97
98
99
     * @throws \CodeException
     * @throws \DownloadException
     * @throws \UserFormException
Carsten  Rose's avatar
Carsten Rose committed
100
101
102
     */
    private function concatPdfFiles(array $files) {

103
104
105
        // Remove empty entries. Might happen if there was no upload
        $files = OnArray::removeEmptyElementsFromArray($files);

Carsten  Rose's avatar
Carsten Rose committed
106
        // Check that all files exist and are readable
107
        foreach ($files AS $filename) {
108
            if (!is_readable($filename)) {
Marc Egger's avatar
Marc Egger committed
109
                throw new \DownloadException("Error reading file $filename. Not found or no permission", ERROR_DOWNLOAD_FILE_NOT_READABLE);
110
111
112
            }
        }

Carsten  Rose's avatar
Carsten Rose committed
113
114
115
        if (count($files) === 0) {
            return '';
        }
116

Carsten  Rose's avatar
Carsten Rose committed
117
118
119
120
        // Check that all files are of type 'application/pdf'
        foreach ($files AS $filename) {
            $mimetype = mime_content_type($filename);
            if ($mimetype != 'application/pdf') {
Marc Egger's avatar
Marc Egger committed
121
                throw new \DownloadException("Error concat file $filename. Mimetype 'application/pdf' expected, got: $mimetype", ERROR_DOWNLOAD_UNEXPECTED_MIME_TYPE);
Carsten  Rose's avatar
Carsten Rose committed
122
123
124
            }
        }

Carsten  Rose's avatar
Carsten Rose committed
125
126
127
        if (count($files) == 1) {
            return $files[0];
        }
Carsten  Rose's avatar
Carsten Rose committed
128

Carsten  Rose's avatar
Carsten Rose committed
129
        $files = OnArray::arrayEscapeshellarg($files);
Carsten  Rose's avatar
Carsten Rose committed
130
131
        $inputFiles = implode(' ', $files);
        if (trim($inputFiles) == '') {
Marc Egger's avatar
Marc Egger committed
132
            throw new \DownloadException('No files to concatenate.', ERROR_DOWNLOAD_NO_FILES);
Carsten  Rose's avatar
Carsten Rose committed
133
134
        }

Carsten  Rose's avatar
Carsten Rose committed
135
136
137
        // Need to create a separate result file, even if it is just a single file (#6929)
        $concatFile = HelperFile::tempnam();
        if (false === $concatFile) {
Marc Egger's avatar
Marc Egger committed
138
            throw new \DownloadException('Error creating output file.', ERROR_DOWNLOAD_CREATE_NEW_FILE);
Carsten  Rose's avatar
Carsten Rose committed
139
140
        }

141
142
143
//        $cmd = "pdftk $inputFiles cat output $concatFile 2>&1";  # Outdated. Hard to install on Ubuntu 18. Fails for recent PDFs.
//        $cmd = "qpdf --empty --pages $inputFiles -- $concatFile 2>&1"; # Fails to merge identical files, if they contain references.
        $cmd = "pdfunite $inputFiles $concatFile 2>&1"; // Based on poppler. URLs are preserved. Orientation and size are preserved.
Carsten  Rose's avatar
Carsten Rose committed
144

145
146
147
148
        if ($this->downloadDebugLog != '') {
            Logger::logMessage("Download: $cmd", $this->downloadDebugLog);
        }

149
        $rc = $this->concatPdfFilesPdfUnite($cmd, $output);
Carsten  Rose's avatar
Carsten Rose committed
150
151

        if ($rc != 0) {
Marc Egger's avatar
Marc Egger committed
152
            throw new \DownloadException (json_encode([ERROR_MESSAGE_TO_USER => "Failed to merge PDF file",
153
154
                    ERROR_MESSAGE_TO_DEVELOPER => "CMD: " . $cmd . "<br>RC: $rc<br>Output: " . implode("<br>", $output)])
                , ERROR_DOWNLOAD_MERGE_FAILED);
Carsten  Rose's avatar
Carsten Rose committed
155
156
157
158
159
        }

        return $concatFile;
    }

160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
    /**
     * Fires the merge command.
     * If for any reason the command fails: check if the reason is 'unencrypted files'.
     * If 'yes': try to decrypt them with qpdf.
     * After one decrypt, try merge again.
     * Try to merge and decrypt as long as there are encrypted files.
     *
     * @param $cmd
     * @param $rcOutput
     * @return mixed
     * @throws \DownloadException
     * @throws \UserFormException
     */
    private function concatPdfFilesPdfUnite($cmd, &$rcOutput) {
        $last = '';
        $rcOutput = '-';

        // Try to merge the PDFs as long as a problematic PDF has been repaired.
        while ($last != $rcOutput) {

            $last = $rcOutput; // Remember last

            // Merge
            exec($cmd, $rcOutput, $rc);

            if ($rc == 0) {
                break; // skip rest if everything is fine
            }

            // Possible output: "Unimplemented Feature: Could not merge encrypted files ('ct.18.06.092-097.pdf')"
            $line = implode(',', $rcOutput);
            if (false !== strstr($line, "Unimplemented Feature: Could not merge encrypted files (")) {

                $arr = explode("'", $line, 3);
                if (!empty($arr[1]) && file_exists($arr[1])) {
                    $file = $arr[1]; // problematic file

                    // Create a backup file: only one per day!
                    $backup = $file . date('.Y-m-d');
                    if (!file_exists($backup)) {
                        HelperFile::copy($file, $backup);
                    }

                    $cmdQpdf = "qpdf --decrypt '$backup' '$file' 2>&1"; // Try to decrypt file
                    exec($cmdQpdf, $outputQpdf, $rcQpdf);

                    if ($rcQpdf != 0) {
                        // qpdf failed: restore origfile in case the $file has been destroyed.
                        HelperFile::copy($backup, $file);
                        throw new \DownloadException (json_encode([ERROR_MESSAGE_TO_USER => "Failed to decrypt PDF",
                                ERROR_MESSAGE_TO_DEVELOPER => "CMD: " . $cmdQpdf . "<br>RC: $rc<br>Output: " . implode("<br>", $outputQpdf)])
                            , ERROR_DOWNLOAD_MERGE_FAILED);
                    }
                }
            } else {
                throw new \DownloadException (json_encode([ERROR_MESSAGE_TO_USER => "Merge PDF file failed.",
                        ERROR_MESSAGE_TO_DEVELOPER => "CMD: " . $cmd . "<br>RC: $rc<br>Output: " . implode("<br>", $rcOutput)])
                    , ERROR_DOWNLOAD_MERGE_FAILED);
            }
        }
        return $rc;
    }

Carsten  Rose's avatar
Carsten Rose committed
223
    /**
224
     * Get the mimetype of $filename and store them in $rcMimetype.
Carsten  Rose's avatar
Carsten Rose committed
225
     *
226
     * @param string $pathFileName
227
228
     * @param string $outputFilename
     * @param string $rcMimetype
Carsten  Rose's avatar
Carsten Rose committed
229
     *
230
     * @return string possible updated $outputFilename, according the mimetype.
Carsten  Rose's avatar
Carsten Rose committed
231
     */
232
    private function targetFilenameExtension($pathFileName, $outputFilename, &$rcMimetype) {
Carsten  Rose's avatar
Carsten Rose committed
233

234
        if ($pathFileName != '' && file_exists($pathFileName)) {
235

236
237
            $rcMimetype = mime_content_type($pathFileName);
        }
238
239
240
241
242
243
        return $outputFilename;
    }

    /**
     * Set header type and output $filename. Be careful not to send any additional characters.
     *
244
     * @param $file
245
     * @param $outputFilename
Marc Egger's avatar
Marc Egger committed
246
     * @throws \DownloadException
247
     */
248
    private function outputFile($file, $outputFilename) {
249

250
251
        $json = '';
        $flagJson = ($this->getOutputFormat() === DOWNLOAD_OUTPUT_FORMAT_JSON);
252

253
        $outputFilename = $this->targetFilenameExtension($file, $outputFilename, $mimeType);
254
        $outputFilename = Sanitize::safeFilename($outputFilename); // be sure that there are no problematic chars in the filename. E.g. MacOS X don't like spaces for downloads.
255

256
257
        if ($flagJson) {
            if (false === ($json = json_encode([JSON_TEXT => file_get_contents($file)]))) {
Marc Egger's avatar
Marc Egger committed
258
                throw new \DownloadException(json_encode(
259
                    [ERROR_MESSAGE_TO_USER => 'Error converting to JSON',
Marc Egger's avatar
Marc Egger committed
260
                        ERROR_MESSAGE_TO_DEVELOPER => "json_last_error()=" . json_last_error() . ", File=" . $file]), ERROR_DOWNLOAD_JSON_CONVERT);
261
262
263
264
265
266
267
268
269
            }
            $length = strlen($json);

            $mimeType = 'application/json';
        } else {
            $length = filesize($file);
        }

        header("Content-type: $mimeType");
Carsten  Rose's avatar
Carsten Rose committed
270
        header("Content-Length: $length");
271
        if (!$flagJson) {
272
273
274
275
            // If defined as 'attachment': PDFs are not shown inside the browser (if user configured that). Instead, always a 'save as'-dialog appears (Chrome, FF)
            // header("Content-Disposition: attachment; filename=$outputFilename");
            header("Content-Disposition: inline; filename=\"$outputFilename\"; name=\"$outputFilename\"");
        }
Carsten  Rose's avatar
Carsten Rose committed
276
277
        header("Pragma: no-cache");
        header("Expires: 0");
Carsten  Rose's avatar
Carsten Rose committed
278

279
280
        if ($flagJson) {
            print $json;
281
        } else {
282
            readfile($file);
283
        }
Carsten  Rose's avatar
Carsten Rose committed
284
285
286
    }

    /**
287
     * Interprets $element and fetches corresponding content, either as a file or the content in a variable.
Carsten  Rose's avatar
Carsten Rose committed
288
289
     *
     * @param string $element - U:id=myExport&r=12, u:http://www.nzz.ch/issue?nr=21, f:fileadmin/sample.pdf
Carsten  Rose's avatar
Carsten Rose committed
290
     *
291
     * @param string $downloadMode - DOWNLOAD_MODE_EXCEL | ....
292
     * @param string $rcData - With $downloadMode=DOWNLOAD_MODE_EXCEL, this contains the rendered code from the given T3 page.
Carsten  Rose's avatar
Carsten Rose committed
293
     * @return string filename - already ready or fresh exported. Fresh exported needs to be deleted later.
Marc Egger's avatar
Marc Egger committed
294
295
296
     * @throws \CodeException
     * @throws \DbException
     * @throws \DownloadException
297
298
299
     * @throws \PhpOffice\PhpSpreadsheet\Exception
     * @throws \PhpOffice\PhpSpreadsheet\Reader\Exception
     * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception
300
301
302
303
304
     * @throws \Twig\Error\LoaderError
     * @throws \Twig\Error\RuntimeError
     * @throws \Twig\Error\SyntaxError
     * @throws \UserFormException
     * @throws \UserReportException
Carsten  Rose's avatar
Carsten Rose committed
305
     */
306
    private function getElement($element, $downloadMode, &$rcData) {
Carsten  Rose's avatar
Carsten Rose committed
307

308
309
310
311
        $filename = '';
        $rcArgs = array();
        $rcSipEncode = false;

Carsten  Rose's avatar
Carsten Rose committed
312
313
        $arr = explode(':', $element, 2);
        if (count($arr) != 2) {
314
            $possibleReason = ($element === '') ? 'If this is a download link, did you forget to include s:1?' : '';
Marc Egger's avatar
Marc Egger committed
315
            throw new \DownloadException("Missing parameter for '$element'. $possibleReason", ERROR_MISSING_REQUIRED_PARAMETER);
Carsten  Rose's avatar
Carsten Rose committed
316
        }
Carsten  Rose's avatar
Carsten Rose committed
317

Carsten  Rose's avatar
Carsten Rose committed
318
319
        $token = $arr[0];
        $value = $arr[1];
320
321
322
323
324
        if ($token === TOKEN_UID) { // extract uid
            $uidParamsArr = explode('&', $value, 2);
            $uid = $uidParamsArr[0];
            $value = $uidParamsArr[1] ?? ''; // additional params
        }
Carsten  Rose's avatar
Carsten Rose committed
325

Carsten  Rose's avatar
Carsten Rose committed
326
327
328
        switch ($token) {
            case TOKEN_URL:
            case TOKEN_URL_PARAM:
329
            case TOKEN_PAGE:
330
            case TOKEN_UID:
331
332
333
334
335
336
                $urlParam = OnString::splitParam($value, $rcArgs, $rcSipEncode);
                $urlParamString = KeyValueStringParser::unparse($urlParam, '=', '&');
                if ($rcSipEncode) {
                    $sip = new Sip();
                    $urlParamString = $sip->queryStringToSip($urlParamString, RETURN_URL);
                }
337

338
                if ($downloadMode == DOWNLOAD_MODE_EXCEL) {
339
                    if ($token === TOKEN_UID) {
340
                        $rcData = $this->getEvaluatedBodytext($uid, $urlParam);
341
342
343
344
                    } else {
                        $baseUrl = $this->store->getVar(SYSTEM_BASE_URL, STORE_SYSTEM);
                        $rcData = DownloadPage::getContent($urlParamString, $baseUrl);
                    }
345
                } else {
346
347
348
349
350
                    if ($token === TOKEN_UID) {
                        // create tmp html document with bodytext
                        $htmlText = $this->getEvaluatedBodytext($uid, $urlParam);
                        $tmpFilename = HelperFile::tempnam() . '.html';

351
                        $tmpFile = fopen($tmpFilename, "w") or die('Cannot create file:  ' . $tmpFilename);
352
353
354
355
356
                        fwrite($tmpFile, $htmlText);
                        fclose($tmpFile);

                        $rcArgsString = KeyValueStringParser::unparse($rcArgs, '=', '&');
                        $url = Support::mergeUrlComponents('', $tmpFilename, $rcArgsString);
357
358
359
                        $filename = $this->html2pdf->page2pdf($token, $url);
                        HelperFile::cleanTempFiles([$tmpFilename]);

360
                    } else {
361
                        $filename = $this->html2pdf->page2pdf($token, $value);
362
                    }
363
                }
Carsten  Rose's avatar
Carsten Rose committed
364
                break;
Carsten  Rose's avatar
Carsten Rose committed
365

Carsten  Rose's avatar
Carsten Rose committed
366
            case TOKEN_FILE:
367
            case TOKEN_FILE_DEPRECATED:
Carsten  Rose's avatar
Carsten Rose committed
368
369
370
                $filename = $value;
                break;
            default:
Marc Egger's avatar
Marc Egger committed
371
                throw new \DownloadException('Unknown token: "' . $token . '"', ERROR_UNKNOWN_TOKEN);
Carsten  Rose's avatar
Carsten Rose committed
372
                break;
Carsten  Rose's avatar
Carsten Rose committed
373
374
375
376
377
        }

        return $filename;
    }

378
379
380
381
382
    /**
     * @param $uid
     * @param array $urlParam
     *
     * @return string
Marc Egger's avatar
Marc Egger committed
383
384
385
     * @throws \CodeException
     * @throws \DbException
     * @throws \DownloadException
386
387
388
     * @throws \PhpOffice\PhpSpreadsheet\Exception
     * @throws \PhpOffice\PhpSpreadsheet\Reader\Exception
     * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception
Carsten  Rose's avatar
Carsten Rose committed
389
390
391
392
393
     * @throws \Twig\Error\LoaderError
     * @throws \Twig\Error\RuntimeError
     * @throws \Twig\Error\SyntaxError
     * @throws \UserFormException
     * @throws \UserReportException
394
     */
395
    private function getEvaluatedBodyText($uid, $urlParam) {
396
        foreach ($urlParam as $key => $paramValue) {
397
398
399
            $this->store->setVar($key, $paramValue, STORE_SIP);
        }

400
        $dbT3 = $this->store->getVar(SYSTEM_DB_NAME_T3, STORE_SYSTEM);
401
402
403
        $sql = "SELECT bodytext FROM $dbT3.tt_content WHERE uid = ?";
        $tt_content = $this->db->sql($sql, ROW_EXPECT_1, [$uid]);

404
        $qfq = new QuickFormQuery([T3DATA_BODYTEXT => $tt_content[T3DATA_BODYTEXT]], false, false);
405
406
407
408
        return $qfq->process();

    }

Carsten  Rose's avatar
Carsten Rose committed
409
410
411
412
413

    /**
     * Creates a ZIP Files of all given $files
     *
     * @param array $files
Carsten  Rose's avatar
Carsten Rose committed
414
     *
Carsten  Rose's avatar
Carsten Rose committed
415
     * @return string ZIP filename - has to be deleted later.
Marc Egger's avatar
Marc Egger committed
416
     * @throws \DownloadException
Carsten  Rose's avatar
Carsten Rose committed
417
     */
418
    private function zipFiles(array $files) {
Carsten  Rose's avatar
Carsten Rose committed
419

420
        $zipFile = HelperFile::tempnam();
Carsten  Rose's avatar
Carsten Rose committed
421
        if (false === $zipFile) {
Marc Egger's avatar
Marc Egger committed
422
            throw new \DownloadException("Error creating output file.", ERROR_DOWNLOAD_CREATE_NEW_FILE);
Carsten  Rose's avatar
Carsten Rose committed
423
424
425
426
        }

        $zip = new \ZipArchive();

Carsten  Rose's avatar
Carsten Rose committed
427
        if ($zip->open($zipFile, \ZipArchive::CREATE) !== true) {
Marc Egger's avatar
Marc Egger committed
428
            throw new \DownloadException("Error creating/opening new empty zip file: $zipFile", ERROR_IO_OPEN);
Carsten  Rose's avatar
Carsten Rose committed
429
430
        }

431
        $len = strlen(TMP_FILE_PREFIX);
432
        $ii = 1;
Carsten  Rose's avatar
Carsten Rose committed
433
        foreach ($files AS $filename) {
434
            $localName = substr($filename, strrpos($filename, '/') + 1);
435

436
437
            if (substr($localName, 0, $len) == TMP_FILE_PREFIX) {
                $localName = 'file-' . $ii;
438
439
440
                $ii++;
            }

441
            $zip->addFile($filename, $localName);
Carsten  Rose's avatar
Carsten Rose committed
442
443
444
445
446
447
        }
        $zip->close();

        return $zipFile;
    }

Carsten  Rose's avatar
Carsten Rose committed
448
    /**
449
450
     * $vars[DOWNLOAD_EXPORT_FILENAME] - Optional. '<new filename>'
     * $vars[DOWNLOAD_MODE] - Optional.  file | pdf | excel | thumbnail | monitor - default is a) 'file' in case of only one or b) 'pdf' in case of multiple sources.
Carsten  Rose's avatar
Carsten Rose committed
451
452
453
454
     * HTML to PDF | Excel
     *   <i>_id=<Typo3 pageId>
     *   <i>_<key>=<value i>
     * Direct
455
     *   <i>_file=<filename>
Carsten  Rose's avatar
Carsten Rose committed
456
     *
Carsten  Rose's avatar
Carsten Rose committed
457
     * @param array $vars [ DOWNLOAD_EXPORT_FILENAME, DOWNLOAD_MODE, SIP_DOWNLOAD_PARAMETER ]
Carsten  Rose's avatar
Carsten Rose committed
458
     *
Carsten  Rose's avatar
Carsten Rose committed
459
460
     * @param string $outputMode OUTPUT_MODE_DIRECT | OUTPUT_MODE_FILE | OUTPUT_MODE_COPY_TO_FILE
     * @return string            Filename of the generated file. The filename only points to a real existing filename with  $outputMode=OUTPUT_MODE_FILE | OUTPUT_MODE_COPY_TO_FILE
Marc Egger's avatar
Marc Egger committed
461
462
463
     * @throws \CodeException
     * @throws \DbException
     * @throws \DownloadException
464
465
466
     * @throws \PhpOffice\PhpSpreadsheet\Exception
     * @throws \PhpOffice\PhpSpreadsheet\Reader\Exception
     * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception
467
468
469
470
471
     * @throws \Twig\Error\LoaderError
     * @throws \Twig\Error\RuntimeError
     * @throws \Twig\Error\SyntaxError
     * @throws \UserFormException
     * @throws \UserReportException
Carsten  Rose's avatar
Carsten Rose committed
472
     */
473
    private function doElements(array $vars, $outputMode) {
Carsten  Rose's avatar
Carsten Rose committed
474
475
476
477

        $tmpFiles = array();

        $workDir = $this->store->getVar(SYSTEM_SITE_PATH, STORE_SYSTEM);
478
        HelperFile::chdir($workDir);
Carsten  Rose's avatar
Carsten Rose committed
479

Carsten  Rose's avatar
Carsten Rose committed
480
        $downloadMode = $vars[DOWNLOAD_MODE];
481

482
        if ($downloadMode == DOWNLOAD_MODE_MONITOR) {
483
            $monitor = new Monitor();
Carsten  Rose's avatar
Carsten Rose committed
484
485

            return $monitor->dump($vars[TOKEN_L_FILE], $vars[TOKEN_L_TAIL], $vars[TOKEN_L_APPEND]);
486
487
        }

488
489
490
491
492
493
494
        if ($downloadMode == DOWNLOAD_MODE_THUMBNAIL) {
            // Fake $vars control array.
            $pathFilenameThumbnail = $this->doThumbnail($vars[SIP_DOWNLOAD_PARAMETER]);
            $downloadMode = DOWNLOAD_MODE_FILE;
            $vars[SIP_DOWNLOAD_PARAMETER] = TOKEN_FILE . ':' . $pathFilenameThumbnail;
        }

Carsten  Rose's avatar
Carsten Rose committed
495
        $elements = explode(PARAM_DELIMITER, $vars[SIP_DOWNLOAD_PARAMETER]);
Carsten  Rose's avatar
Carsten Rose committed
496

497
        // Get all files / content
498
        $tmpData = array();
Carsten  Rose's avatar
Carsten Rose committed
499
        foreach ($elements as $element) {
500
            $data = '';
501
            $tmpFiles[] = $this->getElement($element, $downloadMode, $data);
502
503
            if (!empty($data)) {
                $tmpData[] = $data;
504
            }
Carsten  Rose's avatar
Carsten Rose committed
505
506
        }

Carsten  Rose's avatar
Carsten Rose committed
507
508
509
510
        // Export, Concat File(s)
        switch ($downloadMode) {
            case DOWNLOAD_MODE_ZIP:
                $filename = $this->zipFiles($tmpFiles);
511
512
513
                if (empty($vars[DOWNLOAD_EXPORT_FILENAME])) {
                    $vars[DOWNLOAD_EXPORT_FILENAME] = basename($filename);
                }
Carsten  Rose's avatar
Carsten Rose committed
514
515
516
                break;

            case DOWNLOAD_MODE_EXCEL:
517
518
                $excel = new Excel();
                $filename = $excel->process($tmpFiles, $tmpData);
519
520
521
522
523
524
525

                if (empty($filename) || !file_exists($filename)) {
                    throw new \DownloadException(json_encode(
                        [ERROR_MESSAGE_TO_USER => 'Newly created Excel file is broken.',
                            ERROR_MESSAGE_TO_DEVELOPER => "File: '$filename''"]), ERROR_IO_READ_FILE);
                }

526
527
528
529
530
531
532
                if (empty($vars[DOWNLOAD_EXPORT_FILENAME])) {
                    if (HelperFile::isQfqTemp($filename)) {
                        $vars[DOWNLOAD_EXPORT_FILENAME] = DOWNLOAD_OUTPUT_FILENAME . ".xlsx";
                    } else {
                        $vars[DOWNLOAD_EXPORT_FILENAME] = basename($filename);
                    }
                }
Carsten  Rose's avatar
Carsten Rose committed
533
534
                break;

Carsten  Rose's avatar
Carsten Rose committed
535
            case DOWNLOAD_MODE_FILE:
Carsten  Rose's avatar
Carsten Rose committed
536
                $filename = $tmpFiles[0];
537
                if (empty($vars[DOWNLOAD_EXPORT_FILENAME])) {
538
                    $vars[DOWNLOAD_EXPORT_FILENAME] = basename($filename);
539
                }
Carsten  Rose's avatar
Carsten Rose committed
540
                break;
Carsten  Rose's avatar
Carsten Rose committed
541
542

            case DOWNLOAD_MODE_PDF:
543

Carsten  Rose's avatar
Carsten Rose committed
544
                $filename = $this->concatPdfFiles($tmpFiles);
545

546
547
548
549
550
551
552
553
554
555
556
557
                // try to find a meaningful filename
                if (empty($vars[DOWNLOAD_EXPORT_FILENAME])) {
                    if (count($tmpFiles) > 1) {
                        $vars[DOWNLOAD_EXPORT_FILENAME] = DOWNLOAD_OUTPUT_FILENAME . ".pdf";
                    } else {
                        if (HelperFile::isQfqTemp($filename)) {
                            $vars[DOWNLOAD_EXPORT_FILENAME] = DOWNLOAD_OUTPUT_FILENAME . ".pdf";
                        } else {
                            $vars[DOWNLOAD_EXPORT_FILENAME] = basename($filename);
                        }
                    }
                }
Carsten  Rose's avatar
Carsten Rose committed
558
559
560
                break;

            default:
Marc Egger's avatar
Marc Egger committed
561
                throw new \DownloadException("Unknown downloadMode: $downloadMode", ERROR_UNKNOWN_MODE);
Carsten  Rose's avatar
Carsten Rose committed
562
563
564
                break;
        }

565
        if ($filename != '' && !file_exists($filename)) {
Marc Egger's avatar
Marc Egger committed
566
            throw new \DownloadException(json_encode(
567
                [ERROR_MESSAGE_TO_USER => 'Can\'t read file',
Marc Egger's avatar
Marc Egger committed
568
                    ERROR_MESSAGE_TO_DEVELOPER => "File: $filename"]), ERROR_IO_FILE_EXIST);
569
570
        }

571
572
573
574
575
        switch ($outputMode) {

            case OUTPUT_MODE_FILE:
                break;

Carsten  Rose's avatar
Carsten Rose committed
576
577
578
579
580
            case OUTPUT_MODE_COPY_TO_FILE:
                HelperFile::copy($filename, $vars[DOWNLOAD_EXPORT_FILENAME]);
                HelperFile::cleanTempFiles([$filename]);
                break;

581
            case OUTPUT_MODE_DIRECT:
582
                $this->outputFile($filename, $vars[DOWNLOAD_EXPORT_FILENAME]);
583
                HelperFile::cleanTempFiles([$filename]);
584
                $filename = '';
585
                break;
Carsten  Rose's avatar
Carsten Rose committed
586

587
            default:
Marc Egger's avatar
Marc Egger committed
588
                throw new \CodeException('Unkown mode: ' . $outputMode, ERROR_UNKNOWN_MODE);
589
590
591
        }

        return $filename;
Carsten  Rose's avatar
Carsten Rose committed
592
593
    }

594
595
596
    /**
     * @param string $urlParam
     * @return string
Marc Egger's avatar
Marc Egger committed
597
598
599
     * @throws \CodeException
     * @throws \UserFormException
     * @throws \UserReportException
600
     */
601
    private function doThumbnail($urlParam) {
602
603
604
605
606
607
608

        $thumbnail = new Thumbnail();
        $pathFilenameThumbnail = $thumbnail->process($urlParam, THUMBNAIL_VIA_DOWNLOAD);

        return $pathFilenameThumbnail;
    }

Carsten  Rose's avatar
Carsten Rose committed
609
    /**
610
611
     * Process download as requested in $vars. Output is either directly send to the browser, or a file which has to be deleted later.
     *
612
     * @param string|array $vars - If $config is an array, take it, else get values from STORE_SIP
613
614
     * @param string $outputMode OUTPUT_MODE_DIRECT | OUTPUT_MODE_FILE
     *
Carsten  Rose's avatar
Carsten Rose committed
615
     * @return string
Marc Egger's avatar
Marc Egger committed
616
617
618
     * @throws \CodeException
     * @throws \DbException
     * @throws \DownloadException
619
620
621
     * @throws \PhpOffice\PhpSpreadsheet\Exception
     * @throws \PhpOffice\PhpSpreadsheet\Reader\Exception
     * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception
622
623
624
625
626
     * @throws \Twig\Error\LoaderError
     * @throws \Twig\Error\RuntimeError
     * @throws \Twig\Error\SyntaxError
     * @throws \UserFormException
     * @throws \UserReportException
Carsten  Rose's avatar
Carsten Rose committed
627
     */
628
    public function process($vars, $outputMode = OUTPUT_MODE_DIRECT) {
Carsten  Rose's avatar
Carsten Rose committed
629

630
631
632
        if (!is_array($vars)) {
            $vars = $this->store->getStore(STORE_SIP);
        }
633

634
635
        $this->setOutputFormat(empty($vars[DOWNLOAD_OUTPUT_FORMAT]) ? DOWNLOAD_OUTPUT_FORMAT_RAW : $vars[DOWNLOAD_OUTPUT_FORMAT]);

636
        return $this->doElements($vars, $outputMode);
Carsten  Rose's avatar
Carsten Rose committed
637
638
    }

639
640
641
    /**
     * @param $outputFormat
     */
642
    private function setOutputFormat($outputFormat) {
643
644
645
646
647
648
        $this->outputFormat = $outputFormat;
    }

    /**
     * @return string - DOWNLOAD_OUTPUT_FORMAT_RAW | DOWNLOAD_OUTPUT_FORMAT_JSON
     */
649
    public function getOutputFormat() {
650
651
        return $this->outputFormat;
    }
652
}
Carsten  Rose's avatar
Carsten Rose committed
653