Index: trunk/app/controllers/issues_controller.rb
===================================================================
--- trunk/app/controllers/issues_controller.rb	(revision 872)
+++ trunk/app/controllers/issues_controller.rb	(revision 873)
@@ -174,6 +174,7 @@
     @assignables << @issue.assigned_to if @issue.assigned_to && !@assignables.include?(@issue.assigned_to)
     @can = {:edit => User.current.allowed_to?(:edit_issues, @project),
             :change_status => User.current.allowed_to?(:change_issue_status, @project),
+            :add => User.current.allowed_to?(:add_issues, @project),
             :move => User.current.allowed_to?(:move_issues, @project),
             :delete => User.current.allowed_to?(:delete_issues, @project)}
     render :layout => false
Index: trunk/app/controllers/projects_controller.rb
===================================================================
--- trunk/app/controllers/projects_controller.rb	(revision 872)
+++ trunk/app/controllers/projects_controller.rb	(revision 873)
@@ -192,43 +192,45 @@
   end
 
   # Add a new issue to @project
+  # The new issue will be created from an existing one if copy_from parameter is given
   def add_issue
-    @tracker = Tracker.find(params[:tracker_id])
-    @priorities = Enumeration::get_values('IPRI')
+    @issue = params[:copy_from] ? Issue.new.copy_from(params[:copy_from]) : Issue.new(params[:issue])
+    @issue.project = @project
+    @issue.author = User.current
+    @issue.tracker ||= Tracker.find(params[:tracker_id])
     
     default_status = IssueStatus.default
     unless default_status
-      flash.now[:error] = 'No default issue status defined. Please check your configuration.'
+      flash.now[:error] = 'No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses").'
       render :nothing => true, :layout => true
       return
-    end
-    @issue = Issue.new(:project => @project, :tracker => @tracker)    
+    end    
     @issue.status = default_status
     @allowed_statuses = ([default_status] + default_status.find_new_statuses_allowed_to(logged_in_user.role_for_project(@project), @issue.tracker))if logged_in_user
+    
     if request.get?
-      @issue.start_date = Date.today
-      @custom_values = @project.custom_fields_for_issues(@tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue) }
+      @issue.start_date ||= Date.today
+      @custom_values = @issue.custom_values.empty? ?
+        @project.custom_fields_for_issues(@issue.tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue) } :
+        @issue.custom_values
     else
-      @issue.attributes = params[:issue]
-      
       requested_status = IssueStatus.find_by_id(params[:issue][:status_id])
+      # Check that the user is allowed to apply the requested status
       @issue.status = (@allowed_statuses.include? requested_status) ? requested_status : default_status
-      
-      @issue.author_id = self.logged_in_user.id if self.logged_in_user
-      # Multiple file upload
-      @attachments = []
-      params[:attachments].each { |a|
-        @attachments << Attachment.new(:container => @issue, :file => a, :author => logged_in_user) unless a.size == 0
-      } if params[:attachments] and params[:attachments].is_a? Array
-      @custom_values = @project.custom_fields_for_issues(@tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue, :value => params["custom_fields"][x.id.to_s]) }
+      @custom_values = @project.custom_fields_for_issues(@issue.tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue, :value => params["custom_fields"][x.id.to_s]) }
       @issue.custom_values = @custom_values
       if @issue.save
-        @attachments.each(&:save)
+        if params[:attachments] && params[:attachments].is_a?(Array)
+          # Save attachments
+          params[:attachments].each {|a| Attachment.create(:container => @issue, :file => a, :author => User.current) unless a.size == 0}
+        end
         flash[:notice] = l(:notice_successful_create)
         Mailer.deliver_issue_add(@issue) if Setting.notified_events.include?('issue_added')
         redirect_to :action => 'list_issues', :id => @project
+        return
       end		
     end	
+    @priorities = Enumeration::get_values('IPRI')
   end
 
   # Show filtered/sorted issues list of @project
Index: trunk/app/models/issue.rb
===================================================================
--- trunk/app/models/issue.rb	(revision 872)
+++ trunk/app/models/issue.rb	(revision 873)
@@ -54,6 +54,13 @@
     end
   end
   
+  def copy_from(arg)
+    issue = arg.is_a?(Issue) ? arg : Issue.find(arg)
+    self.attributes = issue.attributes.dup
+    self.custom_values = issue.custom_values.collect {|v| v.clone}
+    self
+  end
+  
   def priority_id=(pid)
     self.priority = nil
     write_attribute(:priority_id, pid)
Index: trunk/app/views/issues/context_menu.rhtml
===================================================================
--- trunk/app/views/issues/context_menu.rhtml	(revision 872)
+++ trunk/app/views/issues/context_menu.rhtml	(revision 873)
@@ -31,6 +31,8 @@
 		                              :selected => @issue.assigned_to.nil?, :disabled => !(@can[:edit] || @can[:change_status]) %></li>
 		</ul>
 	</li>
+	<li><%= context_menu_link l(:button_copy), {:controller => 'projects', :action => 'add_issue', :id => @project, :copy_from => @issue},
+	        :class => 'icon-copy', :disabled => !@can[:add] %></li>
 	<li><%= context_menu_link l(:button_move), {:controller => 'projects', :action => 'move_issues', :id => @project, "issue_ids[]" => @issue.id },
 	                          :class => 'icon-move', :disabled => !@can[:move]  %>
     <li><%= context_menu_link l(:button_delete), {:controller => 'issues', :action => 'destroy', :id => @issue},
Index: trunk/app/views/issues/show.rhtml
===================================================================
--- trunk/app/views/issues/show.rhtml	(revision 872)
+++ trunk/app/views/issues/show.rhtml	(revision 873)
@@ -3,6 +3,7 @@
 <%= link_to_if_authorized l(:button_edit), {:controller => 'issues', :action => 'edit', :id => @issue}, :class => 'icon icon-edit', :accesskey => accesskey(:edit) %>
 <%= link_to_if_authorized l(:button_log_time), {:controller => 'timelog', :action => 'edit', :issue_id => @issue}, :class => 'icon icon-time' %>
 <%= watcher_tag(@issue, User.current) %>
+<%= link_to_if_authorized l(:button_copy), {:controller => 'projects', :action => 'add_issue', :id => @project, :copy_from => @issue }, :class => 'icon icon-copy' %>
 <%= link_to_if_authorized l(:button_move), {:controller => 'projects', :action => 'move_issues', :id => @project, "issue_ids[]" => @issue.id }, :class => 'icon icon-move' %>
 <%= link_to_if_authorized l(:button_delete), {:controller => 'issues', :action => 'destroy', :id => @issue}, :confirm => l(:text_are_you_sure), :method => :post, :class => 'icon icon-del' %>
 </div>
Index: trunk/app/views/projects/add_issue.rhtml
===================================================================
--- trunk/app/views/projects/add_issue.rhtml	(revision 872)
+++ trunk/app/views/projects/add_issue.rhtml	(revision 873)
@@ -1,10 +1,10 @@
-<h2><%=l(:label_issue_new)%>: <%= @tracker.name %></h2>
+<h2><%=l(:label_issue_new)%>: <%= @issue.tracker %></h2>
 
 <% labelled_tabular_form_for :issue, @issue, 
                              :url => {:action => 'add_issue'},
                              :html => {:multipart => true, :id => 'issue-form'} do |f| %>
-    <%= hidden_field_tag 'tracker_id', @tracker.id %>
-    <%= render :partial => 'issues/form', :locals => {:f => f} %>    
+    <%= f.hidden_field :tracker_id %>
+    <%= render :partial => 'issues/form', :locals => {:f => f} %>
     <%= submit_tag l(:button_create) %>
     <%= link_to_remote l(:label_preview), 
                        { :url => { :controller => 'issues', :action => 'preview', :id => @issue },
Index: trunk/lang/en.yml
===================================================================
--- trunk/lang/en.yml	(revision 872)
+++ trunk/lang/en.yml	(revision 873)
@@ -476,6 +476,7 @@
 button_reset: Reset
 button_rename: Rename
 button_change_password: Change password
+button_copy: Copy
 
 status_active: active
 status_registered: registered
Index: trunk/lang/fr.yml
===================================================================
--- trunk/lang/fr.yml	(revision 872)
+++ trunk/lang/fr.yml	(revision 873)
@@ -476,6 +476,7 @@
 button_reset: Réinitialiser
 button_rename: Renommer
 button_change_password: Changer de mot de passe
+button_copy: Copier
 
 status_active: actif
 status_registered: enregistré
Index: trunk/public/images/copy.png
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream

Property changes on: trunk/public/images/copy.png
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+application/octet-stream
\ No newline at end of property
Index: trunk/public/stylesheets/application.css
===================================================================
--- trunk/public/stylesheets/application.css	(revision 872)
+++ trunk/public/stylesheets/application.css	(revision 873)
@@ -421,6 +421,7 @@
 
 .icon-add { background-image: url(../images/add.png); }
 .icon-edit { background-image: url(../images/edit.png); }
+.icon-copy { background-image: url(../images/copy.png); }
 .icon-del { background-image: url(../images/delete.png); }
 .icon-move { background-image: url(../images/move.png); }
 .icon-save { background-image: url(../images/save.png); }
Index: trunk/test/functional/projects_controller_test.rb
===================================================================
--- trunk/test/functional/projects_controller_test.rb	(revision 872)
+++ trunk/test/functional/projects_controller_test.rb	(revision 873)
@@ -22,7 +22,7 @@
 class ProjectsController; def rescue_action(e) raise e end; end
 
 class ProjectsControllerTest < Test::Unit::TestCase
-  fixtures :projects, :users, :roles, :enabled_modules, :enumerations
+  fixtures :projects, :users, :roles, :members, :issues, :enabled_modules, :enumerations
 
   def setup
     @controller = ProjectsController.new
@@ -143,4 +143,23 @@
     assert_redirected_to 'admin/projects'
     assert Project.find(1).active?
   end
+  
+  def test_add_issue
+    @request.session[:user_id] = 2
+    get :add_issue, :id => 1, :tracker_id => 1
+    assert_response :success
+    assert_template 'add_issue'
+    post :add_issue, :id => 1, :issue => {:tracker_id => 1, :subject => 'This is the test_add_issue issue', :description => 'This is the description', :priority_id => 5}
+    assert_redirected_to 'projects/list_issues'
+    assert Issue.find_by_subject('This is the test_add_issue issue')
+  end
+  
+  def test_copy_issue
+    @request.session[:user_id] = 2
+    get :add_issue, :id => 1, :copy_from => 1
+    assert_template 'add_issue'
+    assert_not_nil assigns(:issue)
+    orig = Issue.find(1)
+    assert_equal orig.subject, assigns(:issue).subject
+  end
 end
Index: trunk/test/unit/issue_test.rb
===================================================================
--- trunk/test/unit/issue_test.rb	(revision 872)
+++ trunk/test/unit/issue_test.rb	(revision 873)
@@ -18,7 +18,7 @@
 require File.dirname(__FILE__) + '/../test_helper'
 
 class IssueTest < Test::Unit::TestCase
-  fixtures :projects, :users, :members, :trackers, :issue_statuses, :issue_categories, :enumerations, :issues
+  fixtures :projects, :users, :members, :trackers, :issue_statuses, :issue_categories, :enumerations, :issues, :custom_fields, :custom_values
 
   def test_category_based_assignment
     issue = Issue.create(:project_id => 1, :tracker_id => 1, :author_id => 3, :status_id => 1, :priority => Enumeration.get_values('IPRI').first, :subject => 'Assignment test', :description => 'Assignment test', :category_id => 1)
@@ -25,6 +25,16 @@
     assert_equal IssueCategory.find(1).assigned_to, issue.assigned_to
   end
   
+  def test_copy
+    issue = Issue.new.copy_from(1)
+    assert issue.save
+    issue.reload
+    orig = Issue.find(1)
+    assert_equal orig.subject, issue.subject
+    assert_equal orig.tracker, issue.tracker
+    assert_equal orig.custom_values.first.value, issue.custom_values.first.value
+  end
+  
   def test_close_duplicates
     # Create 3 issues
     issue1 = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :priority => Enumeration.get_values('IPRI').first, :subject => 'Duplicates test', :description => 'Duplicates test')
