Compare commits

..
26 Commits
Author SHA1 Message Date
Jean-Philippe Lang 17517575ec tagged version 1.1.0
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/tags/1.1.0@4672 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-09 16:27:39 +00:00
Jean-Philippe Lang f8af1bebd7 Merged r4670 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4671 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-09 15:58:15 +00:00
Jean-Philippe Lang 6e695a4d1a Merged r4645 to r4651 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4660 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-08 10:14:00 +00:00
Jean-Philippe Lang e5b7c94cb4 Merged r4643 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4659 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-08 10:09:04 +00:00
Jean-Philippe Lang 9028e664c4 Merged r4656 and r4657 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4658 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-08 10:04:38 +00:00
Toshi MARUYAMA 6318affd31 Merged r4652 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4653 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-08 00:24:43 +00:00
Jean-Philippe Lang 8794cd3344 Merged r4610 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4649 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-06 21:04:24 +00:00
Toshi MARUYAMA 2fcd4e5271 Merged r4636 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4642 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-05 15:12:08 +00:00
Toshi MARUYAMA ea60705ca7 Merged r4635 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4641 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-05 15:11:47 +00:00
Toshi MARUYAMA 90bce4e366 Merged r4634 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4640 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-05 15:11:21 +00:00
Toshi MARUYAMA 9f7cc355ad Merged r4633 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4639 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-05 15:10:58 +00:00
Toshi MARUYAMA 6ee4c0bac7 Merged r4632 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4638 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-05 15:10:38 +00:00
Toshi MARUYAMA b0f0bd1848 Merged r4631 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4637 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-05 15:10:17 +00:00
Toshi MARUYAMA fe563a8802 Merged r4629 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4630 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-04 16:39:45 +00:00
Toshi MARUYAMA 8ebab00767 Merged r4625 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4628 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-03 10:55:07 +00:00
Toshi MARUYAMA d97297e45d Merged r4624 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4627 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-03 10:54:29 +00:00
Toshi MARUYAMA f06500dcce Merged r4623 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4626 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-03 10:53:47 +00:00
Jean-Philippe Lang b098e2b63f Merged r4621 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4622 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-02 14:37:55 +00:00
Jean-Philippe Lang 151a49b319 Merged r4618 and r4619 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4620 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-02 12:17:39 +00:00
Jean-Philippe Lang ded234794e Merged r4615 and r4616 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4617 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-02 11:44:19 +00:00
Toshi MARUYAMA 119732c3ee Merged r4613 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4614 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-02 09:52:29 +00:00
Toshi MARUYAMA 730fcef844 Merged r4611 from trunk (Mercurial sorting).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4612 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-02 06:14:59 +00:00
Toshi MARUYAMA 1cb33f3a95 Merged r4608 from trunk (repository: switch darcs cat test if cat supports).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4609 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-01 22:05:48 +00:00
Toshi MARUYAMA 69edd3c53f Merged r4606 from trunk (.hgignore).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4607 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-01-01 21:20:25 +00:00
Jean-Philippe Lang ad784e2146 Merged locales updates (r4592 to 4595) from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4596 e93f8b46-1217-0410-a6f0-8f06a7374b81
2010-12-30 15:54:47 +00:00
Jean-Philippe Lang c783ae4b3d 1.1-stable branch added
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.1-stable@4585 e93f8b46-1217-0410-a6f0-8f06a7374b81
2010-12-30 15:09:18 +00:00
652 changed files with 16480 additions and 58704 deletions
-5
View File
@@ -1,7 +1,4 @@
/.project
/.loadpath
/config/additional_environment.rb /config/additional_environment.rb
/config/configuration.yml
/config/database.yml /config/database.yml
/config/email.yml /config/email.yml
/config/initializers/session_store.rb /config/initializers/session_store.rb
@@ -10,8 +7,6 @@
/db/*.sqlite3 /db/*.sqlite3
/db/schema.rb /db/schema.rb
/files/* /files/*
/lib/redmine/scm/adapters/mercurial/redminehelper.pyc
/lib/redmine/scm/adapters/mercurial/redminehelper.pyo
/log/*.log* /log/*.log*
/log/mongrel_debug /log/mongrel_debug
/public/dispatch.* /public/dispatch.*
-7
View File
@@ -1,9 +1,6 @@
syntax: glob syntax: glob
.project
.loadpath
config/additional_environment.rb config/additional_environment.rb
config/configuration.yml
config/database.yml config/database.yml
config/email.yml config/email.yml
config/initializers/session_store.rb config/initializers/session_store.rb
@@ -12,8 +9,6 @@ db/*.db
db/*.sqlite3 db/*.sqlite3
db/schema.rb db/schema.rb
files/* files/*
lib/redmine/scm/adapters/mercurial/redminehelper.pyc
lib/redmine/scm/adapters/mercurial/redminehelper.pyo
log/*.log* log/*.log*
log/mongrel_debug log/mongrel_debug
public/dispatch.* public/dispatch.*
@@ -25,5 +20,3 @@ tmp/sockets/*
tmp/test/* tmp/test/*
vendor/rails vendor/rails
*.rbc *.rbc
.svn/
.git/
+2 -14
View File
@@ -203,25 +203,13 @@ class AccountController < ApplicationController
self.logged_user = user self.logged_user = user
# generate a key and set cookie if autologin # generate a key and set cookie if autologin
if params[:autologin] && Setting.autologin? if params[:autologin] && Setting.autologin?
set_autologin_cookie(user) token = Token.create(:user => user, :action => 'autologin')
cookies[:autologin] = { :value => token.value, :expires => 1.year.from_now }
end end
call_hook(:controller_account_success_authentication_after, {:user => user }) call_hook(:controller_account_success_authentication_after, {:user => user })
redirect_back_or_default :controller => 'my', :action => 'page' redirect_back_or_default :controller => 'my', :action => 'page'
end end
def set_autologin_cookie(user)
token = Token.create(:user => user, :action => 'autologin')
cookie_name = Redmine::Configuration['autologin_cookie_name'] || 'autologin'
cookie_options = {
:value => token.value,
:expires => 1.year.from_now,
:path => (Redmine::Configuration['autologin_cookie_path'] || '/'),
:secure => (Redmine::Configuration['autologin_cookie_secure'] ? true : false),
:httponly => true
}
cookies[cookie_name] = cookie_options
end
# Onthefly creation failed, display the registration form to fill/fix attributes # Onthefly creation failed, display the registration form to fill/fix attributes
def onthefly_creation_failed(user, auth_source_options = { }) def onthefly_creation_failed(user, auth_source_options = { })
@user = user @user = user
+2 -18
View File
@@ -1,24 +1,7 @@
# Redmine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program 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.
#
# This program 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 Street, Fifth Floor, Boston, MA 02110-1301, USA.
class ActivitiesController < ApplicationController class ActivitiesController < ApplicationController
menu_item :activity menu_item :activity
before_filter :find_optional_project before_filter :find_optional_project
accept_rss_auth :index accept_key_auth :index
def index def index
@days = Setting.activity_days_default.to_i @days = Setting.activity_days_default.to_i
@@ -72,4 +55,5 @@ class ActivitiesController < ApplicationController
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
render_404 render_404
end end
end end
+7 -5
View File
@@ -17,7 +17,9 @@
class AdminController < ApplicationController class AdminController < ApplicationController
layout 'admin' layout 'admin'
before_filter :require_admin before_filter :require_admin
helper :sort helper :sort
include SortHelper include SortHelper
@@ -28,10 +30,12 @@ class AdminController < ApplicationController
def projects def projects
@status = params[:status] ? params[:status].to_i : 1 @status = params[:status] ? params[:status].to_i : 1
c = ARCondition.new(@status == 0 ? "status <> 0" : ["status = ?", @status]) c = ARCondition.new(@status == 0 ? "status <> 0" : ["status = ?", @status])
unless params[:name].blank? unless params[:name].blank?
name = "%#{params[:name].strip.downcase}%" name = "%#{params[:name].strip.downcase}%"
c << ["LOWER(identifier) LIKE ? OR LOWER(name) LIKE ?", name, name] c << ["LOWER(identifier) LIKE ? OR LOWER(name) LIKE ?", name, name]
end end
@projects = Project.find :all, :order => 'lft', @projects = Project.find :all, :order => 'lft',
:conditions => c.conditions :conditions => c.conditions
@@ -73,12 +77,10 @@ class AdminController < ApplicationController
def info def info
@db_adapter_name = ActiveRecord::Base.connection.adapter_name @db_adapter_name = ActiveRecord::Base.connection.adapter_name
@checklist = [ @checklist = [
[:text_default_administrator_account_changed, [:text_default_administrator_account_changed, User.find(:first, :conditions => ["login=? and hashed_password=?", 'admin', User.hash_password('admin')]).nil?],
User.find(:first,
:conditions => ["login=? and hashed_password=?", 'admin', User.hash_password('admin')]).nil?],
[:text_file_repository_writable, File.writable?(Attachment.storage_path)], [:text_file_repository_writable, File.writable?(Attachment.storage_path)],
[:text_plugin_assets_writable, File.writable?(Engines.public_directory)], [:text_plugin_assets_writable, File.writable?(Engines.public_directory)],
[:text_rmagick_available, Object.const_defined?(:Magick)] [:text_rmagick_available, Object.const_defined?(:Magick)]
] ]
end end
end end
+8 -43
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang # Copyright (C) 2006-2007 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -18,8 +18,6 @@
require 'uri' require 'uri'
require 'cgi' require 'cgi'
class Unauthorized < Exception; end
class ApplicationController < ActionController::Base class ApplicationController < ActionController::Base
include Redmine::I18n include Redmine::I18n
@@ -43,7 +41,6 @@ class ApplicationController < ActionController::Base
protect_from_forgery protect_from_forgery
rescue_from ActionController::InvalidAuthenticityToken, :with => :invalid_authenticity_token rescue_from ActionController::InvalidAuthenticityToken, :with => :invalid_authenticity_token
rescue_from ::Unauthorized, :with => :deny_access
include Redmine::Search::Controller include Redmine::Search::Controller
include Redmine::MenuManager::MenuController include Redmine::MenuManager::MenuController
@@ -71,11 +68,11 @@ class ApplicationController < ActionController::Base
user = User.try_to_autologin(cookies[:autologin]) user = User.try_to_autologin(cookies[:autologin])
session[:user_id] = user.id if user session[:user_id] = user.id if user
user user
elsif params[:format] == 'atom' && params[:key] && request.get? && accept_rss_auth? elsif params[:format] == 'atom' && params[:key] && accept_key_auth_actions.include?(params[:action])
# RSS key authentication does not start a session # RSS key authentication does not start a session
User.find_by_rss_key(params[:key]) User.find_by_rss_key(params[:key])
elsif Setting.rest_api_enabled? && accept_api_auth? elsif Setting.rest_api_enabled? && api_request?
if (key = api_key_from_request) if (key = api_key_from_request) && accept_key_auth_actions.include?(params[:action])
# Use API key # Use API key
User.find_by_api_key(key) User.find_by_api_key(key)
else else
@@ -224,10 +221,6 @@ class ApplicationController < ActionController::Base
def find_issues def find_issues
@issues = Issue.find_all_by_id(params[:id] || params[:ids]) @issues = Issue.find_all_by_id(params[:id] || params[:ids])
raise ActiveRecord::RecordNotFound if @issues.empty? raise ActiveRecord::RecordNotFound if @issues.empty?
if @issues.detect {|issue| !issue.visible?}
deny_access
return
end
@projects = @issues.collect(&:project).compact.uniq @projects = @issues.collect(&:project).compact.uniq
@project = @projects.first if @projects.size == 1 @project = @projects.first if @projects.size == 1
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
@@ -278,7 +271,6 @@ class ApplicationController < ActionController::Base
end end
end end
redirect_to default redirect_to default
false
end end
def render_403(options={}) def render_403(options={})
@@ -333,40 +325,13 @@ class ApplicationController < ActionController::Base
render :template => "common/feed.atom.rxml", :layout => false, :content_type => 'application/atom+xml' render :template => "common/feed.atom.rxml", :layout => false, :content_type => 'application/atom+xml'
end end
# TODO: remove in Redmine 1.4
def self.accept_key_auth(*actions) def self.accept_key_auth(*actions)
ActiveSupport::Deprecation.warn "ApplicationController.accept_key_auth is deprecated and will be removed in Redmine 1.4. Use accept_rss_auth (or accept_api_auth) instead." actions = actions.flatten.map(&:to_s)
accept_rss_auth(*actions) write_inheritable_attribute('accept_key_auth_actions', actions)
end end
# TODO: remove in Redmine 1.4
def accept_key_auth_actions def accept_key_auth_actions
ActiveSupport::Deprecation.warn "ApplicationController.accept_key_auth_actions is deprecated and will be removed in Redmine 1.4. Use accept_rss_auth (or accept_api_auth) instead." self.class.read_inheritable_attribute('accept_key_auth_actions') || []
self.class.accept_rss_auth
end
def self.accept_rss_auth(*actions)
if actions.any?
write_inheritable_attribute('accept_rss_auth_actions', actions)
else
read_inheritable_attribute('accept_rss_auth_actions') || []
end
end
def accept_rss_auth?(action=action_name)
self.class.accept_rss_auth.include?(action.to_sym)
end
def self.accept_api_auth(*actions)
if actions.any?
write_inheritable_attribute('accept_api_auth_actions', actions)
else
read_inheritable_attribute('accept_api_auth_actions') || []
end
end
def accept_api_auth?(action=action_name)
self.class.accept_api_auth.include?(action.to_sym)
end end
# Returns the number of objects that should be displayed # Returns the number of objects that should be displayed
+1 -1
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang # Copyright (C) 2006-2008 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
+3 -3
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang # Copyright (C) 2006-2007 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -18,7 +18,7 @@
class BoardsController < ApplicationController class BoardsController < ApplicationController
default_search_scope :messages default_search_scope :messages
before_filter :find_project, :find_board_if_available, :authorize before_filter :find_project, :find_board_if_available, :authorize
accept_rss_auth :index, :show accept_key_auth :index, :show
helper :messages helper :messages
include MessagesHelper include MessagesHelper
+5 -17
View File
@@ -1,20 +1,3 @@
# Redmine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program 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.
#
# This program 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 Street, Fifth Floor, Boston, MA 02110-1301, USA.
class CalendarsController < ApplicationController class CalendarsController < ApplicationController
menu_item :calendar menu_item :calendar
before_filter :find_optional_project before_filter :find_optional_project
@@ -53,4 +36,9 @@ class CalendarsController < ApplicationController
render :action => 'show', :layout => false if request.xhr? render :action => 'show', :layout => false if request.xhr?
end end
def update
show
end
end end
@@ -1,6 +1,5 @@
class ContextMenusController < ApplicationController class ContextMenusController < ApplicationController
helper :watchers helper :watchers
helper :issues
def issues def issues
@issues = Issue.visible.all(:conditions => {:id => params[:ids]}, :include => :project) @issues = Issue.visible.all(:conditions => {:id => params[:ids]}, :include => :project)
@@ -42,17 +41,4 @@ class ContextMenusController < ApplicationController
render :layout => false render :layout => false
end 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 end
+2 -4
View File
@@ -38,9 +38,8 @@ class CustomFieldsController < ApplicationController
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
call_hook(:controller_custom_fields_new_after_save, :params => params, :custom_field => @custom_field) call_hook(:controller_custom_fields_new_after_save, :params => params, :custom_field => @custom_field)
redirect_to :action => 'index', :tab => @custom_field.class.name redirect_to :action => 'index', :tab => @custom_field.class.name
else
@trackers = Tracker.find(:all, :order => 'position')
end end
@trackers = Tracker.find(:all, :order => 'position')
end end
def edit def edit
@@ -49,9 +48,8 @@ class CustomFieldsController < ApplicationController
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
call_hook(:controller_custom_fields_edit_after_save, :params => params, :custom_field => @custom_field) call_hook(:controller_custom_fields_edit_after_save, :params => params, :custom_field => @custom_field)
redirect_to :action => 'index', :tab => @custom_field.class.name redirect_to :action => 'index', :tab => @custom_field.class.name
else
@trackers = Tracker.find(:all, :order => 'position')
end end
@trackers = Tracker.find(:all, :order => 'position')
end end
def destroy def destroy
@@ -75,12 +75,10 @@ class EnumerationsController < ApplicationController
# No associated objects # No associated objects
@enumeration.destroy @enumeration.destroy
redirect_to :action => 'index' redirect_to :action => 'index'
return
elsif params[:reassign_to_id] elsif params[:reassign_to_id]
if reassign_to = @enumeration.class.find_by_id(params[:reassign_to_id]) if reassign_to = @enumeration.class.find_by_id(params[:reassign_to_id])
@enumeration.destroy(reassign_to) @enumeration.destroy(reassign_to)
redirect_to :action => 'index' redirect_to :action => 'index'
return
end end
end end
@enumerations = @enumeration.class.find(:all) - [@enumeration] @enumerations = @enumeration.class.find(:all) - [@enumeration]
+5 -17
View File
@@ -1,20 +1,3 @@
# Redmine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program 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.
#
# This program 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 Street, Fifth Floor, Boston, MA 02110-1301, USA.
class GanttsController < ApplicationController class GanttsController < ApplicationController
menu_item :gantt menu_item :gantt
before_filter :find_optional_project before_filter :find_optional_project
@@ -45,4 +28,9 @@ class GanttsController < ApplicationController
format.pdf { send_data(@gantt.to_pdf, :type => 'application/pdf', :filename => "#{basename}.pdf") } format.pdf { send_data(@gantt.to_pdf, :type => 'application/pdf', :filename => "#{basename}.pdf") }
end end
end end
def update
show
end
end end
+1 -1
View File
@@ -132,7 +132,7 @@ class GroupsController < ApplicationController
def autocomplete_for_user def autocomplete_for_user
@group = Group.find(params[:id]) @group = Group.find(params[:id])
@users = User.active.not_in_group(@group).like(params[:q]).all(:limit => 100) @users = User.active.like(params[:q]).find(:all, :limit => 100) - @group.users
render :layout => false render :layout => false
end end
@@ -65,12 +65,10 @@ class IssueCategoriesController < ApplicationController
# No issue assigned to this category # No issue assigned to this category
@category.destroy @category.destroy
redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'categories' redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'categories'
return
elsif params[:todo] elsif params[:todo]
reassign_to = @project.issue_categories.find_by_id(params[:reassign_to_id]) if params[:todo] == 'reassign' reassign_to = @project.issue_categories.find_by_id(params[:reassign_to_id]) if params[:todo] == 'reassign'
@category.destroy(reassign_to) @category.destroy(reassign_to)
redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'categories' redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'categories'
return
end end
@categories = @project.issue_categories - [@category] @categories = @project.issue_categories - [@category]
end end
@@ -1,4 +1,4 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2007 Jean-Philippe Lang # Copyright (C) 2006-2007 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
@@ -28,7 +28,6 @@ class IssueRelationsController < ApplicationController
respond_to do |format| respond_to do |format|
format.html { redirect_to :controller => 'issues', :action => 'show', :id => @issue } format.html { redirect_to :controller => 'issues', :action => 'show', :id => @issue }
format.js do format.js do
@relations = @issue.relations.select {|r| r.other_issue(@issue) && r.other_issue(@issue).visible? }
render :update do |page| render :update do |page|
page.replace_html "relations", :partial => 'issues/relations' page.replace_html "relations", :partial => 'issues/relations'
if @relation.errors.empty? if @relation.errors.empty?
@@ -48,10 +47,7 @@ class IssueRelationsController < ApplicationController
end end
respond_to do |format| respond_to do |format|
format.html { redirect_to :controller => 'issues', :action => 'show', :id => @issue } format.html { redirect_to :controller => 'issues', :action => 'show', :id => @issue }
format.js { format.js { render(:update) {|page| page.replace_html "relations", :partial => 'issues/relations'} }
@relations = @issue.relations.select {|r| r.other_issue(@issue) && r.other_issue(@issue).visible? }
render(:update) {|page| page.replace_html "relations", :partial => 'issues/relations'}
}
end end
end end
+8 -26
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang # Copyright (C) 2006-2008 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -27,8 +27,7 @@ class IssuesController < ApplicationController
before_filter :find_optional_project, :only => [:index] before_filter :find_optional_project, :only => [:index]
before_filter :check_for_default_issue_status, :only => [:new, :create] before_filter :check_for_default_issue_status, :only => [:new, :create]
before_filter :build_new_issue_from_params, :only => [:new, :create] before_filter :build_new_issue_from_params, :only => [:new, :create]
accept_rss_auth :index, :show accept_key_auth :index, :show, :create, :update, :destroy
accept_api_auth :index, :show, :create, :update, :destroy
rescue_from Query::StatementInvalid, :with => :query_statement_invalid rescue_from Query::StatementInvalid, :with => :query_statement_invalid
@@ -107,17 +106,12 @@ class IssuesController < ApplicationController
@journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC") @journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
@journals.each_with_index {|j,i| j.indice = i+1} @journals.each_with_index {|j,i| j.indice = i+1}
@journals.reverse! if User.current.wants_comments_in_reverse_order? @journals.reverse! if User.current.wants_comments_in_reverse_order?
@changesets = @issue.changesets.visible.all
if User.current.allowed_to?(:view_changesets, @project) @changesets.reverse! if User.current.wants_comments_in_reverse_order?
@changesets = @issue.changesets.visible.all
@changesets.reverse! if User.current.wants_comments_in_reverse_order?
end
@relations = @issue.relations.select {|r| r.other_issue(@issue) && r.other_issue(@issue).visible? }
@allowed_statuses = @issue.new_statuses_allowed_to(User.current) @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
@edit_allowed = User.current.allowed_to?(:edit_issues, @project) @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
@priorities = IssuePriority.all @priorities = IssuePriority.all
@time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project) @time_entry = TimeEntry.new
respond_to do |format| respond_to do |format|
format.html { render :template => 'issues/show.rhtml' } format.html { render :template => 'issues/show.rhtml' }
format.api format.api
@@ -241,13 +235,7 @@ class IssuesController < ApplicationController
return unless api_request? return unless api_request?
end end
end end
@issues.each do |issue| @issues.each(&:destroy)
begin
issue.reload.destroy
rescue ::ActiveRecord::RecordNotFound # raised by #reload if issue no longer exists
# nothing to do, issue was already deleted (eg. by a parent)
end
end
respond_to do |format| respond_to do |format|
format.html { redirect_back_or_default(:action => 'index', :project_id => @project) } format.html { redirect_back_or_default(:action => 'index', :project_id => @project) }
format.api { head :ok } format.api { head :ok }
@@ -256,13 +244,7 @@ class IssuesController < ApplicationController
private private
def find_issue def find_issue
# Issue.visible.find(...) can not be used to redirect user to the login form
# if the issue actually exists but requires authentication
@issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category]) @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
unless @issue.visible?
deny_access
return
end
@project = @issue.project @project = @issue.project
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
render_404 render_404
@@ -282,7 +264,7 @@ private
@allowed_statuses = @issue.new_statuses_allowed_to(User.current) @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
@priorities = IssuePriority.all @priorities = IssuePriority.all
@edit_allowed = User.current.allowed_to?(:edit_issues, @project) @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
@time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project) @time_entry = TimeEntry.new
@time_entry.attributes = params[:time_entry] @time_entry.attributes = params[:time_entry]
@notes = params[:notes] || (params[:issue].present? ? params[:issue][:notes] : nil) @notes = params[:notes] || (params[:issue].present? ? params[:issue][:notes] : nil)
@@ -302,7 +284,6 @@ private
end end
@issue.project = @project @issue.project = @project
@issue.author = User.current
# Tracker must be set before custom field values # Tracker must be set before custom field values
@issue.tracker ||= @project.trackers.find((params[:issue] && params[:issue][:tracker_id]) || params[:tracker_id] || :first) @issue.tracker ||= @project.trackers.find((params[:issue] && params[:issue][:tracker_id]) || params[:tracker_id] || :first)
if @issue.tracker.nil? if @issue.tracker.nil?
@@ -316,6 +297,7 @@ private
@issue.watcher_user_ids = params[:issue]['watcher_user_ids'] @issue.watcher_user_ids = params[:issue]['watcher_user_ids']
end end
end end
@issue.author = User.current
@priorities = IssuePriority.all @priorities = IssuePriority.all
@allowed_statuses = @issue.new_statuses_allowed_to(User.current, true) @allowed_statuses = @issue.new_statuses_allowed_to(User.current, true)
end end
+7 -29
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang # Copyright (C) 2006-2008 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -16,15 +16,13 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class JournalsController < ApplicationController class JournalsController < ApplicationController
before_filter :find_journal, :only => [:edit, :diff] before_filter :find_journal, :only => [:edit]
before_filter :find_issue, :only => [:new] before_filter :find_issue, :only => [:new]
before_filter :find_optional_project, :only => [:index] before_filter :find_optional_project, :only => [:index]
before_filter :authorize, :only => [:new, :edit, :diff] before_filter :authorize, :only => [:new, :edit]
accept_rss_auth :index accept_key_auth :index
menu_item :issues
helper :issues helper :issues
helper :custom_fields
helper :queries helper :queries
include QueriesHelper include QueriesHelper
helper :sort helper :sort
@@ -45,17 +43,6 @@ class JournalsController < ApplicationController
render_404 render_404
end end
def diff
@issue = @journal.issue
if params[:detail_id].present?
@detail = @journal.details.find_by_id(params[:detail_id])
else
@detail = @journal.details.detect {|d| d.prop_key == 'description'}
end
(render_404; return false) unless @issue && @detail
@diff = Redmine::Helpers::Diff.new(@detail.value, @detail.old_value)
end
def new def new
journal = Journal.find(params[:journal_id]) if params[:journal_id] journal = Journal.find(params[:journal_id]) if params[:journal_id]
if journal if journal
@@ -80,7 +67,6 @@ class JournalsController < ApplicationController
end end
def edit def edit
(render_403; return false) unless @journal.editable_by?(User.current)
if request.post? if request.post?
@journal.update_attributes(:notes => params[:notes]) if params[:notes] @journal.update_attributes(:notes => params[:notes]) if params[:notes]
@journal.destroy if @journal.details.empty? && @journal.notes.blank? @journal.destroy if @journal.details.empty? && @journal.notes.blank?
@@ -89,21 +75,13 @@ class JournalsController < ApplicationController
format.html { redirect_to :controller => 'issues', :action => 'show', :id => @journal.journalized_id } format.html { redirect_to :controller => 'issues', :action => 'show', :id => @journal.journalized_id }
format.js { render :action => 'update' } format.js { render :action => 'update' }
end end
else
respond_to do |format|
format.html {
# TODO: implement non-JS journal update
render :nothing => true
}
format.js
end
end end
end end
private private
def find_journal def find_journal
@journal = Journal.find(params[:id]) @journal = Journal.find(params[:id])
(render_403; return false) unless @journal.editable_by?(User.current)
@project = @journal.journalized.project @project = @journal.journalized.project
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
render_404 render_404
+2 -2
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang # Copyright (C) 2006-2008 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
+3 -6
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang # Copyright (C) 2006 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -23,10 +23,7 @@ class NewsController < ApplicationController
before_filter :find_project, :only => [:new, :create] before_filter :find_project, :only => [:new, :create]
before_filter :authorize, :except => [:index] before_filter :authorize, :except => [:index]
before_filter :find_optional_project, :only => :index before_filter :find_optional_project, :only => :index
accept_rss_auth :index accept_key_auth :index
accept_api_auth :index
helper :watchers
def index def index
case params[:format] case params[:format]
-17
View File
@@ -1,20 +1,3 @@
# Redmine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program 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.
#
# This program 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 Street, Fifth Floor, Boston, MA 02110-1301, USA.
class PreviewsController < ApplicationController class PreviewsController < ApplicationController
before_filter :find_project before_filter :find_project
+8 -7
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang # Copyright (C) 2006-2009 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -24,8 +24,7 @@ class ProjectsController < ApplicationController
before_filter :authorize, :except => [ :index, :list, :new, :create, :copy, :archive, :unarchive, :destroy] before_filter :authorize, :except => [ :index, :list, :new, :create, :copy, :archive, :unarchive, :destroy]
before_filter :authorize_global, :only => [:new, :create] before_filter :authorize_global, :only => [:new, :create]
before_filter :require_admin, :only => [ :copy, :archive, :unarchive, :destroy ] before_filter :require_admin, :only => [ :copy, :archive, :unarchive, :destroy ]
accept_rss_auth :index accept_key_auth :index, :show, :create, :update, :destroy
accept_api_auth :index, :show, :create, :update, :destroy
after_filter :only => [:create, :edit, :update, :archive, :unarchive, :destroy] do |controller| after_filter :only => [:create, :edit, :update, :archive, :unarchive, :destroy] do |controller|
if controller.request.post? if controller.request.post?
@@ -118,6 +117,7 @@ class ProjectsController < ApplicationController
Mailer.with_deliveries(params[:notifications] == '1') do Mailer.with_deliveries(params[:notifications] == '1') do
@project = Project.new @project = Project.new
@project.safe_attributes = params[:project] @project.safe_attributes = params[:project]
@project.enabled_module_names = params[:enabled_modules]
if validate_parent_id && @project.copy(@source_project, :only => params[:only]) if validate_parent_id && @project.copy(@source_project, :only => params[:only])
@project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id') @project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
@@ -143,7 +143,7 @@ class ProjectsController < ApplicationController
end end
@users_by_role = @project.users_by_role @users_by_role = @project.users_by_role
@subprojects = @project.children.visible.all @subprojects = @project.children.visible
@news = @project.news.find(:all, :limit => 5, :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC") @news = @project.news.find(:all, :limit => 5, :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC")
@trackers = @project.rolled_up_trackers @trackers = @project.rolled_up_trackers
@@ -156,10 +156,11 @@ class ProjectsController < ApplicationController
:include => [:project, :status, :tracker], :include => [:project, :status, :tracker],
:conditions => cond) :conditions => cond)
if User.current.allowed_to?(:view_time_entries, @project) TimeEntry.visible_by(User.current) do
@total_hours = TimeEntry.visible.sum(:hours, :include => :project, :conditions => cond).to_f @total_hours = TimeEntry.sum(:hours,
:include => :project,
:conditions => cond).to_f
end end
@key = User.current.rss_key @key = User.current.rss_key
respond_to do |format| respond_to do |format|
+4 -7
View File
@@ -25,12 +25,11 @@ class QueriesController < ApplicationController
@query.project = params[:query_is_for_all] ? nil : @project @query.project = params[:query_is_for_all] ? nil : @project
@query.user = User.current @query.user = User.current
@query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin? @query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
@query.add_filters(params[:fields] || params[:f], params[:operators] || params[:op], params[:values] || params[:v]) if params[:fields] || params[:f]
@query.group_by ||= params[:group_by]
@query.column_names = params[:c] if params[:c]
@query.column_names = nil if params[:default_columns] @query.column_names = nil if params[:default_columns]
@query.add_filters(params[:fields], params[:operators], params[:values]) if params[:fields]
@query.group_by ||= params[:group_by]
if request.post? && params[:confirm] && @query.save if request.post? && params[:confirm] && @query.save
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
redirect_to :controller => 'issues', :action => 'index', :project_id => @project, :query_id => @query redirect_to :controller => 'issues', :action => 'index', :project_id => @project, :query_id => @query
@@ -42,12 +41,10 @@ class QueriesController < ApplicationController
def edit def edit
if request.post? if request.post?
@query.filters = {} @query.filters = {}
@query.add_filters(params[:fields] || params[:f], params[:operators] || params[:op], params[:values] || params[:v]) if params[:fields] || params[:f] @query.add_filters(params[:fields], params[:operators], params[:values]) if params[:fields]
@query.attributes = params[:query] @query.attributes = params[:query]
@query.project = nil if params[:query_is_for_all] @query.project = nil if params[:query_is_for_all]
@query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin? @query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
@query.group_by ||= params[:group_by]
@query.column_names = params[:c] if params[:c]
@query.column_names = nil if params[:default_columns] @query.column_names = nil if params[:default_columns]
if @query.save if @query.save
+25 -65
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang # Copyright (C) 2006-2009 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -30,36 +30,24 @@ class RepositoriesController < ApplicationController
before_filter :find_repository, :except => :edit before_filter :find_repository, :except => :edit
before_filter :find_project, :only => :edit before_filter :find_project, :only => :edit
before_filter :authorize before_filter :authorize
accept_rss_auth :revisions accept_key_auth :revisions
rescue_from Redmine::Scm::Adapters::CommandFailed, :with => :show_error_command_failed rescue_from Redmine::Scm::Adapters::CommandFailed, :with => :show_error_command_failed
def edit def edit
@repository = @project.repository @repository = @project.repository
if !@repository && !params[:repository_scm].blank? if !@repository
@repository = Repository.factory(params[:repository_scm]) @repository = Repository.factory(params[:repository_scm])
@repository.project = @project if @repository @repository.project = @project if @repository
end end
if request.post? && @repository if request.post? && @repository
p1 = params[:repository] @repository.attributes = params[:repository]
p = {}
p_extra = {}
p1.each do |k, v|
if k =~ /^extra_/
p_extra[k] = v
else
p[k] = v
end
end
@repository.attributes = p
@repository.merge_extra_info(p_extra)
@repository.save @repository.save
end end
render(:update) do |page| render(:update) do |page|
page.replace_html "tab-content-repository", page.replace_html "tab-content-repository", :partial => 'projects/settings/repository'
:partial => 'projects/settings/repository'
if @repository && !@project.repository if @repository && !@project.repository
@project.reload # needed to reload association @project.reload #needed to reload association
page.replace_html "main-menu", render_main_menu(@project) page.replace_html "main-menu", render_main_menu(@project)
end end
end end
@@ -82,17 +70,13 @@ class RepositoriesController < ApplicationController
def destroy def destroy
@repository.destroy @repository.destroy
redirect_to :controller => 'projects', redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'repository'
:action => 'settings',
:id => @project,
:tab => 'repository'
end end
def show def show
@repository.fetch_changesets if Setting.autofetch_changesets? && @path.empty? @repository.fetch_changesets if Setting.autofetch_changesets? && @path.empty?
@entries = @repository.entries(@path, @rev) @entries = @repository.entries(@path, @rev)
@changeset = @repository.find_changeset_by_name(@rev)
if request.xhr? if request.xhr?
@entries ? render(:partial => 'dir_list_content') : render(:nothing => true) @entries ? render(:partial => 'dir_list_content') : render(:nothing => true)
else else
@@ -110,18 +94,17 @@ class RepositoriesController < ApplicationController
(show_error_not_found; return) unless @entry (show_error_not_found; return) unless @entry
@changesets = @repository.latest_changesets(@path, @rev, Setting.repository_log_display_limit.to_i) @changesets = @repository.latest_changesets(@path, @rev, Setting.repository_log_display_limit.to_i)
@properties = @repository.properties(@path, @rev) @properties = @repository.properties(@path, @rev)
@changeset = @repository.find_changeset_by_name(@rev)
end end
def revisions def revisions
@changeset_count = @repository.changesets.count @changeset_count = @repository.changesets.count
@changeset_pages = Paginator.new self, @changeset_count, @changeset_pages = Paginator.new self, @changeset_count,
per_page_option, per_page_option,
params['page'] params['page']
@changesets = @repository.changesets.find(:all, @changesets = @repository.changesets.find(:all,
:limit => @changeset_pages.items_per_page, :limit => @changeset_pages.items_per_page,
:offset => @changeset_pages.current.offset, :offset => @changeset_pages.current.offset,
:include => [:user, :repository]) :include => [:user, :repository])
respond_to do |format| respond_to do |format|
format.html { render :layout => false if request.xhr? } format.html { render :layout => false if request.xhr? }
@@ -138,46 +121,24 @@ class RepositoriesController < ApplicationController
@content = @repository.cat(@path, @rev) @content = @repository.cat(@path, @rev)
(show_error_not_found; return) unless @content (show_error_not_found; return) unless @content
if 'raw' == params[:format] || if 'raw' == params[:format] || @content.is_binary_data? || (@entry.size && @entry.size > Setting.file_max_size_displayed.to_i.kilobyte)
(@content.size && @content.size > Setting.file_max_size_displayed.to_i.kilobyte) ||
! is_entry_text_data?(@content, @path)
# Force the download # Force the download
send_opt = { :filename => filename_for_content_disposition(@path.split('/').last) } send_data @content, :filename => @path.split('/').last
send_type = Redmine::MimeType.of(@path)
send_opt[:type] = send_type.to_s if send_type
send_data @content, send_opt
else else
# Prevent empty lines when displaying a file with Windows style eol # Prevent empty lines when displaying a file with Windows style eol
# TODO: UTF-16
# Is this needs? AttachmentsController reads file simply.
@content.gsub!("\r\n", "\n") @content.gsub!("\r\n", "\n")
@changeset = @repository.find_changeset_by_name(@rev) end
end
end end
def is_entry_text_data?(ent, path)
# UTF-16 contains "\x00".
# It is very strict that file contains less than 30% of ascii symbols
# in non Western Europe.
return true if Redmine::MimeType.is_type?('text', path)
# Ruby 1.8.6 has a bug of integer divisions.
# http://apidock.com/ruby/v1_8_6_287/String/is_binary_data%3F
return false if ent.is_binary_data?
true
end
private :is_entry_text_data?
def annotate def annotate
@entry = @repository.entry(@path, @rev) @entry = @repository.entry(@path, @rev)
(show_error_not_found; return) unless @entry (show_error_not_found; return) unless @entry
@annotate = @repository.scm.annotate(@path, @rev) @annotate = @repository.scm.annotate(@path, @rev)
(render_error l(:error_scm_annotate); return) if @annotate.nil? || @annotate.empty? (render_error l(:error_scm_annotate); return) if @annotate.nil? || @annotate.empty?
@changeset = @repository.find_changeset_by_name(@rev)
end end
def revision def revision
raise ChangesetNotFound if @rev.blank?
@changeset = @repository.find_changeset_by_name(@rev) @changeset = @repository.find_changeset_by_name(@rev)
raise ChangesetNotFound unless @changeset raise ChangesetNotFound unless @changeset
@@ -207,8 +168,8 @@ class RepositoriesController < ApplicationController
User.current.pref[:diff_type] = @diff_type User.current.pref[:diff_type] = @diff_type
User.current.preference.save User.current.preference.save
end end
@cache_key = "repositories/diff/#{@repository.id}/" +
Digest::MD5.hexdigest("#{@path}-#{@rev}-#{@rev_to}-#{@diff_type}-#{current_language}") @cache_key = "repositories/diff/#{@repository.id}/" + Digest::MD5.hexdigest("#{@path}-#{@rev}-#{@rev_to}-#{@diff_type}")
unless read_fragment(@cache_key) unless read_fragment(@cache_key)
@diff = @repository.diff(@path, @rev, @rev_to) @diff = @repository.diff(@path, @rev, @rev_to)
show_error_not_found unless @diff show_error_not_found unless @diff
@@ -216,7 +177,6 @@ class RepositoriesController < ApplicationController
@changeset = @repository.find_changeset_by_name(@rev) @changeset = @repository.find_changeset_by_name(@rev)
@changeset_to = @rev_to ? @repository.find_changeset_by_name(@rev_to) : nil @changeset_to = @rev_to ? @repository.find_changeset_by_name(@rev_to) : nil
@diff_format_revisions = @repository.diff_format_revisions(@changeset, @changeset_to)
end end
end end
@@ -252,7 +212,7 @@ class RepositoriesController < ApplicationController
@rev = params[:rev].blank? ? @repository.default_branch : params[:rev].strip @rev = params[:rev].blank? ? @repository.default_branch : params[:rev].strip
@rev_to = params[:rev_to] @rev_to = params[:rev_to]
unless @rev.to_s.match(REV_PARAM_RE) && @rev_to.to_s.match(REV_PARAM_RE) unless @rev.to_s.match(REV_PARAM_RE) && @rev.to_s.match(REV_PARAM_RE)
if @repository.branches.blank? if @repository.branches.blank?
raise InvalidRevisionParam raise InvalidRevisionParam
end end
@@ -264,7 +224,7 @@ class RepositoriesController < ApplicationController
end end
def show_error_not_found def show_error_not_found
render_error :message => l(:error_scm_not_found), :status => 404 render_error l(:error_scm_not_found)
end end
# Handler for Redmine::Scm::Adapters::CommandFailed exception # Handler for Redmine::Scm::Adapters::CommandFailed exception
@@ -276,15 +236,11 @@ class RepositoriesController < ApplicationController
@date_to = Date.today @date_to = Date.today
@date_from = @date_to << 11 @date_from = @date_to << 11
@date_from = Date.civil(@date_from.year, @date_from.month, 1) @date_from = Date.civil(@date_from.year, @date_from.month, 1)
commits_by_day = repository.changesets.count( commits_by_day = repository.changesets.count(:all, :group => :commit_date, :conditions => ["commit_date BETWEEN ? AND ?", @date_from, @date_to])
:all, :group => :commit_date,
:conditions => ["commit_date BETWEEN ? AND ?", @date_from, @date_to])
commits_by_month = [0] * 12 commits_by_month = [0] * 12
commits_by_day.each {|c| commits_by_month[c.first.to_date.months_ago] += c.last } commits_by_day.each {|c| commits_by_month[c.first.to_date.months_ago] += c.last }
changes_by_day = repository.changes.count( changes_by_day = repository.changes.count(:all, :group => :commit_date, :conditions => ["commit_date BETWEEN ? AND ?", @date_from, @date_to])
:all, :group => :commit_date,
:conditions => ["commit_date BETWEEN ? AND ?", @date_from, @date_to])
changes_by_month = [0] * 12 changes_by_month = [0] * 12
changes_by_day.each {|c| changes_by_month[c.first.to_date.months_ago] += c.last } changes_by_day.each {|c| changes_by_month[c.first.to_date.months_ago] += c.last }
@@ -345,16 +301,20 @@ class RepositoriesController < ApplicationController
:graph_title => l(:label_commits_per_author), :graph_title => l(:label_commits_per_author),
:show_graph_title => true :show_graph_title => true
) )
graph.add_data( graph.add_data(
:data => commits_data, :data => commits_data,
:title => l(:label_revision_plural) :title => l(:label_revision_plural)
) )
graph.add_data( graph.add_data(
:data => changes_data, :data => changes_data,
:title => l(:label_change_plural) :title => l(:label_change_plural)
) )
graph.burn graph.burn
end end
end end
class Date class Date
+3 -5
View File
@@ -38,10 +38,9 @@ class RolesController < ApplicationController
end end
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
redirect_to :action => 'index' redirect_to :action => 'index'
else
@permissions = @role.setable_permissions
@roles = Role.find :all, :order => 'builtin, position'
end end
@permissions = @role.setable_permissions
@roles = Role.find :all, :order => 'builtin, position'
end end
def edit def edit
@@ -49,9 +48,8 @@ class RolesController < ApplicationController
if request.post? and @role.update_attributes(params[:role]) if request.post? and @role.update_attributes(params[:role])
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'index' redirect_to :action => 'index'
else
@permissions = @role.setable_permissions
end end
@permissions = @role.setable_permissions
end end
def destroy def destroy
+4 -4
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang # Copyright (C) 2006 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -24,8 +24,8 @@ class SearchController < ApplicationController
def index def index
@question = params[:q] || "" @question = params[:q] || ""
@question.strip! @question.strip!
@all_words = params[:all_words] ? params[:all_words].present? : true @all_words = params[:all_words] || (params[:submit] ? false : true)
@titles_only = params[:titles_only] ? params[:titles_only].present? : false @titles_only = !params[:titles_only].nil?
projects_to_search = projects_to_search =
case params[:scope] case params[:scope]
+13 -14
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang # Copyright (C) 2006-2007 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -36,16 +36,16 @@ class SettingsController < ApplicationController
end end
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'edit', :tab => params[:tab] redirect_to :action => 'edit', :tab => params[:tab]
else return
@options = {}
@options[:user_format] = User::USER_FORMATS.keys.collect {|f| [User.current.name(f), f.to_s] }
@deliveries = ActionMailer::Base.perform_deliveries
@guessed_host_and_path = request.host_with_port.dup
@guessed_host_and_path << ('/'+ Redmine::Utils.relative_url_root.gsub(%r{^\/}, '')) unless Redmine::Utils.relative_url_root.blank?
Redmine::Themes.rescan
end end
@options = {}
@options[:user_format] = User::USER_FORMATS.keys.collect {|f| [User.current.name(f), f.to_s] }
@deliveries = ActionMailer::Base.perform_deliveries
@guessed_host_and_path = request.host_with_port.dup
@guessed_host_and_path << ('/'+ Redmine::Utils.relative_url_root.gsub(%r{^\/}, '')) unless Redmine::Utils.relative_url_root.blank?
Redmine::Themes.rescan
end end
def plugin def plugin
@@ -54,10 +54,9 @@ class SettingsController < ApplicationController
Setting["plugin_#{@plugin.id}"] = params[:settings] Setting["plugin_#{@plugin.id}"] = params[:settings]
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'plugin', :id => @plugin.id redirect_to :action => 'plugin', :id => @plugin.id
else
@partial = @plugin.settings[:partial]
@settings = Setting["plugin_#{@plugin.id}"]
end end
@partial = @plugin.settings[:partial]
@settings = Setting["plugin_#{@plugin.id}"]
rescue Redmine::PluginNotFound rescue Redmine::PluginNotFound
render_404 render_404
end end
+67 -119
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang # Copyright (C) 2006-2010 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -18,12 +18,10 @@
class TimelogController < ApplicationController class TimelogController < ApplicationController
menu_item :issues menu_item :issues
before_filter :find_project, :only => [:new, :create] before_filter :find_project, :only => [:new, :create]
before_filter :find_time_entry, :only => [:show, :edit, :update] before_filter :find_time_entry, :only => [:show, :edit, :update, :destroy]
before_filter :find_time_entries, :only => [:bulk_edit, :bulk_update, :destroy]
before_filter :authorize, :except => [:index] before_filter :authorize, :except => [:index]
before_filter :find_optional_project, :only => [:index] before_filter :find_optional_project, :only => [:index]
accept_rss_auth :index accept_key_auth :index, :show, :create, :update, :destroy
accept_api_auth :index, :show, :create, :update, :destroy
helper :sort helper :sort
include SortHelper include SortHelper
@@ -42,56 +40,60 @@ class TimelogController < ApplicationController
'hours' => 'hours' 'hours' => 'hours'
cond = ARCondition.new cond = ARCondition.new
if @issue if @project.nil?
cond << "#{Issue.table_name}.root_id = #{@issue.root_id} AND #{Issue.table_name}.lft >= #{@issue.lft} AND #{Issue.table_name}.rgt <= #{@issue.rgt}" cond << Project.allowed_to_condition(User.current, :view_time_entries)
elsif @project elsif @issue.nil?
cond << @project.project_condition(Setting.display_subprojects_issues?) cond << @project.project_condition(Setting.display_subprojects_issues?)
else
cond << "#{Issue.table_name}.root_id = #{@issue.root_id} AND #{Issue.table_name}.lft >= #{@issue.lft} AND #{Issue.table_name}.rgt <= #{@issue.rgt}"
end end
retrieve_date_range retrieve_date_range
cond << ['spent_on BETWEEN ? AND ?', @from, @to] cond << ['spent_on BETWEEN ? AND ?', @from, @to]
respond_to do |format| TimeEntry.visible_by(User.current) do
format.html { respond_to do |format|
# Paginate results format.html {
@entry_count = TimeEntry.visible.count(:include => [:project, :issue], :conditions => cond.conditions) # Paginate results
@entry_pages = Paginator.new self, @entry_count, per_page_option, params['page'] @entry_count = TimeEntry.count(:include => [:project, :issue], :conditions => cond.conditions)
@entries = TimeEntry.visible.find(:all, @entry_pages = Paginator.new self, @entry_count, per_page_option, params['page']
:include => [:project, :activity, :user, {:issue => :tracker}], @entries = TimeEntry.find(:all,
:conditions => cond.conditions, :include => [:project, :activity, :user, {:issue => :tracker}],
:order => sort_clause, :conditions => cond.conditions,
:limit => @entry_pages.items_per_page, :order => sort_clause,
:offset => @entry_pages.current.offset) :limit => @entry_pages.items_per_page,
@total_hours = TimeEntry.visible.sum(:hours, :include => [:project, :issue], :conditions => cond.conditions).to_f :offset => @entry_pages.current.offset)
@total_hours = TimeEntry.sum(:hours, :include => [:project, :issue], :conditions => cond.conditions).to_f
render :layout => !request.xhr? render :layout => !request.xhr?
} }
format.api { format.api {
@entry_count = TimeEntry.visible.count(:include => [:project, :issue], :conditions => cond.conditions) @entry_count = TimeEntry.count(:include => [:project, :issue], :conditions => cond.conditions)
@offset, @limit = api_offset_and_limit @entry_pages = Paginator.new self, @entry_count, per_page_option, params['page']
@entries = TimeEntry.visible.find(:all, @entries = TimeEntry.find(:all,
:include => [:project, :activity, :user, {:issue => :tracker}], :include => [:project, :activity, :user, {:issue => :tracker}],
:conditions => cond.conditions, :conditions => cond.conditions,
:order => sort_clause, :order => sort_clause,
:limit => @limit, :limit => @entry_pages.items_per_page,
:offset => @offset) :offset => @entry_pages.current.offset)
} }
format.atom { format.atom {
entries = TimeEntry.visible.find(:all, entries = TimeEntry.find(:all,
:include => [:project, :activity, :user, {:issue => :tracker}], :include => [:project, :activity, :user, {:issue => :tracker}],
:conditions => cond.conditions, :conditions => cond.conditions,
:order => "#{TimeEntry.table_name}.created_on DESC", :order => "#{TimeEntry.table_name}.created_on DESC",
:limit => Setting.feeds_limit.to_i) :limit => Setting.feeds_limit.to_i)
render_feed(entries, :title => l(:label_spent_time)) render_feed(entries, :title => l(:label_spent_time))
} }
format.csv { format.csv {
# Export all entries # Export all entries
@entries = TimeEntry.visible.find(:all, @entries = TimeEntry.find(:all,
:include => [:project, :activity, :user, {:issue => [:tracker, :assigned_to, :priority]}], :include => [:project, :activity, :user, {:issue => [:tracker, :assigned_to, :priority]}],
:conditions => cond.conditions, :conditions => cond.conditions,
:order => sort_clause) :order => sort_clause)
send_data(entries_to_csv(@entries), :type => 'text/csv; header=present', :filename => 'timelog.csv') send_data(entries_to_csv(@entries), :type => 'text/csv; header=present', :filename => 'timelog.csv')
} }
end
end end
end end
@@ -162,55 +164,27 @@ class TimelogController < ApplicationController
end end
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 } verify :method => :delete, :only => :destroy, :render => {:nothing => true, :status => :method_not_allowed }
def destroy def destroy
@time_entries.each do |t| if @time_entry.destroy && @time_entry.destroyed?
begin respond_to do |format|
unless t.destroy && t.destroyed? format.html {
respond_to do |format| flash[:notice] = l(:notice_successful_delete)
format.html { redirect_to :back
flash[:error] = l(:notice_unable_delete_time_entry) }
redirect_to :back format.api { head :ok }
} end
format.api { render_validation_errors(t) } else
end respond_to do |format|
return format.html {
end flash[:error] = l(:notice_unable_delete_time_entry)
rescue ::ActionController::RedirectBackError redirect_to :back
redirect_to :action => 'index', :project_id => @projects.first }
return format.api { render_validation_errors(@time_entry) }
end end
end end
rescue ::ActionController::RedirectBackError
respond_to do |format| redirect_to :action => 'index', :project_id => @time_entry.project
format.html {
flash[:notice] = l(:notice_successful_delete)
redirect_back_or_default(:action => 'index', :project_id => @projects.first)
}
format.api { head :ok }
end
end end
private private
@@ -225,26 +199,6 @@ private
render_404 render_404
end 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 def find_project
if (issue_id = (params[:issue_id] || params[:time_entry] && params[:time_entry][:issue_id])).present? if (issue_id = (params[:issue_id] || params[:time_entry] && params[:time_entry][:issue_id])).present?
@issue = Issue.find(issue_id) @issue = Issue.find(issue_id)
@@ -315,10 +269,4 @@ private
@to ||= (TimeEntry.latest_date_for_project(@project) || Date.today) @to ||= (TimeEntry.latest_date_for_project(@project) || Date.today)
end 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 end
+9 -24
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang # Copyright (C) 2006-2010 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -19,8 +19,8 @@ class UsersController < ApplicationController
layout 'admin' layout 'admin'
before_filter :require_admin, :except => :show before_filter :require_admin, :except => :show
before_filter :find_user, :only => [:show, :edit, :update, :destroy, :edit_membership, :destroy_membership] before_filter :find_user, :only => [:show, :edit, :update, :edit_membership, :destroy_membership]
accept_api_auth :index, :show, :create, :update, :destroy accept_key_auth :index, :show, :create, :update
helper :sort helper :sort
include SortHelper include SortHelper
@@ -38,9 +38,6 @@ class UsersController < ApplicationController
@limit = per_page_option @limit = per_page_option
end end
scope = User
scope = scope.in_group(params[:group_id].to_i) if params[:group_id].present?
@status = params[:status] ? params[:status].to_i : 1 @status = params[:status] ? params[:status].to_i : 1
c = ARCondition.new(@status == 0 ? "status <> 0" : ["status = ?", @status]) c = ARCondition.new(@status == 0 ? "status <> 0" : ["status = ?", @status])
@@ -49,27 +46,24 @@ class UsersController < ApplicationController
c << ["LOWER(login) LIKE ? OR LOWER(firstname) LIKE ? OR LOWER(lastname) LIKE ? OR LOWER(mail) LIKE ?", name, name, name, name] c << ["LOWER(login) LIKE ? OR LOWER(firstname) LIKE ? OR LOWER(lastname) LIKE ? OR LOWER(mail) LIKE ?", name, name, name, name]
end end
@user_count = scope.count(:conditions => c.conditions) @user_count = User.count(:conditions => c.conditions)
@user_pages = Paginator.new self, @user_count, @limit, params['page'] @user_pages = Paginator.new self, @user_count, @limit, params['page']
@offset ||= @user_pages.current.offset @offset ||= @user_pages.current.offset
@users = scope.find :all, @users = User.find :all,
:order => sort_clause, :order => sort_clause,
:conditions => c.conditions, :conditions => c.conditions,
:limit => @limit, :limit => @limit,
:offset => @offset :offset => @offset
respond_to do |format| respond_to do |format|
format.html { format.html { render :layout => !request.xhr? }
@groups = Group.all.sort
render :layout => !request.xhr?
}
format.api format.api
end end
end end
def show def show
# show projects based on current user visibility # show projects based on current user visibility
@memberships = @user.memberships.all(:conditions => Project.visible_condition(User.current)) @memberships = @user.memberships.all(:conditions => Project.visible_by(User.current))
events = Redmine::Activity::Fetcher.new(User.current, :author => @user).events(nil, nil, :limit => 10) events = Redmine::Activity::Fetcher.new(User.current, :author => @user).events(nil, nil, :limit => 10)
@events_by_day = events.group_by(&:event_date) @events_by_day = events.group_by(&:event_date)
@@ -183,15 +177,6 @@ class UsersController < ApplicationController
redirect_to :controller => 'users', :action => 'edit', :id => @user redirect_to :controller => 'users', :action => 'edit', :id => @user
end end
verify :method => :delete, :only => :destroy, :render => {:nothing => true, :status => :method_not_allowed }
def destroy
@user.destroy
respond_to do |format|
format.html { redirect_to(users_url) }
format.api { head :ok }
end
end
def edit_membership def edit_membership
@membership = Member.edit_membership(params[:membership_id], params[:membership], @user) @membership = Member.edit_membership(params[:membership_id], params[:membership], @user)
@membership.save if request.post? @membership.save if request.post?
+13 -5
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang # Copyright (C) 2006-2007 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -76,13 +76,21 @@ private
def set_watcher(user, watching) def set_watcher(user, watching)
@watched.set_watcher(user, watching) @watched.set_watcher(user, watching)
if params[:replace].present?
if params[:replace].is_a? Array
replace_ids = params[:replace]
else
replace_ids = [params[:replace]]
end
else
replace_ids = ['watcher']
end
respond_to do |format| respond_to do |format|
format.html { redirect_to :back } format.html { redirect_to :back }
format.js do format.js do
render(:update) do |page| render(:update) do |page|
c = watcher_css(@watched) replace_ids.each do |replace_id|
page.select(".#{c}").each do |item| page.replace_html replace_id, watcher_link(@watched, user, :replace => replace_ids)
page.replace_html item, watcher_link(@watched, user)
end end
end end
end end
+26 -31
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang # Copyright (C) 2006-2007 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -34,27 +34,23 @@ require 'diff'
class WikiController < ApplicationController class WikiController < ApplicationController
default_search_scope :wiki_pages default_search_scope :wiki_pages
before_filter :find_wiki, :authorize before_filter :find_wiki, :authorize
before_filter :find_existing_or_new_page, :only => [:show, :edit, :update]
before_filter :find_existing_page, :only => [:rename, :protect, :history, :diff, :annotate, :add_attachment, :destroy] before_filter :find_existing_page, :only => [:rename, :protect, :history, :diff, :annotate, :add_attachment, :destroy]
verify :method => :post, :only => [:protect], :redirect_to => { :action => :show }
helper :attachments helper :attachments
include AttachmentsHelper include AttachmentsHelper
helper :watchers helper :watchers
# List of pages, sorted alphabetically and by parent (hierarchy) # List of pages, sorted alphabetically and by parent (hierarchy)
def index def index
load_pages_for_index load_pages_grouped_by_date_without_content
@pages_by_parent_id = @pages.group_by(&:parent_id)
end
# List of page, by last update
def date_index
load_pages_for_index
@pages_by_date = @pages.group_by {|p| p.updated_on.to_date}
end end
# display a page (in editing mode if it doesn't exist) # display a page (in editing mode if it doesn't exist)
def show def show
page_title = params[:id]
@page = @wiki.find_or_new_page(page_title)
if @page.new_record? if @page.new_record?
if User.current.allowed_to?(:edit_wiki_pages, @project) && editable? if User.current.allowed_to?(:edit_wiki_pages, @project) && editable?
edit edit
@@ -86,6 +82,7 @@ class WikiController < ApplicationController
# edit an existing page or a new one # edit an existing page or a new one
def edit def edit
@page = @wiki.find_or_new_page(params[:id])
return render_403 unless editable? return render_403 unless editable?
@page.content = WikiContent.new(:page => @page) if @page.new_record? @page.content = WikiContent.new(:page => @page) if @page.new_record?
@@ -96,11 +93,15 @@ class WikiController < ApplicationController
# To prevent StaleObjectError exception when reverting to a previous version # To prevent StaleObjectError exception when reverting to a previous version
@content.version = @page.content.version @content.version = @page.content.version
rescue ActiveRecord::StaleObjectError
# Optimistic locking exception
flash[:error] = l(:notice_locking_conflict)
end end
verify :method => :put, :only => :update, :render => {:nothing => true, :status => :method_not_allowed } verify :method => :put, :only => :update, :render => {:nothing => true, :status => :method_not_allowed }
# Creates a new page or updates an existing one # Creates a new page or updates an existing one
def update def update
@page = @wiki.find_or_new_page(params[:id])
return render_403 unless editable? return render_403 unless editable?
@page.content = WikiContent.new(:page => @page) if @page.new_record? @page.content = WikiContent.new(:page => @page) if @page.new_record?
@@ -130,8 +131,7 @@ class WikiController < ApplicationController
rescue ActiveRecord::StaleObjectError rescue ActiveRecord::StaleObjectError
# Optimistic locking exception # Optimistic locking exception
flash.now[:error] = l(:notice_locking_conflict) flash[:error] = l(:notice_locking_conflict)
render :action => 'edit'
end end
# rename a page # rename a page
@@ -146,7 +146,6 @@ class WikiController < ApplicationController
end end
end end
verify :method => :post, :only => :protect, :redirect_to => { :action => :show }
def protect def protect
@page.update_attribute :protected, params[:protected] @page.update_attribute :protected, params[:protected]
redirect_to :action => 'show', :project_id => @project, :id => @page.title redirect_to :action => 'show', :project_id => @project, :id => @page.title
@@ -217,6 +216,10 @@ class WikiController < ApplicationController
end end
end end
def date_index
load_pages_grouped_by_date_without_content
end
def preview def preview
page = @wiki.find_page(params[:id]) page = @wiki.find_page(params[:id])
# page is nil when previewing a new page # page is nil when previewing a new page
@@ -246,24 +249,10 @@ private
render_404 render_404
end end
# Finds the requested page or a new page if it doesn't exist
def find_existing_or_new_page
@page = @wiki.find_or_new_page(params[:id])
if @wiki.page_found_with_redirect?
redirect_to params.update(:id => @page.title)
end
end
# Finds the requested page and returns a 404 error if it doesn't exist # Finds the requested page and returns a 404 error if it doesn't exist
def find_existing_page def find_existing_page
@page = @wiki.find_page(params[:id]) @page = @wiki.find_page(params[:id])
if @page.nil? render_404 if @page.nil?
render_404
return
end
if @wiki.page_found_with_redirect?
redirect_to params.update(:id => @page.title)
end
end end
# Returns true if the current user is allowed to edit the page, otherwise false # Returns true if the current user is allowed to edit the page, otherwise false
@@ -278,7 +267,13 @@ private
helper.instance_method(:initial_page_content).bind(self).call(page) helper.instance_method(:initial_page_content).bind(self).call(page)
end end
def load_pages_for_index # eager load information about last updates, without loading text
@pages = @wiki.pages.with_updated_on.all(:order => 'title', :include => {:wiki => :project}) def load_pages_grouped_by_date_without_content
@pages = @wiki.pages.find :all, :select => "#{WikiPage.table_name}.*, #{WikiContent.table_name}.updated_on",
:joins => "LEFT JOIN #{WikiContent.table_name} ON #{WikiContent.table_name}.page_id = #{WikiPage.table_name}.id",
:order => 'title'
@pages_by_date = @pages.group_by {|p| p.updated_on.to_date}
@pages_by_parent_id = @pages.group_by(&:parent_id)
end end
end end
+3 -14
View File
@@ -32,17 +32,14 @@ class WorkflowsController < ApplicationController
if request.post? if request.post?
Workflow.destroy_all( ["role_id=? and tracker_id=?", @role.id, @tracker.id]) Workflow.destroy_all( ["role_id=? and tracker_id=?", @role.id, @tracker.id])
(params[:issue_status] || []).each { |status_id, transitions| (params[:issue_status] || []).each { |old, news|
transitions.each { |new_status_id, options| news.each { |new|
author = options.is_a?(Array) && options.include?('author') && !options.include?('always') @role.workflows.build(:tracker_id => @tracker.id, :old_status_id => old, :new_status_id => new)
assignee = options.is_a?(Array) && options.include?('assignee') && !options.include?('always')
@role.workflows.build(:tracker_id => @tracker.id, :old_status_id => status_id, :new_status_id => new_status_id, :author => author, :assignee => assignee)
} }
} }
if @role.save if @role.save
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'edit', :role_id => @role, :tracker_id => @tracker redirect_to :action => 'edit', :role_id => @role, :tracker_id => @tracker
return
end end
end end
@@ -51,14 +48,6 @@ class WorkflowsController < ApplicationController
@statuses = @tracker.issue_statuses @statuses = @tracker.issue_statuses
end end
@statuses ||= IssueStatus.find(:all, :order => 'position') @statuses ||= IssueStatus.find(:all, :order => 'position')
if @tracker && @role && @statuses.any?
workflows = Workflow.all(:conditions => {:role_id => @role.id, :tracker_id => @tracker.id})
@workflows = {}
@workflows['always'] = workflows.select {|w| !w.author && !w.assignee}
@workflows['author'] = workflows.select {|w| w.author}
@workflows['assignee'] = workflows.select {|w| w.assignee}
end
end end
def copy def copy
+51 -81
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang # Copyright (C) 2006-2010 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -110,20 +110,6 @@ module ApplicationHelper
:title => l(:label_revision_id, format_revision(revision))) :title => l(:label_revision_id, format_revision(revision)))
end end
# Generates a link to a message
def link_to_message(message, options={}, html_options = nil)
link_to(
h(truncate(message.subject, :length => 60)),
{ :controller => 'messages', :action => 'show',
:board_id => message.board_id,
:id => message.root,
:r => (message.parent_id && message.id),
:anchor => (message.parent_id ? "message-#{message.id}" : nil)
}.merge(options),
html_options
)
end
# Generates a link to a project if active # Generates a link to a project if active
# Examples: # Examples:
# #
@@ -187,15 +173,15 @@ module ApplicationHelper
end end
end end
def render_page_hierarchy(pages, node=nil, options={}) def render_page_hierarchy(pages, node=nil)
content = '' content = ''
if pages[node] if pages[node]
content << "<ul class=\"pages-hierarchy\">\n" content << "<ul class=\"pages-hierarchy\">\n"
pages[node].each do |page| pages[node].each do |page|
content << "<li>" content << "<li>"
content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title}, content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title},
:title => (options[:timestamp] && page.updated_on ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil)) :title => (page.respond_to?(:updated_on) ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
content << "\n" + render_page_hierarchy(pages, page.id, options) if pages[page.id] content << "\n" + render_page_hierarchy(pages, page.id) if pages[page.id]
content << "</li>\n" content << "</li>\n"
end end
content << "</ul>\n" content << "</ul>\n"
@@ -223,8 +209,8 @@ module ApplicationHelper
# Renders the project quick-jump box # Renders the project quick-jump box
def render_project_jump_box def render_project_jump_box
return unless User.current.logged? # Retrieve them now to avoid a COUNT query
projects = User.current.memberships.collect(&:project).compact.uniq projects = User.current.projects.all
if projects.any? if projects.any?
s = '<select onchange="if (this.value != \'\') { window.location = this.value; }">' + s = '<select onchange="if (this.value != \'\') { window.location = this.value; }">' +
"<option value=''>#{ l(:label_jump_to_a_project) }</option>" + "<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
@@ -336,18 +322,20 @@ module ApplicationHelper
page_param = options.delete(:page_param) || :page page_param = options.delete(:page_param) || :page
per_page_links = options.delete(:per_page_links) per_page_links = options.delete(:per_page_links)
url_param = params.dup url_param = params.dup
# don't reuse query params if filters are present
url_param.merge!(:fields => nil, :values => nil, :operators => nil) if url_param.delete(:set_filter)
html = '' html = ''
if paginator.current.previous if paginator.current.previous
html << link_to_content_update('&#171; ' + l(:label_previous), url_param.merge(page_param => paginator.current.previous)) + ' ' html << link_to_remote_content_update('&#171; ' + l(:label_previous), url_param.merge(page_param => paginator.current.previous)) + ' '
end end
html << (pagination_links_each(paginator, options) do |n| html << (pagination_links_each(paginator, options) do |n|
link_to_content_update(n.to_s, url_param.merge(page_param => n)) link_to_remote_content_update(n.to_s, url_param.merge(page_param => n))
end || '') end || '')
if paginator.current.next if paginator.current.next
html << ' ' + link_to_content_update((l(:label_next) + ' &#187;'), url_param.merge(page_param => paginator.current.next)) html << ' ' + link_to_remote_content_update((l(:label_next) + ' &#187;'), url_param.merge(page_param => paginator.current.next))
end end
unless count.nil? unless count.nil?
@@ -361,8 +349,14 @@ module ApplicationHelper
end end
def per_page_links(selected=nil) def per_page_links(selected=nil)
url_param = params.dup
url_param.clear if url_param.has_key?(:set_filter)
links = Setting.per_page_options_array.collect do |n| links = Setting.per_page_options_array.collect do |n|
n == selected ? n : link_to_content_update(n, params.merge(:per_page => n)) n == selected ? n : link_to_remote(n, {:update => "content",
:url => params.dup.merge(:per_page => n),
:method => :get},
{:href => url_for(url_param.merge(:per_page => n))})
end end
links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
end end
@@ -390,7 +384,7 @@ module ApplicationHelper
h(Setting.app_title) h(Setting.app_title)
else else
b = [] b = []
ancestors = (@project.root? ? [] : @project.ancestors.visible.all) ancestors = (@project.root? ? [] : @project.ancestors.visible)
if ancestors.any? if ancestors.any?
root = ancestors.shift root = ancestors.shift
b << link_to_project(root, {:jump => current_menu_item}, :class => 'root') b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
@@ -550,20 +544,15 @@ module ApplicationHelper
if page =~ /^(.+?)\#(.+)$/ if page =~ /^(.+?)\#(.+)$/
page, anchor = $1, $2 page, anchor = $1, $2
end end
anchor = sanitize_anchor_name(anchor) if anchor.present?
# check if page exists # check if page exists
wiki_page = link_project.wiki.find_page(page) wiki_page = link_project.wiki.find_page(page)
url = if anchor.present? && wiki_page.present? && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) && obj.page == wiki_page url = case options[:wiki_links]
"##{anchor}" when :local; "#{title}.html"
else
case options[:wiki_links]
when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '')
when :anchor; "##{title}" # used for single-file wiki export when :anchor; "##{title}" # used for single-file wiki export
else else
wiki_page_id = page.present? ? Wiki.titleize(page) : nil wiki_page_id = page.present? ? Wiki.titleize(page) : nil
url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project, :id => wiki_page_id, :anchor => anchor) url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project, :id => wiki_page_id, :anchor => anchor)
end end
end
link_to((title || page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new'))) link_to((title || page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
else else
# project or wiki doesn't exist # project or wiki doesn't exist
@@ -599,26 +588,16 @@ module ApplicationHelper
# source:some/file#L120 -> Link to line 120 of the file # source:some/file#L120 -> Link to line 120 of the file
# source:some/file@52#L120 -> Link to line 120 of the file's revision 52 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
# export:some/file -> Force the download of the file # export:some/file -> Force the download of the file
# Forum messages: # Forum messages:
# message#1218 -> Link to message with id 1218 # message#1218 -> Link to message with id 1218
#
# Links can refer other objects from other projects, using project identifier:
# identifier:r52
# identifier:document:"Some document"
# identifier:version:1.0.0
# identifier:source:some/file
def parse_redmine_links(text, project, obj, attr, only_path, options) def parse_redmine_links(text, project, obj, attr, only_path, options)
text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-]+):)?(attachment|document|version|commit|source|export|message|project)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|,|\s|\]|<|$)}) do |m| text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(attachment|document|version|commit|source|export|message|project)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|,|\s|\]|<|$)}) do |m|
leading, esc, project_prefix, project_identifier, prefix, sep, identifier = $1, $2, $3, $4, $5, $7 || $9, $8 || $10 leading, esc, prefix, sep, identifier = $1, $2, $3, $5 || $7, $6 || $8
link = nil link = nil
if project_identifier
project = Project.visible.find_by_identifier(project_identifier)
end
if esc.nil? if esc.nil?
if prefix.nil? && sep == 'r' if prefix.nil? && sep == 'r'
# project.changesets.visible raises an SQL error because of a double join on repositories if project && (changeset = project.changesets.find_by_revision(identifier))
if project && project.repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(project.repository.id, identifier)) link = link_to("r#{identifier}", {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
link = link_to("#{project_prefix}r#{identifier}", {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
:class => 'changeset', :class => 'changeset',
:title => truncate_single_line(changeset.comments, :length => 100)) :title => truncate_single_line(changeset.comments, :length => 100))
end end
@@ -632,18 +611,24 @@ module ApplicationHelper
:title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})") :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
end end
when 'document' when 'document'
if document = Document.visible.find_by_id(oid) if document = Document.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document}, link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
:class => 'document' :class => 'document'
end end
when 'version' when 'version'
if version = Version.visible.find_by_id(oid) if version = Version.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version}, link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
:class => 'version' :class => 'version'
end end
when 'message' when 'message'
if message = Message.visible.find_by_id(oid, :include => :parent) if message = Message.find_by_id(oid, :include => [:parent, {:board => :project}], :conditions => Project.visible_by(User.current))
link = link_to_message(message, {:only_path => only_path}, :class => 'message') link = link_to h(truncate(message.subject, :length => 60)), {:only_path => only_path,
:controller => 'messages',
:action => 'show',
:board_id => message.board,
:id => message.root,
:anchor => (message.parent ? "message-#{message.id}" : nil)},
:class => 'message'
end end
when 'project' when 'project'
if p = Project.visible.find_by_id(oid) if p = Project.visible.find_by_id(oid)
@@ -655,26 +640,26 @@ module ApplicationHelper
name = identifier.gsub(%r{^"(.*)"$}, "\\1") name = identifier.gsub(%r{^"(.*)"$}, "\\1")
case prefix case prefix
when 'document' when 'document'
if project && document = project.documents.visible.find_by_title(name) if project && document = project.documents.find_by_title(name)
link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document}, link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
:class => 'document' :class => 'document'
end end
when 'version' when 'version'
if project && version = project.versions.visible.find_by_name(name) if project && version = project.versions.find_by_name(name)
link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version}, link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
:class => 'version' :class => 'version'
end end
when 'commit' when 'commit'
if project && project.repository && (changeset = Changeset.visible.find(:first, :conditions => ["repository_id = ? AND scmid LIKE ?", project.repository.id, "#{name}%"])) if project && (changeset = project.changesets.find(:first, :conditions => ["scmid LIKE ?", "#{name}%"]))
link = link_to h("#{project_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.identifier}, link = link_to h("#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
:class => 'changeset', :class => 'changeset',
:title => truncate_single_line(changeset.comments, :length => 100) :title => truncate_single_line(changeset.comments, :length => 100)
end end
when 'source', 'export' when 'source', 'export'
if project && project.repository && User.current.allowed_to?(:browse_repository, project) if project && project.repository
name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$} name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
path, rev, anchor = $1, $3, $5 path, rev, anchor = $1, $3, $5
link = link_to h("#{project_prefix}#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project, link = link_to h("#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project,
:path => to_path_param(path), :path => to_path_param(path),
:rev => rev, :rev => rev,
:anchor => anchor, :anchor => anchor,
@@ -694,7 +679,7 @@ module ApplicationHelper
end end
end end
end end
leading + (link || "#{project_prefix}#{prefix}#{sep}#{identifier}") leading + (link || "#{prefix}#{sep}#{identifier}")
end end
end end
@@ -708,9 +693,9 @@ module ApplicationHelper
text.gsub!(HEADING_RE) do text.gsub!(HEADING_RE) do
level, attrs, content = $1.to_i, $2, $3 level, attrs, content = $1.to_i, $2, $3
item = strip_tags(content).strip item = strip_tags(content).strip
anchor = sanitize_anchor_name(item) anchor = item.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
@parsed_headings << [level, anchor, item] @parsed_headings << [level, anchor, item]
"<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>" "<h#{level} #{attrs} id=\"#{anchor}\">#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>"
end end
end end
@@ -852,8 +837,6 @@ module ApplicationHelper
'Calendar._FD = 1;' # Monday 'Calendar._FD = 1;' # Monday
when 7 when 7
'Calendar._FD = 0;' # Sunday 'Calendar._FD = 0;' # Sunday
when 6
'Calendar._FD = 6;' # Saturday
else else
'' # use language '' # use language
end end
@@ -894,27 +877,10 @@ module ApplicationHelper
end end
end end
def sanitize_anchor_name(anchor)
anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
end
# Returns the javascript tags that are included in the html layout head
def javascript_heads
tags = javascript_include_tag(:defaults)
unless User.current.pref.warn_on_leaving_unsaved == '0'
tags << "\n" + javascript_tag("Event.observe(window, 'load', function(){ new WarnLeavingUnsaved('#{escape_javascript( l(:text_warn_on_leaving_unsaved) )}'); });")
end
tags
end
def favicon def favicon
"<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />" "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />"
end end
def robot_exclusion_tag
'<meta name="robots" content="noindex,follow,noarchive" />'
end
# Returns true if arg is expected in the API response # Returns true if arg is expected in the API response
def include_in_api_response?(arg) def include_in_api_response?(arg)
unless @included_in_api_response unless @included_in_api_response
@@ -945,7 +911,11 @@ module ApplicationHelper
return self return self
end end
def link_to_content_update(text, url_params = {}, html_options = {}) def link_to_remote_content_update(text, url_params)
link_to(text, url_params, html_options) link_to_remote(text,
{:url => url_params, :method => :get, :update => 'content', :complete => 'window.scrollTo(0,0)'},
{:href => url_for(:params => url_params)}
)
end end
end end
+3 -15
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang # Copyright (C) 2006-2007 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -29,18 +29,6 @@ module AttachmentsHelper
end end
def to_utf8(str) def to_utf8(str)
if str.respond_to?(:force_encoding) str
str.force_encoding('UTF-8')
return str if str.valid_encoding?
else
return str if /\A[\r\n\t\x20-\x7e]*\Z/n.match(str) # for us-ascii
end
begin
Iconv.conv('UTF-8//IGNORE', 'UTF-8', str + ' ')[0..-3]
rescue Iconv::InvalidEncoding
# "UTF-8//IGNORE" is not supported on some OS
str
end
end end
end end
+9 -1
View File
@@ -32,6 +32,14 @@ module CalendarsHelper
end end
def link_to_month(link_name, year, month, options={}) def link_to_month(link_name, year, month, options={})
link_to_content_update(link_name, params.merge(:year => year, :month => month)) project_id = options[:project].present? ? options[:project].to_param : nil
link_target = calendar_path(:year => year, :month => month, :project_id => project_id)
link_to_remote(link_name,
{:update => "content", :url => link_target, :method => :put},
{:href => link_target})
end end
end end
+9 -9
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang # Copyright (C) 2006 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -37,7 +37,7 @@ module CustomFieldsHelper
field_id = "#{name}_custom_field_values_#{custom_field.id}" field_id = "#{name}_custom_field_values_#{custom_field.id}"
field_format = Redmine::CustomFieldFormat.find_by_name(custom_field.field_format) field_format = Redmine::CustomFieldFormat.find_by_name(custom_field.field_format)
case field_format.try(:edit_as) case field_format.edit_as
when "date" when "date"
text_field_tag(field_name, custom_value.value, :id => field_id, :size => 10) + text_field_tag(field_name, custom_value.value, :id => field_id, :size => 10) +
calendar_for(field_id) calendar_for(field_id)
@@ -49,7 +49,7 @@ module CustomFieldsHelper
blank_option = custom_field.is_required? ? blank_option = custom_field.is_required? ?
(custom_field.default_value.blank? ? "<option value=\"\">--- #{l(:actionview_instancetag_blank_option)} ---</option>" : '') : (custom_field.default_value.blank? ? "<option value=\"\">--- #{l(:actionview_instancetag_blank_option)} ---</option>" : '') :
'<option></option>' '<option></option>'
select_tag(field_name, blank_option + options_for_select(custom_field.possible_values_options(custom_value.customized), custom_value.value), :id => field_id) select_tag(field_name, blank_option + options_for_select(custom_field.possible_values, custom_value.value), :id => field_id)
else else
text_field_tag(field_name, custom_value.value, :id => field_id) text_field_tag(field_name, custom_value.value, :id => field_id)
end end
@@ -68,11 +68,11 @@ module CustomFieldsHelper
custom_field_label_tag(name, custom_value) + custom_field_tag(name, custom_value) custom_field_label_tag(name, custom_value) + custom_field_tag(name, custom_value)
end end
def custom_field_tag_for_bulk_edit(name, custom_field, projects=nil) def custom_field_tag_for_bulk_edit(name, custom_field)
field_name = "#{name}[custom_field_values][#{custom_field.id}]" field_name = "#{name}[custom_field_values][#{custom_field.id}]"
field_id = "#{name}_custom_field_values_#{custom_field.id}" field_id = "#{name}_custom_field_values_#{custom_field.id}"
field_format = Redmine::CustomFieldFormat.find_by_name(custom_field.field_format) field_format = Redmine::CustomFieldFormat.find_by_name(custom_field.field_format)
case field_format.try(:edit_as) case field_format.edit_as
when "date" when "date"
text_field_tag(field_name, '', :id => field_id, :size => 10) + text_field_tag(field_name, '', :id => field_id, :size => 10) +
calendar_for(field_id) calendar_for(field_id)
@@ -83,7 +83,7 @@ module CustomFieldsHelper
[l(:general_text_yes), '1'], [l(:general_text_yes), '1'],
[l(:general_text_no), '0']]), :id => field_id) [l(:general_text_no), '0']]), :id => field_id)
when "list" when "list"
select_tag(field_name, options_for_select([[l(:label_no_change_option), '']] + custom_field.possible_values_options(projects)), :id => field_id) select_tag(field_name, options_for_select([[l(:label_no_change_option), '']] + custom_field.possible_values), :id => field_id)
else else
text_field_tag(field_name, '', :id => field_id) text_field_tag(field_name, '', :id => field_id)
end end
@@ -101,8 +101,8 @@ module CustomFieldsHelper
end end
# Return an array of custom field formats which can be used in select_tag # Return an array of custom field formats which can be used in select_tag
def custom_field_formats_for_select(custom_field) def custom_field_formats_for_select
Redmine::CustomFieldFormat.as_select(custom_field.class.customized_class.name) Redmine::CustomFieldFormat.as_select
end end
# Renders the custom_values in api views # Renders the custom_values in api views
+16 -8
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang # Copyright (C) 2006 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -21,21 +21,29 @@ module GanttHelper
case in_or_out case in_or_out
when :in when :in
if gantt.zoom < 4 if gantt.zoom < 4
link_to_content_update l(:text_zoom_in), link_to_remote(l(:text_zoom_in),
params.merge(gantt.params.merge(:zoom => (gantt.zoom+1))), {:url => gantt.params.merge(:zoom => (gantt.zoom+1)), :method => :get, :update => 'content'},
:class => 'icon icon-zoom-in' {:href => url_for(gantt.params.merge(:zoom => (gantt.zoom+1))),
:class => 'icon icon-zoom-in'})
else else
content_tag('span', l(:text_zoom_in), :class => 'icon icon-zoom-in') content_tag('span', l(:text_zoom_in), :class => 'icon icon-zoom-in')
end end
when :out when :out
if gantt.zoom > 1 if gantt.zoom > 1
link_to_content_update l(:text_zoom_out), link_to_remote(l(:text_zoom_out),
params.merge(gantt.params.merge(:zoom => (gantt.zoom-1))), {:url => gantt.params.merge(:zoom => (gantt.zoom-1)), :method => :get, :update => 'content'},
:class => 'icon icon-zoom-out' {:href => url_for(gantt.params.merge(:zoom => (gantt.zoom-1))),
:class => 'icon icon-zoom-out'})
else else
content_tag('span', l(:text_zoom_out), :class => 'icon icon-zoom-out') content_tag('span', l(:text_zoom_out), :class => 'icon icon-zoom-out')
end end
end end
end end
def number_of_issues_on_versions(gantt)
versions = gantt.events.collect {|event| (event.is_a? Version) ? event : nil}.compact
versions.sum {|v| v.fixed_issues.for_gantt.with_query(@query).count}
end
end end
+8 -68
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang # Copyright (C) 2006 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -55,29 +55,19 @@ module IssuesHelper
"<strong>#{@cached_label_priority}</strong>: #{issue.priority.name}" "<strong>#{@cached_label_priority}</strong>: #{issue.priority.name}"
end end
def issue_heading(issue)
h("#{issue.tracker} ##{issue.id}")
end
def render_issue_subject_with_tree(issue) def render_issue_subject_with_tree(issue)
s = '' s = ''
ancestors = issue.root? ? [] : issue.ancestors.visible.all issue.ancestors.each do |ancestor|
ancestors.each do |ancestor|
s << '<div>' + content_tag('p', link_to_issue(ancestor)) s << '<div>' + content_tag('p', link_to_issue(ancestor))
end end
s << '<div>' s << '<div>' + content_tag('h3', h(issue.subject))
subject = h(issue.subject) s << '</div>' * (issue.ancestors.size + 1)
if issue.is_private?
subject = content_tag('span', l(:field_is_private), :class => 'private') + ' ' + subject
end
s << content_tag('h3', subject)
s << '</div>' * (ancestors.size + 1)
s s
end end
def render_descendants_tree(issue) def render_descendants_tree(issue)
s = '<form><table class="list issues">' s = '<form><table class="list issues">'
issue_list(issue.descendants.visible.sort_by(&:lft)) do |child, level| issue_list(issue.descendants.sort_by(&:lft)) do |child, level|
s << content_tag('tr', s << content_tag('tr',
content_tag('td', check_box_tag("ids[]", child.id, false, :id => nil), :class => 'checkbox') + content_tag('td', check_box_tag("ids[]", child.id, false, :id => nil), :class => 'checkbox') +
content_tag('td', link_to_issue(child, :truncate => 60), :class => 'subject') + content_tag('td', link_to_issue(child, :truncate => 60), :class => 'subject') +
@@ -109,24 +99,6 @@ module IssuesHelper
s s
end end
def issues_destroy_confirmation_message(issues)
issues = [issues] unless issues.is_a?(Array)
message = l(:text_issues_destroy_confirmation)
descendant_count = issues.inject(0) {|memo, i| memo += (i.right - i.left - 1)/2}
if descendant_count > 0
issues.each do |issue|
next if issue.root?
issues.each do |other_issue|
descendant_count -= 1 if issue.is_descendant_of?(other_issue)
end
end
if descendant_count > 0
message << "\n" + l(:text_issues_destroy_descendants_confirmation, :count => descendant_count)
end
end
message
end
def sidebar_queries def sidebar_queries
unless @sidebar_queries unless @sidebar_queries
# User can see public queries and his own queries # User can see public queries and his own queries
@@ -134,32 +106,13 @@ module IssuesHelper
# Project specific queries and global queries # Project specific queries and global queries
visible << (@project.nil? ? ["project_id IS NULL"] : ["project_id IS NULL OR project_id = ?", @project.id]) visible << (@project.nil? ? ["project_id IS NULL"] : ["project_id IS NULL OR project_id = ?", @project.id])
@sidebar_queries = Query.find(:all, @sidebar_queries = Query.find(:all,
:select => 'id, name, is_public', :select => 'id, name',
:order => "name ASC", :order => "name ASC",
:conditions => visible.conditions) :conditions => visible.conditions)
end end
@sidebar_queries @sidebar_queries
end end
def query_links(title, queries)
# links to #index on issues/show
url_params = controller_name == 'issues' ? {:controller => 'issues', :action => 'index', :project_id => @project} : params
content_tag('h3', title) +
queries.collect {|query|
link_to(h(query.name), url_params.merge(:query_id => query))
}.join('<br />')
end
def render_sidebar_queries
out = ''
queries = sidebar_queries.select {|q| !q.is_public?}
out << query_links(l(:label_my_queries), queries) if queries.any?
queries = sidebar_queries.select {|q| q.is_public?}
out << query_links(l(:label_query_plural), queries) if queries.any?
out
end
def show_detail(detail, no_html=false) def show_detail(detail, no_html=false)
case detail.property case detail.property
when 'attr' when 'attr'
@@ -182,10 +135,6 @@ module IssuesHelper
label = l(:field_parent_issue) label = l(:field_parent_issue)
value = "##{detail.value}" unless detail.value.blank? value = "##{detail.value}" unless detail.value.blank?
old_value = "##{detail.old_value}" unless detail.old_value.blank? old_value = "##{detail.old_value}" unless detail.old_value.blank?
when detail.prop_key == 'is_private'
value = l(detail.value == "0" ? :general_text_No : :general_text_Yes) unless detail.value.blank?
old_value = l(detail.old_value == "0" ? :general_text_No : :general_text_Yes) unless detail.old_value.blank?
end end
when 'cf' when 'cf'
custom_field = CustomField.find_by_id(detail.prop_key) custom_field = CustomField.find_by_id(detail.prop_key)
@@ -215,16 +164,7 @@ module IssuesHelper
end end
end end
if detail.property == 'attr' && detail.prop_key == 'description' if !detail.value.blank?
s = l(:text_journal_changed_no_detail, :label => label)
unless no_html
diff_link = link_to 'diff',
{:controller => 'journals', :action => 'diff', :id => detail.journal_id, :detail_id => detail.id},
:title => l(:label_view_diff)
s << " (#{ diff_link })"
end
s
elsif !detail.value.blank?
case detail.property case detail.property
when 'attr', 'cf' when 'attr', 'cf'
if !detail.old_value.blank? if !detail.old_value.blank?
+10
View File
@@ -16,4 +16,14 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module MessagesHelper module MessagesHelper
def link_to_message(message)
return '' unless message
link_to h(truncate(message.subject, :length => 60)), :controller => 'messages',
:action => 'show',
:board_id => message.board_id,
:id => message.root,
:r => (message.parent_id && message.id),
:anchor => (message.parent_id ? "message-#{message.id}" : nil)
end
end end
+2 -2
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang # Copyright (C) 2006 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
+5 -6
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang # Copyright (C) 2006-2007 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -70,7 +70,6 @@ module QueriesHelper
cond = "project_id IS NULL" cond = "project_id IS NULL"
cond << " OR project_id = #{@project.id}" if @project cond << " OR project_id = #{@project.id}" if @project
@query = Query.find(params[:query_id], :conditions => cond) @query = Query.find(params[:query_id], :conditions => cond)
raise ::Unauthorized unless @query.visible?
@query.project = @project @query.project = @project
session[:query] = {:id => @query.id, :project_id => @query.project_id} session[:query] = {:id => @query.id, :project_id => @query.project_id}
sort_clear sort_clear
@@ -79,16 +78,16 @@ module QueriesHelper
# Give it a name, required to be valid # Give it a name, required to be valid
@query = Query.new(:name => "_") @query = Query.new(:name => "_")
@query.project = @project @query.project = @project
if params[:fields] || params[:f] if params[:fields]
@query.filters = {} @query.filters = {}
@query.add_filters(params[:fields] || params[:f], params[:operators] || params[:op], params[:values] || params[:v]) @query.add_filters(params[:fields], params[:operators], params[:values])
else else
@query.available_filters.keys.each do |field| @query.available_filters.keys.each do |field|
@query.add_short_filter(field, params[field]) if params[field] @query.add_short_filter(field, params[field]) if params[field]
end end
end end
@query.group_by = params[:group_by] @query.group_by = params[:group_by]
@query.column_names = params[:c] || (params[:query] && params[:query][:column_names]) @query.column_names = params[:query] && params[:query][:column_names]
session[:query] = {:project_id => @query.project_id, :filters => @query.filters, :group_by => @query.group_by, :column_names => @query.column_names} session[:query] = {:project_id => @query.project_id, :filters => @query.filters, :group_by => @query.group_by, :column_names => @query.column_names}
else else
@query = Query.find_by_id(session[:query][:id]) if session[:query][:id] @query = Query.find_by_id(session[:query][:id]) if session[:query][:id]
+2 -2
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang # Copyright (C) 2006 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
+24 -116
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang # Copyright (C) 2006 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -16,7 +16,6 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
require 'iconv' require 'iconv'
require 'redmine/codeset_util'
module RepositoriesHelper module RepositoriesHelper
def format_revision(revision) def format_revision(revision)
@@ -49,8 +48,7 @@ module RepositoriesHelper
when 'A' when 'A'
# Detects moved/copied files # Detects moved/copied files
if !change.from_path.blank? if !change.from_path.blank?
change.action = change.action = @changeset.changes.detect {|c| c.action == 'D' && c.path == change.from_path} ? 'R' : 'C'
@changeset.changes.detect {|c| c.action == 'D' && c.path == change.from_path} ? 'R' : 'C'
end end
change change
when 'D' when 'D'
@@ -58,7 +56,7 @@ module RepositoriesHelper
else else
change change
end end
end.compact end.compact
tree = { } tree = { }
changes.each do |change| changes.each do |change|
@@ -74,11 +72,13 @@ module RepositoriesHelper
end end
p[:c] = change p[:c] = change
end end
render_changes_tree(tree[:s]) render_changes_tree(tree[:s])
end end
def render_changes_tree(tree) def render_changes_tree(tree)
return '' if tree.nil? return '' if tree.nil?
output = '' output = ''
output << '<ul>' output << '<ul>'
tree.keys.sort.each do |file| tree.keys.sort.each do |file|
@@ -117,24 +117,7 @@ module RepositoriesHelper
end end
def to_utf8(str) def to_utf8(str)
return str if str.nil?
str = to_utf8_internal(str)
if str.respond_to?(:force_encoding)
str.force_encoding('UTF-8')
end
str
end
def to_utf8_internal(str)
return str if str.nil?
if str.respond_to?(:force_encoding)
str.force_encoding('ASCII-8BIT')
end
return str if str.empty?
return str if /\A[\r\n\t\x20-\x7e]*\Z/n.match(str) # for us-ascii return str if /\A[\r\n\t\x20-\x7e]*\Z/n.match(str) # for us-ascii
if str.respond_to?(:force_encoding)
str.force_encoding('UTF-8')
end
@encodings ||= Setting.repositories_encodings.split(',').collect(&:strip) @encodings ||= Setting.repositories_encodings.split(',').collect(&:strip)
@encodings.each do |encoding| @encodings.each do |encoding|
begin begin
@@ -143,38 +126,25 @@ module RepositoriesHelper
# do nothing here and try the next encoding # do nothing here and try the next encoding
end end
end end
str = Redmine::CodesetUtil.replace_invalid_utf8(str) str
end end
private :to_utf8_internal
def repository_field_tags(form, repository) def repository_field_tags(form, repository)
method = repository.class.name.demodulize.underscore + "_field_tags" method = repository.class.name.demodulize.underscore + "_field_tags"
if repository.is_a?(Repository) && send(method, form, repository) if repository.is_a?(Repository) && respond_to?(method) && method != 'repository_field_tags'
respond_to?(method) && method != 'repository_field_tags'
send(method, form, repository)
end
end end
def scm_select_tag(repository) def scm_select_tag(repository)
scm_options = [["--- #{l(:actionview_instancetag_blank_option)} ---", '']] scm_options = [["--- #{l(:actionview_instancetag_blank_option)} ---", '']]
Redmine::Scm::Base.all.each do |scm| Redmine::Scm::Base.all.each do |scm|
if Setting.enabled_scm.include?(scm) || scm_options << ["Repository::#{scm}".constantize.scm_name, scm] if Setting.enabled_scm.include?(scm) || (repository && repository.class.name.demodulize == scm)
(repository && repository.class.name.demodulize == scm)
scm_options << ["Repository::#{scm}".constantize.scm_name, scm]
end
end end
select_tag('repository_scm', select_tag('repository_scm',
options_for_select(scm_options, repository.class.name.demodulize), options_for_select(scm_options, repository.class.name.demodulize),
:disabled => (repository && !repository.new_record?), :disabled => (repository && !repository.new_record?),
:onchange => remote_function( :onchange => remote_function(:url => { :controller => 'repositories', :action => 'edit', :id => @project }, :method => :get, :with => "Form.serialize(this.form)")
:url => { )
:controller => 'repositories',
:action => 'edit',
:id => @project
},
:method => :get,
:with => "Form.serialize(this.form)")
)
end end
def with_leading_slash(path) def with_leading_slash(path)
@@ -186,99 +156,37 @@ module RepositoriesHelper
end end
def subversion_field_tags(form, repository) def subversion_field_tags(form, repository)
content_tag('p', form.text_field(:url, :size => 60, :required => true, content_tag('p', form.text_field(:url, :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?)) +
:disabled => (repository && !repository.root_url.blank?)) +
'<br />(file:///, http://, https://, svn://, svn+[tunnelscheme]://)') + '<br />(file:///, http://, https://, svn://, svn+[tunnelscheme]://)') +
content_tag('p', form.text_field(:login, :size => 30)) + content_tag('p', form.text_field(:login, :size => 30)) +
content_tag('p', form.password_field( content_tag('p', form.password_field(:password, :size => 30, :name => 'ignore',
:password, :size => 30, :name => 'ignore', :value => ((repository.new_record? || repository.password.blank?) ? '' : ('x'*15)),
:value => ((repository.new_record? || repository.password.blank?) ? '' : ('x'*15)), :onfocus => "this.value=''; this.name='repository[password]';",
:onfocus => "this.value=''; this.name='repository[password]';", :onchange => "this.name='repository[password]';"))
:onchange => "this.name='repository[password]';"))
end end
def darcs_field_tags(form, repository) def darcs_field_tags(form, repository)
content_tag('p', form.text_field( content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true, :disabled => (repository && !repository.new_record?)))
:url, :label => l(:field_path_to_repository),
:size => 60, :required => true,
:disabled => (repository && !repository.new_record?))) +
content_tag('p', form.select(
:log_encoding, [nil] + Setting::ENCODINGS,
:label => l(:field_commit_logs_encoding), :required => true))
end end
def mercurial_field_tags(form, repository) def mercurial_field_tags(form, repository)
content_tag('p', form.text_field( content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?)))
:url, :label => l(:field_path_to_repository),
:size => 60, :required => true,
:disabled => (repository && !repository.root_url.blank?)
) +
'<br />' + l(:text_mercurial_repository_note)) +
content_tag('p', form.select(
:path_encoding, [nil] + Setting::ENCODINGS,
:label => l(:field_scm_path_encoding)
) +
'<br />' + l(:text_scm_path_encoding_note))
end end
def git_field_tags(form, repository) def git_field_tags(form, repository)
content_tag('p', form.text_field( content_tag('p', form.text_field(:url, :label => 'Path to .git directory', :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?)))
:url, :label => l(:field_path_to_repository),
:size => 60, :required => true,
:disabled => (repository && !repository.root_url.blank?)
) +
'<br />' + l(:text_git_repository_note)) +
content_tag('p', form.select(
:path_encoding, [nil] + Setting::ENCODINGS,
:label => l(:field_scm_path_encoding)
) +
'<br />' + l(:text_scm_path_encoding_note)) +
content_tag('p', form.check_box(
:extra_report_last_commit,
:label => l(:label_git_report_last_commit)
))
end end
def cvs_field_tags(form, repository) def cvs_field_tags(form, repository)
content_tag('p', form.text_field( content_tag('p', form.text_field(:root_url, :label => 'CVSROOT', :size => 60, :required => true, :disabled => !repository.new_record?)) +
:root_url, content_tag('p', form.text_field(:url, :label => 'Module', :size => 30, :required => true, :disabled => !repository.new_record?))
:label => l(:field_cvsroot),
:size => 60, :required => true,
:disabled => !repository.new_record?)) +
content_tag('p', form.text_field(
:url,
:label => l(:field_cvs_module),
:size => 30, :required => true,
:disabled => !repository.new_record?)) +
content_tag('p', form.select(
:log_encoding, [nil] + Setting::ENCODINGS,
:label => l(:field_commit_logs_encoding), :required => true)) +
content_tag('p', form.select(
:path_encoding, [nil] + Setting::ENCODINGS,
:label => l(:field_scm_path_encoding)
) +
'<br />' + l(:text_scm_path_encoding_note))
end end
def bazaar_field_tags(form, repository) def bazaar_field_tags(form, repository)
content_tag('p', form.text_field( content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true, :disabled => (repository && !repository.new_record?)))
:url, :label => l(:field_path_to_repository),
:size => 60, :required => true,
:disabled => (repository && !repository.new_record?))) +
content_tag('p', form.select(
:log_encoding, [nil] + Setting::ENCODINGS,
:label => l(:field_commit_logs_encoding), :required => true))
end end
def filesystem_field_tags(form, repository) def filesystem_field_tags(form, repository)
content_tag('p', form.text_field( content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?)))
:url, :label => l(:field_root_directory),
:size => 60, :required => true,
:disabled => (repository && !repository.root_url.blank?))) +
content_tag('p', form.select(
:path_encoding, [nil] + Setting::ENCODINGS,
:label => l(:field_scm_path_encoding)
) +
'<br />' + l(:text_scm_path_encoding_note))
end end
end end
+3 -3
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang # Copyright (C) 2006-2007 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -57,7 +57,7 @@ module SearchHelper
c = results_by_type[t] c = results_by_type[t]
next if c == 0 next if c == 0
text = "#{type_label(t)} (#{c})" text = "#{type_label(t)} (#{c})"
links << link_to(text, :q => params[:q], :titles_only => params[:titles_only], :all_words => params[:all_words], :scope => params[:scope], t => 1) links << link_to(text, :q => params[:q], :titles_only => params[:title_only], :all_words => params[:all_words], :scope => params[:scope], t => 1)
end end
('<ul>' + links.map {|link| content_tag('li', link)}.join(' ') + '</ul>') unless links.empty? ('<ul>' + links.map {|link| content_tag('li', link)}.join(' ') + '</ul>') unless links.empty?
end end
+6 -13
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang # Copyright (C) 2006-2009 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -33,9 +33,7 @@ module SettingsHelper
choices = [[blank_text.is_a?(Symbol) ? l(blank_text) : blank_text, '']] + choices choices = [[blank_text.is_a?(Symbol) ? l(blank_text) : blank_text, '']] + choices
end end
setting_label(setting, options) + setting_label(setting, options) +
select_tag("settings[#{setting}]", select_tag("settings[#{setting}]", options_for_select(choices, Setting.send(setting).to_s), options)
options_for_select(choices, Setting.send(setting).to_s),
options)
end end
def setting_multiselect(setting, choices, options={}) def setting_multiselect(setting, choices, options={})
@@ -46,15 +44,10 @@ module SettingsHelper
hidden_field_tag("settings[#{setting}][]", '') + hidden_field_tag("settings[#{setting}][]", '') +
choices.collect do |choice| choices.collect do |choice|
text, value = (choice.is_a?(Array) ? choice : [choice, choice]) text, value = (choice.is_a?(Array) ? choice : [choice, choice])
content_tag( content_tag('label',
'label', check_box_tag("settings[#{setting}][]", value, Setting.send(setting).include?(value)) + text.to_s,
check_box_tag(
"settings[#{setting}][]",
value,
Setting.send(setting).include?(value)
) + text.to_s,
:class => 'block' :class => 'block'
) )
end.join end.join
end end
@@ -71,7 +64,7 @@ module SettingsHelper
def setting_check_box(setting, options={}) def setting_check_box(setting, options={})
setting_label(setting, options) + setting_label(setting, options) +
hidden_field_tag("settings[#{setting}]", 0) + hidden_field_tag("settings[#{setting}]", 0) +
check_box_tag("settings[#{setting}]", 1, Setting.send("#{setting}?"), options) check_box_tag("settings[#{setting}]", 1, Setting.send("#{setting}?"), options)
end end
def setting_label(setting, options={}) def setting_label(setting, options={})
+6 -2
View File
@@ -200,12 +200,16 @@ module SortHelper
caption = column.to_s.humanize unless caption caption = column.to_s.humanize unless caption
sort_options = { :sort => @sort_criteria.add(column.to_s, order).to_param } sort_options = { :sort => @sort_criteria.add(column.to_s, order).to_param }
url_options = params.merge(sort_options) # don't reuse params if filters are present
url_options = params.has_key?(:set_filter) ? sort_options : params.merge(sort_options)
# Add project_id to url_options # Add project_id to url_options
url_options = url_options.merge(:project_id => params[:project_id]) if params.has_key?(:project_id) url_options = url_options.merge(:project_id => params[:project_id]) if params.has_key?(:project_id)
link_to_content_update(caption, url_options, :class => css) link_to_remote(caption,
{:update => "content", :url => url_options, :method => :get},
{:href => url_for(url_options),
:class => css})
end end
# Returns a table header <th> tag with a sort link for the named column # Returns a table header <th> tag with a sort link for the named column
+5 -4
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang # Copyright (C) 2006 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -17,10 +17,11 @@
module VersionsHelper module VersionsHelper
STATUS_BY_CRITERIAS = %w(category tracker status priority author assigned_to) STATUS_BY_CRITERIAS = %w(category tracker priority author assigned_to)
def render_issue_status_by(version, criteria) def render_issue_status_by(version, criteria)
criteria = 'category' unless STATUS_BY_CRITERIAS.include?(criteria) criteria ||= 'category'
raise 'Unknown criteria' unless STATUS_BY_CRITERIAS.include?(criteria)
h = Hash.new {|k,v| k[v] = [0, 0]} h = Hash.new {|k,v| k[v] = [0, 0]}
begin begin
+16 -11
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang # Copyright (C) 2006-2007 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -17,17 +17,27 @@
module WatchersHelper module WatchersHelper
def watcher_tag(object, user, options={}) # Valid options
content_tag("span", watcher_link(object, user), :class => watcher_css(object)) # * :id - the element id
# * :replace - a string or array of element ids that will be
# replaced
def watcher_tag(object, user, options={:replace => 'watcher'})
id = options[:id]
id ||= options[:replace] if options[:replace].is_a? String
content_tag("span", watcher_link(object, user, options), :id => id)
end end
def watcher_link(object, user) # Valid options
# * :replace - a string or array of element ids that will be
# replaced
def watcher_link(object, user, options={:replace => 'watcher'})
return '' unless user && user.logged? && object.respond_to?('watched_by?') return '' unless user && user.logged? && object.respond_to?('watched_by?')
watched = object.watched_by?(user) watched = object.watched_by?(user)
url = {:controller => 'watchers', url = {:controller => 'watchers',
:action => (watched ? 'unwatch' : 'watch'), :action => (watched ? 'unwatch' : 'watch'),
:object_type => object.class.to_s.underscore, :object_type => object.class.to_s.underscore,
:object_id => object.id} :object_id => object.id,
:replace => options[:replace]}
link_to_remote((watched ? l(:button_unwatch) : l(:button_watch)), link_to_remote((watched ? l(:button_unwatch) : l(:button_watch)),
{:url => url}, {:url => url},
:href => url_for(url), :href => url_for(url),
@@ -35,11 +45,6 @@ module WatchersHelper
end end
# Returns the css class used to identify watch links for a given +object+
def watcher_css(object)
"#{object.class.to_s.underscore}-#{object.id}-watcher"
end
# Returns a comma separated list of users watching the given object # Returns a comma separated list of users watching the given object
def watchers_list(object) def watchers_list(object)
remove_allowed = User.current.allowed_to?("delete_#{object.class.name.underscore}_watchers".to_sym, object.project) remove_allowed = User.current.allowed_to?("delete_#{object.class.name.underscore}_watchers".to_sym, object.project)
+45 -11
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang # Copyright (C) 2006-2007 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -18,18 +18,52 @@
module WikiHelper module WikiHelper
def wiki_page_options_for_select(pages, selected = nil, parent = nil, level = 0) def wiki_page_options_for_select(pages, selected = nil, parent = nil, level = 0)
pages = pages.group_by(&:parent) unless pages.is_a?(Hash)
s = '' s = ''
if pages.has_key?(parent) pages.select {|p| p.parent == parent}.each do |page|
pages[parent].each do |page| attrs = "value='#{page.id}'"
attrs = "value='#{page.id}'" attrs << " selected='selected'" if selected == page
attrs << " selected='selected'" if selected == page indent = (level > 0) ? ('&nbsp;' * level * 2 + '&#187; ') : nil
indent = (level > 0) ? ('&nbsp;' * level * 2 + '&#187; ') : nil
s << "<option #{attrs}>#{indent}#{h page.pretty_title}</option>\n" + s << "<option #{attrs}>#{indent}#{h page.pretty_title}</option>\n" +
wiki_page_options_for_select(pages, selected, page, level + 1) wiki_page_options_for_select(pages, selected, page, level + 1)
end
end end
s s
end end
def html_diff(wdiff)
words = wdiff.words.collect{|word| h(word)}
words_add = 0
words_del = 0
dels = 0
del_off = 0
wdiff.diff.diffs.each do |diff|
add_at = nil
add_to = nil
del_at = nil
deleted = ""
diff.each do |change|
pos = change[1]
if change[0] == "+"
add_at = pos + dels unless add_at
add_to = pos + dels
words_add += 1
else
del_at = pos unless del_at
deleted << ' ' + h(change[2])
words_del += 1
end
end
if add_at
words[add_at] = '<span class="diff_in">' + words[add_at]
words[add_to] = words[add_to] + '</span>'
end
if del_at
words.insert del_at - del_off + dels + words_add, '<span class="diff_out">' + deleted + '</span>'
dels += 1
del_off += words_del
words_del = 0
end
end
simple_format_without_paragraph(words.join(' '))
end
end end
+3 -3
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang # Copyright (C) 2006-2007 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -43,7 +43,7 @@ class Attachment < ActiveRecord::Base
"LEFT JOIN #{Project.table_name} ON #{Document.table_name}.project_id = #{Project.table_name}.id"} "LEFT JOIN #{Project.table_name} ON #{Document.table_name}.project_id = #{Project.table_name}.id"}
cattr_accessor :storage_path cattr_accessor :storage_path
@@storage_path = Redmine::Configuration['attachments_storage_path'] || "#{RAILS_ROOT}/files" @@storage_path = "#{RAILS_ROOT}/files"
def validate def validate
if self.filesize > Setting.attachment_max_size.to_i.kilobytes if self.filesize > Setting.attachment_max_size.to_i.kilobytes
-10
View File
@@ -16,8 +16,6 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class AuthSource < ActiveRecord::Base class AuthSource < ActiveRecord::Base
include Redmine::Ciphering
has_many :users has_many :users
validates_presence_of :name validates_presence_of :name
@@ -34,14 +32,6 @@ class AuthSource < ActiveRecord::Base
"Abstract" "Abstract"
end end
def account_password
read_ciphered_attribute(:account_password)
end
def account_password=(arg)
write_ciphered_attribute(:account_password, arg)
end
def allow_password_changes? def allow_password_changes?
self.class.allow_password_changes? self.class.allow_password_changes?
end end
+2 -2
View File
@@ -20,8 +20,8 @@ require 'iconv'
class AuthSourceLdap < AuthSource class AuthSourceLdap < AuthSource
validates_presence_of :host, :port, :attr_login validates_presence_of :host, :port, :attr_login
validates_length_of :name, :host, :maximum => 60, :allow_nil => true validates_length_of :name, :host, :account_password, :maximum => 60, :allow_nil => true
validates_length_of :account, :account_password, :base_dn, :maximum => 255, :allow_nil => true validates_length_of :account, :base_dn, :maximum => 255, :allow_nil => true
validates_length_of :attr_login, :attr_firstname, :attr_lastname, :attr_mail, :maximum => 30, :allow_nil => true validates_length_of :attr_login, :attr_firstname, :attr_lastname, :attr_mail, :maximum => 30, :allow_nil => true
validates_numericality_of :port, :only_integer => true validates_numericality_of :port, :only_integer => true
+2 -7
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang # Copyright (C) 2006-2007 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -25,11 +25,6 @@ class Change < ActiveRecord::Base
changeset.repository.relative_path(path) changeset.repository.relative_path(path)
end end
def before_validation
self.path = Redmine::CodesetUtil.replace_invalid_utf8(self.path)
self.from_path = Redmine::CodesetUtil.replace_invalid_utf8(self.from_path)
end
def init_path def init_path
self.path ||= "" self.path ||= ""
end end
+41 -67
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang # Copyright (C) 2006-2010 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -42,7 +42,7 @@ class Changeset < ActiveRecord::Base
validates_uniqueness_of :scmid, :scope => :repository_id, :allow_nil => true validates_uniqueness_of :scmid, :scope => :repository_id, :allow_nil => true
named_scope :visible, lambda {|*args| { :include => {:repository => :project}, named_scope :visible, lambda {|*args| { :include => {:repository => :project},
:conditions => Project.allowed_to_condition(args.shift || User.current, :view_changesets, *args) } } :conditions => Project.allowed_to_condition(args.first || User.current, :view_changesets) } }
def revision=(r) def revision=(r)
write_attribute :revision, (r.nil? ? nil : r.to_s) write_attribute :revision, (r.nil? ? nil : r.to_s)
@@ -57,6 +57,10 @@ class Changeset < ActiveRecord::Base
end end
end end
def comments=(comment)
write_attribute(:comments, Changeset.normalize_comments(comment))
end
def committed_on=(date) def committed_on=(date)
self.commit_date = date self.commit_date = date
super super
@@ -71,6 +75,10 @@ class Changeset < ActiveRecord::Base
end end
end end
def committer=(arg)
write_attribute(:committer, self.class.to_utf8(arg.to_s))
end
def project def project
repository.project repository.project
end end
@@ -80,10 +88,7 @@ class Changeset < ActiveRecord::Base
end end
def before_create def before_create
self.committer = self.class.to_utf8(self.committer, repository.repo_log_encoding) self.user = repository.find_committer_user(committer)
self.comments = self.class.normalize_comments(
self.comments, repository.repo_log_encoding)
self.user = repository.find_committer_user(self.committer)
end end
def after_create def after_create
@@ -92,13 +97,11 @@ class Changeset < ActiveRecord::Base
TIMELOG_RE = / TIMELOG_RE = /
( (
((\d+)(h|hours?))((\d+)(m|min)?)? (\d+([.,]\d+)?)h?
|
((\d+)(h|hours?|m|min))
| |
(\d+):(\d+) (\d+):(\d+)
| |
(\d+([\.,]\d+)?)h? ((\d+)(h|hours?))?((\d+)(m|min)?)?
) )
/x /x
@@ -150,26 +153,25 @@ class Changeset < ActiveRecord::Base
# Returns the previous changeset # Returns the previous changeset
def previous def previous
@previous ||= Changeset.find(:first, @previous ||= Changeset.find(:first, :conditions => ['id < ? AND repository_id = ?', self.id, self.repository_id], :order => 'id DESC')
:conditions => ['id < ? AND repository_id = ?',
self.id, self.repository_id],
:order => 'id DESC')
end end
# Returns the next changeset # Returns the next changeset
def next def next
@next ||= Changeset.find(:first, @next ||= Changeset.find(:first, :conditions => ['id > ? AND repository_id = ?', self.id, self.repository_id], :order => 'id ASC')
:conditions => ['id > ? AND repository_id = ?', end
self.id, self.repository_id],
:order => 'id ASC') # Strips and reencodes a commit log before insertion into the database
def self.normalize_comments(str)
to_utf8(str.to_s.strip)
end end
# Creates a new Change from it's common parameters # Creates a new Change from it's common parameters
def create_change(change) def create_change(change)
Change.create(:changeset => self, Change.create(:changeset => self,
:action => change[:action], :action => change[:action],
:path => change[:path], :path => change[:path],
:from_path => change[:from_path], :from_path => change[:from_path],
:from_revision => change[:from_revision]) :from_revision => change[:from_revision])
end end
@@ -181,9 +183,7 @@ class Changeset < ActiveRecord::Base
return nil if id.blank? return nil if id.blank?
issue = Issue.find_by_id(id.to_i, :include => :project) issue = Issue.find_by_id(id.to_i, :include => :project)
if issue if issue
unless issue.project && unless project == issue.project || project.is_ancestor_of?(issue.project) || project.is_descendant_of?(issue.project)
(project == issue.project || project.is_ancestor_of?(issue.project) ||
project.is_descendant_of?(issue.project))
issue = nil issue = nil
end end
end end
@@ -193,7 +193,7 @@ class Changeset < ActiveRecord::Base
def fix_issue(issue) def fix_issue(issue)
status = IssueStatus.find_by_id(Setting.commit_fix_status_id.to_i) status = IssueStatus.find_by_id(Setting.commit_fix_status_id.to_i)
if status.nil? if status.nil?
logger.warn("No status matches commit_fix_status_id setting (#{Setting.commit_fix_status_id})") if logger logger.warn("No status macthes commit_fix_status_id setting (#{Setting.commit_fix_status_id})") if logger
return issue return issue
end end
@@ -221,8 +221,7 @@ class Changeset < ActiveRecord::Base
:hours => hours, :hours => hours,
:issue => issue, :issue => issue,
:spent_on => commit_date, :spent_on => commit_date,
:comments => l(:text_time_logged_by_changeset, :value => text_tag, :comments => l(:text_time_logged_by_changeset, :value => text_tag, :locale => Setting.default_language)
:locale => Setting.default_language)
) )
time_entry.activity = log_time_activity unless log_time_activity.nil? time_entry.activity = log_time_activity unless log_time_activity.nil?
@@ -245,47 +244,22 @@ class Changeset < ActiveRecord::Base
return @short_comments, @long_comments return @short_comments, @long_comments
end end
public def self.to_utf8(str)
return str if /\A[\r\n\t\x20-\x7e]*\Z/n.match(str) # for us-ascii
# Strips and reencodes a commit log before insertion into the database encoding = Setting.commit_logs_encoding.to_s.strip
def self.normalize_comments(str, encoding) unless encoding.blank? || encoding == 'UTF-8'
Changeset.to_utf8(str.to_s.strip, encoding)
end
def self.to_utf8(str, encoding)
return str if str.nil?
str.force_encoding("ASCII-8BIT") if str.respond_to?(:force_encoding)
if str.empty?
str.force_encoding("UTF-8") if str.respond_to?(:force_encoding)
return str
end
enc = encoding.blank? ? "UTF-8" : encoding
if str.respond_to?(:force_encoding)
if enc.upcase != "UTF-8"
str.force_encoding(enc)
str = str.encode("UTF-8", :invalid => :replace,
:undef => :replace, :replace => '?')
else
str.force_encoding("UTF-8")
if ! str.valid_encoding?
str = str.encode("US-ASCII", :invalid => :replace,
:undef => :replace, :replace => '?').encode("UTF-8")
end
end
else
ic = Iconv.new('UTF-8', enc)
txtar = ""
begin begin
txtar += ic.iconv(str) str = Iconv.conv('UTF-8', encoding, str)
rescue Iconv::IllegalSequence rescue Iconv::Failure
txtar += $!.success # do nothing here
str = '?' + $!.failed[1,$!.failed.length]
retry
rescue
txtar += $!.success
end end
str = txtar
end end
str # removes invalid UTF8 sequences
begin
Iconv.conv('UTF-8//IGNORE', 'UTF-8', str + ' ')[0..-3]
rescue Iconv::InvalidEncoding
# "UTF-8//IGNORE" is not supported on some OS
str
end
end end
end end
-24
View File
@@ -1,24 +0,0 @@
# Redmine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program 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.
#
# This program 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 Street, Fifth Floor, Boston, MA 02110-1301, USA.
class CommentObserver < ActiveRecord::Observer
def after_create(comment)
if comment.commented.is_a?(News) && Setting.notified_events.include?('news_comment_added')
Mailer.deliver_news_comment_added(comment)
end
end
end
+3 -41
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang # Copyright (C) 2006 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -23,6 +23,7 @@ class CustomField < ActiveRecord::Base
validates_presence_of :name, :field_format validates_presence_of :name, :field_format
validates_uniqueness_of :name, :scope => :type validates_uniqueness_of :name, :scope => :type
validates_length_of :name, :maximum => 30 validates_length_of :name, :maximum => 30
validates_format_of :name, :with => /^[\w\s\.\'\-]*$/i
validates_inclusion_of :field_format, :in => Redmine::CustomFieldFormat.available_formats validates_inclusion_of :field_format, :in => Redmine::CustomFieldFormat.available_formats
def initialize(attributes = nil) def initialize(attributes = nil)
@@ -42,49 +43,12 @@ class CustomField < ActiveRecord::Base
errors.add(:possible_values, :invalid) unless self.possible_values.is_a? Array errors.add(:possible_values, :invalid) unless self.possible_values.is_a? Array
end end
if regexp.present?
begin
Regexp.new(regexp)
rescue
errors.add(:regexp, :invalid)
end
end
# validate default value # validate default value
v = CustomValue.new(:custom_field => self.clone, :value => default_value, :customized => nil) v = CustomValue.new(:custom_field => self.clone, :value => default_value, :customized => nil)
v.custom_field.is_required = false v.custom_field.is_required = false
errors.add(:default_value, :invalid) unless v.valid? errors.add(:default_value, :invalid) unless v.valid?
end end
def possible_values_options(obj=nil)
case field_format
when 'user', 'version'
if obj.respond_to?(:project) && obj.project
case field_format
when 'user'
obj.project.users.sort.collect {|u| [u.to_s, u.id.to_s]}
when 'version'
obj.project.shared_versions.sort.collect {|u| [u.to_s, u.id.to_s]}
end
elsif obj.is_a?(Array)
obj.collect {|o| possible_values_options(o)}.inject {|memo, v| memo & v}
else
[]
end
else
read_attribute :possible_values
end
end
def possible_values(obj=nil)
case field_format
when 'user', 'version'
possible_values_options(obj).collect(&:last)
else
read_attribute :possible_values
end
end
# Makes possible_values accept a multiline string # Makes possible_values accept a multiline string
def possible_values=(arg) def possible_values=(arg)
if arg.is_a?(Array) if arg.is_a?(Array)
@@ -108,8 +72,6 @@ class CustomField < ActiveRecord::Base
casted = value.to_i casted = value.to_i
when 'float' when 'float'
casted = value.to_f casted = value.to_f
when 'user', 'version'
casted = (value.blank? ? nil : field_format.classify.constantize.find_by_id(value.to_i))
end end
end end
casted casted
+2 -5
View File
@@ -1,5 +1,5 @@
# RedMine - project management software # redMine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang # Copyright (C) 2006 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -29,9 +29,6 @@ class Document < ActiveRecord::Base
validates_presence_of :project, :title, :category validates_presence_of :project, :title, :category
validates_length_of :title, :maximum => 60 validates_length_of :title, :maximum => 60
named_scope :visible, lambda {|*args| { :include => :project,
:conditions => Project.allowed_to_condition(args.shift || User.current, :view_documents, *args) } }
def visible?(user=User.current) def visible?(user=User.current)
!user.nil? && user.allowed_to?(:view_documents, project) !user.nil? && user.allowed_to?(:view_documents, project)
end end
+2 -2
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang # Copyright (C) 2006 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
+3 -2
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang # Copyright (C) 2006 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -20,3 +20,4 @@ class DocumentCategoryCustomField < CustomField
:enumeration_doc_categories :enumeration_doc_categories
end end
end end
+2 -2
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang # Copyright (C) 2006-2007 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
+1 -1
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang # Copyright (C) 2006-2009 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
-1
View File
@@ -32,7 +32,6 @@ class Enumeration < ActiveRecord::Base
named_scope :shared, :conditions => { :project_id => nil } named_scope :shared, :conditions => { :project_id => nil }
named_scope :active, :conditions => { :active => true } named_scope :active, :conditions => { :active => true }
named_scope :named, lambda {|arg| { :conditions => ["LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip]}}
def self.default def self.default
# Creates a fake default scope so Enumeration.default will check # Creates a fake default scope so Enumeration.default will check
+41 -90
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang # Copyright (C) 2006-2007 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -34,7 +34,7 @@ class Issue < ActiveRecord::Base
has_many :relations_from, :class_name => 'IssueRelation', :foreign_key => 'issue_from_id', :dependent => :delete_all has_many :relations_from, :class_name => 'IssueRelation', :foreign_key => 'issue_from_id', :dependent => :delete_all
has_many :relations_to, :class_name => 'IssueRelation', :foreign_key => 'issue_to_id', :dependent => :delete_all has_many :relations_to, :class_name => 'IssueRelation', :foreign_key => 'issue_to_id', :dependent => :delete_all
acts_as_nested_set :scope => 'root_id', :dependent => :destroy acts_as_nested_set :scope => 'root_id'
acts_as_attachable :after_remove => :attachment_removed acts_as_attachable :after_remove => :attachment_removed
acts_as_customizable acts_as_customizable
acts_as_watchable acts_as_watchable
@@ -60,7 +60,7 @@ class Issue < ActiveRecord::Base
validates_numericality_of :estimated_hours, :allow_nil => true validates_numericality_of :estimated_hours, :allow_nil => true
named_scope :visible, lambda {|*args| { :include => :project, named_scope :visible, lambda {|*args| { :include => :project,
:conditions => Issue.visible_condition(args.shift || User.current, *args) } } :conditions => Project.allowed_to_condition(args.first || User.current, :view_issues) } }
named_scope :open, :conditions => ["#{IssueStatus.table_name}.is_closed = ?", false], :include => :status named_scope :open, :conditions => ["#{IssueStatus.table_name}.is_closed = ?", false], :include => :status
@@ -68,6 +68,11 @@ class Issue < ActiveRecord::Base
named_scope :with_limit, lambda { |limit| { :limit => limit} } named_scope :with_limit, lambda { |limit| { :limit => limit} }
named_scope :on_active_project, :include => [:status, :project, :tracker], named_scope :on_active_project, :include => [:status, :project, :tracker],
:conditions => ["#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"] :conditions => ["#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"]
named_scope :for_gantt, lambda {
{
:include => [:tracker, :status, :assigned_to, :priority, :project, :fixed_version]
}
}
named_scope :without_version, lambda { named_scope :without_version, lambda {
{ {
@@ -84,38 +89,12 @@ class Issue < ActiveRecord::Base
before_create :default_assign before_create :default_assign
before_save :close_duplicates, :update_done_ratio_from_issue_status before_save :close_duplicates, :update_done_ratio_from_issue_status
after_save :reschedule_following_issues, :update_nested_set_attributes, :update_parent_attributes, :create_journal after_save :reschedule_following_issues, :update_nested_set_attributes, :update_parent_attributes, :create_journal
after_destroy :destroy_children
after_destroy :update_parent_attributes after_destroy :update_parent_attributes
# Returns a SQL conditions string used to find all issues visible by the specified user
def self.visible_condition(user, options={})
Project.allowed_to_condition(user, :view_issues, options) do |role, user|
case role.issues_visibility
when 'all'
nil
when 'default'
"(#{table_name}.is_private = #{connection.quoted_false} OR #{table_name}.author_id = #{user.id} OR #{table_name}.assigned_to_id = #{user.id})"
when 'own'
"(#{table_name}.author_id = #{user.id} OR #{table_name}.assigned_to_id = #{user.id})"
else
'1=0'
end
end
end
# Returns true if usr or current user is allowed to view the issue # Returns true if usr or current user is allowed to view the issue
def visible?(usr=nil) def visible?(usr=nil)
(usr || User.current).allowed_to?(:view_issues, self.project) do |role, user| (usr || User.current).allowed_to?(:view_issues, self.project)
case role.issues_visibility
when 'all'
true
when 'default'
!self.is_private? || self.author == user || self.assigned_to == user
when 'own'
self.author == user || self.assigned_to == user
else
false
end
end
end end
def after_initialize def after_initialize
@@ -128,7 +107,7 @@ class Issue < ActiveRecord::Base
# Overrides Redmine::Acts::Customizable::InstanceMethods#available_custom_fields # Overrides Redmine::Acts::Customizable::InstanceMethods#available_custom_fields
def available_custom_fields def available_custom_fields
(project && tracker) ? (project.all_issue_custom_fields & tracker.custom_fields.all) : [] (project && tracker) ? project.all_issue_custom_fields.select {|c| tracker.custom_fields.include? c } : []
end end
def copy_from(arg) def copy_from(arg)
@@ -175,7 +154,6 @@ class Issue < ActiveRecord::Base
issue.reset_custom_values! issue.reset_custom_values!
end end
if options[:copy] if options[:copy]
issue.author = User.current
issue.custom_field_values = self.custom_field_values.inject({}) {|h,v| h[v.custom_field_id] = v.value; h} issue.custom_field_values = self.custom_field_values.inject({}) {|h,v| h[v.custom_field_id] = v.value; h}
issue.status = if options[:attributes] && options[:attributes][:status_id] issue.status = if options[:attributes] && options[:attributes][:status_id]
IssueStatus.find_by_id(options[:attributes][:status_id]) IssueStatus.find_by_id(options[:attributes][:status_id])
@@ -188,13 +166,7 @@ class Issue < ActiveRecord::Base
issue.attributes = options[:attributes] issue.attributes = options[:attributes]
end end
if issue.save if issue.save
if options[:copy] unless options[:copy]
if current_journal && current_journal.notes.present?
issue.init_journal(current_journal.user, current_journal.notes)
issue.current_journal.notify = false
issue.save
end
else
# Manually update project_id on related time entries # Manually update project_id on related time entries
TimeEntry.update_all("project_id = #{new_project.id}", {:issue_id => id}) TimeEntry.update_all("project_id = #{new_project.id}", {:issue_id => id})
@@ -228,13 +200,6 @@ class Issue < ActiveRecord::Base
result result
end end
def description=(arg)
if arg.is_a?(String)
arg = arg.gsub(/(\r\n|\n|\r)/, "\r\n")
end
write_attribute(:description, arg)
end
# Overrides attributes= so that tracker_id gets assigned first # Overrides attributes= so that tracker_id gets assigned first
def attributes_with_tracker_first=(new_attributes, *args) def attributes_with_tracker_first=(new_attributes, *args)
return if new_attributes.nil? return if new_attributes.nil?
@@ -275,12 +240,6 @@ class Issue < ActiveRecord::Base
'done_ratio', 'done_ratio',
:if => lambda {|issue, user| issue.new_statuses_allowed_to(user).any? } :if => lambda {|issue, user| issue.new_statuses_allowed_to(user).any? }
safe_attributes 'is_private',
:if => lambda {|issue, user|
user.allowed_to?(:set_issues_private, issue.project) ||
(issue.author == user && user.allowed_to?(:set_own_issues_private, issue.project))
}
# Safely sets attributes # Safely sets attributes
# Should be called from controllers instead of #attributes= # Should be called from controllers instead of #attributes=
# attr_accessible is too rough because we still want things like # attr_accessible is too rough because we still want things like
@@ -449,7 +408,6 @@ class Issue < ActiveRecord::Base
def assignable_users def assignable_users
users = project.assignable_users users = project.assignable_users
users << author if author users << author if author
users << assigned_to if assigned_to
users.uniq.sort users.uniq.sort
end end
@@ -465,12 +423,7 @@ class Issue < ActiveRecord::Base
# Returns an array of status that user is able to apply # Returns an array of status that user is able to apply
def new_statuses_allowed_to(user, include_default=false) def new_statuses_allowed_to(user, include_default=false)
statuses = status.find_new_statuses_allowed_to( statuses = status.find_new_statuses_allowed_to(user.roles_for_project(project), tracker)
user.roles_for_project(project),
tracker,
author == user,
assigned_to_id_changed? ? assigned_to_id_was == user.id : assigned_to_id == user.id
)
statuses << status unless statuses.empty? statuses << status unless statuses.empty?
statuses << IssueStatus.default if include_default statuses << IssueStatus.default if include_default
statuses = statuses.uniq.sort statuses = statuses.uniq.sort
@@ -503,14 +456,11 @@ class Issue < ActiveRecord::Base
(relations_from + relations_to).sort (relations_from + relations_to).sort
end end
def all_dependent_issues(except=[]) def all_dependent_issues
except << self
dependencies = [] dependencies = []
relations_from.each do |relation| relations_from.each do |relation|
if relation.issue_to && !except.include?(relation.issue_to) dependencies << relation.issue_to
dependencies << relation.issue_to dependencies += relation.issue_to.all_dependent_issues
dependencies += relation.issue_to.all_dependent_issues(except)
end
end end
dependencies dependencies
end end
@@ -575,9 +525,6 @@ class Issue < ActiveRecord::Base
s = "issue status-#{status.position} priority-#{priority.position}" s = "issue status-#{status.position} priority-#{priority.position}"
s << ' closed' if closed? s << ' closed' if closed?
s << ' overdue' if overdue? s << ' overdue' if overdue?
s << ' child' if child?
s << ' parent' unless leaf?
s << ' private' if is_private?
s << ' created-by-me' if User.current.logged? && author_id == User.current.id s << ' created-by-me' if User.current.logged? && author_id == User.current.id
s << ' assigned-to-me' if User.current.logged? && assigned_to_id == User.current.id s << ' assigned-to-me' if User.current.logged? && assigned_to_id == User.current.id
s s
@@ -587,7 +534,7 @@ class Issue < ActiveRecord::Base
# Returns false if save fails # Returns false if save fails
def save_issue_with_child_records(params, existing_time_entry=nil) def save_issue_with_child_records(params, existing_time_entry=nil)
Issue.transaction do Issue.transaction do
if params[:time_entry] && (params[:time_entry][:hours].present? || params[:time_entry][:comments].present?) && User.current.allowed_to?(:log_time, project) if params[:time_entry] && params[:time_entry][:hours].present? && User.current.allowed_to?(:log_time, project)
@time_entry = existing_time_entry || TimeEntry.new @time_entry = existing_time_entry || TimeEntry.new
@time_entry.project = project @time_entry.project = project
@time_entry.issue = self @time_entry.issue = self
@@ -691,16 +638,14 @@ class Issue < ActiveRecord::Base
def self.by_subproject(project) def self.by_subproject(project)
ActiveRecord::Base.connection.select_all("select s.id as status_id, ActiveRecord::Base.connection.select_all("select s.id as status_id,
s.is_closed as closed, s.is_closed as closed,
#{Issue.table_name}.project_id as project_id, i.project_id as project_id,
count(#{Issue.table_name}.id) as total count(i.id) as total
from from
#{Issue.table_name}, #{Project.table_name}, #{IssueStatus.table_name} s #{Issue.table_name} i, #{IssueStatus.table_name} s
where where
#{Issue.table_name}.status_id=s.id i.status_id=s.id
and #{Issue.table_name}.project_id = #{Project.table_name}.id and i.project_id IN (#{project.descendants.active.collect{|p| p.id}.join(',')})
and #{visible_condition(User.current, :project => project, :with_subprojects => true)} group by s.id, s.is_closed, i.project_id") if project.descendants.active.any?
and #{Issue.table_name}.project_id <> #{project.id}
group by s.id, s.is_closed, #{Issue.table_name}.project_id") if project.descendants.active.any?
end end
# End ReportsController extraction # End ReportsController extraction
@@ -810,6 +755,14 @@ class Issue < ActiveRecord::Base
end end
end end
def destroy_children
unless leaf?
children.each do |child|
child.destroy
end
end
end
# Update issues so their versions are not pointing to a # Update issues so their versions are not pointing to a
# fixed_version that is not shared with the issue's project # fixed_version that is not shared with the issue's project
def self.update_versions(conditions=nil) def self.update_versions(conditions=nil)
@@ -877,14 +830,11 @@ class Issue < ActiveRecord::Base
def create_journal def create_journal
if @current_journal if @current_journal
# attributes changes # attributes changes
(Issue.column_names - %w(id root_id lft rgt lock_version created_on updated_on)).each {|c| (Issue.column_names - %w(id description root_id lft rgt lock_version created_on updated_on)).each {|c|
before = @issue_before_change.send(c)
after = send(c)
next if before == after || (before.blank? && after.blank?)
@current_journal.details << JournalDetail.new(:property => 'attr', @current_journal.details << JournalDetail.new(:property => 'attr',
:prop_key => c, :prop_key => c,
:old_value => @issue_before_change.send(c), :old_value => @issue_before_change.send(c),
:value => send(c)) :value => send(c)) unless send(c)==@issue_before_change.send(c)
} }
# custom fields changes # custom fields changes
custom_values.each {|c| custom_values.each {|c|
@@ -913,19 +863,20 @@ class Issue < ActiveRecord::Base
select_field = options.delete(:field) select_field = options.delete(:field)
joins = options.delete(:joins) joins = options.delete(:joins)
where = "#{Issue.table_name}.#{select_field}=j.id" where = "i.#{select_field}=j.id"
ActiveRecord::Base.connection.select_all("select s.id as status_id, ActiveRecord::Base.connection.select_all("select s.id as status_id,
s.is_closed as closed, s.is_closed as closed,
j.id as #{select_field}, j.id as #{select_field},
count(#{Issue.table_name}.id) as total count(i.id) as total
from from
#{Issue.table_name}, #{Project.table_name}, #{IssueStatus.table_name} s, #{joins} j #{Issue.table_name} i, #{IssueStatus.table_name} s, #{joins} j
where where
#{Issue.table_name}.status_id=s.id i.status_id=s.id
and #{where} and #{where}
and #{Issue.table_name}.project_id=#{Project.table_name}.id and i.project_id=#{project.id}
and #{visible_condition(User.current, :project => project)}
group by s.id, s.is_closed, j.id") group by s.id, s.is_closed, j.id")
end end
end end
+2 -4
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang # Copyright (C) 2006 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -24,8 +24,6 @@ class IssueCategory < ActiveRecord::Base
validates_uniqueness_of :name, :scope => [:project_id] validates_uniqueness_of :name, :scope => [:project_id]
validates_length_of :name, :maximum => 30 validates_length_of :name, :maximum => 30
named_scope :named, lambda {|arg| { :conditions => ["LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip]}}
alias :destroy_without_reassign :destroy alias :destroy_without_reassign :destroy
# Destroy the category # Destroy the category
+1 -6
View File
@@ -47,12 +47,7 @@ class IssueRelation < ActiveRecord::Base
if issue_from && issue_to if issue_from && issue_to
errors.add :issue_to_id, :invalid if issue_from_id == issue_to_id errors.add :issue_to_id, :invalid if issue_from_id == issue_to_id
errors.add :issue_to_id, :not_same_project unless issue_from.project_id == issue_to.project_id || Setting.cross_project_issue_relations? errors.add :issue_to_id, :not_same_project unless issue_from.project_id == issue_to.project_id || Setting.cross_project_issue_relations?
#detect circular dependencies depending wether the relation should be reversed errors.add_to_base :circular_dependency if issue_to.all_dependent_issues.include? issue_from
if TYPES.has_key?(relation_type) && TYPES[relation_type][:reverse]
errors.add_to_base :circular_dependency if issue_from.all_dependent_issues.include? issue_to
else
errors.add_to_base :circular_dependency if issue_to.all_dependent_issues.include? issue_from
end
errors.add_to_base :cant_link_an_issue_with_a_descendant if issue_from.is_descendant_of?(issue_to) || issue_from.is_ancestor_of?(issue_to) errors.add_to_base :cant_link_an_issue_with_a_descendant if issue_from.is_descendant_of?(issue_to) || issue_from.is_ancestor_of?(issue_to)
end end
end end
+16 -20
View File
@@ -25,10 +25,9 @@ class IssueStatus < ActiveRecord::Base
validates_presence_of :name validates_presence_of :name
validates_uniqueness_of :name validates_uniqueness_of :name
validates_length_of :name, :maximum => 30 validates_length_of :name, :maximum => 30
validates_format_of :name, :with => /^[\w\s\'\-]*$/i
validates_inclusion_of :default_done_ratio, :in => 0..100, :allow_nil => true validates_inclusion_of :default_done_ratio, :in => 0..100, :allow_nil => true
named_scope :named, lambda {|arg| { :conditions => ["LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip]}}
def after_save def after_save
IssueStatus.update_all("is_default=#{connection.quoted_false}", ['id <> ?', id]) if self.is_default? IssueStatus.update_all("is_default=#{connection.quoted_false}", ['id <> ?', id]) if self.is_default?
end end
@@ -52,15 +51,10 @@ class IssueStatus < ActiveRecord::Base
# Returns an array of all statuses the given role can switch to # Returns an array of all statuses the given role can switch to
# Uses association cache when called more than one time # Uses association cache when called more than one time
def new_statuses_allowed_to(roles, tracker, author=false, assignee=false) def new_statuses_allowed_to(roles, tracker)
if roles && tracker if roles && tracker
role_ids = roles.collect(&:id) role_ids = roles.collect(&:id)
transitions = workflows.select do |w| new_statuses = workflows.select {|w| role_ids.include?(w.role_id) && w.tracker_id == tracker.id}.collect{|w| w.new_status}.compact.sort
role_ids.include?(w.role_id) &&
w.tracker_id == tracker.id &&
((!w.author && !w.assignee) || (author && w.author) || (assignee && w.assignee))
end
transitions.collect{|w| w.new_status}.compact.sort
else else
[] []
end end
@@ -68,23 +62,25 @@ class IssueStatus < ActiveRecord::Base
# Same thing as above but uses a database query # Same thing as above but uses a database query
# More efficient than the previous method if called just once # More efficient than the previous method if called just once
def find_new_statuses_allowed_to(roles, tracker, author=false, assignee=false) def find_new_statuses_allowed_to(roles, tracker)
if roles.present? && tracker if roles && tracker
conditions = "(author = :false AND assignee = :false)"
conditions << " OR author = :true" if author
conditions << " OR assignee = :true" if assignee
workflows.find(:all, workflows.find(:all,
:include => :new_status, :include => :new_status,
:conditions => ["role_id IN (:role_ids) AND tracker_id = :tracker_id AND (#{conditions})", :conditions => { :role_id => roles.collect(&:id),
{:role_ids => roles.collect(&:id), :tracker_id => tracker.id, :true => true, :false => false} :tracker_id => tracker.id}).collect{ |w| w.new_status }.compact.sort
]
).collect{|w| w.new_status}.compact.sort
else else
[] []
end end
end end
def new_status_allowed_to?(status, roles, tracker)
if status && roles && tracker
!workflows.find(:first, :conditions => {:new_status_id => status.id, :role_id => roles.collect(&:id), :tracker_id => tracker.id}).nil?
else
false
end
end
def <=>(status) def <=>(status)
position <=> status.position position <=> status.position
end end
+3 -15
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang # Copyright (C) 2006 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -32,16 +32,12 @@ class Journal < ActiveRecord::Base
:url => Proc.new {|o| {:controller => 'issues', :action => 'show', :id => o.issue.id, :anchor => "change-#{o.id}"}} :url => Proc.new {|o| {:controller => 'issues', :action => 'show', :id => o.issue.id, :anchor => "change-#{o.id}"}}
acts_as_activity_provider :type => 'issues', acts_as_activity_provider :type => 'issues',
:permission => :view_issues,
:author_key => :user_id, :author_key => :user_id,
:find_options => {:include => [{:issue => :project}, :details, :user], :find_options => {:include => [{:issue => :project}, :details, :user],
:conditions => "#{Journal.table_name}.journalized_type = 'Issue' AND" + :conditions => "#{Journal.table_name}.journalized_type = 'Issue' AND" +
" (#{JournalDetail.table_name}.prop_key = 'status_id' OR #{Journal.table_name}.notes <> '')"} " (#{JournalDetail.table_name}.prop_key = 'status_id' OR #{Journal.table_name}.notes <> '')"}
named_scope :visible, lambda {|*args| {
:include => {:issue => :project},
:conditions => Issue.visible_condition(args.shift || User.current, *args)
}}
def save(*args) def save(*args)
# Do not save an empty journal # Do not save an empty journal
(details.empty? && notes.blank?) ? false : super (details.empty? && notes.blank?) ? false : super
@@ -77,12 +73,4 @@ class Journal < ActiveRecord::Base
s << ' has-details' unless details.blank? s << ' has-details' unless details.blank?
s s
end end
def notify?
@notify != false
end
def notify=(arg)
@notify = arg
end
end end
+5 -18
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang # Copyright (C) 2006 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -17,22 +17,9 @@
class JournalDetail < ActiveRecord::Base class JournalDetail < ActiveRecord::Base
belongs_to :journal belongs_to :journal
before_save :normalize_values
private def before_save
self.value = value[0..254] if value && value.is_a?(String)
def normalize_values self.old_value = old_value[0..254] if old_value && old_value.is_a?(String)
self.value = normalize(value)
self.old_value = normalize(old_value)
end
def normalize(v)
if v == true
"1"
elsif v == false
"0"
else
v
end
end end
end end
+6 -8
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang # Copyright (C) 2006-2007 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -17,12 +17,10 @@
class JournalObserver < ActiveRecord::Observer class JournalObserver < ActiveRecord::Observer
def after_create(journal) def after_create(journal)
if journal.notify? && if Setting.notified_events.include?('issue_updated') ||
(Setting.notified_events.include?('issue_updated') || (Setting.notified_events.include?('issue_note_added') && journal.notes.present?) ||
(Setting.notified_events.include?('issue_note_added') && journal.notes.present?) || (Setting.notified_events.include?('issue_status_updated') && journal.new_status.present?) ||
(Setting.notified_events.include?('issue_status_updated') && journal.new_status.present?) || (Setting.notified_events.include?('issue_priority_updated') && journal.new_value_for('priority_id').present?)
(Setting.notified_events.include?('issue_priority_updated') && journal.new_value_for('priority_id').present?)
)
Mailer.deliver_issue_edit(journal) Mailer.deliver_issue_edit(journal)
end end
end end
+12 -17
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang # Copyright (C) 2006-2007 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -100,7 +100,7 @@ class MailHandler < ActionMailer::Base
elsif m = email.subject.match(MESSAGE_REPLY_SUBJECT_RE) elsif m = email.subject.match(MESSAGE_REPLY_SUBJECT_RE)
receive_message_reply(m[1].to_i) receive_message_reply(m[1].to_i)
else else
dispatch_to_default receive_issue
end end
rescue ActiveRecord::RecordInvalid => e rescue ActiveRecord::RecordInvalid => e
# TODO: send a email to the user # TODO: send a email to the user
@@ -114,10 +114,6 @@ class MailHandler < ActionMailer::Base
false false
end end
def dispatch_to_default
receive_issue
end
# Creates a new issue # Creates a new issue
def receive_issue def receive_issue
project = target_project project = target_project
@@ -155,10 +151,9 @@ class MailHandler < ActionMailer::Base
# ignore CLI-supplied defaults for new issues # ignore CLI-supplied defaults for new issues
@@handler_options[:issue].clear @@handler_options[:issue].clear
journal = issue.init_journal(user) journal = issue.init_journal(user, cleaned_up_text_body)
issue.safe_attributes = issue_attributes_from_keywords(issue) issue.safe_attributes = issue_attributes_from_keywords(issue)
issue.safe_attributes = {'custom_field_values' => custom_field_values_from_keywords(issue)} issue.safe_attributes = {'custom_field_values' => custom_field_values_from_keywords(issue)}
journal.notes = cleaned_up_text_body
add_attachments(issue) add_attachments(issue)
issue.save! issue.save!
logger.info "MailHandler: issue ##{issue.id} updated by #{user}" if logger && logger.info logger.info "MailHandler: issue ##{issue.id} updated by #{user}" if logger && logger.info
@@ -198,7 +193,7 @@ class MailHandler < ActionMailer::Base
end end
def add_attachments(obj) def add_attachments(obj)
if email.attachments && email.attachments.any? if email.has_attachments?
email.attachments.each do |attachment| email.attachments.each do |attachment|
Attachment.create(:container => obj, Attachment.create(:container => obj,
:file => attachment, :file => attachment,
@@ -240,8 +235,8 @@ class MailHandler < ActionMailer::Base
def extract_keyword!(text, attr, format=nil) def extract_keyword!(text, attr, format=nil)
keys = [attr.to_s.humanize] keys = [attr.to_s.humanize]
if attr.is_a?(Symbol) if attr.is_a?(Symbol)
keys << l("field_#{attr}", :default => '', :locale => user.language) if user && user.language.present? keys << l("field_#{attr}", :default => '', :locale => user.language) if user
keys << l("field_#{attr}", :default => '', :locale => Setting.default_language) if Setting.default_language.present? keys << l("field_#{attr}", :default => '', :locale => Setting.default_language)
end end
keys.reject! {|k| k.blank?} keys.reject! {|k| k.blank?}
keys.collect! {|k| Regexp.escape(k)} keys.collect! {|k| Regexp.escape(k)}
@@ -265,12 +260,12 @@ class MailHandler < ActionMailer::Base
assigned_to = nil if assigned_to && !issue.assignable_users.include?(assigned_to) assigned_to = nil if assigned_to && !issue.assignable_users.include?(assigned_to)
attrs = { attrs = {
'tracker_id' => (k = get_keyword(:tracker)) && issue.project.trackers.named(k).first.try(:id), 'tracker_id' => (k = get_keyword(:tracker)) && issue.project.trackers.find_by_name(k).try(:id),
'status_id' => (k = get_keyword(:status)) && IssueStatus.named(k).first.try(:id), 'status_id' => (k = get_keyword(:status)) && IssueStatus.find_by_name(k).try(:id),
'priority_id' => (k = get_keyword(:priority)) && IssuePriority.named(k).first.try(:id), 'priority_id' => (k = get_keyword(:priority)) && IssuePriority.find_by_name(k).try(:id),
'category_id' => (k = get_keyword(:category)) && issue.project.issue_categories.named(k).first.try(:id), 'category_id' => (k = get_keyword(:category)) && issue.project.issue_categories.find_by_name(k).try(:id),
'assigned_to_id' => assigned_to.try(:id), 'assigned_to_id' => assigned_to.try(:id),
'fixed_version_id' => (k = get_keyword(:fixed_version, :override => true)) && issue.project.shared_versions.named(k).first.try(:id), 'fixed_version_id' => (k = get_keyword(:fixed_version, :override => true)) && issue.project.shared_versions.find_by_name(k).try(:id),
'start_date' => get_keyword(:start_date, :override => true, :format => '\d{4}-\d{2}-\d{2}'), 'start_date' => get_keyword(:start_date, :override => true, :format => '\d{4}-\d{2}-\d{2}'),
'due_date' => get_keyword(:due_date, :override => true, :format => '\d{4}-\d{2}-\d{2}'), 'due_date' => get_keyword(:due_date, :override => true, :format => '\d{4}-\d{2}-\d{2}'),
'estimated_hours' => get_keyword(:estimated_hours, :override => true), 'estimated_hours' => get_keyword(:estimated_hours, :override => true),
+7 -25
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang # Copyright (C) 2006-2007 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -85,7 +85,7 @@ class Mailer < ActionMailer::Base
subject l(:mail_subject_reminder, :count => issues.size, :days => days) subject l(:mail_subject_reminder, :count => issues.size, :days => days)
body :issues => issues, body :issues => issues,
:days => days, :days => days,
:issues_url => url_for(:controller => 'issues', :action => 'index', :set_filter => 1, :assigned_to_id => user.id, :sort => 'due_date:asc') :issues_url => url_for(:controller => 'issues', :action => 'index', :set_filter => 1, :assigned_to_id => user.id, :sort_key => 'due_date', :sort_order => 'asc')
render_multipart('reminder', body) render_multipart('reminder', body)
end end
@@ -114,11 +114,11 @@ class Mailer < ActionMailer::Base
added_to_url = '' added_to_url = ''
case container.class.name case container.class.name
when 'Project' when 'Project'
added_to_url = url_for(:controller => 'files', :action => 'index', :project_id => container) added_to_url = url_for(:controller => 'projects', :action => 'list_files', :id => container)
added_to = "#{l(:label_project)}: #{container}" added_to = "#{l(:label_project)}: #{container}"
recipients container.project.notified_users.select {|user| user.allowed_to?(:view_files, container.project)}.collect {|u| u.mail} recipients container.project.notified_users.select {|user| user.allowed_to?(:view_files, container.project)}.collect {|u| u.mail}
when 'Version' when 'Version'
added_to_url = url_for(:controller => 'files', :action => 'index', :project_id => container.project) added_to_url = url_for(:controller => 'projects', :action => 'list_files', :id => container.project_id)
added_to = "#{l(:label_version)}: #{container.name}" added_to = "#{l(:label_version)}: #{container.name}"
recipients container.project.notified_users.select {|user| user.allowed_to?(:view_files, container.project)}.collect {|u| u.mail} recipients container.project.notified_users.select {|user| user.allowed_to?(:view_files, container.project)}.collect {|u| u.mail}
when 'Document' when 'Document'
@@ -149,24 +149,6 @@ class Mailer < ActionMailer::Base
render_multipart('news_added', body) render_multipart('news_added', body)
end end
# Builds a tmail object used to email recipients of a news' project when a news comment is added.
#
# Example:
# news_comment_added(comment) => tmail object
# Mailer.news_comment_added(comment) => sends an email to the news' project recipients
def news_comment_added(comment)
news = comment.commented
redmine_headers 'Project' => news.project.identifier
message_id comment
recipients news.recipients
cc news.watcher_recipients
subject "Re: [#{news.project.name}] #{l(:label_news)}: #{news.title}"
body :news => news,
:comment => comment,
:news_url => url_for(:controller => 'news', :action => 'show', :id => news)
render_multipart('news_comment_added', body)
end
# Builds a tmail object used to email the recipients of the specified message that was posted. # Builds a tmail object used to email the recipients of the specified message that was posted.
# #
# Example: # Example:
@@ -314,7 +296,7 @@ class Mailer < ActionMailer::Base
if raise_errors if raise_errors
raise e raise e
elsif mylogger elsif mylogger
mylogger.error "The following error occured while sending email notification: \"#{e.message}\". Check your configuration in config/configuration.yml." mylogger.error "The following error occured while sending email notification: \"#{e.message}\". Check your configuration in config/email.yml."
end end
ensure ensure
self.class.raise_delivery_errors = raise_errors self.class.raise_delivery_errors = raise_errors
@@ -344,7 +326,7 @@ class Mailer < ActionMailer::Base
:conditions => s.conditions :conditions => s.conditions
).group_by(&:assigned_to) ).group_by(&:assigned_to)
issues_by_assignee.each do |assignee, issues| issues_by_assignee.each do |assignee, issues|
deliver_reminder(assignee, issues, days) if assignee && assignee.active? deliver_reminder(assignee, issues, days) unless assignee.nil?
end end
end end
+3 -6
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang # Copyright (C) 2006-2007 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -24,7 +24,7 @@ class Message < ActiveRecord::Base
acts_as_searchable :columns => ['subject', 'content'], acts_as_searchable :columns => ['subject', 'content'],
:include => {:board => :project}, :include => {:board => :project},
:project_key => "#{Board.table_name}.project_id", :project_key => 'project_id',
:date_column => "#{table_name}.created_on" :date_column => "#{table_name}.created_on"
acts_as_event :title => Proc.new {|o| "#{o.board.name}: #{o.subject}"}, acts_as_event :title => Proc.new {|o| "#{o.board.name}: #{o.subject}"},
:description => :content, :description => :content,
@@ -42,9 +42,6 @@ class Message < ActiveRecord::Base
after_create :add_author_as_watcher after_create :add_author_as_watcher
named_scope :visible, lambda {|*args| { :include => {:board => :project},
:conditions => Project.allowed_to_condition(args.shift || User.current, :view_messages, *args) } }
def visible?(user=User.current) def visible?(user=User.current)
!user.nil? && user.allowed_to?(:view_messages, project) !user.nil? && user.allowed_to?(:view_messages, project)
end end
+2 -2
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang # Copyright (C) 2006-2007 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
+2 -11
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang # Copyright (C) 2006-2008 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -28,13 +28,10 @@ class News < ActiveRecord::Base
acts_as_event :url => Proc.new {|o| {:controller => 'news', :action => 'show', :id => o.id}} acts_as_event :url => Proc.new {|o| {:controller => 'news', :action => 'show', :id => o.id}}
acts_as_activity_provider :find_options => {:include => [:project, :author]}, acts_as_activity_provider :find_options => {:include => [:project, :author]},
:author_key => :author_id :author_key => :author_id
acts_as_watchable
after_create :add_author_as_watcher
named_scope :visible, lambda {|*args| { named_scope :visible, lambda {|*args| {
:include => :project, :include => :project,
:conditions => Project.allowed_to_condition(args.shift || User.current, :view_news, *args) :conditions => Project.allowed_to_condition(args.first || User.current, :view_news)
}} }}
def visible?(user=User.current) def visible?(user=User.current)
@@ -45,10 +42,4 @@ class News < ActiveRecord::Base
def self.latest(user = User.current, count = 5) def self.latest(user = User.current, count = 5)
find(:all, :limit => count, :conditions => Project.allowed_to_condition(user, :view_news), :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC") find(:all, :limit => count, :conditions => Project.allowed_to_condition(user, :view_news), :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC")
end end
private
def add_author_as_watcher
Watcher.create(:watchable => self, :user => author)
end
end end
+44 -85
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang # Copyright (C) 2006 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -43,7 +43,7 @@ class Project < ActiveRecord::Base
has_many :time_entries, :dependent => :delete_all has_many :time_entries, :dependent => :delete_all
has_many :queries, :dependent => :delete_all has_many :queries, :dependent => :delete_all
has_many :documents, :dependent => :destroy has_many :documents, :dependent => :destroy
has_many :news, :dependent => :destroy, :include => :author has_many :news, :dependent => :delete_all, :include => :author
has_many :issue_categories, :dependent => :delete_all, :order => "#{IssueCategory.table_name}.name" has_many :issue_categories, :dependent => :delete_all, :order => "#{IssueCategory.table_name}.name"
has_many :boards, :dependent => :destroy, :order => "position ASC" has_many :boards, :dependent => :destroy, :order => "position ASC"
has_one :repository, :dependent => :destroy has_one :repository, :dependent => :destroy
@@ -56,7 +56,7 @@ class Project < ActiveRecord::Base
:join_table => "#{table_name_prefix}custom_fields_projects#{table_name_suffix}", :join_table => "#{table_name_prefix}custom_fields_projects#{table_name_suffix}",
:association_foreign_key => 'custom_field_id' :association_foreign_key => 'custom_field_id'
acts_as_nested_set :order => 'name', :dependent => :destroy acts_as_nested_set :order => 'name'
acts_as_attachable :view_permission => :view_files, acts_as_attachable :view_permission => :view_files,
:delete_permission => :manage_files :delete_permission => :manage_files
@@ -79,12 +79,12 @@ class Project < ActiveRecord::Base
# reserved words # reserved words
validates_exclusion_of :identifier, :in => %w( new ) validates_exclusion_of :identifier, :in => %w( new )
before_destroy :delete_all_members before_destroy :delete_all_members, :destroy_children
named_scope :has_module, lambda { |mod| { :conditions => ["#{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name=?)", mod.to_s] } } named_scope :has_module, lambda { |mod| { :conditions => ["#{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name=?)", mod.to_s] } }
named_scope :active, { :conditions => "#{Project.table_name}.status = #{STATUS_ACTIVE}"} named_scope :active, { :conditions => "#{Project.table_name}.status = #{STATUS_ACTIVE}"}
named_scope :all_public, { :conditions => { :is_public => true } } named_scope :all_public, { :conditions => { :is_public => true } }
named_scope :visible, lambda {|*args| {:conditions => Project.visible_condition(args.shift || User.current, *args) }} named_scope :visible, lambda { { :conditions => Project.visible_by(User.current) } }
def initialize(attributes = nil) def initialize(attributes = nil)
super super
@@ -115,36 +115,27 @@ class Project < ActiveRecord::Base
# returns latest created projects # returns latest created projects
# non public projects will be returned only if user is a member of those # non public projects will be returned only if user is a member of those
def self.latest(user=nil, count=5) def self.latest(user=nil, count=5)
visible(user).find(:all, :limit => count, :order => "created_on DESC") find(:all, :limit => count, :conditions => visible_by(user), :order => "created_on DESC")
end end
# Returns true if the project is visible to +user+ or to the current user. # Returns a SQL :conditions string used to find all active projects for the specified user.
def visible?(user=User.current)
user.allowed_to?(:view_project, self)
end
def self.visible_by(user=nil)
ActiveSupport::Deprecation.warn "Project.visible_by is deprecated and will be removed in Redmine 1.3.0. Use Project.visible_condition instead."
visible_condition(user || User.current)
end
# Returns a SQL conditions string used to find all projects visible by the specified user.
# #
# Examples: # Examples:
# Project.visible_condition(admin) => "projects.status = 1" # Projects.visible_by(admin) => "projects.status = 1"
# Project.visible_condition(normal_user) => "((projects.status = 1) AND (projects.is_public = 1 OR projects.id IN (1,3,4)))" # Projects.visible_by(normal_user) => "projects.status = 1 AND projects.is_public = 1"
# Project.visible_condition(anonymous) => "((projects.status = 1) AND (projects.is_public = 1))" def self.visible_by(user=nil)
def self.visible_condition(user, options={}) user ||= User.current
allowed_to_condition(user, :view_project, options) if user && user.admin?
return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"
elsif user && user.memberships.any?
return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE} AND (#{Project.table_name}.is_public = #{connection.quoted_true} or #{Project.table_name}.id IN (#{user.memberships.collect{|m| m.project_id}.join(',')}))"
else
return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE} AND #{Project.table_name}.is_public = #{connection.quoted_true}"
end
end end
# Returns a SQL conditions string used to find all projects for which +user+ has the given +permission+
#
# Valid options:
# * :project => limit the condition to project
# * :with_subprojects => limit the condition to project and its subprojects
# * :member => limit the condition to the user projects
def self.allowed_to_condition(user, permission, options={}) def self.allowed_to_condition(user, permission, options={})
statements = []
base_statement = "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}" base_statement = "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"
if perm = Redmine::AccessControl.permission(permission) if perm = Redmine::AccessControl.permission(permission)
unless perm.project_module.nil? unless perm.project_module.nil?
@@ -157,37 +148,24 @@ class Project < ActiveRecord::Base
project_statement << " OR (#{Project.table_name}.lft > #{options[:project].lft} AND #{Project.table_name}.rgt < #{options[:project].rgt})" if options[:with_subprojects] project_statement << " OR (#{Project.table_name}.lft > #{options[:project].lft} AND #{Project.table_name}.rgt < #{options[:project].rgt})" if options[:with_subprojects]
base_statement = "(#{project_statement}) AND (#{base_statement})" base_statement = "(#{project_statement}) AND (#{base_statement})"
end end
if user.admin? if user.admin?
base_statement # no restriction
else else
statement_by_role = {} statements << "1=0"
unless options[:member]
role = user.logged? ? Role.non_member : Role.anonymous
if role.allowed_to?(permission)
statement_by_role[role] = "#{Project.table_name}.is_public = #{connection.quoted_true}"
end
end
if user.logged? if user.logged?
user.projects_by_role.each do |role, projects| if Role.non_member.allowed_to?(permission) && !options[:member]
if role.allowed_to?(permission) statements << "#{Project.table_name}.is_public = #{connection.quoted_true}"
statement_by_role[role] = "#{Project.table_name}.id IN (#{projects.collect(&:id).join(',')})"
end
end end
end allowed_project_ids = user.memberships.select {|m| m.roles.detect {|role| role.allowed_to?(permission)}}.collect {|m| m.project_id}
if statement_by_role.empty? statements << "#{Project.table_name}.id IN (#{allowed_project_ids.join(',')})" if allowed_project_ids.any?
"1=0"
else else
if block_given? if Role.anonymous.allowed_to?(permission) && !options[:member]
statement_by_role.each do |role, statement| # anonymous user allowed on public project
if s = yield(role, user) statements << "#{Project.table_name}.is_public = #{connection.quoted_true}"
statement_by_role[role] = "(#{statement} AND (#{s}))"
end
end
end end
"((#{base_statement}) AND (#{statement_by_role.values.join(' OR ')}))"
end end
end end
statements.empty? ? base_statement : "((#{base_statement}) AND (#{statements.join(' OR ')}))"
end end
# Returns the Systemwide and project specific activities # Returns the Systemwide and project specific activities
@@ -369,7 +347,7 @@ class Project < ActiveRecord::Base
# Returns an array of the trackers used by the project and its active sub projects # Returns an array of the trackers used by the project and its active sub projects
def rolled_up_trackers def rolled_up_trackers
@rolled_up_trackers ||= @rolled_up_trackers ||=
Tracker.find(:all, :joins => :projects, Tracker.find(:all, :include => :projects,
:select => "DISTINCT #{Tracker.table_name}.*", :select => "DISTINCT #{Tracker.table_name}.*",
:conditions => ["#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status = #{STATUS_ACTIVE}", lft, rgt], :conditions => ["#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status = #{STATUS_ACTIVE}", lft, rgt],
:order => "#{Tracker.table_name}.position") :order => "#{Tracker.table_name}.position")
@@ -395,17 +373,15 @@ class Project < ActiveRecord::Base
# Returns a scope of the Versions used by the project # Returns a scope of the Versions used by the project
def shared_versions def shared_versions
@shared_versions ||= begin @shared_versions ||=
r = root? ? self : root
Version.scoped(:include => :project, Version.scoped(:include => :project,
:conditions => "#{Project.table_name}.id = #{id}" + :conditions => "#{Project.table_name}.id = #{id}" +
" OR (#{Project.table_name}.status = #{Project::STATUS_ACTIVE} AND (" + " OR (#{Project.table_name}.status = #{Project::STATUS_ACTIVE} AND (" +
" #{Version.table_name}.sharing = 'system'" + " #{Version.table_name}.sharing = 'system'" +
" OR (#{Project.table_name}.lft >= #{r.lft} AND #{Project.table_name}.rgt <= #{r.rgt} AND #{Version.table_name}.sharing = 'tree')" + " OR (#{Project.table_name}.lft >= #{root.lft} AND #{Project.table_name}.rgt <= #{root.rgt} AND #{Version.table_name}.sharing = 'tree')" +
" OR (#{Project.table_name}.lft < #{lft} AND #{Project.table_name}.rgt > #{rgt} AND #{Version.table_name}.sharing IN ('hierarchy', 'descendants'))" + " OR (#{Project.table_name}.lft < #{lft} AND #{Project.table_name}.rgt > #{rgt} AND #{Version.table_name}.sharing IN ('hierarchy', 'descendants'))" +
" OR (#{Project.table_name}.lft > #{lft} AND #{Project.table_name}.rgt < #{rgt} AND #{Version.table_name}.sharing = 'hierarchy')" + " OR (#{Project.table_name}.lft > #{lft} AND #{Project.table_name}.rgt < #{rgt} AND #{Version.table_name}.sharing = 'hierarchy')" +
"))") "))")
end
end end
# Returns a hash of project users grouped by role # Returns a hash of project users grouped by role
@@ -448,12 +424,6 @@ class Project < ActiveRecord::Base
@all_issue_custom_fields ||= (IssueCustomField.for_all + issue_custom_fields).uniq.sort @all_issue_custom_fields ||= (IssueCustomField.for_all + issue_custom_fields).uniq.sort
end 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 def project
self self
end end
@@ -539,7 +509,10 @@ class Project < ActiveRecord::Base
def enabled_module_names=(module_names) def enabled_module_names=(module_names)
if module_names && module_names.is_a?(Array) if module_names && module_names.is_a?(Array)
module_names = module_names.collect(&:to_s).reject(&:blank?) module_names = module_names.collect(&:to_s).reject(&:blank?)
self.enabled_modules = module_names.collect {|name| enabled_modules.detect {|mod| mod.name == name} || EnabledModule.new(:name => name)} # remove disabled modules
enabled_modules.each {|mod| mod.destroy unless module_names.include?(mod.name)}
# add new modules
module_names.reject {|name| module_enabled?(name)}.each {|name| enabled_modules << EnabledModule.new(:name => name)}
else else
enabled_modules.clear enabled_modules.clear
end end
@@ -550,26 +523,6 @@ class Project < ActiveRecord::Base
enabled_modules.collect(&:name) enabled_modules.collect(&:name)
end end
# Enable a specific module
#
# Examples:
# project.enable_module!(:issue_tracking)
# project.enable_module!("issue_tracking")
def enable_module!(name)
enabled_modules << EnabledModule.new(:name => name.to_s) unless module_enabled?(name)
end
# Disable a module if it exists
#
# Examples:
# project.disable_module!(:issue_tracking)
# project.disable_module!("issue_tracking")
# project.disable_module!(project.enabled_modules.first)
def disable_module!(target)
target = enabled_modules.detect{|mod| target.to_s == mod.name} unless enabled_modules.include?(target)
target.destroy unless target.blank?
end
safe_attributes 'name', safe_attributes 'name',
'description', 'description',
'homepage', 'homepage',
@@ -668,6 +621,13 @@ class Project < ActiveRecord::Base
private private
# Destroys children before destroying self
def destroy_children
children.each do |child|
child.destroy
end
end
# Copies wiki from +project+ # Copies wiki from +project+
def copy_wiki(project) def copy_wiki(project)
# Check that the source project has a wiki first # Check that the source project has a wiki first
@@ -714,7 +674,6 @@ class Project < ActiveRecord::Base
end end
# Copies issues from +project+ # Copies issues from +project+
# Note: issues assigned to a closed version won't be copied due to validation rules
def copy_issues(project) def copy_issues(project)
# Stores the source issue id as a key and the copied issues as the # Stores the source issue id as a key and the copied issues as the
# value. Used to map the two togeather for issue relations. # value. Used to map the two togeather for issue relations.
+32 -66
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang # Copyright (C) 2006-2008 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -42,10 +42,6 @@ class QueryColumn
def value(issue) def value(issue)
issue.send name issue.send name
end end
def css_classes
name
end
end end
class QueryCustomFieldColumn < QueryColumn class QueryCustomFieldColumn < QueryColumn
@@ -72,10 +68,6 @@ class QueryCustomFieldColumn < QueryColumn
cv = issue.custom_values.detect {|v| v.custom_field_id == @cf.id} cv = issue.custom_values.detect {|v| v.custom_field_id == @cf.id}
cv && @cf.cast_value(cv.value) cv && @cf.cast_value(cv.value)
end end
def css_classes
@css_classes ||= "#{name} #{@cf.field_format}"
end
end end
class Query < ActiveRecord::Base class Query < ActiveRecord::Base
@@ -166,11 +158,6 @@ class Query < ActiveRecord::Base
end if filters end if filters
end end
# Returns true if the query is visible to +user+ or the current user.
def visible?(user=User.current)
self.is_public? || self.user_id == user.id
end
def editable_by?(user) def editable_by?(user)
return false unless user return false unless user
# Admin can edit them all and regular users can edit their private queries # Admin can edit them all and regular users can edit their private queries
@@ -229,19 +216,14 @@ class Query < ActiveRecord::Base
if project if project
# project specific filters # project specific filters
categories = @project.issue_categories.all unless @project.issue_categories.empty?
unless categories.empty? @available_filters["category_id"] = { :type => :list_optional, :order => 6, :values => @project.issue_categories.collect{|s| [s.name, s.id.to_s] } }
@available_filters["category_id"] = { :type => :list_optional, :order => 6, :values => categories.collect{|s| [s.name, s.id.to_s] } }
end end
versions = @project.shared_versions.all unless @project.shared_versions.empty?
unless versions.empty? @available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => @project.shared_versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] } }
@available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] } }
end end
unless @project.leaf? unless @project.descendants.active.empty?
subprojects = @project.descendants.visible.all @available_filters["subproject_id"] = { :type => :list_subprojects, :order => 13, :values => @project.descendants.visible.collect{|s| [s.name, s.id.to_s] } }
unless subprojects.empty?
@available_filters["subproject_id"] = { :type => :list_subprojects, :order => 13, :values => subprojects.collect{|s| [s.name, s.id.to_s] } }
end
end end
add_custom_fields_filters(@project.all_issue_custom_fields) add_custom_fields_filters(@project.all_issue_custom_fields)
else else
@@ -333,17 +315,14 @@ class Query < ActiveRecord::Base
end end
def columns def columns
# preserve the column_names order if has_default_columns?
(has_default_columns? ? default_columns_names : column_names).collect do |name| available_columns.select do |c|
available_columns.find { |col| col.name == name } # Adds the project column by default for cross-project lists
end.compact Setting.issue_list_default_columns.include?(c.name.to_s) || (c.name == :project && project.nil?)
end end
else
def default_columns_names # preserve the column_names order
@default_columns_names ||= begin column_names.collect {|name| available_columns.find {|col| col.name == name}}.compact
default_columns = Setting.issue_list_default_columns.map(&:to_sym)
project.present? ? default_columns : [:project] | default_columns
end end
end end
@@ -352,7 +331,7 @@ class Query < ActiveRecord::Base
names = names.select {|n| n.is_a?(Symbol) || !n.blank? } names = names.select {|n| n.is_a?(Symbol) || !n.blank? }
names = names.collect {|n| n.is_a?(Symbol) ? n : n.to_sym } names = names.collect {|n| n.is_a?(Symbol) ? n : n.to_sym }
# Set column_names to nil if default columns # Set column_names to nil if default columns
if names == default_columns_names if names.map(&:to_s) == Setting.issue_list_default_columns
names = nil names = nil
end end
end end
@@ -432,7 +411,8 @@ class Query < ActiveRecord::Base
elsif project elsif project
project_clauses << "#{Project.table_name}.id = %d" % project.id project_clauses << "#{Project.table_name}.id = %d" % project.id
end end
project_clauses.any? ? project_clauses.join(' AND ') : nil project_clauses << Project.allowed_to_condition(User.current, :view_issues)
project_clauses.join(' AND ')
end end
def statement def statement
@@ -513,15 +493,12 @@ class Query < ActiveRecord::Base
end if filters and valid? end if filters and valid?
filters_clauses << project_statement (filters_clauses << project_statement).join(' AND ')
filters_clauses.reject!(&:blank?)
filters_clauses.any? ? filters_clauses.join(' AND ') : nil
end end
# Returns the issue count # Returns the issue count
def issue_count def issue_count
Issue.visible.count(:include => [:status, :project], :conditions => statement) Issue.count(:include => [:status, :project], :conditions => statement)
rescue ::ActiveRecord::StatementInvalid => e rescue ::ActiveRecord::StatementInvalid => e
raise StatementInvalid.new(e.message) raise StatementInvalid.new(e.message)
end end
@@ -532,7 +509,7 @@ class Query < ActiveRecord::Base
if grouped? if grouped?
begin begin
# Rails will raise an (unexpected) RecordNotFound if there's only a nil group value # Rails will raise an (unexpected) RecordNotFound if there's only a nil group value
r = Issue.visible.count(:group => group_by_statement, :include => [:status, :project], :conditions => statement) r = Issue.count(:group => group_by_statement, :include => [:status, :project], :conditions => statement)
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
r = {nil => issue_count} r = {nil => issue_count}
end end
@@ -552,7 +529,7 @@ class Query < ActiveRecord::Base
order_option = [group_by_sort_order, options[:order]].reject {|s| s.blank?}.join(',') order_option = [group_by_sort_order, options[:order]].reject {|s| s.blank?}.join(',')
order_option = nil if order_option.blank? order_option = nil if order_option.blank?
Issue.visible.find :all, :include => ([:status, :project] + (options[:include] || [])).uniq, Issue.find :all, :include => ([:status, :project] + (options[:include] || [])).uniq,
:conditions => Query.merge_conditions(statement, options[:conditions]), :conditions => Query.merge_conditions(statement, options[:conditions]),
:order => order_option, :order => order_option,
:limit => options[:limit], :limit => options[:limit],
@@ -564,7 +541,7 @@ class Query < ActiveRecord::Base
# Returns the journals # Returns the journals
# Valid options are :order, :offset, :limit # Valid options are :order, :offset, :limit
def journals(options={}) def journals(options={})
Journal.visible.find :all, :include => [:details, :user, {:issue => [:project, :author, :tracker, :status]}], Journal.find :all, :include => [:details, :user, {:issue => [:project, :author, :tracker, :status]}],
:conditions => statement, :conditions => statement,
:order => options[:order], :order => options[:order],
:limit => options[:limit], :limit => options[:limit],
@@ -576,7 +553,7 @@ class Query < ActiveRecord::Base
# Returns the versions # Returns the versions
# Valid options are :conditions # Valid options are :conditions
def versions(options={}) def versions(options={})
Version.visible.find :all, :include => :project, Version.find :all, :include => :project,
:conditions => Query.merge_conditions(project_statement, options[:conditions]) :conditions => Query.merge_conditions(project_statement, options[:conditions])
rescue ::ActiveRecord::StatementInvalid => e rescue ::ActiveRecord::StatementInvalid => e
raise StatementInvalid.new(e.message) raise StatementInvalid.new(e.message)
@@ -589,19 +566,9 @@ class Query < ActiveRecord::Base
sql = '' sql = ''
case operator case operator
when "=" when "="
if value.any? sql = "#{db_table}.#{db_field} IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")"
sql = "#{db_table}.#{db_field} IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")"
else
# IN an empty set
sql = "1=0"
end
when "!" when "!"
if value.any? sql = "(#{db_table}.#{db_field} IS NULL OR #{db_table}.#{db_field} NOT IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + "))"
sql = "(#{db_table}.#{db_field} IS NULL OR #{db_table}.#{db_field} NOT IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + "))"
else
# NOT IN an empty set
sql = "1=1"
end
when "!*" when "!*"
sql = "#{db_table}.#{db_field} IS NULL" sql = "#{db_table}.#{db_field} IS NULL"
sql << " OR #{db_table}.#{db_field} = ''" if is_custom_filter sql << " OR #{db_table}.#{db_field} = ''" if is_custom_filter
@@ -631,10 +598,12 @@ class Query < ActiveRecord::Base
when "t" when "t"
sql = date_range_clause(db_table, db_field, 0, 0) sql = date_range_clause(db_table, db_field, 0, 0)
when "w" when "w"
first_day_of_week = l(:general_first_day_of_week).to_i from = l(:general_first_day_of_week) == '7' ?
day_of_week = Date.today.cwday # week starts on sunday
days_ago = (day_of_week >= first_day_of_week ? day_of_week - first_day_of_week : day_of_week + 7 - first_day_of_week) ((Date.today.cwday == 7) ? Time.now.at_beginning_of_day : Time.now.at_beginning_of_week - 1.day) :
sql = date_range_clause(db_table, db_field, - days_ago, - days_ago + 6) # week starts on monday (Rails default)
Time.now.at_beginning_of_week
sql = "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date(from), connection.quoted_date(from + 7.days)]
when "~" when "~"
sql = "LOWER(#{db_table}.#{db_field}) LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'" sql = "LOWER(#{db_table}.#{db_field}) LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'"
when "!~" when "!~"
@@ -657,9 +626,6 @@ class Query < ActiveRecord::Base
options = { :type => :date, :order => 20 } options = { :type => :date, :order => 20 }
when "bool" when "bool"
options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]], :order => 20 } options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]], :order => 20 }
when "user", "version"
next unless project
options = { :type => :list_optional, :values => field.possible_values_options(project), :order => 20}
else else
options = { :type => :string, :order => 20 } options = { :type => :string, :order => 20 }
end end
+16 -121
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang # Copyright (C) 2006-2007 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -16,30 +16,17 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class Repository < ActiveRecord::Base class Repository < ActiveRecord::Base
include Redmine::Ciphering
belongs_to :project belongs_to :project
has_many :changesets, :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC" has_many :changesets, :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC"
has_many :changes, :through => :changesets has_many :changes, :through => :changesets
serialize :extra_info
# Raw SQL to delete changesets and changes in the database # Raw SQL to delete changesets and changes in the database
# has_many :changesets, :dependent => :destroy is too slow for big repositories # has_many :changesets, :dependent => :destroy is too slow for big repositories
before_destroy :clear_changesets before_destroy :clear_changesets
validates_length_of :password, :maximum => 255, :allow_nil => true
# Checks if the SCM is enabled when creating a repository # Checks if the SCM is enabled when creating a repository
validate_on_create { |r| r.errors.add(:type, :invalid) unless Setting.enabled_scm.include?(r.class.name.demodulize) } validate_on_create { |r| r.errors.add(:type, :invalid) unless Setting.enabled_scm.include?(r.class.name.demodulize) }
def self.human_attribute_name(attribute_key_name)
attr_name = attribute_key_name
if attr_name == "log_encoding"
attr_name = "commit_logs_encoding"
end
super(attr_name)
end
# Removes leading and trailing whitespace # Removes leading and trailing whitespace
def url=(arg) def url=(arg)
write_attribute(:url, arg ? arg.to_s.strip : nil) write_attribute(:url, arg ? arg.to_s.strip : nil)
@@ -50,21 +37,8 @@ class Repository < ActiveRecord::Base
write_attribute(:root_url, arg ? arg.to_s.strip : nil) write_attribute(:root_url, arg ? arg.to_s.strip : nil)
end end
def password
read_ciphered_attribute(:password)
end
def password=(arg)
write_ciphered_attribute(:password, arg)
end
def scm_adapter
self.class.scm_adapter_class
end
def scm def scm
@scm ||= self.scm_adapter.new(url, root_url, @scm ||= self.scm_adapter.new url, root_url, login, password
login, password, path_encoding)
update_attribute(:root_url, @scm.root_url) if root_url.blank? update_attribute(:root_url, @scm.root_url) if root_url.blank?
@scm @scm
end end
@@ -73,17 +47,6 @@ class Repository < ActiveRecord::Base
self.class.scm_name self.class.scm_name
end end
def merge_extra_info(arg)
h = extra_info || {}
return h if arg.nil?
h.merge!(arg)
write_attribute(:extra_info, h)
end
def report_last_commit
true
end
def supports_cat? def supports_cat?
scm.supports_cat? scm.supports_cat?
end end
@@ -92,14 +55,6 @@ class Repository < ActiveRecord::Base
scm.supports_annotate? scm.supports_annotate?
end end
def supports_all_revisions?
true
end
def supports_directory_revisions?
false
end
def entry(path=nil, identifier=nil) def entry(path=nil, identifier=nil)
scm.entry(path, identifier) scm.entry(path, identifier)
end end
@@ -117,7 +72,7 @@ class Repository < ActiveRecord::Base
end end
def default_branch def default_branch
nil scm.default_branch
end end
def properties(path, identifier=nil) def properties(path, identifier=nil)
@@ -132,13 +87,6 @@ class Repository < ActiveRecord::Base
scm.diff(path, rev, rev_to) scm.diff(path, rev, rev_to)
end end
def diff_format_revisions(cs, cs_to, sep=':')
text = ""
text << cs_to.format_identifier + sep if cs_to
text << cs.format_identifier if cs
text
end
# Returns a path relative to the url of the repository # Returns a path relative to the url of the repository
def relative_path(path) def relative_path(path)
path path
@@ -146,9 +94,7 @@ class Repository < ActiveRecord::Base
# Finds and returns a revision with a number or the beginning of a hash # Finds and returns a revision with a number or the beginning of a hash
def find_changeset_by_name(name) def find_changeset_by_name(name)
return nil if name.blank? changesets.find(:first, :conditions => (name.match(/^\d*$/) ? ["revision = ?", name.to_s] : ["revision LIKE ?", name + '%']))
changesets.find(:first, :conditions => (name.match(/^\d*$/) ?
["revision = ?", name.to_s] : ["revision LIKE ?", name + '%']))
end end
def latest_changeset def latest_changeset
@@ -159,19 +105,14 @@ class Repository < ActiveRecord::Base
# Default behaviour is to search in cached changesets # Default behaviour is to search in cached changesets
def latest_changesets(path, rev, limit=10) def latest_changesets(path, rev, limit=10)
if path.blank? if path.blank?
changesets.find( changesets.find(:all, :include => :user,
:all, :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC",
:include => :user, :limit => limit)
:order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC",
:limit => limit)
else else
changes.find( changes.find(:all, :include => {:changeset => :user},
:all, :conditions => ["path = ?", path.with_leading_slash],
:include => {:changeset => :user}, :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC",
:conditions => ["path = ?", path.with_leading_slash], :limit => limit).collect(&:changeset)
:order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC",
:limit => limit
).collect(&:changeset)
end end
end end
@@ -181,8 +122,7 @@ class Repository < ActiveRecord::Base
# Returns an array of committers usernames and associated user_id # Returns an array of committers usernames and associated user_id
def committers def committers
@committers ||= Changeset.connection.select_rows( @committers ||= Changeset.connection.select_rows("SELECT DISTINCT committer, user_id FROM #{Changeset.table_name} WHERE repository_id = #{id}")
"SELECT DISTINCT committer, user_id FROM #{Changeset.table_name} WHERE repository_id = #{id}")
end end
# Maps committers username to a user ids # Maps committers username to a user ids
@@ -192,12 +132,10 @@ class Repository < ActiveRecord::Base
new_user_id = h[committer] new_user_id = h[committer]
if new_user_id && (new_user_id.to_i != user_id.to_i) if new_user_id && (new_user_id.to_i != user_id.to_i)
new_user_id = (new_user_id.to_i > 0 ? new_user_id.to_i : nil) new_user_id = (new_user_id.to_i > 0 ? new_user_id.to_i : nil)
Changeset.update_all( Changeset.update_all("user_id = #{ new_user_id.nil? ? 'NULL' : new_user_id }", ["repository_id = ? AND committer = ?", id, committer])
"user_id = #{ new_user_id.nil? ? 'NULL' : new_user_id }",
["repository_id = ? AND committer = ?", id, committer])
end end
end end
@committers = nil @committers = nil
@found_committer_users = nil @found_committer_users = nil
true true
else else
@@ -228,22 +166,13 @@ class Repository < ActiveRecord::Base
end end
end end
def repo_log_encoding
encoding = log_encoding.to_s.strip
encoding.blank? ? 'UTF-8' : encoding
end
# Fetches new changesets for all repositories of active projects # Fetches new changesets for all repositories of active projects
# Can be called periodically by an external script # Can be called periodically by an external script
# eg. ruby script/runner "Repository.fetch_changesets" # eg. ruby script/runner "Repository.fetch_changesets"
def self.fetch_changesets def self.fetch_changesets
Project.active.has_module(:repository).find(:all, :include => :repository).each do |project| Project.active.has_module(:repository).find(:all, :include => :repository).each do |project|
if project.repository if project.repository
begin project.repository.fetch_changesets
project.repository.fetch_changesets
rescue Redmine::Scm::Adapters::CommandFailed => e
logger.error "scm: error during fetching changesets: #{e.message}"
end
end end
end end
end end
@@ -268,40 +197,6 @@ class Repository < ActiveRecord::Base
nil nil
end end
def self.scm_adapter_class
nil
end
def self.scm_command
ret = ""
begin
ret = self.scm_adapter_class.client_command if self.scm_adapter_class
rescue Exception => e
logger.error "scm: error during get command: #{e.message}"
end
ret
end
def self.scm_version_string
ret = ""
begin
ret = self.scm_adapter_class.client_version_string if self.scm_adapter_class
rescue Exception => e
logger.error "scm: error during get version string: #{e.message}"
end
ret
end
def self.scm_available
ret = false
begin
ret = self.scm_adapter_class.client_available if self.scm_adapter_class
rescue Exception => e
logger.error "scm: error during get scm available: #{e.message}"
end
ret
end
private private
def before_save def before_save
+18 -31
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang # Copyright (C) 2006-2007 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -19,17 +19,9 @@ require 'redmine/scm/adapters/bazaar_adapter'
class Repository::Bazaar < Repository class Repository::Bazaar < Repository
attr_protected :root_url attr_protected :root_url
validates_presence_of :url, :log_encoding validates_presence_of :url
def self.human_attribute_name(attribute_key_name) def scm_adapter
attr_name = attribute_key_name
if attr_name == "url"
attr_name = "path_to_repository"
end
super(attr_name)
end
def self.scm_adapter_class
Redmine::Scm::Adapters::BazaarAdapter Redmine::Scm::Adapters::BazaarAdapter
end end
@@ -47,19 +39,14 @@ class Repository::Bazaar < Repository
full_path = File.join(root_url, e.path) full_path = File.join(root_url, e.path)
e.size = File.stat(full_path).size if File.file?(full_path) e.size = File.stat(full_path).size if File.file?(full_path)
end end
c = Change.find( c = Change.find(:first,
:first, :include => :changeset,
:include => :changeset, :conditions => ["#{Change.table_name}.revision = ? and #{Changeset.table_name}.repository_id = ?", e.lastrev.revision, id],
:conditions => [ :order => "#{Changeset.table_name}.revision DESC")
"#{Change.table_name}.revision = ? and #{Changeset.table_name}.repository_id = ?",
e.lastrev.revision,
id
],
:order => "#{Changeset.table_name}.revision DESC")
if c if c
e.lastrev.identifier = c.changeset.revision e.lastrev.identifier = c.changeset.revision
e.lastrev.name = c.changeset.revision e.lastrev.name = c.changeset.revision
e.lastrev.author = c.changeset.committer e.lastrev.author = c.changeset.committer
end end
end end
end end
@@ -81,18 +68,18 @@ class Repository::Bazaar < Repository
revisions = scm.revisions('', identifier_to, identifier_from, :with_paths => true) revisions = scm.revisions('', identifier_to, identifier_from, :with_paths => true)
transaction do transaction do
revisions.reverse_each do |revision| revisions.reverse_each do |revision|
changeset = Changeset.create(:repository => self, changeset = Changeset.create(:repository => self,
:revision => revision.identifier, :revision => revision.identifier,
:committer => revision.author, :committer => revision.author,
:committed_on => revision.time, :committed_on => revision.time,
:scmid => revision.scmid, :scmid => revision.scmid,
:comments => revision.message) :comments => revision.message)
revision.paths.each do |change| revision.paths.each do |change|
Change.create(:changeset => changeset, Change.create(:changeset => changeset,
:action => change[:action], :action => change[:action],
:path => change[:path], :path => change[:path],
:revision => change[:revision]) :revision => change[:revision])
end end
end end
end unless revisions.nil? end unless revisions.nil?
+58 -102
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang # Copyright (C) 2006-2007 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -19,19 +19,9 @@ require 'redmine/scm/adapters/cvs_adapter'
require 'digest/sha1' require 'digest/sha1'
class Repository::Cvs < Repository class Repository::Cvs < Repository
validates_presence_of :url, :root_url, :log_encoding validates_presence_of :url, :root_url
def self.human_attribute_name(attribute_key_name) def scm_adapter
attr_name = attribute_key_name
if attr_name == "root_url"
attr_name = "cvsroot"
elsif attr_name == "url"
attr_name = "cvs_module"
end
super(attr_name)
end
def self.scm_adapter_class
Redmine::Scm::Adapters::CvsAdapter Redmine::Scm::Adapters::CvsAdapter
end end
@@ -45,23 +35,17 @@ class Repository::Cvs < Repository
end end
def entries(path=nil, identifier=nil) def entries(path=nil, identifier=nil)
rev = nil rev = identifier.nil? ? nil : changesets.find_by_revision(identifier)
if ! identifier.nil?
rev = changesets.find_by_revision(identifier)
return nil if rev.nil?
end
entries = scm.entries(path, rev.nil? ? nil : rev.committed_on) entries = scm.entries(path, rev.nil? ? nil : rev.committed_on)
if entries if entries
entries.each() do |entry| entries.each() do |entry|
if ( ! entry.lastrev.nil? ) && ( ! entry.lastrev.revision.nil? ) unless entry.lastrev.nil? || entry.lastrev.identifier
change=changes.find_by_revision_and_path( change=changes.find_by_revision_and_path( entry.lastrev.revision, scm.with_leading_slash(entry.path) )
entry.lastrev.revision,
scm.with_leading_slash(entry.path) )
if change if change
entry.lastrev.identifier = change.changeset.revision entry.lastrev.identifier=change.changeset.revision
entry.lastrev.revision = change.changeset.revision entry.lastrev.author=change.changeset.committer
entry.lastrev.author = change.changeset.committer entry.lastrev.revision=change.revision
# entry.lastrev.branch = change.branch entry.lastrev.branch=change.branch
end end
end end
end end
@@ -70,44 +54,32 @@ class Repository::Cvs < Repository
end end
def cat(path, identifier=nil) def cat(path, identifier=nil)
rev = nil rev = identifier.nil? ? nil : changesets.find_by_revision(identifier)
if ! identifier.nil?
rev = changesets.find_by_revision(identifier)
return nil if rev.nil?
end
scm.cat(path, rev.nil? ? nil : rev.committed_on) scm.cat(path, rev.nil? ? nil : rev.committed_on)
end end
def annotate(path, identifier=nil)
rev = nil
if ! identifier.nil?
rev = changesets.find_by_revision(identifier)
return nil if rev.nil?
end
scm.annotate(path, rev.nil? ? nil : rev.committed_on)
end
def diff(path, rev, rev_to) def diff(path, rev, rev_to)
# convert rev to revision. CVS can't handle changesets here #convert rev to revision. CVS can't handle changesets here
diff=[] diff=[]
changeset_from = changesets.find_by_revision(rev) changeset_from=changesets.find_by_revision(rev)
if rev_to.to_i > 0 if rev_to.to_i > 0
changeset_to = changesets.find_by_revision(rev_to) changeset_to=changesets.find_by_revision(rev_to)
end end
changeset_from.changes.each() do |change_from| changeset_from.changes.each() do |change_from|
revision_from = nil
revision_to = nil revision_from=nil
if path.nil? || (change_from.path.starts_with? scm.with_leading_slash(path)) revision_to=nil
revision_from = change_from.revision
end revision_from=change_from.revision if path.nil? || (change_from.path.starts_with? scm.with_leading_slash(path))
if revision_from if revision_from
if changeset_to if changeset_to
changeset_to.changes.each() do |change_to| changeset_to.changes.each() do |change_to|
revision_to = change_to.revision if change_to.path == change_from.path revision_to=change_to.revision if change_to.path==change_from.path
end end
end end
unless revision_to unless revision_to
revision_to = scm.get_previous_revision(revision_from) revision_to=scm.get_previous_revision(revision_from)
end end
file_diff = scm.diff(change_from.path, revision_from, revision_to) file_diff = scm.diff(change_from.path, revision_from, revision_to)
diff = diff + file_diff unless file_diff.nil? diff = diff + file_diff unless file_diff.nil?
@@ -118,78 +90,64 @@ class Repository::Cvs < Repository
def fetch_changesets def fetch_changesets
# some nifty bits to introduce a commit-id with cvs # some nifty bits to introduce a commit-id with cvs
# natively cvs doesn't provide any kind of changesets, # natively cvs doesn't provide any kind of changesets, there is only a revision per file.
# there is only a revision per file.
# we now take a guess using the author, the commitlog and the commit-date. # we now take a guess using the author, the commitlog and the commit-date.
# last one is the next step to take. the commit-date is not equal for all # last one is the next step to take. the commit-date is not equal for all
# commits in one changeset. cvs update the commit-date when the *,v file was touched. so # commits in one changeset. cvs update the commit-date when the *,v file was touched. so
# we use a small delta here, to merge all changes belonging to _one_ changeset # we use a small delta here, to merge all changes belonging to _one_ changeset
time_delta = 10.seconds time_delta=10.seconds
fetch_since = latest_changeset ? latest_changeset.committed_on : nil fetch_since = latest_changeset ? latest_changeset.committed_on : nil
transaction do transaction do
tmp_rev_num = 1 tmp_rev_num = 1
scm.revisions('', fetch_since, nil, :log_encoding => repo_log_encoding) do |revision| scm.revisions('', fetch_since, nil, :with_paths => true) do |revision|
# only add the change to the database, if it doen't exists. the cvs log # only add the change to the database, if it doen't exists. the cvs log
# is not exclusive at all. # is not exclusive at all.
tmp_time = revision.time.clone unless changes.find_by_path_and_revision(scm.with_leading_slash(revision.paths[0][:path]), revision.paths[0][:revision])
unless changes.find_by_path_and_revision( revision
scm.with_leading_slash(revision.paths[0][:path]), cs = changesets.find(:first, :conditions=>{
revision.paths[0][:revision] :committed_on=>revision.time-time_delta..revision.time+time_delta,
) :committer=>revision.author,
cmt = Changeset.normalize_comments(revision.message, repo_log_encoding) :comments=>Changeset.normalize_comments(revision.message)
author_utf8 = Changeset.to_utf8(revision.author, repo_log_encoding) })
cs = changesets.find(
:first,
:conditions => {
:committed_on => tmp_time - time_delta .. tmp_time + time_delta,
:committer => author_utf8,
:comments => cmt
}
)
# create a new changeset.... # create a new changeset....
unless cs unless cs
# we use a temporaray revision number here (just for inserting) # we use a temporaray revision number here (just for inserting)
# later on, we calculate a continous positive number # later on, we calculate a continous positive number
tmp_time2 = tmp_time.clone.gmtime latest = changesets.find(:first, :order => 'id DESC')
branch = revision.paths[0][:branch] cs = Changeset.create(:repository => self,
scmid = branch + "-" + tmp_time2.strftime("%Y%m%d-%H%M%S") :revision => "_#{tmp_rev_num}",
cs = Changeset.create(:repository => self, :committer => revision.author,
:revision => "tmp#{tmp_rev_num}", :committed_on => revision.time,
:scmid => scmid, :comments => revision.message)
:committer => revision.author,
:committed_on => tmp_time,
:comments => revision.message)
tmp_rev_num += 1 tmp_rev_num += 1
end end
# convert CVS-File-States to internal Action-abbrevations
# default action is (M)odified #convert CVS-File-States to internal Action-abbrevations
action = "M" #default action is (M)odified
if revision.paths[0][:action] == "Exp" && revision.paths[0][:revision] == "1.1" action="M"
action = "A" # add-action always at first revision (= 1.1) if revision.paths[0][:action]=="Exp" && revision.paths[0][:revision]=="1.1"
elsif revision.paths[0][:action] == "dead" action="A" #add-action always at first revision (= 1.1)
action = "D" # dead-state is similar to Delete elsif revision.paths[0][:action]=="dead"
action="D" #dead-state is similar to Delete
end end
Change.create(
:changeset => cs, Change.create(:changeset => cs,
:action => action, :action => action,
:path => scm.with_leading_slash(revision.paths[0][:path]), :path => scm.with_leading_slash(revision.paths[0][:path]),
:revision => revision.paths[0][:revision], :revision => revision.paths[0][:revision],
:branch => revision.paths[0][:branch] :branch => revision.paths[0][:branch]
) )
end end
end end
# Renumber new changesets in chronological order # Renumber new changesets in chronological order
changesets.find( changesets.find(:all, :order => 'committed_on ASC, id ASC', :conditions => "revision LIKE '_%'").each do |changeset|
:all,
:order => 'committed_on ASC, id ASC',
:conditions => "revision LIKE 'tmp%'"
).each do |changeset|
changeset.update_attribute :revision, next_revision_number changeset.update_attribute :revision, next_revision_number
end end
end # transaction end # transaction
@current_revision_number = nil
end end
private private
@@ -197,9 +155,7 @@ class Repository::Cvs < Repository
# Returns the next revision number to assign to a CVS changeset # Returns the next revision number to assign to a CVS changeset
def next_revision_number def next_revision_number
# Need to retrieve existing revision numbers to sort them as integers # Need to retrieve existing revision numbers to sort them as integers
sql = "SELECT revision FROM #{Changeset.table_name} " @current_revision_number ||= (connection.select_values("SELECT revision FROM #{Changeset.table_name} WHERE repository_id = #{id} AND revision NOT LIKE '_%'").collect(&:to_i).max || 0)
sql << "WHERE repository_id = #{id} AND revision NOT LIKE 'tmp%'"
@current_revision_number ||= (connection.select_values(sql).collect(&:to_i).max || 0)
@current_revision_number += 1 @current_revision_number += 1
end end
end end
+16 -33
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang # Copyright (C) 2006-2007 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -18,17 +18,9 @@
require 'redmine/scm/adapters/darcs_adapter' require 'redmine/scm/adapters/darcs_adapter'
class Repository::Darcs < Repository class Repository::Darcs < Repository
validates_presence_of :url, :log_encoding validates_presence_of :url
def self.human_attribute_name(attribute_key_name) def scm_adapter
attr_name = attribute_key_name
if attr_name == "url"
attr_name = "path_to_repository"
end
super(attr_name)
end
def self.scm_adapter_class
Redmine::Scm::Adapters::DarcsAdapter Redmine::Scm::Adapters::DarcsAdapter
end end
@@ -36,33 +28,23 @@ class Repository::Darcs < Repository
'Darcs' 'Darcs'
end end
def supports_directory_revisions?
true
end
def entry(path=nil, identifier=nil) def entry(path=nil, identifier=nil)
patch = identifier.nil? ? nil : changesets.find_by_revision(identifier) patch = identifier.nil? ? nil : changesets.find_by_revision(identifier)
scm.entry(path, patch.nil? ? nil : patch.scmid) scm.entry(path, patch.nil? ? nil : patch.scmid)
end end
def entries(path=nil, identifier=nil) def entries(path=nil, identifier=nil)
patch = nil patch = identifier.nil? ? nil : changesets.find_by_revision(identifier)
if ! identifier.nil?
patch = changesets.find_by_revision(identifier)
return nil if patch.nil?
end
entries = scm.entries(path, patch.nil? ? nil : patch.scmid) entries = scm.entries(path, patch.nil? ? nil : patch.scmid)
if entries if entries
entries.each do |entry| entries.each do |entry|
# Search the DB for the entry's last change # Search the DB for the entry's last change
if entry.lastrev && !entry.lastrev.scmid.blank? changeset = changesets.find_by_scmid(entry.lastrev.scmid) if entry.lastrev && !entry.lastrev.scmid.blank?
changeset = changesets.find_by_scmid(entry.lastrev.scmid)
end
if changeset if changeset
entry.lastrev.identifier = changeset.revision entry.lastrev.identifier = changeset.revision
entry.lastrev.name = changeset.revision entry.lastrev.name = changeset.revision
entry.lastrev.time = changeset.committed_on entry.lastrev.time = changeset.committed_on
entry.lastrev.author = changeset.committer entry.lastrev.author = changeset.committer
end end
end end
end end
@@ -88,19 +70,20 @@ class Repository::Darcs < Repository
scm_info = scm.info scm_info = scm.info
if scm_info if scm_info
db_last_id = latest_changeset ? latest_changeset.scmid : nil db_last_id = latest_changeset ? latest_changeset.scmid : nil
next_rev = latest_changeset ? latest_changeset.revision.to_i + 1 : 1 next_rev = latest_changeset ? latest_changeset.revision.to_i + 1 : 1
# latest revision in the repository # latest revision in the repository
scm_revision = scm_info.lastrev.scmid scm_revision = scm_info.lastrev.scmid
unless changesets.find_by_scmid(scm_revision) unless changesets.find_by_scmid(scm_revision)
revisions = scm.revisions('', db_last_id, nil, :with_path => true) revisions = scm.revisions('', db_last_id, nil, :with_path => true)
transaction do transaction do
revisions.reverse_each do |revision| revisions.reverse_each do |revision|
changeset = Changeset.create(:repository => self, changeset = Changeset.create(:repository => self,
:revision => next_rev, :revision => next_rev,
:scmid => revision.scmid, :scmid => revision.scmid,
:committer => revision.author, :committer => revision.author,
:committed_on => revision.time, :committed_on => revision.time,
:comments => revision.message) :comments => revision.message)
revision.paths.each do |change| revision.paths.each do |change|
changeset.create_change(change) changeset.create_change(change)
end end
+4 -15
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang # Copyright (C) 2006-2007 Jean-Philippe Lang
# #
# FileSystem adapter # FileSystem adapter
# File written by Paul Rivier, at Demotera. # File written by Paul Rivier, at Demotera.
@@ -24,15 +24,7 @@ class Repository::Filesystem < Repository
attr_protected :root_url attr_protected :root_url
validates_presence_of :url validates_presence_of :url
def self.human_attribute_name(attribute_key_name) def scm_adapter
attr_name = attribute_key_name
if attr_name == "url"
attr_name = "root_directory"
end
super(attr_name)
end
def self.scm_adapter_class
Redmine::Scm::Adapters::FilesystemAdapter Redmine::Scm::Adapters::FilesystemAdapter
end end
@@ -40,10 +32,6 @@ class Repository::Filesystem < Repository
'Filesystem' 'Filesystem'
end end
def supports_all_revisions?
false
end
def entries(path=nil, identifier=nil) def entries(path=nil, identifier=nil)
scm.entries(path, identifier) scm.entries(path, identifier)
end end
@@ -51,4 +39,5 @@ class Repository::Filesystem < Repository
def fetch_changesets def fetch_changesets
nil nil
end end
end end
+25 -125
View File
@@ -1,7 +1,6 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang # Copyright (C) 2006-2007 Jean-Philippe Lang
# Copyright (C) 2007 Patrick Aljord patcito@ŋmail.com # Copyright (C) 2007 Patrick Aljord patcito@ŋmail.com
#
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
@@ -22,15 +21,7 @@ class Repository::Git < Repository
attr_protected :root_url attr_protected :root_url
validates_presence_of :url validates_presence_of :url
def self.human_attribute_name(attribute_key_name) def scm_adapter
attr_name = attribute_key_name
if attr_name == "url"
attr_name = "path_to_repository"
end
super(attr_name)
end
def self.scm_adapter_class
Redmine::Scm::Adapters::GitAdapter Redmine::Scm::Adapters::GitAdapter
end end
@@ -38,25 +29,6 @@ class Repository::Git < Repository
'Git' 'Git'
end end
def report_last_commit
extra_report_last_commit
end
def extra_report_last_commit
return false if extra_info.nil?
v = extra_info["extra_report_last_commit"]
return false if v.nil?
v.to_s != '0'
end
def supports_directory_revisions?
true
end
def repo_log_encoding
'UTF-8'
end
# Returns the identifier for the given git changeset # Returns the identifier for the given git changeset
def self.changeset_identifier(changeset) def self.changeset_identifier(changeset)
changeset.scmid changeset.scmid
@@ -75,105 +47,33 @@ class Repository::Git < Repository
scm.tags scm.tags
end end
def default_branch # With SCM's that have a sequential commit numbering, redmine is able to be
scm.default_branch # clever and only fetch changesets going forward from the most recent one
end # it knows about. However, with git, you never know if people have merged
# commits into the middle of the repository history, so we should parse
def find_changeset_by_name(name) # the entire log. Since it's way too slow for large repositories, we only
return nil if name.nil? || name.empty? # parse 1 week before the last known commit.
e = changesets.find(:first, :conditions => ['revision = ?', name.to_s])
return e if e
changesets.find(:first, :conditions => ['scmid LIKE ?', "#{name}%"])
end
def entries(path=nil, identifier=nil)
scm.entries(path,
identifier,
options = {:report_last_commit => extra_report_last_commit})
end
# With SCMs that have a sequential commit numbering,
# such as Subversion and Mercurial,
# Redmine is able to be clever and only fetch changesets
# going forward from the most recent one it knows about.
#
# However, Git does not have a sequential commit numbering.
#
# In order to fetch only new adding revisions,
# Redmine needs to parse revisions per branch.
# Branch "last_scmid" is for this requirement.
#
# In Git and Mercurial, revisions are not in date order.
# Redmine Mercurial fixed issues.
# * Redmine Takes Too Long On Large Mercurial Repository
# http://www.redmine.org/issues/3449
# * Sorting for changesets might go wrong on Mercurial repos
# http://www.redmine.org/issues/3567
#
# Database revision column is text, so Redmine can not sort by revision.
# Mercurial has revision number, and revision number guarantees revision order.
# Redmine Mercurial model stored revisions ordered by database id to database.
# So, Redmine Mercurial model can use correct ordering revisions.
#
# Redmine Mercurial adapter uses "hg log -r 0:tip --limit 10"
# to get limited revisions from old to new.
# But, Git 1.7.3.4 does not support --reverse with -n or --skip.
#
# The repository can still be fully reloaded by calling #clear_changesets # The repository can still be fully reloaded by calling #clear_changesets
# before fetching changesets (eg. for offline resync) # before fetching changesets (eg. for offline resync)
def fetch_changesets def fetch_changesets
scm_brs = branches c = changesets.find(:first, :order => 'committed_on DESC')
return if scm_brs.nil? || scm_brs.empty? since = (c ? c.committed_on - 7.days : nil)
h1 = extra_info || {}
h = h1.dup
h["branches"] ||= {}
h["db_consistent"] ||= {}
if changesets.count == 0
h["db_consistent"]["ordering"] = 1
merge_extra_info(h)
self.save
elsif ! h["db_consistent"].has_key?("ordering")
h["db_consistent"]["ordering"] = 0
merge_extra_info(h)
self.save
end
scm_brs.each do |br|
from_scmid = nil
from_scmid = h["branches"][br]["last_scmid"] if h["branches"][br]
h["branches"][br] ||= {}
scm.revisions('', from_scmid, br, {:reverse => true}) do |rev|
db_rev = find_changeset_by_name(rev.revision)
transaction do
if db_rev.nil?
save_revision(rev)
end
h["branches"][br]["last_scmid"] = rev.scmid
merge_extra_info(h)
self.save
end
end
end
end
def save_revision(rev) revisions = scm.revisions('', nil, nil, :all => true, :since => since)
changeset = Changeset.new( return if revisions.nil? || revisions.empty?
:repository => self,
:revision => rev.identifier, recent_changesets = changesets.find(:all, :conditions => ['committed_on >= ?', since])
:scmid => rev.scmid,
:committer => rev.author, # Clean out revisions that are no longer in git
:committed_on => rev.time, recent_changesets.each {|c| c.destroy unless revisions.detect {|r| r.scmid.to_s == c.scmid.to_s }}
:comments => rev.message
) # Subtract revisions that redmine already knows about
if changeset.save recent_revisions = recent_changesets.map{|c| c.scmid}
rev.paths.each do |file| revisions.reject!{|r| recent_revisions.include?(r.scmid)}
Change.create(
:changeset => changeset, # Save the remaining ones to the database
:action => file[:action], revisions.each{|r| r.save(self)} unless revisions.nil?
:path => file[:path])
end
end
end end
private :save_revision
def latest_changesets(path,rev,limit=10) def latest_changesets(path,rev,limit=10)
revisions = scm.revisions(path, nil, rev, :limit => limit, :all => false) revisions = scm.revisions(path, nil, rev, :limit => limit, :all => false)
+69 -111
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang # Copyright (C) 2006-2007 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -24,17 +24,7 @@ class Repository::Mercurial < Repository
attr_protected :root_url attr_protected :root_url
validates_presence_of :url validates_presence_of :url
FETCH_AT_ONCE = 100 # number of changesets to fetch at once def scm_adapter
def self.human_attribute_name(attribute_key_name)
attr_name = attribute_key_name
if attr_name == "url"
attr_name = "path_to_repository"
end
super(attr_name)
end
def self.scm_adapter_class
Redmine::Scm::Adapters::MercurialAdapter Redmine::Scm::Adapters::MercurialAdapter
end end
@@ -42,106 +32,74 @@ class Repository::Mercurial < Repository
'Mercurial' 'Mercurial'
end end
def supports_directory_revisions? def entries(path=nil, identifier=nil)
true entries=scm.entries(path, identifier)
end if entries
entries.each do |entry|
def repo_log_encoding next unless entry.is_file?
'UTF-8' # Set the filesize unless browsing a specific revision
end if identifier.nil?
full_path = File.join(root_url, entry.path)
# Returns the readable identifier for the given mercurial changeset entry.size = File.stat(full_path).size if File.file?(full_path)
def self.format_changeset_identifier(changeset) end
"#{changeset.revision}:#{changeset.scmid}" # Search the DB for the entry's last change
end change = changes.find(:first, :conditions => ["path = ?", scm.with_leading_slash(entry.path)], :order => "#{Changeset.table_name}.committed_on DESC")
if change
# Returns the identifier for the given Mercurial changeset entry.lastrev.identifier = change.changeset.revision
def self.changeset_identifier(changeset) entry.lastrev.name = change.changeset.revision
changeset.scmid entry.lastrev.author = change.changeset.committer
end entry.lastrev.revision = change.revision
end
def diff_format_revisions(cs, cs_to, sep=':') end
super(cs, cs_to, ' ') end
end entries
end
# Finds and returns a revision with a number or the beginning of a hash
def find_changeset_by_name(name) # Returns the latest changesets for +path+; sorted by revision number
return nil if name.nil? || name.empty? def latest_changesets(path, rev, limit=10)
if /[^\d]/ =~ name or name.to_s.size > 8 if path.blank?
e = changesets.find(:first, :conditions => ['scmid = ?', name.to_s]) changesets.find(:all, :include => :user, :limit => limit)
else else
e = changesets.find(:first, :conditions => ['revision = ?', name.to_s]) changes.find(:all, :include => {:changeset => :user},
end :conditions => ["path = ?", path.with_leading_slash],
return e if e :order => "#{Changeset.table_name}.id DESC",
changesets.find(:first, :conditions => ['scmid LIKE ?', "#{name}%"]) # last ditch :limit => limit).collect(&:changeset)
end end
end
# Returns the latest changesets for +path+; sorted by revision number
# def fetch_changesets
# Because :order => 'id DESC' is defined at 'has_many', scm_info = scm.info
# there is no need to set 'order'. if scm_info
# But, MySQL test fails. # latest revision found in database
# Sqlite3 and PostgreSQL pass. db_revision = latest_changeset ? latest_changeset.revision.to_i : -1
# Is this MySQL bug? # latest revision in the repository
def latest_changesets(path, rev, limit=10) latest_revision = scm_info.lastrev
changesets.find(:all, :include => :user, return if latest_revision.nil?
:conditions => latest_changesets_cond(path, rev, limit), scm_revision = latest_revision.identifier.to_i
:limit => limit, :order => "#{Changeset.table_name}.id DESC") if db_revision < scm_revision
end logger.debug "Fetching changesets for repository #{url}" if logger && logger.debug?
identifier_from = db_revision + 1
def latest_changesets_cond(path, rev, limit) while (identifier_from <= scm_revision)
cond, args = [], [] # loads changesets by batches of 100
if scm.branchmap.member? rev identifier_to = [identifier_from + 99, scm_revision].min
# Mercurial named branch is *stable* in each revision. revisions = scm.revisions('', identifier_from, identifier_to, :with_paths => true)
# So, named branch can be stored in database. transaction do
# Mercurial provides *bookmark* which is equivalent with git branch. revisions.each do |revision|
# But, bookmark is not implemented. changeset = Changeset.create(:repository => self,
cond << "#{Changeset.table_name}.scmid IN (?)" :revision => revision.identifier,
# Revisions in root directory and sub directory are not equal. :scmid => revision.scmid,
# So, in order to get correct limit, we need to get all revisions. :committer => revision.author,
# But, it is very heavy. :committed_on => revision.time,
# Mercurial does not treat direcotry. :comments => revision.message)
# So, "hg log DIR" is very heavy.
branch_limit = path.blank? ? limit : ( limit * 5 ) revision.paths.each do |change|
args << scm.nodes_in_branch(rev, :limit => branch_limit) changeset.create_change(change)
elsif last = rev ? find_changeset_by_name(scm.tagmap[rev] || rev) : nil end
cond << "#{Changeset.table_name}.id <= ?" end
args << last.id end unless revisions.nil?
end identifier_from = identifier_to + 1
unless path.blank?
cond << "EXISTS (SELECT * FROM #{Change.table_name}
WHERE #{Change.table_name}.changeset_id = #{Changeset.table_name}.id
AND (#{Change.table_name}.path = ?
OR #{Change.table_name}.path LIKE ? ESCAPE ?))"
args << path.with_leading_slash
args << "#{path.with_leading_slash.gsub(/[%_\\]/) { |s| "\\#{s}" }}/%" << '\\'
end
[cond.join(' AND '), *args] unless cond.empty?
end
private :latest_changesets_cond
def fetch_changesets
return if scm.info.nil?
scm_rev = scm.info.lastrev.revision.to_i
db_rev = latest_changeset ? latest_changeset.revision.to_i : -1
return unless db_rev < scm_rev # already up-to-date
logger.debug "Fetching changesets for repository #{url}" if logger
(db_rev + 1).step(scm_rev, FETCH_AT_ONCE) do |i|
transaction do
scm.each_revision('', i, [i + FETCH_AT_ONCE - 1, scm_rev].min) do |re|
cs = Changeset.create(:repository => self,
:revision => re.revision,
:scmid => re.scmid,
:committer => re.author,
:committed_on => re.time,
:comments => re.message)
re.paths.each { |e| cs.create_change(e) }
end end
end end
end end
self
end end
end end
+7 -15
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang # Copyright (C) 2006-2007 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -22,7 +22,7 @@ class Repository::Subversion < Repository
validates_presence_of :url validates_presence_of :url
validates_format_of :url, :with => /^(http|https|svn(\+[^\s:\/\\]+)?|file):\/\/.+/i validates_format_of :url, :with => /^(http|https|svn(\+[^\s:\/\\]+)?|file):\/\/.+/i
def self.scm_adapter_class def scm_adapter
Redmine::Scm::Adapters::SubversionAdapter Redmine::Scm::Adapters::SubversionAdapter
end end
@@ -30,14 +30,6 @@ class Repository::Subversion < Repository
'Subversion' 'Subversion'
end end
def supports_directory_revisions?
true
end
def repo_log_encoding
'UTF-8'
end
def latest_changesets(path, rev, limit=10) def latest_changesets(path, rev, limit=10)
revisions = scm.revisions(path, rev, nil, :limit => limit) revisions = scm.revisions(path, rev, nil, :limit => limit)
revisions ? changesets.find_all_by_revision(revisions.collect(&:identifier), :order => "committed_on DESC", :include => :user) : [] revisions ? changesets.find_all_by_revision(revisions.collect(&:identifier), :order => "committed_on DESC", :include => :user) : []
@@ -64,11 +56,11 @@ class Repository::Subversion < Repository
revisions = scm.revisions('', identifier_to, identifier_from, :with_paths => true) revisions = scm.revisions('', identifier_to, identifier_from, :with_paths => true)
revisions.reverse_each do |revision| revisions.reverse_each do |revision|
transaction do transaction do
changeset = Changeset.create(:repository => self, changeset = Changeset.create(:repository => self,
:revision => revision.identifier, :revision => revision.identifier,
:committer => revision.author, :committer => revision.author,
:committed_on => revision.time, :committed_on => revision.time,
:comments => revision.message) :comments => revision.message)
revision.paths.each do |change| revision.paths.each do |change|
changeset.create_change(change) changeset.create_change(change)
+3 -19
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang # Copyright (C) 2006 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -20,12 +20,6 @@ class Role < ActiveRecord::Base
BUILTIN_NON_MEMBER = 1 BUILTIN_NON_MEMBER = 1
BUILTIN_ANONYMOUS = 2 BUILTIN_ANONYMOUS = 2
ISSUES_VISIBILITY_OPTIONS = [
['all', :label_issues_visibility_all],
['default', :label_issues_visibility_public],
['own', :label_issues_visibility_own]
]
named_scope :givable, { :conditions => "builtin = 0", :order => 'position' } named_scope :givable, { :conditions => "builtin = 0", :order => 'position' }
named_scope :builtin, lambda { |*args| named_scope :builtin, lambda { |*args|
compare = 'not' if args.first == true compare = 'not' if args.first == true
@@ -49,9 +43,7 @@ class Role < ActiveRecord::Base
validates_presence_of :name validates_presence_of :name
validates_uniqueness_of :name validates_uniqueness_of :name
validates_length_of :name, :maximum => 30 validates_length_of :name, :maximum => 30
validates_inclusion_of :issues_visibility, validates_format_of :name, :with => /^[\w\s\'\-]*$/i
:in => ISSUES_VISIBILITY_OPTIONS.collect(&:first),
:if => lambda {|role| role.respond_to?(:issues_visibility)}
def permissions def permissions
read_attribute(:permissions) || [] read_attribute(:permissions) || []
@@ -93,14 +85,6 @@ class Role < ActiveRecord::Base
name name
end end
def name
case builtin
when 1; l(:label_role_non_member, :default => read_attribute(:name))
when 2; l(:label_role_anonymous, :default => read_attribute(:name))
else; read_attribute(:name)
end
end
# Return true if the role is a builtin role # Return true if the role is a builtin role
def builtin? def builtin?
self.builtin != 0 self.builtin != 0
+2 -3
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang # Copyright (C) 2006-2007 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -65,7 +65,6 @@ class Setting < ActiveRecord::Base
UTF-16LE UTF-16LE
EUC-JP EUC-JP
Shift_JIS Shift_JIS
CP932
GB18030 GB18030
GBK GBK
ISCII91 ISCII91
+2 -12
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # redMine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang # Copyright (C) 2006-2008 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -39,11 +39,6 @@ class TimeEntry < ActiveRecord::Base
validates_numericality_of :hours, :allow_nil => true, :message => :invalid validates_numericality_of :hours, :allow_nil => true, :message => :invalid
validates_length_of :comments, :maximum => 255, :allow_nil => true validates_length_of :comments, :maximum => 255, :allow_nil => true
named_scope :visible, lambda {|*args| {
:include => :project,
:conditions => Project.allowed_to_condition(args.shift || User.current, :view_time_entries, *args)
}}
def after_initialize def after_initialize
if new_record? && self.activity.nil? if new_record? && self.activity.nil?
if default_activity = TimeEntryActivity.default if default_activity = TimeEntryActivity.default
@@ -71,9 +66,6 @@ class TimeEntry < ActiveRecord::Base
# these attributes make time aggregations easier # these attributes make time aggregations easier
def spent_on=(date) def spent_on=(date)
super super
if spent_on.is_a?(Time)
self.spent_on = spent_on.to_date
end
self.tyear = spent_on ? spent_on.year : nil self.tyear = spent_on ? spent_on.year : nil
self.tmonth = spent_on ? spent_on.month : nil self.tmonth = spent_on ? spent_on.month : nil
self.tweek = spent_on ? Date.civil(spent_on.year, spent_on.month, spent_on.day).cweek : nil self.tweek = spent_on ? Date.civil(spent_on.year, spent_on.month, spent_on.day).cweek : nil
@@ -84,9 +76,7 @@ class TimeEntry < ActiveRecord::Base
(usr == user && usr.allowed_to?(:edit_own_time_entries, project)) || usr.allowed_to?(:edit_time_entries, project) (usr == user && usr.allowed_to?(:edit_own_time_entries, project)) || usr.allowed_to?(:edit_time_entries, project)
end end
# TODO: remove this method in 1.3.0
def self.visible_by(usr) def self.visible_by(usr)
ActiveSupport::Deprecation.warn "TimeEntry.visible_by is deprecated and will be removed in Redmine 1.3.0. Use the visible scope instead."
with_scope(:find => { :conditions => Project.allowed_to_condition(usr, :view_time_entries) }) do with_scope(:find => { :conditions => Project.allowed_to_condition(usr, :view_time_entries) }) do
yield yield
end end
+1 -2
View File
@@ -31,8 +31,7 @@ class Tracker < ActiveRecord::Base
validates_presence_of :name validates_presence_of :name
validates_uniqueness_of :name validates_uniqueness_of :name
validates_length_of :name, :maximum => 30 validates_length_of :name, :maximum => 30
validates_format_of :name, :with => /^[\w\s\'\-]*$/i
named_scope :named, lambda {|arg| { :conditions => ["LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip]}}
def to_s; name end def to_s; name end
+19 -123
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang # Copyright (C) 2006-2009 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -48,8 +48,8 @@ class User < Principal
has_many :issue_categories, :foreign_key => 'assigned_to_id', :dependent => :nullify has_many :issue_categories, :foreign_key => 'assigned_to_id', :dependent => :nullify
has_many :changesets, :dependent => :nullify has_many :changesets, :dependent => :nullify
has_one :preference, :dependent => :destroy, :class_name => 'UserPreference' has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
has_one :rss_token, :class_name => 'Token', :conditions => "action='feeds'" has_one :rss_token, :dependent => :destroy, :class_name => 'Token', :conditions => "action='feeds'"
has_one :api_token, :class_name => 'Token', :conditions => "action='api'" has_one :api_token, :dependent => :destroy, :class_name => 'Token', :conditions => "action='api'"
belongs_to :auth_source belongs_to :auth_source
# Active non-anonymous users scope # Active non-anonymous users scope
@@ -68,23 +68,13 @@ class User < Principal
# Login must contain lettres, numbers, underscores only # Login must contain lettres, numbers, underscores only
validates_format_of :login, :with => /^[a-z0-9_\-@\.]*$/i validates_format_of :login, :with => /^[a-z0-9_\-@\.]*$/i
validates_length_of :login, :maximum => 30 validates_length_of :login, :maximum => 30
validates_format_of :firstname, :lastname, :with => /^[\w\s\'\-\.]*$/i
validates_length_of :firstname, :lastname, :maximum => 30 validates_length_of :firstname, :lastname, :maximum => 30
validates_format_of :mail, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :allow_nil => true validates_format_of :mail, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :allow_nil => true
validates_length_of :mail, :maximum => 60, :allow_nil => true validates_length_of :mail, :maximum => 60, :allow_nil => true
validates_confirmation_of :password, :allow_nil => true validates_confirmation_of :password, :allow_nil => true
validates_inclusion_of :mail_notification, :in => MAIL_NOTIFICATION_OPTIONS.collect(&:first), :allow_blank => true validates_inclusion_of :mail_notification, :in => MAIL_NOTIFICATION_OPTIONS.collect(&:first), :allow_blank => true
before_destroy :remove_references_before_destroy
named_scope :in_group, lambda {|group|
group_id = group.is_a?(Group) ? group.id : group.to_i
{ :conditions => ["#{User.table_name}.id IN (SELECT gu.user_id FROM #{table_name_prefix}groups_users#{table_name_suffix} gu WHERE gu.group_id = ?)", group_id] }
}
named_scope :not_in_group, lambda {|group|
group_id = group.is_a?(Group) ? group.id : group.to_i
{ :conditions => ["#{User.table_name}.id NOT IN (SELECT gu.user_id FROM #{table_name_prefix}groups_users#{table_name_suffix} gu WHERE gu.group_id = ?)", group_id] }
}
def before_create def before_create
self.mail_notification = Setting.default_notification_option if self.mail_notification.blank? self.mail_notification = Setting.default_notification_option if self.mail_notification.blank?
true true
@@ -92,14 +82,11 @@ class User < Principal
def before_save def before_save
# update hashed_password if password was set # update hashed_password if password was set
if self.password && self.auth_source_id.blank? self.hashed_password = User.hash_password(self.password) if self.password && self.auth_source_id.blank?
salt_password(password)
end
end end
def reload(*args) def reload(*args)
@name = nil @name = nil
@projects_by_role = nil
super super
end end
@@ -133,7 +120,7 @@ class User < Principal
return nil unless user.auth_source.authenticate(login, password) return nil unless user.auth_source.authenticate(login, password)
else else
# authentication with local password # authentication with local password
return nil unless user.check_password?(password) return nil unless User.hash_password(password) == user.hashed_password
end end
else else
# user is not yet registered, try to authenticate with available sources # user is not yet registered, try to authenticate with available sources
@@ -212,22 +199,14 @@ class User < Principal
update_attribute(:status, STATUS_LOCKED) update_attribute(:status, STATUS_LOCKED)
end end
# Returns true if +clear_password+ is the correct user's password, otherwise false
def check_password?(clear_password) def check_password?(clear_password)
if auth_source_id.present? if auth_source_id.present?
auth_source.authenticate(self.login, clear_password) auth_source.authenticate(self.login, clear_password)
else else
User.hash_password("#{salt}#{User.hash_password clear_password}") == hashed_password User.hash_password(clear_password) == self.hashed_password
end end
end end
# Generates a random salt and computes hashed_password for +clear_password+
# The hashed password is stored in the following form: SHA1(salt + SHA1(password))
def salt_password(clear_password)
self.salt = User.generate_salt
self.hashed_password = User.hash_password("#{salt}#{User.hash_password clear_password}")
end
# Does the backend storage allow this user to change their password? # Does the backend storage allow this user to change their password?
def change_password_allowed? def change_password_allowed?
return true if auth_source_id.blank? return true if auth_source_id.blank?
@@ -282,16 +261,12 @@ class User < Principal
notified_projects_ids notified_projects_ids
end end
def valid_notification_options
self.class.valid_notification_options(self)
end
# Only users that belong to more than 1 project can select projects for which they are notified # Only users that belong to more than 1 project can select projects for which they are notified
def self.valid_notification_options(user=nil) def valid_notification_options
# Note that @user.membership.size would fail since AR ignores # Note that @user.membership.size would fail since AR ignores
# :include association option when doing a count # :include association option when doing a count
if user.nil? || user.memberships.length < 1 if memberships.length < 1
MAIL_NOTIFICATION_OPTIONS.reject {|option| option.first == 'selected'} MAIL_NOTIFICATION_OPTIONS.delete_if {|option| option.first == 'selected'}
else else
MAIL_NOTIFICATION_OPTIONS MAIL_NOTIFICATION_OPTIONS
end end
@@ -371,33 +346,16 @@ class User < Principal
!roles_for_project(project).detect {|role| role.member?}.nil? !roles_for_project(project).detect {|role| role.member?}.nil?
end end
# Returns a hash of user's projects grouped by roles
def projects_by_role
return @projects_by_role if @projects_by_role
@projects_by_role = Hash.new {|h,k| h[k]=[]}
memberships.each do |membership|
membership.roles.each do |role|
@projects_by_role[role] << membership.project if membership.project
end
end
@projects_by_role.each do |role, projects|
projects.uniq!
end
@projects_by_role
end
# Return true if the user is allowed to do the specified action on a specific context # Return true if the user is allowed to do the specified action on a specific context
# Action can be: # Action can be:
# * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit') # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
# * a permission Symbol (eg. :edit_project) # * a permission Symbol (eg. :edit_project)
# Context can be: # Context can be:
# * a project : returns true if user is allowed to do the specified action on this project # * a project : returns true if user is allowed to do the specified action on this project
# * an array of projects : returns true if user is allowed on every project # * a group of projects : returns true if user is allowed on every project
# * nil with options[:global] set : check if user has at least one role allowed for this action, # * nil with options[:global] set : check if user has at least one role allowed for this action,
# or falls back to Non Member / Anonymous permissions depending if the user is logged # or falls back to Non Member / Anonymous permissions depending if the user is logged
def allowed_to?(action, context, options={}, &block) def allowed_to?(action, context, options={})
if context && context.is_a?(Project) if context && context.is_a?(Project)
# No action allowed on archived projects # No action allowed on archived projects
return false unless context.active? return false unless context.active?
@@ -408,15 +366,12 @@ class User < Principal
roles = roles_for_project(context) roles = roles_for_project(context)
return false unless roles return false unless roles
roles.detect {|role| roles.detect {|role| (context.is_public? || role.member?) && role.allowed_to?(action)}
(context.is_public? || role.member?) &&
role.allowed_to?(action) &&
(block_given? ? yield(role, self) : true)
}
elsif context && context.is_a?(Array) elsif context && context.is_a?(Array)
# Authorize if user is authorized on every element of the array # Authorize if user is authorized on every element of the array
context.map do |project| context.map do |project|
allowed_to?(action, project, options, &block) allowed_to?(action,project,options)
end.inject do |memo,allowed| end.inject do |memo,allowed|
memo && allowed memo && allowed
end end
@@ -426,11 +381,7 @@ class User < Principal
# authorize if user has at least one role that has this permission # authorize if user has at least one role that has this permission
roles = memberships.collect {|m| m.roles}.flatten.uniq roles = memberships.collect {|m| m.roles}.flatten.uniq
roles << (self.logged? ? Role.non_member : Role.anonymous) roles.detect {|r| r.allowed_to?(action)} || (self.logged? ? Role.non_member.allowed_to?(action) : Role.anonymous.allowed_to?(action))
roles.detect {|role|
role.allowed_to?(action) &&
(block_given? ? yield(role, self) : true)
}
else else
false false
end end
@@ -438,8 +389,8 @@ class User < Principal
# Is the user allowed to do the specified action on any project? # Is the user allowed to do the specified action on any project?
# See allowed_to? for the actions and valid options. # See allowed_to? for the actions and valid options.
def allowed_to_globally?(action, options, &block) def allowed_to_globally?(action, options)
allowed_to?(action, nil, options.reverse_merge(:global => true), &block) allowed_to?(action, nil, options.reverse_merge(:global => true))
end end
safe_attributes 'login', safe_attributes 'login',
@@ -468,12 +419,7 @@ class User < Principal
when 'all' when 'all'
true true
when 'selected' when 'selected'
# user receives notifications for created/assigned issues on unselected projects # Handled by the Project
if object.is_a?(Issue) && (object.author == self || object.assigned_to == self)
true
else
false
end
when 'none' when 'none'
false false
when 'only_my_events' when 'only_my_events'
@@ -518,20 +464,6 @@ class User < Principal
anonymous_user anonymous_user
end end
# Salts all existing unsalted passwords
# It changes password storage scheme from SHA1(password) to SHA1(salt + SHA1(password))
# This method is used in the SaltPasswords migration and is to be kept as is
def self.salt_unsalted_passwords!
transaction do
User.find_each(:conditions => "salt IS NULL OR salt = ''") do |user|
next if user.hashed_password.blank?
salt = User.generate_salt
hashed_password = User.hash_password("#{salt}#{user.hashed_password}")
User.update_all("salt = '#{salt}', hashed_password = '#{hashed_password}'", ["id = ?", user.id] )
end
end
end
protected protected
def validate def validate
@@ -543,41 +475,10 @@ class User < Principal
private private
# Removes references that are not handled by associations
# Things that are not deleted are reassociated with the anonymous user
def remove_references_before_destroy
return if self.id.nil?
substitute = User.anonymous
Attachment.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
Comment.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
Issue.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
Issue.update_all 'assigned_to_id = NULL', ['assigned_to_id = ?', id]
Journal.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
JournalDetail.update_all ['old_value = ?', substitute.id.to_s], ["property = 'attr' AND prop_key = 'assigned_to_id' AND old_value = ?", id.to_s]
JournalDetail.update_all ['value = ?', substitute.id.to_s], ["property = 'attr' AND prop_key = 'assigned_to_id' AND value = ?", id.to_s]
Message.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
News.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
# Remove private queries and keep public ones
Query.delete_all ['user_id = ? AND is_public = ?', id, false]
Query.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
TimeEntry.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
Token.delete_all ['user_id = ?', id]
Watcher.delete_all ['user_id = ?', id]
WikiContent.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
WikiContent::Version.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
end
# Return password digest # Return password digest
def self.hash_password(clear_password) def self.hash_password(clear_password)
Digest::SHA1.hexdigest(clear_password || "") Digest::SHA1.hexdigest(clear_password || "")
end end
# Returns a 128bits random salt as a hex string (32 chars long)
def self.generate_salt
ActiveSupport::SecureRandom.hex(16)
end
end end
class AnonymousUser < User class AnonymousUser < User
@@ -598,9 +499,4 @@ class AnonymousUser < User
def mail; nil end def mail; nil end
def time_zone; nil end def time_zone; nil end
def rss_key; nil end def rss_key; nil end
# Anonymous user can not be destroyed
def destroy
false
end
end end
-3
View File
@@ -51,7 +51,4 @@ class UserPreference < ActiveRecord::Base
def comments_sorting; self[:comments_sorting] end def comments_sorting; self[:comments_sorting] end
def comments_sorting=(order); self[:comments_sorting]=order end def comments_sorting=(order); self[:comments_sorting]=order end
def warn_on_leaving_unsaved; self[:warn_on_leaving_unsaved] || '1'; end
def warn_on_leaving_unsaved=(value); self[:warn_on_leaving_unsaved]=value; end
end end
-1
View File
@@ -33,7 +33,6 @@ class Version < ActiveRecord::Base
validates_inclusion_of :status, :in => VERSION_STATUSES validates_inclusion_of :status, :in => VERSION_STATUSES
validates_inclusion_of :sharing, :in => VERSION_SHARINGS validates_inclusion_of :sharing, :in => VERSION_SHARINGS
named_scope :named, lambda {|arg| { :conditions => ["LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip]}}
named_scope :open, :conditions => {:status => 'open'} named_scope :open, :conditions => {:status => 'open'}
named_scope :visible, lambda {|*args| { :include => :project, named_scope :visible, lambda {|*args| { :include => :project,
:conditions => Project.allowed_to_condition(args.first || User.current, :view_issues) } } :conditions => Project.allowed_to_condition(args.first || User.current, :view_issues) } }
+4 -13
View File
@@ -44,26 +44,17 @@ class Wiki < ActiveRecord::Base
# find the page with the given title # find the page with the given title
def find_page(title, options = {}) def find_page(title, options = {})
@page_found_with_redirect = false
title = start_page if title.blank? title = start_page if title.blank?
title = Wiki.titleize(title) title = Wiki.titleize(title).downcase
page = pages.first(:conditions => ["LOWER(title) = LOWER(?)", title]) page = pages.first(:conditions => ["LOWER(title) LIKE ?", title])
if !page && !(options[:with_redirect] == false) if !page && !(options[:with_redirect] == false)
# search for a redirect # search for a redirect
redirect = redirects.first(:conditions => ["LOWER(title) = LOWER(?)", title]) redirect = redirects.first(:conditions => ["LOWER(title) LIKE ?", title])
if redirect page = find_page(redirect.redirects_to, :with_redirect => false) if redirect
page = find_page(redirect.redirects_to, :with_redirect => false)
@page_found_with_redirect = true
end
end end
page page
end end
# Returns true if the last page was found with a redirect
def page_found_with_redirect?
@page_found_with_redirect
end
# Finds a page by title # Finds a page by title
# The given string can be of one of the forms: "title" or "project:title" # The given string can be of one of the forms: "title" or "project:title"
# Examples: # Examples:
+2 -2
View File
@@ -1,5 +1,5 @@
# RedMine - project management software # redMine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang # Copyright (C) 2006-2007 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
+10 -29
View File
@@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang # Copyright (C) 2006-2009 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@@ -30,9 +30,8 @@ class WikiPage < ActiveRecord::Base
:datetime => :created_on, :datetime => :created_on,
:url => Proc.new {|o| {:controller => 'wiki', :action => 'show', :project_id => o.wiki.project, :id => o.title}} :url => Proc.new {|o| {:controller => 'wiki', :action => 'show', :project_id => o.wiki.project, :id => o.title}}
acts_as_searchable :columns => ['title', "#{WikiContent.table_name}.text"], acts_as_searchable :columns => ['title', 'text'],
:include => [{:wiki => :project}, :content], :include => [{:wiki => :project}, :content],
:permission => :view_wiki_pages,
:project_key => "#{Wiki.table_name}.project_id" :project_key => "#{Wiki.table_name}.project_id"
attr_accessor :redirect_existing_links attr_accessor :redirect_existing_links
@@ -42,12 +41,6 @@ class WikiPage < ActiveRecord::Base
validates_uniqueness_of :title, :scope => :wiki_id, :case_sensitive => false validates_uniqueness_of :title, :scope => :wiki_id, :case_sensitive => false
validates_associated :content validates_associated :content
# eager load information about last updates, without loading text
named_scope :with_updated_on, {
:select => "#{WikiPage.table_name}.*, #{WikiContent.table_name}.updated_on",
:joins => "LEFT JOIN #{WikiContent.table_name} ON #{WikiContent.table_name}.page_id = #{WikiPage.table_name}.id"
}
# Wiki pages that are protected by default # Wiki pages that are protected by default
DEFAULT_PROTECTED_PAGES = %w(sidebar) DEFAULT_PROTECTED_PAGES = %w(sidebar)
@@ -128,18 +121,6 @@ class WikiPage < ActiveRecord::Base
content.text if content content.text if content
end end
def updated_on
unless @updated_on
if time = read_attribute(:updated_on)
# content updated_on was eager loaded with the page
@updated_on = Time.parse(time) rescue nil
else
@updated_on = content && content.updated_on
end
end
@updated_on
end
# Returns true if usr is allowed to edit the page, otherwise false # Returns true if usr is allowed to edit the page, otherwise false
def editable_by?(usr) def editable_by?(usr)
!protected? || usr.allowed_to?(:protect_wiki_pages, wiki.project) !protected? || usr.allowed_to?(:protect_wiki_pages, wiki.project)
@@ -168,13 +149,17 @@ class WikiPage < ActiveRecord::Base
end end
end end
class WikiDiff < Redmine::Helpers::Diff class WikiDiff
attr_reader :content_to, :content_from attr_reader :diff, :words, :content_to, :content_from
def initialize(content_to, content_from) def initialize(content_to, content_from)
@content_to = content_to @content_to = content_to
@content_from = content_from @content_from = content_from
super(content_to.text, content_from.text) @words = content_to.text.split(/(\s+)/)
@words = @words.select {|word| word != ' '}
words_from = content_from.text.split(/(\s+)/)
words_from = words_from.select {|word| word != ' '}
@diff = words_from.diff @words
end end
end end
@@ -212,10 +197,6 @@ class WikiAnnotate
break unless @lines.detect { |line| line[0].nil? } break unless @lines.detect { |line| line[0].nil? }
current = current.previous current = current.previous
end end
@lines.each { |line| @lines.each { |line| line[0] ||= current.version }
line[0] ||= current.version
# if the last known version is > 1 (eg. history was cleared), we don't know the author
line[1] ||= current.author if current.version == 1
}
end end
end end
+2 -2
View File
@@ -89,8 +89,8 @@ class Workflow < ActiveRecord::Base
else else
transaction do transaction do
delete_all :tracker_id => target_tracker.id, :role_id => target_role.id delete_all :tracker_id => target_tracker.id, :role_id => target_role.id
connection.insert "INSERT INTO #{Workflow.table_name} (tracker_id, role_id, old_status_id, new_status_id, author, assignee)" + connection.insert "INSERT INTO #{Workflow.table_name} (tracker_id, role_id, old_status_id, new_status_id)" +
" SELECT #{target_tracker.id}, #{target_role.id}, old_status_id, new_status_id, author, assignee" + " SELECT #{target_tracker.id}, #{target_role.id}, old_status_id, new_status_id" +
" FROM #{Workflow.table_name}" + " FROM #{Workflow.table_name}" +
" WHERE tracker_id = #{source_tracker.id} AND role_id = #{source_role.id}" " WHERE tracker_id = #{source_tracker.id} AND role_id = #{source_role.id}"
end end
+8 -6
View File
@@ -21,14 +21,16 @@
<%= content_tag('p', l(:label_no_data), :class => 'nodata') if @events_by_day.empty? %> <%= content_tag('p', l(:label_no_data), :class => 'nodata') if @events_by_day.empty? %>
<div style="float:left;"> <div style="float:left;">
<%= link_to_content_update('&#171; ' + l(:label_previous), <%= link_to_remote(('&#171; ' + l(:label_previous)),
params.merge(:from => @date_to - @days - 1), {:update => "content", :url => params.merge(:from => @date_to - @days - 1), :method => :get, :complete => 'window.scrollTo(0,0)'},
:title => l(:label_date_from_to, :start => format_date(@date_to - 2*@days), :end => format_date(@date_to - @days - 1))) %> {:href => url_for(params.merge(:from => @date_to - @days - 1)),
:title => l(:label_date_from_to, :start => format_date(@date_to - 2*@days), :end => format_date(@date_to - @days - 1))}) %>
</div> </div>
<div style="float:right;"> <div style="float:right;">
<%= link_to_content_update(l(:label_next) + ' &#187;', <%= link_to_remote((l(:label_next) + ' &#187;'),
params.merge(:from => @date_to + @days - 1), {:update => "content", :url => params.merge(:from => @date_to + @days - 1), :method => :get, :complete => 'window.scrollTo(0,0)'},
:title => l(:label_date_from_to, :start => format_date(@date_to), :end => format_date(@date_to + @days - 1))) unless @date_to >= Date.today %> {:href => url_for(params.merge(:from => @date_to + @days - 1)),
:title => l(:label_date_from_to, :start => format_date(@date_to), :end => format_date(@date_to + @days - 1))}) unless @date_to >= Date.today %>
</div> </div>
&nbsp; &nbsp;
<% other_formats_links do |f| %> <% other_formats_links do |f| %>
+4 -5
View File
@@ -4,11 +4,10 @@
<table class="list"> <table class="list">
<% @checklist.each do |label, result| %> <% @checklist.each do |label, result| %>
<tr class="<%= cycle 'odd', 'even' %>"> <tr class="<%= cycle 'odd', 'even' %>">
<td><%= l(label) %></td> <td><%= l(label) %></td>
<td width="30px"><%= image_tag((result ? 'true.png' : 'exclamation.png'), <td width="30px"><%= image_tag((result ? 'true.png' : 'exclamation.png'), :style => "vertical-align:bottom;") %></td>
:style => "vertical-align:bottom;") %></td> </tr>
</tr>
<% end %> <% end %>
</table> </table>
-1
View File
@@ -11,7 +11,6 @@
<label><%= l(:label_project) %>:</label> <label><%= l(:label_project) %>:</label>
<%= text_field_tag 'name', params[:name], :size => 30 %> <%= text_field_tag 'name', params[:name], :size => 30 %>
<%= submit_tag l(:button_apply), :class => "small", :name => nil %> <%= submit_tag l(:button_apply), :class => "small", :name => nil %>
<%= link_to l(:button_clear), {:controller => 'admin', :action => 'projects'}, :class => 'icon icon-reload' %>
</fieldset> </fieldset>
<% end %> <% end %>
&nbsp; &nbsp;
+4 -4
View File
@@ -15,10 +15,10 @@
<tbody> <tbody>
<% for source in @auth_sources %> <% for source in @auth_sources %>
<tr class="<%= cycle("odd", "even") %>"> <tr class="<%= cycle("odd", "even") %>">
<td><%= link_to(h(source.name), :action => 'edit', :id => source)%></td> <td><%= link_to source.name, :action => 'edit', :id => source%></td>
<td align="center"><%= h source.auth_method_name %></td> <td align="center"><%= source.auth_method_name %></td>
<td align="center"><%= h source.host %></td> <td align="center"><%= source.host %></td>
<td align="center"><%= h source.users.count %></td> <td align="center"><%= source.users.count %></td>
<td class="buttons"> <td class="buttons">
<%= link_to l(:button_test), :action => 'test_connection', :id => source %> <%= link_to l(:button_test), :action => 'test_connection', :id => source %>
<%= link_to l(:button_delete), { :action => 'destroy', :id => source }, <%= link_to l(:button_delete), { :action => 'destroy', :id => source },
-1
View File
@@ -69,5 +69,4 @@
<% content_for :header_tags do %> <% content_for :header_tags do %>
<%= auto_discovery_link_tag(:atom, {:format => 'atom', :key => User.current.rss_key}, :title => "#{@project}: #{@board}") %> <%= auto_discovery_link_tag(:atom, {:format => 'atom', :key => User.current.rss_key}, :title => "#{@project}: #{@board}") %>
<%= stylesheet_link_tag 'scm' %>
<% end %> <% end %>

Some files were not shown because too many files have changed in this diff Show More