File.php 6.1 KB
Newer Older
Carsten  Rose's avatar
Carsten Rose committed
1
2
3
4
5
6
7
8
<?php
/**
 * Created by PhpStorm.
 * User: crose
 * Date: 4/25/16
 * Time: 10:39 PM
 */

Marc Egger's avatar
Marc Egger committed
9
namespace IMATHUZH\Qfq\Core;
Carsten  Rose's avatar
Carsten Rose committed
10

Marc Egger's avatar
Marc Egger committed
11
12
 
use IMATHUZH\Qfq\Core\Helper\Logger;
13
use IMATHUZH\Qfq\Core\Helper\Path;
Marc Egger's avatar
Marc Egger committed
14
15
16
17
use IMATHUZH\Qfq\Core\Helper\Support;
use IMATHUZH\Qfq\Core\Store\Session;
use IMATHUZH\Qfq\Core\Store\Store;
use IMATHUZH\Qfq\Core\Helper\HelperFile;
Carsten  Rose's avatar
Carsten Rose committed
18

19
20
21
22
/**
 * Class File
 * @package qfq
 */
Carsten  Rose's avatar
Carsten Rose committed
23
24
class File {

25
26
    private $uploadErrMsg = array();

Carsten  Rose's avatar
Carsten Rose committed
27
    /**
28
     * @var Store
Carsten  Rose's avatar
Carsten Rose committed
29
30
31
     */
    private $store = null;

32
33
34
35
36
    /**
     * @var Session
     */
    private $session = null;

37
    private $qfqLogPathFilenameAbsolute = '';
38

39
40
    /**
     * @param bool|false $phpUnit
Marc Egger's avatar
Marc Egger committed
41
42
43
     * @throws \CodeException
     * @throws \UserFormException
     * @throws \UserReportException
44
     */
Carsten  Rose's avatar
Carsten Rose committed
45
    public function __construct($phpUnit = false) {
46
47

        #TODO: rewrite $phpUnit to: "if (!defined('PHPUNIT_QFQ')) {...}"
48
        $this->session = Session::getInstance($phpUnit);
49

50
        $this->store = Store::getInstance('', $phpUnit);
51
        $this->qfqLogPathFilenameAbsolute = Path::absoluteQfqLogFile();
52

53
        $this->uploadErrMsg = [
Carsten  Rose's avatar
Carsten Rose committed
54
            UPLOAD_ERR_INI_SIZE => "The uploaded file exceeds the upload_max_filesize directive in php.ini",
55
            UPLOAD_ERR_FORM_SIZE => "The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form",
Carsten  Rose's avatar
Carsten Rose committed
56
57
            UPLOAD_ERR_PARTIAL => "The uploaded file was only partially uploaded",
            UPLOAD_ERR_NO_FILE => "No file was uploaded",
58
59
            UPLOAD_ERR_NO_TMP_DIR => "Missing a temporary folder",
            UPLOAD_ERR_CANT_WRITE => "Failed to write file to disk",
Carsten  Rose's avatar
Carsten Rose committed
60
            UPLOAD_ERR_EXTENSION => "File upload stopped by extension",
61
        ];
Carsten  Rose's avatar
Carsten Rose committed
62
63
    }

64
    /**
Marc Egger's avatar
Marc Egger committed
65
66
     * @throws \CodeException
     * @throws \UserFormException
67
     */
Carsten  Rose's avatar
Carsten Rose committed
68
69
    public function process() {

70
71
        $sipUpload = $this->store->getVar(SIP_SIP, STORE_SIP);
        if ($sipUpload === false) {
72

73
            if (empty($_FILES)) {
Marc Egger's avatar
Marc Egger committed
74
                throw new \UserFormException('Missing $_FILES[] - the whole request seems broken', ERROR_UPLOAD_FILES_BROKEN);
75
76
            }

Marc Egger's avatar
Marc Egger committed
77
            throw new \UserFormException('SIP invalid: ' . $sipUpload, ERROR_SIP_INVALID);
78
        }
79

80
        // Throws an exception if content is too big - if content is bigger than 'post_max_size', the POST is lost together with the PHP Upload error message.
Carsten  Rose's avatar
Carsten Rose committed
81
        $size = $_SERVER['CONTENT_LENGTH'] ?? 0;
82
83
        $this->checkMaxFileSize($size);

84
        $statusUpload = $this->store->getVar($sipUpload, STORE_EXTRA, SANITIZE_ALLOW_ALL);
85
86
        if ($statusUpload === false) {
            $statusUpload = array();
Carsten  Rose's avatar
Carsten Rose committed
87
88
        }

89
90
91
92
93
94
95
96
97
        $action = $this->store->getVar(FILE_ACTION, STORE_CLIENT, SANITIZE_ALLOW_ALNUMX);
        switch ($action) {
            case FILE_ACTION_UPLOAD:
                $this->doUpload($sipUpload, $statusUpload);
                break;
            case FILE_ACTION_DELETE:
                $this->doDelete($sipUpload, $statusUpload);
                break;
            default:
Marc Egger's avatar
Marc Egger committed
98
                throw new \UserFormException("Unknown FILE_ACTION: $action", ERROR_UPLOAD_UNKNOWN_ACTION);
99
100
        }
    }
Carsten  Rose's avatar
Carsten Rose committed
101

102
    /**
103
104
105
     * Checks the max file size, defined per FormElement.
     * Only possible if there is a valid SIP Store.
     *
106
     * @param $size
Marc Egger's avatar
Marc Egger committed
107
108
     * @throws \CodeException
     * @throws \UserFormException
109
110
111
     */
    private function checkMaxFileSize($size) {

112
113
        // Checked and set during form build.
        $maxFileSize = $this->store->getVar(FE_FILE_MAX_FILE_SIZE, STORE_SIP . STORE_ZERO);
114

115
        if ($size >= $maxFileSize) {
116
            throw new \UserFormException('File too big. Max allowed size: ' . $maxFileSize . ' Bytes', ERROR_UPLOAD_TOO_BIG);
117
118
119
        }
    }

120
    /**
121
122
     * doUpload
     *
123
     * @param string $sipUpload
Carsten  Rose's avatar
Carsten Rose committed
124
     * @param array $statusUpload
Carsten  Rose's avatar
Carsten Rose committed
125
     *
Marc Egger's avatar
Marc Egger committed
126
127
     * @throws \CodeException
     * @throws \UserFormException
128
     */
129
    private function doUpload($sipUpload, array $statusUpload) {
130
131

        // New upload
132
        $newArr = reset($_FILES);
133
        // Merge new upload date to existing status information
134
        $statusUpload = array_merge($statusUpload, $newArr);
135

136
        if ($statusUpload[FILES_ERROR] !== UPLOAD_ERR_OK) {
Marc Egger's avatar
Marc Egger committed
137
            throw new \UserFormException(
Marc Egger's avatar
Marc Egger committed
138
                json_encode([ERROR_MESSAGE_TO_USER => 'Upload: Error', ERROR_MESSAGE_TO_DEVELOPER => $this->uploadErrMsg[$newArr[FILES_ERROR]]]),
139
                ERROR_UPLOAD);
Carsten  Rose's avatar
Carsten Rose committed
140
141
        }

142
        Logger::logMessageWithPrefix(UPLOAD_LOG_PREFIX . ': File under ' . $statusUpload['tmp_name'], $this->qfqLogPathFilenameAbsolute);
143

144
        $this->checkMaxFileSize($statusUpload['size']);
145

146
        $accept = $this->store->getVar(FE_FILE_MIME_TYPE_ACCEPT, STORE_SIP);
147
        if ($accept != '' && !HelperFile::checkFileType($statusUpload['tmp_name'], $statusUpload['name'], $accept)) {
Marc Egger's avatar
Marc Egger committed
148
            throw new \UserFormException('Filetype not allowed. Allowed: ' . $accept, ERROR_UPLOAD_FILE_TYPE);
149
        }
150
151
152

        // rename uploaded file: ?.cached
        $filenameCached = Support::extendFilename($statusUpload[FILES_TMP_NAME], UPLOAD_CACHED);
153
154
155
156

        error_clear_last();
        if (!move_uploaded_file($newArr[FILES_TMP_NAME], $filenameCached)) {
            $msg = error_get_last();
Marc Egger's avatar
Marc Egger committed
157
            throw new \UserFormException(
Marc Egger's avatar
Marc Egger committed
158
                json_encode([ERROR_MESSAGE_TO_USER => 'Upload: Error', ERROR_MESSAGE_TO_DEVELOPER => $msg]),
159
160
                ERROR_UPLOAD_FILE_TYPE);
        }
Carsten  Rose's avatar
Carsten Rose committed
161

162
        $this->store->setVar($sipUpload, $statusUpload, STORE_EXTRA);
Carsten  Rose's avatar
Carsten Rose committed
163
    }
Carsten  Rose's avatar
Carsten Rose committed
164

165
166
167
    /**
     * @param $sipUpload
     * @param $statusUpload
Carsten  Rose's avatar
Carsten Rose committed
168
     *
Marc Egger's avatar
Marc Egger committed
169
170
     * @throws \CodeException
     * @throws \UserFormException
171
     * @internal param string $keyStoreExtra
172
173
174
175
176
177
     */
    private function doDelete($sipUpload, $statusUpload) {

        if (isset($statusUpload[FILES_TMP_NAME]) && $statusUpload[FILES_TMP_NAME] != '') {
            $file = Support::extendFilename($statusUpload[FILES_TMP_NAME], UPLOAD_CACHED);
            if (file_exists($file)) {
178
                HelperFile::unlink($file, $this->qfqLogPathFilenameAbsolute);
179
180
181
            }
            $statusUpload[FILES_TMP_NAME] = '';
        }
182

183
184
185
        $statusUpload[FILES_FLAG_DELETE] = '1';
        $this->store->setVar($sipUpload, $statusUpload, STORE_EXTRA);
    }
Carsten  Rose's avatar
Carsten Rose committed
186
}