4 * Utility classes to manage uploaded files
6 * Copyright (c) 2011 Olivier Berger & Institut Telecom
8 * This program was developped in the frame of the COCLICO project
9 * (http://www.coclico-project.org/) with financial support of the Paris
12 * This file is part of FusionForge.
14 * FusionForge is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation; either version 2 of the License, or
17 * (at your option) any later version.
19 * FusionForge is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
24 * You should have received a copy of the GNU General Public License
25 * along with GForge; if not, write to the Free Software
26 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
29 // TODO : add cleanup mechanism for storage
33 * Generic file storage management class
34 * The files are stored in an arbitrary dir on the server side
36 * @author Olivier Berger
39 class AbstractFilesDirectory extends Error {
43 protected $html_generator;
46 * Initializes the directory permissions
48 protected function createStorage() {
49 // initialize the group storage dir
50 if (!is_dir($this->dir_path)) {
51 mkdir($this->dir_path,0755);
53 if ( fileperms($this->dir_path) != 0x4755 ) {
54 chmod($this->dir_path,0755);
61 * @param HTML generator $HTML
62 * @param string $storage_base path to the storage directory (if omitted, uses a temprary dir in /tmp)
64 public function AbstractFilesDirectory($HTML, $storage_base=False) {
66 $this->html_generator = $HTML;
68 $storage_base = tempnam("/tmp", "ff-projectimport");
70 $this->dir_path = $storage_base;
71 $this->createStorage();
75 * Move a file on the server (typically an uploaded one) into the storage dir
76 * @param string $filename
77 * @param string $newfilename (optional) rename file on the fly
78 * @return boolean|string
80 public function addFileMovingIt($filename, $newfilename=FALSE) {
82 $newpath = $this->dir_path . $newfilename;
84 $newpath = $this->dir_path . basename($filename);
86 // do the move of the file
87 $ret = rename($filename, $newpath);
89 $this->setError(sprintf(_('File %s cannot be moved to the storage location %s'), $filename, $newpath));
96 * Displays an HTML list of the directory contents
99 public function displayContents() {
101 if (is_dir($this->dir_path)) {
102 // maybe use scandir instead ?
103 if ($dh = opendir($this->dir_path)) {
106 while (($file = readdir($dh)) !== false) {
107 if ($file != '.' && $file != '..') {
108 $html.='<li>'.'filename: '. $file .': filetype: ' . filetype($this->dir_path . $file) . '</li>';
119 * Returns an HTML box/table containing (single) file selection radio buttons
120 * @param string $preselected filename
121 * @return boolean|string
123 public function displayFileSelectionForm($preselected = False) {
125 $finfo = new finfo(FILEINFO_MIME, "/usr/share/misc/magic"); // return mime type ala mimetype extension
128 $this->setError(_('Opening fileinfo database failed'));
132 if (is_dir($this->dir_path)) {
133 $contents = scandir($this->dir_path);
134 if(count($contents) > 2) {
135 $html .= $this->html_generator->boxTop(_("Uploaded files available"));
136 $html .= '<table width="100%"><thead><tr>';
137 $html .= '<th>'._('name').'</th>';
138 $html .= '<th>'._('type').'</th>';
139 $html .= '<th>'._('selected ?').'</th>';
140 $html .= '</tr></thead><tbody>';
142 foreach($contents as $file) {
143 if ($file != '.' && $file != '..') {
144 $filepath = $this->dir_path . $file;
145 $filetype = filetype($filepath);
147 $html .= '<td style="white-space: nowrap;"><tt>'. $file .'</tt></td>';
148 if ($filetype == 'file') {
149 $mimetype = $finfo->file($filepath);
150 $html .= '<td style="white-space: nowrap;">' . $mimetype . '</td>';
151 $sha_filename = sha1($file);
152 if ($file == $preselected) {
153 $html .= '<td><input type="radio" name="file_'.$sha_filename.'" value="'.$sha_filename.'" checked="checked" /></td>';
155 $html .= '<td><input type="radio" $group = group_get_object($group_id);
156 name="file_'.$sha_filename.'" value="'.$sha_filename.'" /></td>';
160 $html .= '<td style="white-space: nowrap;">' . $filetype . '</td>';
161 $html .= '<td style="white-space: nowrap;" />';
165 $html .= '<input type="hidden" name="submit_file" value="y" />';
166 $html .= '</tbody></table>';
167 $html .= $this->html_generator->boxBottom();
175 * Returns the path given a SHA1 hash for a filename
176 * @param unknown_type $filesha1
177 * @return Ambigous <boolean, string>
179 public function getFilePath($filesha1) {
181 if (is_dir($this->dir_path)) {
182 $contents = scandir($this->dir_path);
183 foreach($contents as $file) {
184 if ($filesha1 == sha1($file)) {
185 $filepath = $this->dir_path . $file;
196 * Specialized file storage management class for site-level files
198 * Files are stored inside $storage_base/projectimport-plugin (for instance '$core/data_path/plugins/projectimport/)
200 * @author Olivier Berger
203 class SiteAdminFilesDirectory extends AbstractFilesDirectory {
204 public function SiteAdminFilesDirectory($HTML) {
206 $storage_base = forge_get_config('storage_base', 'projectimport-plugin');
208 parent::AbstractFilesDirectory($HTML, $storage_base);
214 * Specialized file storage management class for project-level files
216 * Files are stored inside subdirs of $storage_base/projectimport-plugin (for instance '$core/data_path/plugins/projectimport/_projname_/)
218 * @author Olivier Berger
221 class ProjectFilesDirectory extends AbstractFilesDirectory {
225 * @param HTML generator $HTML
226 * @param integer $group_id
228 public function ProjectFilesDirectory($HTML, $group_id) {
230 // store the project files inside a group unix name's subdir
231 $group = group_get_object($group_id);
232 $storage_base = forge_get_config('storage_base', 'projectimport-plugin');
233 $storage_base .= '/'. $group->getUnixName().'/';
235 parent::AbstractFilesDirectory($HTML, $storage_base);
242 * Utility HTML display class for pages containing a file upload and selection form
244 * @author Olivier Berger
247 class FileManagerPage {
250 * filename of the selected file POSTed
253 protected $posted_selecteddumpfile;
255 * filename of the uploaded file POSTed
258 protected $posted_uploadedfile;
260 protected $html_generator;
266 * @var AbstractFilesDirectory
272 * @param HTML generator $HTML
273 * @param AbstractFilesDirectory $storage (optional)
275 function FileManagerPage($HTML, $storage=False) {
276 $this->html_generator = $HTML;
279 // If specialized storage provided, then use it
281 $this->storage = $storage;
283 // otherwise create one with temporary directory
284 $this->storage = new AbstractFilesDirectory($this->html_generator);
286 $this->posted_selecteddumpfile = False;
287 $this->posted_uploadedfile = False;
291 * Adds a $feedback message
292 * @param string $message
294 function feedback($message) {
296 if ($feedback) $feedback .= '<br />';
297 $feedback .= $message;
301 * Parses the POSTed data to initialize the $posted_selecteddumpfile and $posted_uploadedfile and returns selected file name (if any)
302 * @return Ambigous <boolean, Ambigous, string>
304 function initialize_chosenfile_from_submitted() {
308 $uploaded_file = getUploadedFile('uploaded_file');
309 //print_r($uploaded_file);
311 // process chosen file -> $filechosen set after this (or not)
312 if (getStringFromPost('submit_file')) {
313 $filesha1s = array();
314 foreach (array_keys($_POST) as $key) {
315 if(!strncmp($key, 'file_', 5)) {
316 $filesha1 = substr($key, 5);
317 $filesha1s[] = $filesha1;
320 if (count($filesha1s) > 1) {
322 $this->feedback(_('Please select only one file'));
324 if (count($filesha1s) == 1) {
325 $filechosen = $this->storage->getFilePath($filesha1s[0]);
327 $this->feedback(_('File not found on server'));
333 // Process uploaded file : $this->posted_selecteddumpfile set afterwards (or not)
335 // May use codendi's rules to check results of upload ?
336 //$rule_file = new Rule_File();
337 //if ($rule_file->isValid($uploaded_file)) {
338 if($uploaded_file['error'] == UPLOAD_ERR_OK ) {
340 $this->feedback(_('Please either select existing file OR upload new file'));
344 $imported_file = $uploaded_file['tmp_name'];
345 $imported_file = $this->storage->addFileMovingIt($imported_file, $uploaded_file['name']);
346 if(! $imported_file) {
347 $this->feedback($this->storage->getErrorMessage());
350 $this->posted_uploadedfile = $uploaded_file['name'];
351 $this->message .= sprintf(_('File "%s" uploaded and pre-selected'),$this->posted_uploadedfile);
356 $error_code = $uploaded_file['error'];
357 if ($error_code != UPLOAD_ERR_NO_FILE ) {
358 switch ($error_code) {
359 case UPLOAD_ERR_INI_SIZE:
360 $this->feedback(_('The uploaded file exceeds the upload_max_filesize directive in php.ini'));
361 case UPLOAD_ERR_FORM_SIZE:
362 $this->feedback(_('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form'));
363 case UPLOAD_ERR_PARTIAL:
364 $this->feedback(_('The uploaded file was only partially uploaded'));
365 /* case UPLOAD_ERR_NO_FILE:
366 return 'No file was uploaded';*/
367 case UPLOAD_ERR_NO_TMP_DIR:
368 $this->feedback(_('Missing a temporary folder'));
369 case UPLOAD_ERR_CANT_WRITE:
370 $this->feedback(_('Failed to write file to disk'));
371 case UPLOAD_ERR_EXTENSION:
372 $this->feedback(_('File upload stopped by extension'));
374 $this->feedback(_('Unknown upload error %d', $error_code));
380 $this->posted_selecteddumpfile = $filechosen;