Download.php 9.67 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
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
 */

namespace qfq;

use TYPO3\CMS\Core\Resource\Exception\FileDoesNotExistException;

require_once(__DIR__ . '/../Constants.php');
require_once(__DIR__ . '/../store/Session.php');
require_once(__DIR__ . '/../store/Store.php');
require_once(__DIR__ . '/../helper/OnArray.php');
require_once(__DIR__ . '/../report/Html2Pdf.php');
//require_once(__DIR__ . '/Link.php');
//require_once(__DIR__ . '/Sendmail.php');
require_once(__DIR__ . '/../exceptions/DownloadException.php');
//require_once(__DIR__ . '/../Evaluate.php');
//require_once(__DIR__ . '/../helper/KeyValueStringParser.php');
//

/**
 * Class Download
 *
 * 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;

    /**
     * @param bool|false $phpUnit
     */
    public function __construct($phpUnit = false) {

        $this->session = Session::getInstance($phpUnit);
        $this->store = Store::getInstance('', $phpUnit);
        $this->db = new Database();
        $this->html2pdf = new Html2Pdf($this->store->getStore(STORE_SYSTEM));
    }

    /**
     * Concatenate all named files to one PDF file. Return name of new full PDF.
     *
     * @param array $files
     * @return string  - fileName of concatenated file
     * @throws DownloadException
     */
    private function concatPdfFiles(array $files) {

        $concatFile = tempnam(sys_get_temp_dir(), DOWNLOAD_FILE_PREFIX);
        if (false === $concatFile) {
            throw new DownloadException('Error creating output file.', ERROR_DOWNLOAD_CREATE_NEW_FILE);
        }

        // Check that all files are of type 'application/pdf'
        foreach ($files AS $filename) {
            $mimetype = mime_content_type($filename);
            if ($mimetype != 'application/pdf') {
                throw new downloadException("Error concat file $filename. Mimetype 'application/pdf' expected, got: $mimetype", ERROR_DOWNLOAD_UNEXPECTED_MIMETYPE);
            }
        }

        $files = OnArray::arrayEscapeshellarg($files);

        $inputFiles = implode(' ', $files);
        if (trim($inputFiles) == '') {
            throw new DownloadException('No files to concatenate.', ERROR_DOWNLOAD_NO_FILES);
        }

        $cmd = "pdftk $inputFiles cat output $concatFile";

        exec($cmd, $output, $rc);

        if ($rc != 0) {
            throw new DownloadException ("<p>Failed: RC=$rc   $cmd</p>" . implode("<br>", $output));
        }

        return $concatFile;
    }

    /**
111
112
     * Get the mimetype of $filename and store them in $rcMimetype.
     * Checks if the extension of $outputFilename fit's to the mimetype. If not, append the mimetype extension.
Carsten  Rose's avatar
Carsten Rose committed
113
     *
114
115
116
117
     * @param string $filename
     * @param string $outputFilename
     * @param string $rcMimetype
     * @return string possible updated $outputFilename, according the mimetype.
Carsten  Rose's avatar
Carsten Rose committed
118
     */
119
    private function targetFilenameExtension($filename, $outputFilename, &$rcMimetype) {
Carsten  Rose's avatar
Carsten Rose committed
120

121
        $rcMimetype = mime_content_type($filename);
122
123

        // In case there is a wrong filenameextension on the outputFilename: extend it.
124
        $ext = '.' . substr($rcMimetype, strrpos($rcMimetype, '/') + 1); // very very dirty way of getting an extension - only valid for a limited set of mimetypes
125
126
127
128
        $len = strlen($ext);
        if (substr($outputFilename, 0 - $len) != $ext) {
            $outputFilename .= $ext;
        }
Carsten  Rose's avatar
Carsten Rose committed
129

130
131
132
133
134
135
136
137
138
139
140
141
142
143
        return $outputFilename;
    }

    /**
     * Set header type and output $filename. Be careful not to send any additional characters.
     *
     * @param $filename
     * @param $outputFilename
     */
    private function outputFile($filename, $outputFilename) {

        $length = filesize($filename);
        $outputFilename = $this->targetFilenameExtension($filename, $outputFilename, $mimetype);

Carsten  Rose's avatar
Carsten Rose committed
144
145
        header("Content-type: $mimetype");
        header("Content-Length: $length");
146
147
        // 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");
Carsten  Rose's avatar
Carsten Rose committed
148
        header("Content-Disposition: inline; filename=$outputFilename");
Carsten  Rose's avatar
Carsten Rose committed
149
150
        header("Pragma: no-cache");
        header("Expires: 0");
Carsten  Rose's avatar
Carsten Rose committed
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171

        print file_get_contents($filename);
    }

    /**
     * Iterate over array $files. Delete all named files which exist in DOWNLOAD_TMP_DIR.
     *
     * @param array $files
     */
    private function cleanTempFiles(array $files) {
        $prefix = sys_get_temp_dir() . '/' . DOWNLOAD_FILE_PREFIX;
        $len = strlen($prefix);

        foreach ($files as $file) {
            if (substr($file, 0, $len) == $prefix) {
                unlink($file);
            }
        }
    }

    /**
Carsten  Rose's avatar
Carsten Rose committed
172
173
174
175
     * 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.
Carsten  Rose's avatar
Carsten Rose committed
176
177
178
     * @throws DownloadException
     * @throws \exception
     */
Carsten  Rose's avatar
Carsten Rose committed
179
    private function getElement($element) {
Carsten  Rose's avatar
Carsten Rose committed
180

Carsten  Rose's avatar
Carsten Rose committed
181
182
183
184
        $arr = explode(':', $element, 2);
        if (count($arr) != 2) {
            throw new DownloadException('Missing parameter for "' . $element . '"', ERROR_MISSING_REQUIRED_PARAMETER);
        }
Carsten  Rose's avatar
Carsten Rose committed
185

Carsten  Rose's avatar
Carsten Rose committed
186
187
        $token = $arr[0];
        $value = $arr[1];
Carsten  Rose's avatar
Carsten Rose committed
188

Carsten  Rose's avatar
Carsten Rose committed
189
190
191
192
193
        switch ($token) {
            case TOKEN_URL:
            case TOKEN_URL_PARAM:
                $filename = $this->html2pdf->page2pdf($token, $value);
                break;
Carsten  Rose's avatar
Carsten Rose committed
194

Carsten  Rose's avatar
Carsten Rose committed
195
196
197
198
199
200
            case TOKEN_FILE:
                $filename = $value;
                break;
            default:
                throw new DownloadException('Unknown token: "' . $token . '"', ERROR_UNKNOWN_TOKEN);
                break;
Carsten  Rose's avatar
Carsten Rose committed
201
202
203
204
205
        }

        return $filename;
    }

Carsten  Rose's avatar
Carsten Rose committed
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226

    /**
     * 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);
        }

227
228
        $len = strlen(DOWNLOAD_FILE_PREFIX);
        $ii = 1;
Carsten  Rose's avatar
Carsten Rose committed
229
        foreach ($files AS $filename) {
230
231
232
233
234
235
236
237
            $localname = substr($filename, strrpos($filename, '/') + 1);

            if (substr($localname, 0, $len) == DOWNLOAD_FILE_PREFIX) {
                $localname = 'file-' . $ii;
                $ii++;
            }
            $localname = $this->targetFilenameExtension($filename, $localname, $mimetype);

Carsten  Rose's avatar
Carsten Rose committed
238
239
240
241
242
243
244
            $zip->addFile($filename, $localname);
        }
        $zip->close();

        return $zipFile;
    }

Carsten  Rose's avatar
Carsten Rose committed
245
246
247
248
249
250
251
252
253
    /**
     * exportFilename=<new filename>
     * mode=file | pdf | excel - default is 'file' in case of only one or 'pdf' in case of multiple sources.
     * HTML to PDF | Excel
     *   <i>_id=<Typo3 pageId>
     *   <i>_<key>=<value i>
     * Direct
     * <i>_file=<filename>
     *
Carsten  Rose's avatar
Carsten Rose committed
254
     * @param array $vars [ DOWNLOAD_EXPORT_FILENAME, DOWNLOAD_MODE, SIP_DOWNLOAD_PARAMETER ]
Carsten  Rose's avatar
Carsten Rose committed
255
     * @throws DownloadException
Carsten  Rose's avatar
Carsten Rose committed
256
     * @internal param array $elements
Carsten  Rose's avatar
Carsten Rose committed
257
     */
Carsten  Rose's avatar
Carsten Rose committed
258
    private function doElements(array $vars) {
Carsten  Rose's avatar
Carsten Rose committed
259
260
261
262
263
264
265
266

        $tmpFiles = array();

        $workDir = $this->store->getVar(SYSTEM_SITE_PATH, STORE_SYSTEM);
        if (!chdir($workDir)) {
            throw new DownloadException ("Error chdir($workDir)", ERROR_IO_CHDIR);
        }

Carsten  Rose's avatar
Carsten Rose committed
267
268
        $downloadMode = $vars[DOWNLOAD_MODE];
        $elements = explode(PARAM_DELIMITER, $vars[SIP_DOWNLOAD_PARAMETER]);
Carsten  Rose's avatar
Carsten Rose committed
269

Carsten  Rose's avatar
Carsten Rose committed
270
        // Get all files
Carsten  Rose's avatar
Carsten Rose committed
271
        foreach ($elements as $element) {
Carsten  Rose's avatar
Carsten Rose committed
272
            $tmpFiles[] = $this->getElement($element);
Carsten  Rose's avatar
Carsten Rose committed
273
274
        }

Carsten  Rose's avatar
Carsten Rose committed
275
276
277
278
279
280
281
282
        // 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);
Carsten  Rose's avatar
Carsten Rose committed
283
284
                break;

Carsten  Rose's avatar
Carsten Rose committed
285
            case DOWNLOAD_MODE_FILE:
Carsten  Rose's avatar
Carsten Rose committed
286
                $filename = $tmpFiles[0];
287
288
289
                if (empty($vars[DOWNLOAD_EXPORT_FILENAME])) {
                    $vars[DOWNLOAD_EXPORT_FILENAME] = substr($filename, strpos($filename, '/'));
                }
Carsten  Rose's avatar
Carsten Rose committed
290
                break;
Carsten  Rose's avatar
Carsten Rose committed
291
292

            case DOWNLOAD_MODE_PDF:
Carsten  Rose's avatar
Carsten Rose committed
293
                $filename = $this->concatPdfFiles($tmpFiles);
Carsten  Rose's avatar
Carsten Rose committed
294
295
296
297
                break;

            default:
                throw new DownloadException("Unknown downloadMode: $downloadMode", ERROR_UNKNOWN_MODE);
Carsten  Rose's avatar
Carsten Rose committed
298
299
300
                break;
        }

301
302
        $exportFilename = empty($vars[DOWNLOAD_EXPORT_FILENAME]) ? DOWNLOAD_OUTPUT_PDF : $vars[DOWNLOAD_EXPORT_FILENAME];

Carsten  Rose's avatar
Carsten Rose committed
303
304
305
306
307
308
309
310
311
312
313
314
315
316
        $this->outputFile($filename, $exportFilename);

        $tmpFiles[] = $filename;
        $this->cleanTempFiles($tmpFiles);
    }

    /**
     * @return string
     * @throws CodeException
     * @throws UserFormException
     */
    public function process() {

        $vars = $this->store->getStore(STORE_SIP);
Carsten  Rose's avatar
Carsten Rose committed
317
        $this->doElements($vars);
Carsten  Rose's avatar
Carsten Rose committed
318
319
320
321
322
    }
}