diff --git a/app/controllers/context_menus_controller.rb b/app/controllers/context_menus_controller.rb
index 373938a..4e0403a 100644
--- a/app/controllers/context_menus_controller.rb
+++ b/app/controllers/context_menus_controller.rb
@@ -40,5 +40,22 @@ class ContextMenusController < ApplicationController
     
     render :layout => false
   end
-  
+
+  def time_entries
+    @time_entries = TimeEntry.all(:conditions => {:id => params[:ids]}, :include => :project)
+    
+    @projects = @time_entries.collect(&:project).compact.uniq
+    @project = @projects.first if @projects.size == 1
+    
+    @activities = TimeEntryActivity.shared.active
+
+    @can = {:edit => User.current.allowed_to?(:log_time, @projects),
+            :update => User.current.allowed_to?(:log_time, @projects),
+            :delete => User.current.allowed_to?(:log_time, @projects)
+            }
+
+    @back = back_url
+
+    render :layout => false
+  end  
 end
diff --git a/app/controllers/timelog_controller.rb b/app/controllers/timelog_controller.rb
index 604a8f2..8820008 100644
--- a/app/controllers/timelog_controller.rb
+++ b/app/controllers/timelog_controller.rb
@@ -18,7 +18,8 @@
 class TimelogController < ApplicationController
   menu_item :issues
   before_filter :find_project, :only => [:new, :create]
-  before_filter :find_time_entry, :only => [:show, :edit, :update, :destroy]
+  before_filter :find_time_entry, :only => [:show, :edit, :update]
+  before_filter :find_time_entries, :only => [:bulk_edit, :bulk_update, :destroy]
   before_filter :authorize, :except => [:index]
   before_filter :find_optional_project, :only => [:index]
   accept_key_auth :index, :show, :create, :update, :destroy
@@ -160,27 +161,53 @@ class TimelogController < ApplicationController
     end    
   end
 
+  def bulk_edit
+    @available_activities = TimeEntryActivity.shared.active
+    @custom_fields = TimeEntry.first.available_custom_fields
+  end
+
+  def bulk_update
+    attributes = parse_params_for_bulk_time_entry_attributes(params)
+
+    unsaved_time_entry_ids = []
+    @time_entries.each do |time_entry|
+      time_entry.reload
+      time_entry.attributes = attributes
+      call_hook(:controller_time_entries_bulk_edit_before_save, { :params => params, :time_entry => time_entry })
+      unless time_entry.save
+        # Keep unsaved time_entry ids to display them in flash error
+        unsaved_time_entry_ids << time_entry.id
+      end
+    end
+    set_flash_from_bulk_time_entry_save(@time_entries, unsaved_time_entry_ids)
+    redirect_back_or_default({:controller => 'timelog', :action => 'index', :project_id => @projects.first})
+  end
+
   verify :method => :delete, :only => :destroy, :render => {:nothing => true, :status => :method_not_allowed }
   def destroy
-    if @time_entry.destroy && @time_entry.destroyed?
-      respond_to do |format|
-        format.html {
-          flash[:notice] = l(:notice_successful_delete)
-          redirect_to :back
-        }
-        format.api  { head :ok }
-      end
-    else
-      respond_to do |format|
-        format.html {
-          flash[:error] = l(:notice_unable_delete_time_entry)
-          redirect_to :back
-        }
-        format.api  { render_validation_errors(@time_entry) }
+    @time_entries.each do |t| 
+      begin
+        unless t.destroy && t.destroyed?
+          respond_to do |format|
+            format.html {
+              flash[:error] = l(:notice_unable_delete_time_entry)
+              redirect_to :back
+            }
+            format.api  { render_validation_errors(t) }
+          end
+        end
+      rescue ::ActionController::RedirectBackError
+        redirect_to :action => 'index', :project_id => @projects.first
       end
     end
-  rescue ::ActionController::RedirectBackError
-    redirect_to :action => 'index', :project_id => @time_entry.project
+
+    respond_to do |format|
+      format.html {
+        flash[:notice] = l(:notice_successful_delete)
+        redirect_back_or_default(:action => 'index', :project_id => @projects.first)
+      }
+      format.api  { head :ok }
+    end
   end
 
 private
@@ -195,6 +222,26 @@ private
     render_404
   end
 
+  def find_time_entries
+    @time_entries = TimeEntry.find_all_by_id(params[:id] || params[:ids])
+    raise ActiveRecord::RecordNotFound if @time_entries.empty?
+    @projects = @time_entries.collect(&:project).compact.uniq
+    @project = @projects.first if @projects.size == 1
+  rescue ActiveRecord::RecordNotFound
+    render_404
+  end
+
+  def set_flash_from_bulk_time_entry_save(time_entries, unsaved_time_entry_ids)
+    if unsaved_time_entry_ids.empty?
+      flash[:notice] = l(:notice_successful_update) unless time_entries.empty?
+    else
+      flash[:error] = l(:notice_failed_to_save_time_entries,
+                        :count => unsaved_time_entry_ids.size,
+                        :total => time_entries.size,
+                        :ids => '#' + unsaved_time_entry_ids.join(', #'))
+    end
+  end
+
   def find_project
     if (issue_id = (params[:issue_id] || params[:time_entry] && params[:time_entry][:issue_id])).present?
       @issue = Issue.find(issue_id)
@@ -265,4 +312,10 @@ private
     @to   ||= (TimeEntry.latest_date_for_project(@project) || Date.today)
   end
 
+  def parse_params_for_bulk_time_entry_attributes(params)
+    attributes = (params[:time_entry] || {}).reject {|k,v| v.blank?}
+    attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'}
+    attributes[:custom_field_values].reject! {|k,v| v.blank?} if attributes[:custom_field_values]
+    attributes
+  end
 end
diff --git a/app/models/project.rb b/app/models/project.rb
index 0953d78..ce08906 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -431,6 +431,12 @@ class Project < ActiveRecord::Base
   def all_issue_custom_fields
     @all_issue_custom_fields ||= (IssueCustomField.for_all + issue_custom_fields).uniq.sort
   end
+
+# Returns an array of all custom fields enabled for project time entries
+  # (explictly associated custom fields and custom fields enabled for all projects)
+  def all_time_entry_custom_fields
+    @all_time_entry_custom_fields ||= (TimeEntryCustomField.for_all + time_entry_custom_fields).uniq.sort
+  end
   
   def project
     self
diff --git a/app/views/context_menus/time_entries.html.erb b/app/views/context_menus/time_entries.html.erb
new file mode 100644
index 0000000..22b2ecd
--- /dev/null
+++ b/app/views/context_menus/time_entries.html.erb
@@ -0,0 +1,32 @@
+<ul>
+  <%= call_hook(:view_time_entries_context_menu_start, {:time_entries => @time_entries, :can => @can, :back => @back }) %>
+
+<% if !@time_entry.nil? -%>
+	<li><%= context_menu_link l(:button_edit), {:controller => 'timelog', :action => 'edit', :id => @time_entry},
+	        :class => 'icon-edit', :disabled => !@can[:edit] %></li>
+<% else %>
+	<li><%= context_menu_link l(:button_edit), {:controller => 'timelog', :action => 'bulk_edit', :ids => @time_entries.collect(&:id)},
+	        :class => 'icon-edit', :disabled => !@can[:edit] %></li>
+<% end %>
+	<% if @activities.present? -%>
+	<li class="folder">			
+		<a href="#" class="submenu"><%= l(:field_activity) %></a>
+		<ul>
+		<% @activities.each do |u| -%>
+		    <li><%= context_menu_link u.name, {:controller => 'timelog', :action => 'bulk_edit', :ids => @time_entries.collect(&:id), :time_entry => {'activity_id' => u}, :back_url => @back}, :method => :post,
+		                              :selected => (@time_entry && u == @time_entry.activity), :disabled => !@can[:update] %></li>
+		<% end -%>
+		    <li><%= context_menu_link l(:label_nobody), {:controller => 'timelog', :action => 'bulk_edit', :ids => @time_entries.collect(&:id), :time_entry => {'activity_id' => 'none'}, :back_url => @back}, :method => :post,
+		                              :selected => (@time_entry && @time_entry.activity.nil?), :disabled => !@can[:update] %></li>
+		</ul>
+	</li>
+	<% end %>
+
+  <li>
+    <%= context_menu_link l(:button_delete), 
+      {:controller => 'timelog', :action => 'destroy', :ids => @time_entries.collect(&:id), :back_url => @back},
+      :method => :delete, :confirm => l(:text_time_entries_destroy_confirmation), :class => 'icon-del', :disabled => !@can[:delete] %>
+  </li>
+
+  <%= call_hook(:view_time_entries_context_menu_end, {:time_entries => @time_entries, :can => @can, :back => @back }) %>
+</ul>
diff --git a/app/views/timelog/_list.rhtml b/app/views/timelog/_list.rhtml
index 28f1f52..327314e 100644
--- a/app/views/timelog/_list.rhtml
+++ b/app/views/timelog/_list.rhtml
@@ -1,6 +1,20 @@
+<% content_for :header_tags do %>
+    <%= javascript_include_tag 'context_menu' %>
+    <%= stylesheet_link_tag 'context_menu' %>
+    <%= stylesheet_link_tag 'context_menu_rtl' if l(:direction) == 'rtl' %>
+<% end %>
+
+<% form_tag({}) do -%>	
+<%= hidden_field_tag 'back_url', url_for(params) %>
 <table class="list time-entries">
 <thead>
 <tr>
+<th class="checkbox hide-when-print">
+  <%= link_to image_tag('toggle_check.png'), 
+    {}, 
+    :onclick => 'toggleIssuesSelection(Element.up(this, "form")); return false;', 
+    :title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}" %>
+</th>
 <%= sort_header_tag('spent_on', :caption => l(:label_date), :default_order => 'desc') %>
 <%= sort_header_tag('user', :caption => l(:label_member)) %>
 <%= sort_header_tag('activity', :caption => l(:label_activity)) %>
@@ -13,7 +27,8 @@
 </thead>
 <tbody>
 <% entries.each do |entry| -%>
-<tr class="time-entry <%= cycle("odd", "even") %>">
+<tr class="time-entry <%= cycle("odd", "even") %> hascontextmenu">
+<td class="checkbox hide-when-print"><%= check_box_tag("ids[]", entry.id, false, :id => nil) %></td>
 <td class="spent_on"><%= format_date(entry.spent_on) %></td>
 <td class="user"><%=h entry.user %></td>
 <td class="activity"><%=h entry.activity %></td>
@@ -39,3 +54,6 @@
 <% end -%>
 </tbody>
 </table>
+<% end -%>
+
+<%= context_menu time_entries_context_menu_path %>
diff --git a/app/views/timelog/bulk_edit.rhtml b/app/views/timelog/bulk_edit.rhtml
new file mode 100644
index 0000000..3045e25
--- /dev/null
+++ b/app/views/timelog/bulk_edit.rhtml
@@ -0,0 +1,49 @@
+<h2><%= l(:label_bulk_edit_selected_time_entries) %></h2>
+
+<ul><%= @time_entries.collect {|i| content_tag('li', link_to(h("#{i.spent_on.strftime("%Y-%m-%d")} -- #{i.project}:  #{l(:label_f_hour_plural, :value => i.hours)}"), { :action => 'edit', :id => i }))} %></ul>
+
+<% form_tag(:action => 'bulk_update') do %>
+<%= @time_entries.collect {|i| hidden_field_tag('ids[]', i.id)}.join %>
+<div class="box tabular">
+  <fieldset class="attributes">
+  <legend><%= l(:label_change_properties) %></legend>
+  <div>
+    <p>
+      <label><%= l(:field_issue) %></label>
+      <%= text_field :time_entry, :issue_id, :size => 6 %>
+    </p>
+
+    <p>
+      <label><%= l(:field_spent_on) %></label>
+      <%= text_field :time_entry, :spent_on, :size => 10 %><%= calendar_for('time_entry_spent_on') %>
+    </p>
+
+    <p>
+      <label><%= l(:field_hours) %></label>
+      <%= text_field :time_entry, :hours, :size => 6 %>
+    </p>
+
+    <% if @available_activities.any? %>
+    <p>
+      <label><%= l(:field_activity) %></label>
+      <%= select_tag('time_entry[activity_id]', "<option value=\"\">#{l(:label_no_change_option)}</option>" + options_from_collection_for_select(@available_activities, :id, :name)) %>
+    </p>
+    <% end %>
+
+    <p>
+      <label><%= l(:field_comments) %></label>
+      <%= text_field(:time_entry, :comments, :size => 100) %>
+    </p>
+
+    <% @custom_fields.each do |custom_field| %>
+      <p><label><%= h(custom_field.name) %></label> <%= custom_field_tag_for_bulk_edit('time_entry', custom_field) %></p>
+    <% end %>
+
+    <%= call_hook(:view_time_entries_bulk_edit_details_bottom, { :time_entries => @time_entries }) %>
+  </div>
+
+  </fieldset>
+</div>
+
+<p><%= submit_tag l(:button_submit) %></p>
+<% end %>
diff --git a/config/locales/bg.yml b/config/locales/bg.yml
index 1e9402e..693e10f 100644
--- a/config/locales/bg.yml
+++ b/config/locales/bg.yml
@@ -943,3 +943,5 @@ bg:
 
   button_expand_all: Expand all
   button_collapse_all: Collapse all
+  label_bulk_edit_selected_time_entries: Bulk edit selected time entries
+  text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies) ?
diff --git a/config/locales/bs.yml b/config/locales/bs.yml
index 00df00a..f338231 100644
--- a/config/locales/bs.yml
+++ b/config/locales/bs.yml
@@ -957,3 +957,5 @@ bs:
   label_news_comment_added: Comment added to a news
   button_expand_all: Expand all
   button_collapse_all: Collapse all
+  label_bulk_edit_selected_time_entries: Bulk edit selected time entries
+  text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies) ?
diff --git a/config/locales/ca.yml b/config/locales/ca.yml
index acd8dbe..97da589 100644
--- a/config/locales/ca.yml
+++ b/config/locales/ca.yml
@@ -946,3 +946,5 @@ ca:
   label_news_comment_added: Comment added to a news
   button_expand_all: Expand all
   button_collapse_all: Collapse all
+  label_bulk_edit_selected_time_entries: Bulk edit selected time entries
+  text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies) ?
diff --git a/config/locales/cs.yml b/config/locales/cs.yml
index cc76d1f..1e3c9f1 100644
--- a/config/locales/cs.yml
+++ b/config/locales/cs.yml
@@ -947,3 +947,5 @@ cs:
   label_news_comment_added: Comment added to a news
   button_expand_all: Expand all
   button_collapse_all: Collapse all
+  label_bulk_edit_selected_time_entries: Bulk edit selected time entries
+  text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies) ?
diff --git a/config/locales/da.yml b/config/locales/da.yml
index 115ffe5..ffb36dc 100644
--- a/config/locales/da.yml
+++ b/config/locales/da.yml
@@ -959,3 +959,5 @@ da:
   label_news_comment_added: Comment added to a news
   button_expand_all: Expand all
   button_collapse_all: Collapse all
+  label_bulk_edit_selected_time_entries: Bulk edit selected time entries
+  text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies) ?
diff --git a/config/locales/de.yml b/config/locales/de.yml
index 8917a77..fc832ea 100644
--- a/config/locales/de.yml
+++ b/config/locales/de.yml
@@ -960,3 +960,5 @@ de:
   label_news_comment_added: Comment added to a news
   button_expand_all: Expand all
   button_collapse_all: Collapse all
+  label_bulk_edit_selected_time_entries: Bulk edit selected time entries
+  text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies) ?
diff --git a/config/locales/el.yml b/config/locales/el.yml
index 0cd9307..b18325a 100644
--- a/config/locales/el.yml
+++ b/config/locales/el.yml
@@ -943,3 +943,5 @@ el:
   label_news_comment_added: Comment added to a news
   button_expand_all: Expand all
   button_collapse_all: Collapse all
+  label_bulk_edit_selected_time_entries: Bulk edit selected time entries
+  text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies) ?
diff --git a/config/locales/en-GB.yml b/config/locales/en-GB.yml
index 8753e7e..1804129 100644
--- a/config/locales/en-GB.yml
+++ b/config/locales/en-GB.yml
@@ -947,3 +947,5 @@ en-GB:
   label_news_comment_added: Comment added to a news
   button_expand_all: Expand all
   button_collapse_all: Collapse all
+  label_bulk_edit_selected_time_entries: Bulk edit selected time entries
+  text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies) ?
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 277a461..8efd40f 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -735,6 +735,7 @@ en:
   label_default_columns: Default columns
   label_no_change_option: (No change)
   label_bulk_edit_selected_issues: Bulk edit selected issues
+  label_bulk_edit_selected_time_entries: Bulk edit selected time entries
   label_theme: Theme
   label_default: Default
   label_search_titles_only: Search titles only
@@ -891,6 +892,7 @@ en:
   text_status_changed_by_changeset: "Applied in changeset %{value}."
   text_time_logged_by_changeset: "Applied in changeset %{value}."
   text_issues_destroy_confirmation: 'Are you sure you want to delete the selected issue(s) ?'
+  text_time_entries_destroy_confirmation: 'Are you sure you want to delete the selected time entr(y/ies) ?'
   text_select_project_modules: 'Select modules to enable for this project:'
   text_default_administrator_account_changed: Default administrator account changed
   text_file_repository_writable: Attachments directory writable
diff --git a/config/locales/es.yml b/config/locales/es.yml
index 0a1081c..c724012 100644
--- a/config/locales/es.yml
+++ b/config/locales/es.yml
@@ -980,3 +980,5 @@ es:
   label_news_comment_added: Comment added to a news
   button_expand_all: Expand all
   button_collapse_all: Collapse all
+  label_bulk_edit_selected_time_entries: Bulk edit selected time entries
+  text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies) ?
diff --git a/config/locales/eu.yml b/config/locales/eu.yml
index e7bc80c..6f3e604 100644
--- a/config/locales/eu.yml
+++ b/config/locales/eu.yml
@@ -947,3 +947,5 @@ eu:
   label_news_comment_added: Comment added to a news
   button_expand_all: Expand all
   button_collapse_all: Collapse all
+  label_bulk_edit_selected_time_entries: Bulk edit selected time entries
+  text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies) ?
diff --git a/config/locales/fa.yml b/config/locales/fa.yml
index 3f06a9e..145ae7e 100644
--- a/config/locales/fa.yml
+++ b/config/locales/fa.yml
@@ -946,3 +946,5 @@ fa:
   label_news_comment_added: Comment added to a news
   button_expand_all: Expand all
   button_collapse_all: Collapse all
+  label_bulk_edit_selected_time_entries: Bulk edit selected time entries
+  text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies) ?
diff --git a/config/locales/fi.yml b/config/locales/fi.yml
index e1df44f..bb1b6ca 100644
--- a/config/locales/fi.yml
+++ b/config/locales/fi.yml
@@ -964,3 +964,5 @@ fi:
   label_news_comment_added: Comment added to a news
   button_expand_all: Expand all
   button_collapse_all: Collapse all
+  label_bulk_edit_selected_time_entries: Bulk edit selected time entries
+  text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies) ?
diff --git a/config/locales/fr.yml b/config/locales/fr.yml
index e017ea9..ef1880f 100644
--- a/config/locales/fr.yml
+++ b/config/locales/fr.yml
@@ -961,3 +961,5 @@ fr:
   field_member_of_group: Groupe de l'assigné
   field_assigned_to_role: Rôle de l'assigné
   setting_emails_header: En-tête des emails
+  label_bulk_edit_selected_time_entries: Bulk edit selected time entries
+  text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies) ?
diff --git a/config/locales/gl.yml b/config/locales/gl.yml
index ed18b08..72aa180 100644
--- a/config/locales/gl.yml
+++ b/config/locales/gl.yml
@@ -955,3 +955,5 @@ gl:
   label_news_comment_added: Comment added to a news
   button_expand_all: Expand all
   button_collapse_all: Collapse all
+  label_bulk_edit_selected_time_entries: Bulk edit selected time entries
+  text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies) ?
diff --git a/config/locales/he.yml b/config/locales/he.yml
index d4018b8..351756e 100644
--- a/config/locales/he.yml
+++ b/config/locales/he.yml
@@ -948,3 +948,5 @@ he:
   label_news_comment_added: Comment added to a news
   button_expand_all: Expand all
   button_collapse_all: Collapse all
+  label_bulk_edit_selected_time_entries: Bulk edit selected time entries
+  text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies) ?
diff --git a/config/locales/hr.yml b/config/locales/hr.yml
index dfdfe9d..d2f0bc1 100644
--- a/config/locales/hr.yml
+++ b/config/locales/hr.yml
@@ -950,3 +950,5 @@ hr:
   label_news_comment_added: Comment added to a news
   button_expand_all: Expand all
   button_collapse_all: Collapse all
+  label_bulk_edit_selected_time_entries: Bulk edit selected time entries
+  text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies) ?
diff --git a/config/locales/hu.yml b/config/locales/hu.yml
index 450b2a4..e1ea133 100644
--- a/config/locales/hu.yml
+++ b/config/locales/hu.yml
@@ -962,3 +962,5 @@
   label_news_comment_added: Comment added to a news
   button_expand_all: Expand all
   button_collapse_all: Collapse all
+  label_bulk_edit_selected_time_entries: Bulk edit selected time entries
+  text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies) ?
diff --git a/config/locales/id.yml b/config/locales/id.yml
index 0b3f8af..d5f9d18 100644
--- a/config/locales/id.yml
+++ b/config/locales/id.yml
@@ -951,3 +951,5 @@ id:
   label_news_comment_added: Comment added to a news
   button_expand_all: Expand all
   button_collapse_all: Collapse all
+  label_bulk_edit_selected_time_entries: Bulk edit selected time entries
+  text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies) ?
diff --git a/config/locales/it.yml b/config/locales/it.yml
index b5a13ee..c8aebec 100644
--- a/config/locales/it.yml
+++ b/config/locales/it.yml
@@ -944,3 +944,5 @@ it:
   label_news_comment_added: Comment added to a news
   button_expand_all: Expand all
   button_collapse_all: Collapse all
+  label_bulk_edit_selected_time_entries: Bulk edit selected time entries
+  text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies) ?
diff --git a/config/locales/ja.yml b/config/locales/ja.yml
index d137bd7..76c7a57 100644
--- a/config/locales/ja.yml
+++ b/config/locales/ja.yml
@@ -964,3 +964,5 @@ ja:
   label_news_comment_added: Comment added to a news
   button_expand_all: Expand all
   button_collapse_all: Collapse all
+  label_bulk_edit_selected_time_entries: Bulk edit selected time entries
+  text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies) ?
diff --git a/config/locales/ko.yml b/config/locales/ko.yml
index a394708..526fc58 100644
--- a/config/locales/ko.yml
+++ b/config/locales/ko.yml
@@ -995,3 +995,5 @@ ko:
   label_news_comment_added: Comment added to a news
   button_expand_all: Expand all
   button_collapse_all: Collapse all
+  label_bulk_edit_selected_time_entries: Bulk edit selected time entries
+  text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies) ?
diff --git a/config/locales/lt.yml b/config/locales/lt.yml
index 2100266..1d0564d 100644
--- a/config/locales/lt.yml
+++ b/config/locales/lt.yml
@@ -1003,3 +1003,5 @@ lt:
   label_news_comment_added: Comment added to a news
   button_expand_all: Expand all
   button_collapse_all: Collapse all
+  label_bulk_edit_selected_time_entries: Bulk edit selected time entries
+  text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies) ?
diff --git a/config/locales/lv.yml b/config/locales/lv.yml
index be481cc..4e9dfac 100644
--- a/config/locales/lv.yml
+++ b/config/locales/lv.yml
@@ -938,3 +938,5 @@ lv:
   label_news_comment_added: Comment added to a news
   button_expand_all: Expand all
   button_collapse_all: Collapse all
+  label_bulk_edit_selected_time_entries: Bulk edit selected time entries
+  text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies) ?
diff --git a/config/locales/mk.yml b/config/locales/mk.yml
index 3dfdfe3..ff11c57 100644
--- a/config/locales/mk.yml
+++ b/config/locales/mk.yml
@@ -943,3 +943,5 @@ mk:
   label_news_comment_added: Comment added to a news
   button_expand_all: Expand all
   button_collapse_all: Collapse all
+  label_bulk_edit_selected_time_entries: Bulk edit selected time entries
+  text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies) ?
diff --git a/config/locales/mn.yml b/config/locales/mn.yml
index 9aa6876..f986e1f 100644
--- a/config/locales/mn.yml
+++ b/config/locales/mn.yml
@@ -944,3 +944,5 @@ mn:
   label_news_comment_added: Comment added to a news
   button_expand_all: Expand all
   button_collapse_all: Collapse all
+  label_bulk_edit_selected_time_entries: Bulk edit selected time entries
+  text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies) ?
diff --git a/config/locales/nl.yml b/config/locales/nl.yml
index 72ab538..7c24c8e 100644
--- a/config/locales/nl.yml
+++ b/config/locales/nl.yml
@@ -925,3 +925,5 @@ nl:
   label_news_comment_added: Commentaar toegevoegd aan een nieuwsitem
   button_expand_all: Expand all
   button_collapse_all: Collapse all
+  label_bulk_edit_selected_time_entries: Bulk edit selected time entries
+  text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies) ?
diff --git a/config/locales/no.yml b/config/locales/no.yml
index 903dddd..70f6be0 100644
--- a/config/locales/no.yml
+++ b/config/locales/no.yml
@@ -930,3 +930,5 @@
   label_news_comment_added: Comment added to a news
   button_expand_all: Expand all
   button_collapse_all: Collapse all
+  label_bulk_edit_selected_time_entries: Bulk edit selected time entries
+  text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies) ?
diff --git a/config/locales/pl.yml b/config/locales/pl.yml
index 7609e5e..e685ae1 100644
--- a/config/locales/pl.yml
+++ b/config/locales/pl.yml
@@ -960,3 +960,5 @@ pl:
   label_news_comment_added: Comment added to a news
   button_expand_all: Expand all
   button_collapse_all: Collapse all
+  label_bulk_edit_selected_time_entries: Bulk edit selected time entries
+  text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies) ?
diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml
index a8c2b85..d67aca7 100644
--- a/config/locales/pt-BR.yml
+++ b/config/locales/pt-BR.yml
@@ -966,3 +966,5 @@ pt-BR:
   label_news_comment_added: Notícia recebeu um comentário
   button_expand_all: Expand all
   button_collapse_all: Collapse all
+  label_bulk_edit_selected_time_entries: Bulk edit selected time entries
+  text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies) ?
diff --git a/config/locales/pt.yml b/config/locales/pt.yml
index ec58345..41d4b23 100644
--- a/config/locales/pt.yml
+++ b/config/locales/pt.yml
@@ -947,3 +947,5 @@ pt:
   label_news_comment_added: Comment added to a news
   button_expand_all: Expand all
   button_collapse_all: Collapse all
+  label_bulk_edit_selected_time_entries: Bulk edit selected time entries
+  text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies) ?
diff --git a/config/locales/ro.yml b/config/locales/ro.yml
index 21713e7..de45ccf 100644
--- a/config/locales/ro.yml
+++ b/config/locales/ro.yml
@@ -936,3 +936,5 @@ ro:
   label_news_comment_added: Comment added to a news
   button_expand_all: Expand all
   button_collapse_all: Collapse all
+  label_bulk_edit_selected_time_entries: Bulk edit selected time entries
+  text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies) ?
diff --git a/config/locales/ru.yml b/config/locales/ru.yml
index 91c5cd2..ade3e84 100644
--- a/config/locales/ru.yml
+++ b/config/locales/ru.yml
@@ -1056,3 +1056,5 @@ ru:
   label_news_comment_added: Comment added to a news
   button_expand_all: Expand all
   button_collapse_all: Collapse all
+  label_bulk_edit_selected_time_entries: Bulk edit selected time entries
+  text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies) ?
diff --git a/config/locales/sk.yml b/config/locales/sk.yml
index 40fde42..f714d4a 100644
--- a/config/locales/sk.yml
+++ b/config/locales/sk.yml
@@ -938,3 +938,5 @@ sk:
   label_news_comment_added: Comment added to a news
   button_expand_all: Expand all
   button_collapse_all: Collapse all
+  label_bulk_edit_selected_time_entries: Bulk edit selected time entries
+  text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies) ?
diff --git a/config/locales/sl.yml b/config/locales/sl.yml
index 0ee2a2a..8b499c9 100644
--- a/config/locales/sl.yml
+++ b/config/locales/sl.yml
@@ -939,3 +939,5 @@ sl:
   label_news_comment_added: Comment added to a news
   button_expand_all: Expand all
   button_collapse_all: Collapse all
+  label_bulk_edit_selected_time_entries: Bulk edit selected time entries
+  text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies) ?
diff --git a/config/locales/sr-YU.yml b/config/locales/sr-YU.yml
index afbbf19..952107d 100644
--- a/config/locales/sr-YU.yml
+++ b/config/locales/sr-YU.yml
@@ -943,3 +943,5 @@ sr-YU:
   label_news_comment_added: Comment added to a news
   button_expand_all: Expand all
   button_collapse_all: Collapse all
+  label_bulk_edit_selected_time_entries: Bulk edit selected time entries
+  text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies) ?
diff --git a/config/locales/sr.yml b/config/locales/sr.yml
index 733c96b..624ae2f 100644
--- a/config/locales/sr.yml
+++ b/config/locales/sr.yml
@@ -944,3 +944,5 @@ sr:
   label_news_comment_added: Comment added to a news
   button_expand_all: Expand all
   button_collapse_all: Collapse all
+  label_bulk_edit_selected_time_entries: Bulk edit selected time entries
+  text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies) ?
diff --git a/config/locales/sv.yml b/config/locales/sv.yml
index 2c30f56..0637c3a 100644
--- a/config/locales/sv.yml
+++ b/config/locales/sv.yml
@@ -984,3 +984,5 @@ sv:
   label_news_comment_added: Comment added to a news
   button_expand_all: Expand all
   button_collapse_all: Collapse all
+  label_bulk_edit_selected_time_entries: Bulk edit selected time entries
+  text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies) ?
diff --git a/config/locales/th.yml b/config/locales/th.yml
index 5a72100..384e138 100644
--- a/config/locales/th.yml
+++ b/config/locales/th.yml
@@ -940,3 +940,5 @@ th:
   label_news_comment_added: Comment added to a news
   button_expand_all: Expand all
   button_collapse_all: Collapse all
+  label_bulk_edit_selected_time_entries: Bulk edit selected time entries
+  text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies) ?
diff --git a/config/locales/tr.yml b/config/locales/tr.yml
index 55ba92b..725d60a 100644
--- a/config/locales/tr.yml
+++ b/config/locales/tr.yml
@@ -962,3 +962,5 @@ tr:
   label_news_comment_added: Comment added to a news
   button_expand_all: Expand all
   button_collapse_all: Collapse all
+  label_bulk_edit_selected_time_entries: Bulk edit selected time entries
+  text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies) ?
diff --git a/config/locales/uk.yml b/config/locales/uk.yml
index 4da5cdf..7b580c6 100644
--- a/config/locales/uk.yml
+++ b/config/locales/uk.yml
@@ -939,3 +939,5 @@ uk:
   label_news_comment_added: Comment added to a news
   button_expand_all: Expand all
   button_collapse_all: Collapse all
+  label_bulk_edit_selected_time_entries: Bulk edit selected time entries
+  text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies) ?
diff --git a/config/locales/vi.yml b/config/locales/vi.yml
index 5caee5e..1a4f4a7 100644
--- a/config/locales/vi.yml
+++ b/config/locales/vi.yml
@@ -994,3 +994,5 @@ vi:
   label_news_comment_added: Comment added to a news
   button_expand_all: Expand all
   button_collapse_all: Collapse all
+  label_bulk_edit_selected_time_entries: Bulk edit selected time entries
+  text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies) ?
diff --git a/config/locales/zh-TW.yml b/config/locales/zh-TW.yml
index 20a2414..326cdb7 100644
--- a/config/locales/zh-TW.yml
+++ b/config/locales/zh-TW.yml
@@ -1025,3 +1025,5 @@
   label_news_comment_added: Comment added to a news
   button_expand_all: Expand all
   button_collapse_all: Collapse all
+  label_bulk_edit_selected_time_entries: Bulk edit selected time entries
+  text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies) ?
diff --git a/config/locales/zh.yml b/config/locales/zh.yml
index 4700cb9..46680c2 100644
--- a/config/locales/zh.yml
+++ b/config/locales/zh.yml
@@ -957,3 +957,5 @@ zh:
   label_news_comment_added: Comment added to a news
   button_expand_all: Expand all
   button_collapse_all: Collapse all
+  label_bulk_edit_selected_time_entries: Bulk edit selected time entries
+  text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies) ?
diff --git a/config/routes.rb b/config/routes.rb
index 383d9cf..03e9142 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -21,6 +21,10 @@ ActionController::Routing::Routes.draw do |map|
     time_report.connect 'projects/:project_id/time_entries/report.:format'
   end
 
+  map.bulk_edit_time_entry 'time_entries/bulk_edit', :controller => 'timelog', :action => 'bulk_edit', :conditions => { :method => :get }
+  map.bulk_update_time_entry 'time_entries/bulk_edit', :controller => 'timelog', :action => 'bulk_update', :conditions => { :method => :post }
+  map.time_entries_context_menu '/time_entries/context_menu', :controller => 'context_menus', :action => 'time_entries'
+
   # TODO: wasteful since this is also nested under issues, projects, and projects/issues
   map.resources :time_entries, :controller => 'timelog'
   
@@ -101,6 +105,7 @@ ActionController::Routing::Routes.draw do |map|
   map.resources :issues, :path_prefix => '/projects/:project_id', :collection => { :create => :post } do |issues|
     issues.resources :time_entries, :controller => 'timelog'
   end
+  # map.time_entries_context_menu '/time_entries/context_menu', :controller => 'context_menus', :action => 'time_entries'
 
   map.with_options  :controller => 'issue_relations', :conditions => {:method => :post} do |relations|
     relations.connect 'issues/:issue_id/relations/:id', :action => 'new'
diff --git a/lib/redmine.rb b/lib/redmine.rb
index 87efc72..7770476 100644
--- a/lib/redmine.rb
+++ b/lib/redmine.rb
@@ -84,10 +84,10 @@ Redmine::AccessControl.map do |map|
   end
   
   map.project_module :time_tracking do |map|
-    map.permission :log_time, {:timelog => [:new, :create, :edit, :update]}, :require => :loggedin
+    map.permission :log_time, {:timelog => [:new, :create, :edit, :update, :bulk_edit, :bulk_update]}, :require => :loggedin
     map.permission :view_time_entries, :timelog => [:index, :show], :time_entry_reports => [:report]
-    map.permission :edit_time_entries, {:timelog => [:new, :create, :edit, :update, :destroy]}, :require => :member
-    map.permission :edit_own_time_entries, {:timelog => [:new, :create, :edit, :update, :destroy]}, :require => :loggedin
+    map.permission :edit_time_entries, {:timelog => [:new, :create, :edit, :update, :destroy, :bulk_edit, :bulk_update]}, :require => :member
+    map.permission :edit_own_time_entries, {:timelog => [:new, :create, :edit, :update, :destroy,:bulk_edit, :bulk_update]}, :require => :loggedin
     map.permission :manage_project_activities, {:project_enumerations => [:update, :destroy]}, :require => :member
   end
   
diff --git a/test/fixtures/custom_fields.yml b/test/fixtures/custom_fields.yml
index c80f85d..b99de35 100644
--- a/test/fixtures/custom_fields.yml
+++ b/test/fixtures/custom_fields.yml
@@ -129,3 +129,17 @@ custom_fields_009:
   field_format: date
   default_value: ""
   editable: true
+custom_fields_010: 
+  name: Overtime
+  min_length: 0
+  regexp: ""
+  is_for_all: false
+  is_filter: false
+  type: TimeEntryCustomField
+  max_length: 0
+  possible_values: ""
+  id: 10
+  is_required: false
+  field_format: bool
+  default_value: 0
+  editable: true
diff --git a/test/fixtures/time_entries.yml b/test/fixtures/time_entries.yml
index f56b05a..1b3c9ca 100644
--- a/test/fixtures/time_entries.yml
+++ b/test/fixtures/time_entries.yml
@@ -55,4 +55,18 @@ time_entries_004:
   hours: 7.65
   user_id: 1
   tyear: 2007
+time_entries_005: 
+  created_on: 2011-03-22 12:20:48 +02:00
+  tweek: 12
+  tmonth: 3
+  project_id: 5
+  comments: Time spent on a subproject
+  updated_on: 2011-03-22 12:20:48 +02:00
+  activity_id: 10
+  spent_on: 2011-03-22
+  issue_id: 
+  id: 5
+  hours: 7.65
+  user_id: 1
+  tyear: 2011
   
diff --git a/test/functional/timelog_controller_test.rb b/test/functional/timelog_controller_test.rb
index 634363d..b520f92 100644
--- a/test/functional/timelog_controller_test.rb
+++ b/test/functional/timelog_controller_test.rb
@@ -131,6 +131,77 @@ class TimelogControllerTest < ActionController::TestCase
     assert_equal 2, entry.issue_id
     assert_equal 2, entry.user_id
   end
+
+  def test_get_bulk_edit
+    @request.session[:user_id] = 2
+    get :bulk_edit, :ids => [1, 2]
+    assert_response :success
+    assert_template 'bulk_edit'
+    
+    # System wide custom field
+    assert_tag :select, :attributes => {:name => 'time_entry[custom_field_values][10]'}
+  end
+
+  def test_get_bulk_edit_on_different_projects
+    @request.session[:user_id] = 2
+    get :bulk_edit, :ids => [1, 2, 6]
+    assert_response :success
+    assert_template 'bulk_edit'
+  end
+
+  def test_bulk_update
+    @request.session[:user_id] = 2
+    # update time entry activity
+    post :bulk_update, :ids => [1, 2], :time_entry => { :activity_id => 9}
+                                     
+    assert_response 302
+    # check that the issues were updated
+    assert_equal [9, 9], TimeEntry.find_all_by_id([1, 2]).collect {|i| i.activity_id}
+  end
+
+  def test_bulk_update_on_different_projects
+    @request.session[:user_id] = 2
+    # update time entry activity
+    post :bulk_update, :ids => [1, 2, 4], :time_entry => { :activity_id => 9 }
+    
+    assert_response 302
+    # check that the issues were updated
+    assert_equal [9, 9, 9], TimeEntry.find_all_by_id([1, 2, 4]).collect {|i| i.activity_id}
+  end
+
+  def test_bulk_update_on_different_projects_without_rights
+    @request.session[:user_id] = 3
+    user = User.find(3)
+    action = { :controller => "timelog", :action => "bulk_update" }
+    assert user.allowed_to?(action, TimeEntry.find(1).project)
+    assert ! user.allowed_to?(action, TimeEntry.find(5).project)
+    post :bulk_update, :ids => [1, 5], :time_entry => { :activity_id => 9 }
+    assert_response 403
+  end
+
+  def test_bulk_update_custom_field
+    @request.session[:user_id] = 2
+    post :bulk_update, :ids => [1, 2], :time_entry => { :custom_field_values => {'10' => '0'} }
+                                     
+    assert_response 302
+    assert_equal ["0", "0"], TimeEntry.find_all_by_id([1, 2]).collect {|i| i.custom_value_for(10).value}
+  end
+
+  def test_post_bulk_update_should_redirect_back_using_the_back_url_parameter
+    @request.session[:user_id] = 2
+    post :bulk_update, :ids => [1,2], :back_url => '/time_entries'
+
+    assert_response :redirect
+    assert_redirected_to '/time_entries'
+  end
+
+  def test_post_bulk_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host
+    @request.session[:user_id] = 2
+    post :bulk_update, :ids => [1,2], :back_url => 'http://google.com'
+
+    assert_response :redirect
+    assert_redirected_to :controller => 'timelog', :action => 'index', :project_id => Project.find(1).identifier
+  end
   
   def test_destroy
     @request.session[:user_id] = 2
@@ -236,8 +307,8 @@ class TimelogControllerTest < ActionController::TestCase
     get :index, :format => 'csv'
     assert_response :success
     assert_equal 'text/csv', @response.content_type
-    assert @response.body.include?("Date,User,Activity,Project,Issue,Tracker,Subject,Hours,Comment\n")
-    assert @response.body.include?("\n04/21/2007,redMine Admin,Design,eCookbook,3,Bug,Error 281 when updating a recipe,1.0,\"\"\n")
+    assert @response.body.include?("Date,User,Activity,Project,Issue,Tracker,Subject,Hours,Comment,Overtime\n")
+    assert @response.body.include?("\n04/21/2007,redMine Admin,Design,eCookbook,3,Bug,Error 281 when updating a recipe,1.0,\"\",\"\"\n")
   end
   
   def test_index_csv_export
@@ -245,7 +316,7 @@ class TimelogControllerTest < ActionController::TestCase
     get :index, :project_id => 1, :format => 'csv'
     assert_response :success
     assert_equal 'text/csv', @response.content_type
-    assert @response.body.include?("Date,User,Activity,Project,Issue,Tracker,Subject,Hours,Comment\n")
-    assert @response.body.include?("\n04/21/2007,redMine Admin,Design,eCookbook,3,Bug,Error 281 when updating a recipe,1.0,\"\"\n")
+    assert @response.body.include?("Date,User,Activity,Project,Issue,Tracker,Subject,Hours,Comment,Overtime\n")
+    assert @response.body.include?("\n04/21/2007,redMine Admin,Design,eCookbook,3,Bug,Error 281 when updating a recipe,1.0,\"\",\"\"\n")
   end
 end
