Fixed ticket 36, attach a file to a ticket.
It is now possible to attach a file to a ticket.
This commit is contained in:
		| @@ -64,6 +64,26 @@ class IDF_Form_IssueCreate extends Pluf_Form | ||||
|                                                        'rows' => 13, | ||||
|                                                                     ), | ||||
|                                             )); | ||||
|         $upload_path = Pluf::f('upload_issue_path', false); | ||||
|         if (false === $upload_path) { | ||||
|             throw new Pluf_Exception_SettingError(__('The "upload_issue_path" configuration variable was not set.')); | ||||
|         } | ||||
|         $md5 = md5(rand().microtime().Pluf_Utils::getRandomString()); | ||||
|         // We add .dummy to try to mitigate security issues in the | ||||
|         // case of someone allowing the upload path to be accessible | ||||
|         // to everybody. | ||||
|         $filename = substr($md5, 0, 2).'/'.substr($md5, 2, 2).'/'.substr($md5, 4).'/%s.dummy';  | ||||
|         $this->fields['attachment'] = new Pluf_Form_Field_File( | ||||
|                 array('required' => false, | ||||
|                       'label' => __('Attach a file'), | ||||
|                       'move_function_params' =>  | ||||
|                       array('upload_path' => $upload_path, | ||||
|                             'upload_path_create' => true, | ||||
|                             'file_name' => $filename, | ||||
|                             ) | ||||
|                       ) | ||||
|                 ); | ||||
|  | ||||
|         if ($this->show_full) { | ||||
|             $this->fields['status'] = new Pluf_Form_Field_Varchar( | ||||
|                                       array('required' => true, | ||||
| @@ -226,6 +246,15 @@ class IDF_Form_IssueCreate extends Pluf_Form | ||||
|             $comment->content = $this->cleaned_data['content']; | ||||
|             $comment->submitter = $this->user; | ||||
|             $comment->create(); | ||||
|             // If we have a file, create the IDF_IssueFile and attach | ||||
|             // it to the comment. | ||||
|             if ($this->cleaned_data['attachment']) { | ||||
|                 $file = new IDF_IssueFile(); | ||||
|                 $file->attachment = $this->cleaned_data['attachment']; | ||||
|                 $file->submitter = $this->user; | ||||
|                 $file->comment = $comment; | ||||
|                 $file->create(); | ||||
|             } | ||||
|             return $issue; | ||||
|         } | ||||
|         throw new Exception(__('Cannot save the model from an invalid form.')); | ||||
|   | ||||
| @@ -60,6 +60,26 @@ class IDF_Form_IssueUpdate  extends IDF_Form_IssueCreate | ||||
|                                                        'rows' => 9, | ||||
|                                                                     ), | ||||
|                                             )); | ||||
|         $upload_path = Pluf::f('upload_issue_path', false); | ||||
|         if (false === $upload_path) { | ||||
|             throw new Pluf_Exception_SettingError(__('The "upload_issue_path" configuration variable was not set.')); | ||||
|         } | ||||
|         $md5 = md5(rand().microtime().Pluf_Utils::getRandomString()); | ||||
|         // We add .dummy to try to mitigate security issues in the | ||||
|         // case of someone allowing the upload path to be accessible | ||||
|         // to everybody. | ||||
|         $filename = substr($md5, 0, 2).'/'.substr($md5, 2, 2).'/'.substr($md5, 4).'/%s.dummy';  | ||||
|         $this->fields['attachment'] = new Pluf_Form_Field_File( | ||||
|                 array('required' => false, | ||||
|                       'label' => __('Attach a file'), | ||||
|                       'move_function_params' =>  | ||||
|                       array('upload_path' => $upload_path, | ||||
|                             'upload_path_create' => true, | ||||
|                             'file_name' => $filename, | ||||
|                             ) | ||||
|                       ) | ||||
|                 ); | ||||
|  | ||||
|         if ($this->show_full) { | ||||
|             $this->fields['status'] = new Pluf_Form_Field_Varchar( | ||||
|                                       array('required' => true, | ||||
| @@ -257,6 +277,13 @@ class IDF_Form_IssueUpdate  extends IDF_Form_IssueCreate | ||||
|                 $this->issue->submitter != $this->user->id) { | ||||
|                 $this->issue->setAssoc($this->user); // interested user. | ||||
|             } | ||||
|             if ($this->cleaned_data['attachment']) { | ||||
|                 $file = new IDF_IssueFile(); | ||||
|                 $file->attachment = $this->cleaned_data['attachment']; | ||||
|                 $file->submitter = $this->user; | ||||
|                 $file->comment = $comment; | ||||
|                 $file->create(); | ||||
|             } | ||||
|             return $this->issue; | ||||
|         } | ||||
|         throw new Exception(__('Cannot save the model from an invalid form.')); | ||||
|   | ||||
							
								
								
									
										125
									
								
								src/IDF/IssueFile.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								src/IDF/IssueFile.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,125 @@ | ||||
| <?php | ||||
| /* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ | ||||
| /* | ||||
| # ***** BEGIN LICENSE BLOCK ***** | ||||
| # This file is part of InDefero, an open source project management application. | ||||
| # Copyright (C) 2008 Céondo Ltd and contributors. | ||||
| # | ||||
| # InDefero is free software; you can redistribute it and/or modify | ||||
| # it under the terms of the GNU General Public License as published by | ||||
| # the Free Software Foundation; either version 2 of the License, or | ||||
| # (at your option) any later version. | ||||
| # | ||||
| # InDefero is distributed in the hope that it will be useful, | ||||
| # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| # GNU General Public License for more details. | ||||
| # | ||||
| # You should have received a copy of the GNU General Public License | ||||
| # along with this program; if not, write to the Free Software | ||||
| # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA | ||||
| # | ||||
| # ***** END LICENSE BLOCK ***** */ | ||||
|  | ||||
| /** | ||||
|  * A file uploaded with an issue or a comment to an issue. | ||||
|  * | ||||
|  */ | ||||
| class IDF_IssueFile extends Pluf_Model | ||||
| { | ||||
|     public $_model = __CLASS__; | ||||
|  | ||||
|     function init() | ||||
|     { | ||||
|         $this->_a['table'] = 'idf_issuefiles'; | ||||
|         $this->_a['model'] = __CLASS__; | ||||
|         $this->_a['cols'] = array( | ||||
|                              // It is mandatory to have an "id" column. | ||||
|                             'id' => | ||||
|                             array( | ||||
|                                   'type' => 'Pluf_DB_Field_Sequence', | ||||
|                                   //It is automatically added. | ||||
|                                   'blank' => true,  | ||||
|                                   ), | ||||
|                             'comment' =>  | ||||
|                             array( | ||||
|                                   'type' => 'Pluf_DB_Field_Foreignkey', | ||||
|                                   'model' => 'IDF_IssueComment', | ||||
|                                   'blank' => false, | ||||
|                                   'verbose' => __('comment'), | ||||
|                                   'relate_name' => 'attachment', | ||||
|                                    ), | ||||
|                             'submitter' =>  | ||||
|                             array( | ||||
|                                   'type' => 'Pluf_DB_Field_Foreignkey', | ||||
|                                   'model' => 'Pluf_User', | ||||
|                                   'blank' => false, | ||||
|                                   'verbose' => __('submitter'), | ||||
|                                    ), | ||||
|                             'filename' => | ||||
|                             array( | ||||
|                                   'type' => 'Pluf_DB_Field_Varchar', | ||||
|                                   'blank' => true, | ||||
|                                   'size' => 100, | ||||
|                                   'verbose' => __('file name'), | ||||
|                                   ), | ||||
|                             'attachment' =>  | ||||
|                             array( | ||||
|                                   'type' => 'Pluf_DB_Field_File', | ||||
|                                   'blank' => false, | ||||
|                                   'verbose' => __('the file'), | ||||
|                                   ), | ||||
|                             'filesize' => | ||||
|                             array( | ||||
|                                   'type' => 'Pluf_DB_Field_Integer', | ||||
|                                   'blank' => true, | ||||
|                                   'verbose' => __('file size'), | ||||
|                                   'help_text' => 'Size in bytes.', | ||||
|                                   ), | ||||
|                             'type' =>  | ||||
|                             array( | ||||
|                                   'type' => 'Pluf_DB_Field_Varchar', | ||||
|                                   'blank' => false, | ||||
|                                   'size' => 10, | ||||
|                                   'verbose' => __('type'), | ||||
|                                   'choices' => array( | ||||
|                                                      __('Image') => 'img', | ||||
|                                                      __('Other') => 'other', | ||||
|                                                      ), | ||||
|                                   'default' => 'other', | ||||
|                                   'help_text' => 'The type is to display a thumbnail of the image.', | ||||
|                                   ), | ||||
|                             'creation_dtime' => | ||||
|                             array( | ||||
|                                   'type' => 'Pluf_DB_Field_Datetime', | ||||
|                                   'blank' => true, | ||||
|                                   'verbose' => __('creation date'), | ||||
|                                   ), | ||||
|                             'modif_dtime' => | ||||
|                             array( | ||||
|                                   'type' => 'Pluf_DB_Field_Datetime', | ||||
|                                   'blank' => true, | ||||
|                                   'verbose' => __('modification date'), | ||||
|                                   ), | ||||
|                             ); | ||||
|     } | ||||
|  | ||||
|     function preSave($create=false) | ||||
|     { | ||||
|         if ($this->id == '') { | ||||
|             $this->creation_dtime = gmdate('Y-m-d H:i:s'); | ||||
|             $file = Pluf::f('upload_issue_path').'/'.$this->attachment; | ||||
|             $this->filesize = filesize($file); | ||||
|             // remove .dummy | ||||
|             $this->filename = substr(basename($file), 0, -6);  | ||||
|             $img_extensions = array('jpeg', 'jpg', 'png', 'gif'); | ||||
|             $info = pathinfo($this->filename); | ||||
|             if (in_array(strtolower($info['extension']), $img_extensions)) { | ||||
|                 $this->type = 'img'; | ||||
|             } else { | ||||
|                 $this->type = 'other'; | ||||
|             } | ||||
|         } | ||||
|         $this->modif_dtime = gmdate('Y-m-d H:i:s'); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										52
									
								
								src/IDF/Migrations/3Attachments.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								src/IDF/Migrations/3Attachments.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,52 @@ | ||||
| <?php | ||||
| /* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ | ||||
| /* | ||||
| # ***** BEGIN LICENSE BLOCK ***** | ||||
| # This file is part of InDefero, an open source project management application. | ||||
| # Copyright (C) 2008 Céondo Ltd and contributors. | ||||
| # | ||||
| # InDefero is free software; you can redistribute it and/or modify | ||||
| # it under the terms of the GNU General Public License as published by | ||||
| # the Free Software Foundation; either version 2 of the License, or | ||||
| # (at your option) any later version. | ||||
| # | ||||
| # InDefero is distributed in the hope that it will be useful, | ||||
| # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| # GNU General Public License for more details. | ||||
| # | ||||
| # You should have received a copy of the GNU General Public License | ||||
| # along with this program; if not, write to the Free Software | ||||
| # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA | ||||
| # | ||||
| # ***** END LICENSE BLOCK ***** */ | ||||
|  | ||||
| /** | ||||
|  * Add the download of files. | ||||
|  */ | ||||
|  | ||||
| function IDF_Migrations_3Attachments_up($params=null) | ||||
| { | ||||
|     $models = array( | ||||
|                     'IDF_IssueFile', | ||||
|                     ); | ||||
|     $db = Pluf::db(); | ||||
|     $schema = new Pluf_DB_Schema($db); | ||||
|     foreach ($models as $model) { | ||||
|         $schema->model = new $model(); | ||||
|         $schema->createTables(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| function IDF_Migrations_3Attachments_down($params=null) | ||||
| { | ||||
|     $models = array( | ||||
|                     'IDF_IssueFile', | ||||
|                     ); | ||||
|     $db = Pluf::db(); | ||||
|     $schema = new Pluf_DB_Schema($db); | ||||
|     foreach ($models as $model) { | ||||
|         $schema->model = new $model(); | ||||
|         $schema->dropTables(); | ||||
|     } | ||||
| } | ||||
| @@ -37,6 +37,7 @@ function IDF_Migrations_Install_setup($params=null) | ||||
|                     'IDF_Conf', | ||||
|                     'IDF_Upload', | ||||
|                     'IDF_Search_Occ', | ||||
|                     'IDF_IssueFile', | ||||
|                     ); | ||||
|     $db = Pluf::db(); | ||||
|     $schema = new Pluf_DB_Schema($db); | ||||
|   | ||||
| @@ -141,7 +141,9 @@ class IDF_Views_Issue | ||||
|                         'project' => $prj, | ||||
|                         'user' => $request->user); | ||||
|         if ($request->method == 'POST') { | ||||
|             $form = new IDF_Form_IssueCreate($request->POST, $params); | ||||
|             $form = new IDF_Form_IssueCreate(array_merge($request->POST, | ||||
|                                                          $request->FILES), | ||||
|                                              $params); | ||||
|             if ($form->isValid()) { | ||||
|                 $issue = $form->save(); | ||||
|                 $url = Pluf_HTTP_URL_urlForView('IDF_Views_Issue::index', | ||||
| @@ -246,7 +248,9 @@ class IDF_Views_Issue | ||||
|                             'issue' => $issue, | ||||
|                             ); | ||||
|             if ($request->method == 'POST') { | ||||
|                 $form = new IDF_Form_IssueUpdate($request->POST, $params); | ||||
|                 $form = new IDF_Form_IssueUpdate(array_merge($request->POST,  | ||||
|                                                              $request->FILES), | ||||
|                                                  $params); | ||||
|                 if ($form->isValid()) { | ||||
|                     $issue = $form->save(); | ||||
|                     $url = Pluf_HTTP_URL_urlForView('IDF_Views_Issue::index', | ||||
| @@ -307,6 +311,22 @@ class IDF_Views_Issue | ||||
|                                                $request); | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * Download a given attachment. | ||||
|      */ | ||||
|     public $getAttachment_precond = array('IDF_Precondition::accessIssues'); | ||||
|     public function getAttachment($request, $match) | ||||
|     { | ||||
|         $prj = $request->project; | ||||
|         $attach = Pluf_Shortcuts_GetObjectOr404('IDF_IssueFile', $match[2]); | ||||
|         $prj->inOr404($attach->get_comment()->get_issue()); | ||||
|         $res = new Pluf_HTTP_Response_File(Pluf::f('upload_issue_path').'/'.$attach->attachment, | ||||
|                                            'application/octet-stream'); | ||||
|         $res->headers['Content-Disposition'] = 'attachment; filename="'.$attach->filename.'"'; | ||||
|         return $res; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * View list of issues for a given project with a given status. | ||||
|      */ | ||||
|   | ||||
| @@ -87,6 +87,13 @@ $cfg['url_media'] = 'http://projects.ceondo.com/media'; | ||||
| $cfg['url_upload'] = 'http://projects/ceondo.com/media/upload'; | ||||
| $cfg['upload_path'] = '/path/to/media/upload'; | ||||
|  | ||||
| # | ||||
| # The following path *MUST NOT* be accessible through a web browser  | ||||
| # as user will be able to upload .html, .php files and this can  | ||||
| # create *TERRIBLE* security issues. | ||||
| # | ||||
| $cfg['upload_issue_path'] = '/path/to/attachments'; | ||||
|  | ||||
| $cfg['login_success_url'] = $cfg['url_base'].$cfg['idf_base']; | ||||
| $cfg['after_logout_page'] = $cfg['url_base'].$cfg['idf_base']; | ||||
|  | ||||
|   | ||||
| @@ -133,6 +133,12 @@ $ctl[] = array('regex' => '#^/p/([\-\w]+)/issues/my/(\w+)/$#', | ||||
|                'model' => 'IDF_Views_Issue', | ||||
|                'method' => 'myIssues'); | ||||
|  | ||||
| $ctl[] = array('regex' => '#^/p/([\-\w]+)/issues/attachment/(\d+)/(.*)$#', | ||||
|                'base' => $base, | ||||
|                'priority' => 4, | ||||
|                'model' => 'IDF_Views_Issue', | ||||
|                'method' => 'getAttachment'); | ||||
|  | ||||
| // ---------- SCM ---------------------------------------- | ||||
|  | ||||
| $ctl[] = array('regex' => '#^/p/([\-\w]+)/source/tree/(\w+)/$#', | ||||
|   | ||||
| @@ -26,6 +26,7 @@ $m['IDF_Tag'] = array('relate_to' => array('IDF_Project')); | ||||
| $m['IDF_Issue'] = array('relate_to' => array('IDF_Project', 'Pluf_User', 'IDF_Tag'), | ||||
|                         'relate_to_many' => array('IDF_Tag', 'Pluf_User')); | ||||
| $m['IDF_IssueComment'] = array('relate_to' => array('IDF_Issue', 'Pluf_User')); | ||||
| $m['IDF_IssueFile'] = array('relate_to' => array('IDF_IssueComment', 'Pluf_User')); | ||||
| $m['IDF_Upload'] = array('relate_to' => array('IDF_Project', 'Pluf_User'), | ||||
|                          'relate_to_many' => array('IDF_Tag')); | ||||
| $m['IDF_Search_Occ'] = array('relate_to' => array('IDF_Project'),); | ||||
|   | ||||
| @@ -23,6 +23,12 @@ | ||||
| <td>{if $form.f.content.errors}{$form.f.content.fieldErrors}{/if} | ||||
| {$form.f.content|unsafe} | ||||
| </td> | ||||
| </tr> | ||||
| <tr> | ||||
| <th>{$form.f.attachment.labelTag}:</th> | ||||
| <td>{if $form.f.attachment.errors}{$form.f.attachment.fieldErrors}{/if} | ||||
| {$form.f.attachment|unsafe} | ||||
| </td> | ||||
| </tr>{if $isOwner or $isMember} | ||||
| <tr> | ||||
| <th><strong>{$form.f.status.labelTag}:</strong></th> | ||||
|   | ||||
| @@ -15,7 +15,12 @@ | ||||
| {/if} | ||||
|  | ||||
| <pre class="issue-comment-text">{if strlen($c.content) > 0}{issuetext $c.content, $request}{else}<i>{trans '(No comments were given for this change.)'}</i>{/if}</pre> | ||||
|  | ||||
| {assign $attachments = $c.get_attachment_list()} | ||||
| {if $attachments.count() > 0} | ||||
| <div class="attach"> | ||||
| <ul> | ||||
| {foreach $attachments as $a}<li><a href="{url 'IDF_Views_Issue::getAttachment', array($project.shortname, $a.id, $a.filename)}">{$a.filename}</a> - {$a.filesize|size}</li>{/foreach} | ||||
| </ul></div>{/if} | ||||
| {if $i> 0 and $c.changedIssue()} | ||||
| <div class="issue-changes"> | ||||
| {foreach $c.changes as $w => $v} | ||||
| @@ -54,7 +59,12 @@ | ||||
| {$form.f.content|unsafe} | ||||
| </td> | ||||
| </tr> | ||||
| {if $isOwner or $isMember} | ||||
| <tr> | ||||
| <th>{$form.f.attachment.labelTag}:</th> | ||||
| <td>{if $form.f.attachment.errors}{$form.f.attachment.fieldErrors}{/if} | ||||
| {$form.f.attachment|unsafe} | ||||
| </td> | ||||
| </tr>{if $isOwner or $isMember} | ||||
| <tr> | ||||
| <th><strong>{$form.f.summary.labelTag}:</strong></th> | ||||
| <td>{if $form.f.summary.errors}{$form.f.summary.fieldErrors}{/if} | ||||
|   | ||||
| @@ -228,6 +228,11 @@ span.nobrk { | ||||
|  | ||||
| hr { visibility: hidden; } | ||||
|  | ||||
| div.attach {  | ||||
|   border-top: 2px solid #d3d7cf; | ||||
|   width: 40%; | ||||
| } | ||||
|  | ||||
| textarea {  | ||||
|   font-family: monospace; | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user