Subversion Repositories SmartDukaan

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
14509 anikendra 1
<?php
2
 
3
class qqFileUploader {
4
 
5
    public $allowedExtensions = array();
6
    public $sizeLimit = null;
7
    public $inputName = 'qqfile';
8
    public $chunksFolder = 'chunks';
9
 
10
    public $chunksCleanupProbability = 0.001; // Once in 1000 requests on avg
11
    public $chunksExpireIn = 604800; // One week
12
 
13
    protected $uploadName;
14
 
15
    function __construct(){
16
        $this->sizeLimit = $this->toBytes(ini_get('upload_max_filesize'));
17
    }
18
 
19
    /**
20
     * Get the original filename
21
     */
22
    public function getName(){
23
        if (isset($_REQUEST['qqfilename']))
24
            return $_REQUEST['qqfilename'];
25
 
26
        if (isset($_FILES[$this->inputName]))
27
            return $_FILES[$this->inputName]['name'];
28
    }
29
 
30
    /**
31
     * Get the name of the uploaded file
32
     */
33
    public function getUploadName(){
34
        return $this->uploadName;
35
    }
36
 
37
    /**
38
     * Process the upload.
39
     * @param string $uploadDirectory Target directory.
40
     * @param string $name Overwrites the name of the file.
41
     */
42
    public function handleUpload($uploadDirectory, $name = null){
43
 
44
        if (is_writable($this->chunksFolder) &&
45
            1 == mt_rand(1, 1/$this->chunksCleanupProbability)){
46
 
47
            // Run garbage collection
48
            $this->cleanupChunks();
49
        }
50
 
51
        // Check that the max upload size specified in class configuration does not
52
        // exceed size allowed by server config
53
        if ($this->toBytes(ini_get('post_max_size')) < $this->sizeLimit ||
54
            $this->toBytes(ini_get('upload_max_filesize')) < $this->sizeLimit){
55
            $size = max(1, $this->sizeLimit / 1024 / 1024) . 'M';
56
            return array('error'=>"Server error. Increase post_max_size and upload_max_filesize to ".$size);
57
        }
58
 
59
		// is_writable() is not reliable on Windows (http://www.php.net/manual/en/function.is-executable.php#111146)
60
		// The following tests if the current OS is Windows and if so, merely checks if the folder is writable;
61
		// otherwise, it checks additionally for executable status (like before).
62
 
63
        $isWin = (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN');
64
        $folderInaccessible = ($isWin) ? !is_writable($uploadDirectory) : ( !is_writable($uploadDirectory) && !is_executable($uploadDirectory) );
65
 
66
        if ($folderInaccessible){
67
            return array('error' => "Server error. Uploads directory isn't writable" . ((!$isWin) ? " or executable." : "."));
68
        }
69
 
70
        if(!isset($_SERVER['CONTENT_TYPE'])) {
71
            return array('error' => "No files were uploaded.");
72
        } else if (strpos(strtolower($_SERVER['CONTENT_TYPE']), 'multipart/') !== 0){
73
            return array('error' => "Server error. Not a multipart request. Please set forceMultipart to default value (true).");
74
        }
75
 
76
        // Get size and name
77
 
78
        $file = $_FILES[$this->inputName];
79
        $size = $file['size'];
80
 
81
        if ($name === null){
82
            $name = $this->getName();
83
        }
84
 
85
        // Validate name
86
 
87
        if ($name === null || $name === ''){
88
            return array('error' => 'File name empty.');
89
        }
90
 
91
        // Validate file size
92
 
93
        if ($size == 0){
94
            return array('error' => 'File is empty.');
95
        }
96
 
97
        if ($size > $this->sizeLimit){
98
            return array('error' => 'File is too large.');
99
        }
100
 
101
        // Validate file extension
102
 
103
        $pathinfo = pathinfo($name);
104
        $ext = isset($pathinfo['extension']) ? $pathinfo['extension'] : '';
105
 
106
        if($this->allowedExtensions && !in_array(strtolower($ext), array_map("strtolower", $this->allowedExtensions))){
107
            $these = implode(', ', $this->allowedExtensions);
108
            return array('error' => 'File has an invalid extension, it should be one of '. $these . '.');
109
        }
110
 
111
        // Save a chunk
112
 
113
        $totalParts = isset($_REQUEST['qqtotalparts']) ? (int)$_REQUEST['qqtotalparts'] : 1;
114
 
115
        if ($totalParts > 1){
116
 
117
            $chunksFolder = $this->chunksFolder;
118
            $partIndex = (int)$_REQUEST['qqpartindex'];
119
            $uuid = $_REQUEST['qquuid'];
120
 
121
            if (!is_writable($chunksFolder) && !is_executable($uploadDirectory)){
122
                return array('error' => "Server error. Chunks directory isn't writable or executable.");
123
            }
124
 
125
            $targetFolder = $this->chunksFolder.DIRECTORY_SEPARATOR.$uuid;
126
 
127
            if (!file_exists($targetFolder)){
128
                mkdir($targetFolder);
129
            }
130
 
131
            $target = $targetFolder.'/'.$partIndex;
132
            $success = move_uploaded_file($_FILES[$this->inputName]['tmp_name'], $target);
133
 
134
            // Last chunk saved successfully
135
            if ($success AND ($totalParts-1 == $partIndex)){
136
 
137
                $target = $this->getUniqueTargetPath($uploadDirectory, $name);
138
                $this->uploadName = basename($target);
139
 
140
                $target = fopen($target, 'wb');
141
 
142
                for ($i=0; $i<$totalParts; $i++){
143
                    $chunk = fopen($targetFolder.DIRECTORY_SEPARATOR.$i, "rb");
144
                    stream_copy_to_stream($chunk, $target);
145
                    fclose($chunk);
146
                }
147
 
148
                // Success
149
                fclose($target);
150
 
151
                for ($i=0; $i<$totalParts; $i++){
152
                    unlink($targetFolder.DIRECTORY_SEPARATOR.$i);
153
                }
154
 
155
                rmdir($targetFolder);
156
 
157
                return array("success" => true);
158
 
159
            }
160
 
161
            return array("success" => true);
162
 
163
        } else {
164
 
165
            $target = $this->getUniqueTargetPath($uploadDirectory, $name);
166
 
167
            if ($target){
168
                $this->uploadName = basename($target);
169
 
170
                if (move_uploaded_file($file['tmp_name'], $target)){
171
                    return array('success'=> true);
172
                }
173
            }
174
 
175
            return array('error'=> 'Could not save uploaded file.' .
176
                'The upload was cancelled, or server error encountered');
177
        }
178
    }
179
 
180
    /**
181
     * Returns a path to use with this upload. Check that the name does not exist,
182
     * and appends a suffix otherwise.
183
     * @param string $uploadDirectory Target directory
184
     * @param string $filename The name of the file to use.
185
     */
186
    protected function getUniqueTargetPath($uploadDirectory, $filename)
187
    {
188
        // Allow only one process at the time to get a unique file name, otherwise
189
        // if multiple people would upload a file with the same name at the same time
190
        // only the latest would be saved.
191
 
192
        if (function_exists('sem_acquire')){
193
            $lock = sem_get(ftok(__FILE__, 'u'));
194
            sem_acquire($lock);
195
        }
196
 
197
        $pathinfo = pathinfo($filename);
198
        $base = $pathinfo['filename'];
199
        $ext = isset($pathinfo['extension']) ? $pathinfo['extension'] : '';
200
        $ext = $ext == '' ? $ext : '.' . $ext;
201
 
202
        $unique = $base;
203
        $suffix = 0;
204
 
205
        // Get unique file name for the file, by appending random suffix.
206
 
207
        while (file_exists($uploadDirectory . DIRECTORY_SEPARATOR . $unique . $ext)){
208
            $suffix += rand(1, 999);
209
            $unique = $base.'-'.$suffix;
210
        }
211
 
212
        $result =  $uploadDirectory . DIRECTORY_SEPARATOR . $unique . $ext;
213
 
214
        // Create an empty target file
215
        if (!touch($result)){
216
            // Failed
217
            $result = false;
218
        }
219
 
220
        if (function_exists('sem_acquire')){
221
            sem_release($lock);
222
        }
223
 
224
        return $result;
225
    }
226
 
227
    /**
228
     * Deletes all file parts in the chunks folder for files uploaded
229
     * more than chunksExpireIn seconds ago
230
     */
231
    protected function cleanupChunks(){
232
        foreach (scandir($this->chunksFolder) as $item){
233
            if ($item == "." || $item == "..")
234
                continue;
235
 
236
            $path = $this->chunksFolder.DIRECTORY_SEPARATOR.$item;
237
 
238
            if (!is_dir($path))
239
                continue;
240
 
241
            if (time() - filemtime($path) > $this->chunksExpireIn){
242
                $this->removeDir($path);
243
            }
244
        }
245
    }
246
 
247
    /**
248
     * Removes a directory and all files contained inside
249
     * @param string $dir
250
     */
251
    protected function removeDir($dir){
252
        foreach (scandir($dir) as $item){
253
            if ($item == "." || $item == "..")
254
                continue;
255
 
256
            unlink($dir.DIRECTORY_SEPARATOR.$item);
257
        }
258
        rmdir($dir);
259
    }
260
 
261
    /**
262
     * Converts a given size with units to bytes.
263
     * @param string $str
264
     */
265
    protected function toBytes($str){
266
        $val = trim($str);
267
        $last = strtolower($str[strlen($str)-1]);
268
        switch($last) {
269
            case 'g': $val *= 1024;
270
            case 'm': $val *= 1024;
271
            case 'k': $val *= 1024;
272
        }
273
        return $val;
274
    }
275
}