1 | <?php |
||
22 | class Upload extends Controller { |
||
23 | |||
24 | private static $allowed_actions = array( |
||
|
|||
25 | 'index', |
||
26 | 'load' |
||
27 | ); |
||
28 | |||
29 | /** |
||
30 | * A File object |
||
31 | * |
||
32 | * @var File |
||
33 | */ |
||
34 | protected $file; |
||
35 | |||
36 | /** |
||
37 | * Validator for this upload field |
||
38 | * |
||
39 | * @var Upload_Validator |
||
40 | */ |
||
41 | protected $validator; |
||
42 | |||
43 | /** |
||
44 | * Information about the temporary file produced |
||
45 | * by the PHP-runtime. |
||
46 | * |
||
47 | * @var array |
||
48 | */ |
||
49 | protected $tmpFile; |
||
50 | |||
51 | /** |
||
52 | * Replace an existing file rather than renaming the new one. |
||
53 | * |
||
54 | * @var boolean |
||
55 | */ |
||
56 | protected $replaceFile; |
||
57 | |||
58 | /** |
||
59 | * Processing errors that can be evaluated, |
||
60 | * e.g. by Form-validation. |
||
61 | * |
||
62 | * @var array |
||
63 | */ |
||
64 | protected $errors = array(); |
||
65 | |||
66 | /** |
||
67 | * A foldername relative to /assets, |
||
68 | * where all uploaded files are stored by default. |
||
69 | * |
||
70 | * @config |
||
71 | * @var string |
||
72 | */ |
||
73 | private static $uploads_folder = "Uploads"; |
||
74 | |||
75 | /** |
||
76 | * A prefix for the version number added to an uploaded file |
||
77 | * when a file with the same name already exists. |
||
78 | * Example using no prefix: IMG001.jpg becomes IMG2.jpg |
||
79 | * Example using '-v' prefix: IMG001.jpg becomes IMG001-v2.jpg |
||
80 | * |
||
81 | * @config |
||
82 | * @var string |
||
83 | */ |
||
84 | private static $version_prefix = ''; // a default value will be introduced in SS4.0 |
||
85 | |||
86 | public function __construct() { |
||
87 | parent::__construct(); |
||
88 | $this->validator = Injector::inst()->create('Upload_Validator'); |
||
89 | $this->replaceFile = self::config()->replaceFile; |
||
90 | } |
||
91 | |||
92 | /** |
||
93 | * Get current validator |
||
94 | * |
||
95 | * @return Upload_Validator $validator |
||
96 | */ |
||
97 | public function getValidator() { |
||
100 | |||
101 | /** |
||
102 | * Set a different instance than {@link Upload_Validator} |
||
103 | * for this upload session. |
||
104 | * |
||
105 | * @param object $validator |
||
106 | */ |
||
107 | public function setValidator($validator) { |
||
110 | |||
111 | /** |
||
112 | * Save an file passed from a form post into this object. |
||
113 | * File names are filtered through {@link FileNameFilter}, see class documentation |
||
114 | * on how to influence this behaviour. |
||
115 | * |
||
116 | * @param $tmpFile array Indexed array that PHP generated for every file it uploads. |
||
117 | * @param $folderPath string Folder path relative to /assets |
||
118 | * @return Boolean|string Either success or error-message. |
||
119 | */ |
||
120 | public function load($tmpFile, $folderPath = false) { |
||
121 | $this->clearErrors(); |
||
122 | |||
123 | if(!$folderPath) $folderPath = $this->config()->uploads_folder; |
||
124 | |||
125 | if(!is_array($tmpFile)) { |
||
126 | user_error("Upload::load() Not passed an array. Most likely, the form hasn't got the right enctype", |
||
127 | E_USER_ERROR); |
||
128 | } |
||
129 | |||
130 | if(!$tmpFile['size']) { |
||
131 | $this->errors[] = _t('File.NOFILESIZE', 'File size is zero bytes.'); |
||
132 | return false; |
||
133 | } |
||
134 | |||
135 | $valid = $this->validate($tmpFile); |
||
136 | if(!$valid) return false; |
||
137 | |||
138 | // @TODO This puts a HUGE limitation on files especially when lots |
||
139 | // have been uploaded. |
||
140 | $base = Director::baseFolder(); |
||
141 | $parentFolder = Folder::find_or_make($folderPath); |
||
142 | |||
143 | // Generate default filename |
||
144 | $nameFilter = FileNameFilter::create(); |
||
145 | $file = $nameFilter->filter($tmpFile['name']); |
||
146 | $fileName = basename($file); |
||
147 | |||
148 | $relativeFolderPath = $parentFolder |
||
149 | ? $parentFolder->getRelativePath() |
||
150 | : ASSETS_DIR . '/'; |
||
151 | $relativeFilePath = $relativeFolderPath . $fileName; |
||
152 | |||
153 | // Create a new file record (or try to retrieve an existing one) |
||
154 | if(!$this->file) { |
||
155 | $fileClass = File::get_class_for_file_extension(pathinfo($tmpFile['name'], PATHINFO_EXTENSION)); |
||
156 | $this->file = new $fileClass(); |
||
157 | } |
||
158 | if(!$this->file->ID && $this->replaceFile) { |
||
159 | $fileClass = $this->file->class; |
||
160 | $file = File::get() |
||
161 | ->filter(array( |
||
162 | 'ClassName' => $fileClass, |
||
163 | 'Name' => $fileName, |
||
164 | 'ParentID' => $parentFolder ? $parentFolder->ID : 0 |
||
165 | ))->First(); |
||
166 | if($file) { |
||
167 | $this->file = $file; |
||
168 | } |
||
169 | } |
||
170 | |||
171 | // if filename already exists, version the filename (e.g. test.gif to test2.gif, test2.gif to test3.gif) |
||
172 | if(!$this->replaceFile) { |
||
173 | $fileSuffixArray = explode('.', $fileName); |
||
174 | $fileTitle = array_shift($fileSuffixArray); |
||
175 | $fileSuffix = !empty($fileSuffixArray) |
||
176 | ? '.' . implode('.', $fileSuffixArray) |
||
177 | : null; |
||
178 | |||
179 | // make sure files retain valid extensions |
||
180 | $oldFilePath = $relativeFilePath; |
||
181 | $relativeFilePath = $relativeFolderPath . $fileTitle . $fileSuffix; |
||
182 | if($oldFilePath !== $relativeFilePath) { |
||
183 | user_error("Couldn't fix $relativeFilePath", E_USER_ERROR); |
||
184 | } |
||
185 | while(file_exists("$base/$relativeFilePath")) { |
||
186 | $i = isset($i) ? ($i+1) : 2; |
||
187 | $oldFilePath = $relativeFilePath; |
||
188 | |||
189 | $prefix = $this->config()->version_prefix; |
||
190 | $pattern = '/' . preg_quote($prefix) . '([0-9]+$)/'; |
||
191 | if(preg_match($pattern, $fileTitle, $matches)) { |
||
192 | $fileTitle = preg_replace($pattern, $prefix . ($matches[1] + 1), $fileTitle); |
||
193 | } else { |
||
194 | $fileTitle .= $prefix . $i; |
||
195 | } |
||
196 | $relativeFilePath = $relativeFolderPath . $fileTitle . $fileSuffix; |
||
197 | |||
198 | if($oldFilePath == $relativeFilePath && $i > 2) { |
||
199 | user_error("Couldn't fix $relativeFilePath with $i tries", E_USER_ERROR); |
||
200 | } |
||
201 | } |
||
202 | } else { |
||
203 | //reset the ownerID to the current member when replacing files |
||
204 | $this->file->OwnerID = (Member::currentUser() ? Member::currentUser()->ID : 0); |
||
205 | } |
||
206 | |||
207 | if(file_exists($tmpFile['tmp_name']) && copy($tmpFile['tmp_name'], "$base/$relativeFilePath")) { |
||
208 | $this->file->ParentID = $parentFolder ? $parentFolder->ID : 0; |
||
209 | // This is to prevent it from trying to rename the file |
||
210 | $this->file->Name = basename($relativeFilePath); |
||
211 | $this->file->write(); |
||
212 | $this->file->onAfterUpload(); |
||
213 | $this->extend('onAfterLoad', $this->file, $tmpFile); //to allow extensions to e.g. create a version after an upload |
||
214 | return true; |
||
215 | } else { |
||
216 | $this->errors[] = _t('File.NOFILESIZE', 'File size is zero bytes.'); |
||
217 | return false; |
||
218 | } |
||
219 | } |
||
220 | |||
221 | /** |
||
222 | * Load temporary PHP-upload into File-object. |
||
223 | * |
||
224 | * @param array $tmpFile |
||
225 | * @param File $file |
||
226 | * @return Boolean |
||
227 | */ |
||
228 | public function loadIntoFile($tmpFile, $file, $folderPath = false) { |
||
232 | |||
233 | /** |
||
234 | * @return Boolean |
||
235 | */ |
||
236 | public function setReplaceFile($bool) { |
||
239 | |||
240 | /** |
||
241 | * @return Boolean |
||
242 | */ |
||
243 | public function getReplaceFile() { |
||
246 | |||
247 | /** |
||
248 | * Container for all validation on the file |
||
249 | * (e.g. size and extension restrictions). |
||
250 | * Is NOT connected to the {Validator} classes, |
||
251 | * please have a look at {FileField->validate()} |
||
252 | * for an example implementation of external validation. |
||
253 | * |
||
254 | * @param array $tmpFile |
||
255 | * @return boolean |
||
256 | */ |
||
257 | public function validate($tmpFile) { |
||
258 | $validator = $this->validator; |
||
259 | $validator->setTmpFile($tmpFile); |
||
260 | $isValid = $validator->validate(); |
||
261 | if($validator->getErrors()) { |
||
262 | $this->errors = array_merge($this->errors, $validator->getErrors()); |
||
263 | } |
||
264 | return $isValid; |
||
265 | } |
||
266 | |||
267 | /** |
||
268 | * Get file-object, either generated from {load()}, |
||
269 | * or manually set. |
||
270 | * |
||
271 | * @return File |
||
272 | */ |
||
273 | public function getFile() { |
||
276 | |||
277 | /** |
||
278 | * Set a file-object (similiar to {loadIntoFile()}) |
||
279 | * |
||
280 | * @param File $file |
||
281 | */ |
||
282 | public function setFile($file) { |
||
285 | |||
286 | /** |
||
287 | * Clear out all errors (mostly set by {loadUploaded()}) |
||
288 | * including the validator's errors |
||
289 | */ |
||
290 | public function clearErrors() { |
||
294 | |||
295 | /** |
||
296 | * Determines wether previous operations caused an error. |
||
297 | * |
||
298 | * @return boolean |
||
299 | */ |
||
300 | public function isError() { |
||
303 | |||
304 | /** |
||
305 | * Return all errors that occurred while processing so far |
||
306 | * (mostly set by {loadUploaded()}) |
||
307 | * |
||
308 | * @return array |
||
309 | */ |
||
310 | public function getErrors() { |
||
313 | |||
314 | } |
||
315 | |||
316 | /** |
||
565 |