Compare commits

..
1 Commits
Author SHA1 Message Date
Jean-Philippe Lang 66866954f6 tagged version 0.4.2
git-svn-id: http://redmine.rubyforge.org/svn/tags/0.4.2@260 e93f8b46-1217-0410-a6f0-8f06a7374b81
2007-02-18 19:59:19 +00:00
803 changed files with 13577 additions and 44756 deletions
-25
View File
@@ -1,25 +0,0 @@
# redMine - project management software
# Copyright (C) 2006-2007 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 SysApi < ActionWebService::API::Base
api_method :projects,
:expects => [],
:returns => [[Project]]
api_method :repository_created,
:expects => [:string, :string],
:returns => [:int]
end
+130 -152
View File
@@ -1,153 +1,131 @@
# 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
# 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
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# 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 AccountController < ApplicationController class AccountController < ApplicationController
layout 'base' layout 'base'
helper :custom_fields helper :custom_fields
include CustomFieldsHelper include CustomFieldsHelper
# prevents login action to be filtered by check_if_login_required application scope filter # prevents login action to be filtered by check_if_login_required application scope filter
skip_before_filter :check_if_login_required, :only => [:login, :lost_password, :register] skip_before_filter :check_if_login_required, :only => [:login, :lost_password, :register]
before_filter :require_login, :only => :logout
# Show user's account
def show # Show user's account
@user = User.find_active(params[:id]) def show
@custom_values = @user.custom_values.find(:all, :include => :custom_field) @user = User.find(params[:id])
@custom_values = @user.custom_values.find(:all, :include => :custom_field)
# show only public projects and private projects that the logged in user is also a member of rescue ActiveRecord::RecordNotFound
@memberships = @user.memberships.select do |membership| render_404
membership.project.is_public? || (User.current.role_for_project(membership.project)) end
end
rescue ActiveRecord::RecordNotFound # Login request and validation
render_404 def login
end if request.get?
# Logout user
# Login request and validation self.logged_in_user = nil
def login else
if request.get? # Authenticate user
# Logout user user = User.try_to_login(params[:login], params[:password])
self.logged_user = nil if user
else self.logged_in_user = user
# Authenticate user redirect_back_or_default :controller => 'my', :action => 'page'
user = User.try_to_login(params[:login], params[:password]) else
if user flash.now[:notice] = l(:notice_account_invalid_creditentials)
self.logged_user = user end
# generate a key and set cookie if autologin end
if params[:autologin] && Setting.autologin? end
token = Token.create(:user => user, :action => 'autologin')
cookies[:autologin] = { :value => token.value, :expires => 1.year.from_now } # Log out current user and redirect to welcome page
end def logout
redirect_back_or_default :controller => 'my', :action => 'page' self.logged_in_user = nil
else redirect_to :controller => 'welcome'
flash.now[:error] = l(:notice_account_invalid_creditentials) end
end
end # Enable user to choose a new password
end def lost_password
redirect_to :controller => 'welcome' and return unless Setting.lost_password?
# Log out current user and redirect to welcome page if params[:token]
def logout @token = Token.find_by_action_and_value("recovery", params[:token])
cookies.delete :autologin redirect_to :controller => 'welcome' and return unless @token and !@token.expired?
Token.delete_all(["user_id = ? AND action = ?", User.current.id, 'autologin']) if User.current.logged? @user = @token.user
self.logged_user = nil if request.post?
redirect_to home_url @user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation]
end if @user.save
@token.destroy
# Enable user to choose a new password flash[:notice] = l(:notice_account_password_updated)
def lost_password redirect_to :action => 'login'
redirect_to(home_url) && return unless Setting.lost_password? return
if params[:token] end
@token = Token.find_by_action_and_value("recovery", params[:token]) end
redirect_to(home_url) && return unless @token and !@token.expired? render :template => "account/password_recovery"
@user = @token.user return
if request.post? else
@user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation] if request.post?
if @user.save user = User.find_by_mail(params[:mail])
@token.destroy # user not found in db
flash[:notice] = l(:notice_account_password_updated) flash.now[:notice] = l(:notice_account_unknown_email) and return unless user
redirect_to :action => 'login' # user uses an external authentification
return flash.now[:notice] = l(:notice_can_t_change_password) and return if user.auth_source_id
end # create a new token for password recovery
end token = Token.new(:user => user, :action => "recovery")
render :template => "account/password_recovery" if token.save
return Mailer.deliver_lost_password(token)
else flash[:notice] = l(:notice_account_lost_email_sent)
if request.post? redirect_to :action => 'login'
user = User.find_by_mail(params[:mail]) return
# user not found in db end
flash.now[:error] = l(:notice_account_unknown_email) and return unless user end
# user uses an external authentification end
flash.now[:error] = l(:notice_can_t_change_password) and return if user.auth_source_id end
# create a new token for password recovery
token = Token.new(:user => user, :action => "recovery") # User self-registration
if token.save def register
Mailer.deliver_lost_password(token) redirect_to :controller => 'welcome' and return unless Setting.self_registration?
flash[:notice] = l(:notice_account_lost_email_sent) if params[:token]
redirect_to :action => 'login' token = Token.find_by_action_and_value("register", params[:token])
return redirect_to :controller => 'welcome' and return unless token and !token.expired?
end user = token.user
end redirect_to :controller => 'welcome' and return unless user.status == User::STATUS_REGISTERED
end user.status = User::STATUS_ACTIVE
end if user.save
token.destroy
# User self-registration flash[:notice] = l(:notice_account_activated)
def register redirect_to :action => 'login'
redirect_to(home_url) && return unless Setting.self_registration? return
if params[:token] end
token = Token.find_by_action_and_value("register", params[:token]) else
redirect_to(home_url) && return unless token and !token.expired? if request.get?
user = token.user @user = User.new(:language => Setting.default_language)
redirect_to(home_url) && return unless user.status == User::STATUS_REGISTERED @custom_values = UserCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x, :customized => @user) }
user.status = User::STATUS_ACTIVE else
if user.save @user = User.new(params[:user])
token.destroy @user.admin = false
flash[:notice] = l(:notice_account_activated) @user.login = params[:user][:login]
redirect_to :action => 'login' @user.status = User::STATUS_REGISTERED
return @user.password, @user.password_confirmation = params[:password], params[:password_confirmation]
end @custom_values = UserCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x, :customized => @user, :value => params["custom_fields"][x.id.to_s]) }
else @user.custom_values = @custom_values
if request.get? token = Token.new(:user => @user, :action => "register")
@user = User.new(:language => Setting.default_language) if @user.save and token.save
@custom_values = UserCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x, :customized => @user) } Mailer.deliver_register(token)
else flash[:notice] = l(:notice_account_register_done)
@user = User.new(params[:user]) redirect_to :controller => 'welcome' and return
@user.admin = false end
@user.login = params[:user][:login] end
@user.status = User::STATUS_REGISTERED end
@user.password, @user.password_confirmation = params[:password], params[:password_confirmation] end
@custom_values = UserCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x, :customized => @user, :value => params["custom_fields"][x.id.to_s]) }
@user.custom_values = @custom_values
token = Token.new(:user => @user, :action => "register")
if @user.save and token.save
Mailer.deliver_register(token)
flash[:notice] = l(:notice_account_register_done)
redirect_to :controller => 'account', :action => 'login'
end
end
end
end
private
def logged_user=(user)
if user && user.is_a?(User)
User.current = user
session[:user_id] = user.id
else
User.current = User.anonymous
session[:user_id] = nil
end
end
end end
+54 -80
View File
@@ -1,81 +1,55 @@
# redMine - project management software # redMine - project management software
# Copyright (C) 2006 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# 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 AdminController < ApplicationController class AdminController < ApplicationController
layout 'base' layout 'base'
before_filter :require_admin before_filter :require_admin
helper :sort helper :sort
include SortHelper include SortHelper
def index def index
end end
def projects def projects
sort_init 'name', 'asc' sort_init 'name', 'asc'
sort_update sort_update
@project_count = Project.count
@status = params[:status] ? params[:status].to_i : 0 @project_pages = Paginator.new self, @project_count,
conditions = nil 15,
conditions = ["status=?", @status] unless @status == 0 params['page']
@projects = Project.find :all, :order => sort_clause,
@project_count = Project.count(:conditions => conditions) :limit => @project_pages.items_per_page,
@project_pages = Paginator.new self, @project_count, :offset => @project_pages.current.offset
25,
params['page'] render :action => "projects", :layout => false if request.xhr?
@projects = Project.find :all, :order => sort_clause, end
:conditions => conditions,
:limit => @project_pages.items_per_page, def mail_options
:offset => @project_pages.current.offset @actions = Permission.find(:all, :conditions => ["mail_option=?", true]) || []
if request.post?
render :action => "projects", :layout => false if request.xhr? @actions.each { |a|
end a.mail_enabled = (params[:action_ids] || []).include? a.id.to_s
a.save
def mail_options }
@notifiables = %w(issue_added issue_updated news_added document_added file_added message_posted) flash.now[:notice] = l(:notice_successful_update)
if request.post? end
Setting.notified_events = (params[:notified_events] || []) end
Setting.emails_footer = params[:emails_footer] if params[:emails_footer]
flash[:notice] = l(:notice_successful_update) def info
redirect_to :controller => 'admin', :action => 'mail_options' end
end
end
def test_email
raise_delivery_errors = ActionMailer::Base.raise_delivery_errors
# Force ActionMailer to raise delivery errors so we can catch it
ActionMailer::Base.raise_delivery_errors = true
begin
@test = Mailer.deliver_test(User.current)
flash[:notice] = l(:notice_email_sent, User.current.mail)
rescue Exception => e
flash[:error] = l(:notice_email_error, e.message)
end
ActionMailer::Base.raise_delivery_errors = raise_delivery_errors
redirect_to :action => 'mail_options'
end
def info
@db_adapter_name = ActiveRecord::Base.connection.adapter_name
@flags = {
:default_admin_changed => User.find(:first, :conditions => ["login=? and hashed_password=?", 'admin', User.hash_password('admin')]).nil?,
:file_repository_writable => File.writable?(Attachment.storage_path),
:rmagick_available => Object.const_defined?(:Magick)
}
@plugins = Redmine::Plugin.registered_plugins
end
end end
+117 -155
View File
@@ -1,111 +1,94 @@
# 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
# 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
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# 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 ApplicationController < ActionController::Base class ApplicationController < ActionController::Base
before_filter :user_setup, :check_if_login_required, :set_localization before_filter :check_if_login_required, :set_localization
filter_parameter_logging :password
def logged_in_user=(user)
REDMINE_SUPPORTED_SCM.each do |scm| @logged_in_user = user
require_dependency "repository/#{scm.underscore}" session[:user_id] = (user ? user.id : nil)
end end
def logged_in_user def logged_in_user
User.current.logged? ? User.current : nil if session[:user_id]
end @logged_in_user ||= User.find(session[:user_id])
else
def current_role nil
@current_role ||= User.current.role_for_project(@project) end
end end
def user_setup # check if login is globally required to access the application
Setting.check_cache def check_if_login_required
if session[:user_id] require_login if Setting.login_required?
# existing session end
User.current = User.find(session[:user_id])
elsif cookies[:autologin] && Setting.autologin? def set_localization
# auto-login feature lang = begin
User.current = User.find_by_autologin_key(cookies[:autologin]) if self.logged_in_user and self.logged_in_user.language and !self.logged_in_user.language.empty? and GLoc.valid_languages.include? self.logged_in_user.language.to_sym
elsif params[:key] && accept_key_auth_actions.include?(params[:action]) self.logged_in_user.language
# RSS key authentication elsif request.env['HTTP_ACCEPT_LANGUAGE']
User.current = User.find_by_rss_key(params[:key]) accept_lang = parse_qvalues(request.env['HTTP_ACCEPT_LANGUAGE']).first.split('-').first
else if accept_lang and !accept_lang.empty? and GLoc.valid_languages.include? accept_lang.to_sym
User.current = User.anonymous accept_lang
end end
end end
rescue
# check if login is globally required to access the application nil
def check_if_login_required end || Setting.default_language
# no check needed if user is already logged in set_language_if_valid(lang)
return true if User.current.logged? end
require_login if Setting.login_required?
end def require_login
unless self.logged_in_user
def set_localization store_location
lang = begin redirect_to :controller => "account", :action => "login"
if !User.current.language.blank? and GLoc.valid_languages.include? User.current.language.to_sym return false
User.current.language end
elsif request.env['HTTP_ACCEPT_LANGUAGE'] true
accept_lang = parse_qvalues(request.env['HTTP_ACCEPT_LANGUAGE']).first.split('-').first end
if accept_lang and !accept_lang.empty? and GLoc.valid_languages.include? accept_lang.to_sym
accept_lang def require_admin
end return unless require_login
end unless self.logged_in_user.admin?
rescue render :nothing => true, :status => 403
nil return false
end || Setting.default_language end
set_language_if_valid(lang) true
end end
def require_login # authorizes the user for the requested action.
if !User.current.logged? def authorize(ctrl = params[:controller], action = params[:action])
store_location # check if action is allowed on public projects
redirect_to :controller => "account", :action => "login" if @project.is_public? and Permission.allowed_to_public "%s/%s" % [ ctrl, action ]
return false return true
end end
true # if action is not public, force login
end return unless require_login
# admin is always authorized
def require_admin return true if self.logged_in_user.admin?
return unless require_login # if not admin, check membership permission
if !User.current.admin? @user_membership ||= Member.find(:first, :conditions => ["user_id=? and project_id=?", self.logged_in_user.id, @project.id])
render_403 if @user_membership and Permission.allowed_to_role( "%s/%s" % [ ctrl, action ], @user_membership.role_id )
return false return true
end end
true render :nothing => true, :status => 403
end false
end
# Authorize the user for the requested action
def authorize(ctrl = params[:controller], action = params[:action])
allowed = User.current.allowed_to?({:controller => ctrl, :action => action}, @project)
allowed ? true : (User.current.logged? ? render_403 : require_login)
end
# make sure that the user is a member of the project (or admin) if project is private
# used as a before_filter for actions that do not require any particular permission on the project
def check_project_privacy
unless @project.active?
@project = nil
render_404
return false
end
return true if @project.is_public? || User.current.member_of?(@project) || User.current.admin?
User.current.logged? ? render_403 : require_login
end
# store current uri in session. # store current uri in session.
# return to this location by calling redirect_back_or_default # return to this location by calling redirect_back_or_default
def store_location def store_location
@@ -120,51 +103,30 @@ class ApplicationController < ActionController::Base
redirect_to session[:return_to_params] redirect_to session[:return_to_params]
session[:return_to_params] = nil session[:return_to_params] = nil
end end
end
def render_404
@html_title = "404"
render :template => "common/404", :layout => true, :status => 404
return false
end end
def render_403 # qvalues http header parser
@project = nil # code taken from webrick
render :template => "common/403", :layout => !request.xhr?, :status => 403 def parse_qvalues(value)
return false tmp = []
end if value
parts = value.split(/,\s*/)
def render_404 parts.each {|part|
render :template => "common/404", :layout => !request.xhr?, :status => 404 if m = %r{^([^\s,]+?)(?:;\s*q=(\d+(?:\.\d+)?))?$}.match(part)
return false val = m[1]
end q = (m[2] or 1).to_f
tmp.push([val, q])
def render_feed(items, options={}) end
@items = items || [] }
@items.sort! {|x,y| y.event_datetime <=> x.event_datetime } tmp = tmp.sort_by{|val, q| -q}
@title = options[:title] || Setting.app_title tmp.collect!{|val, q| val}
render :template => "common/feed.atom.rxml", :layout => false, :content_type => 'application/atom+xml' end
end return tmp
end
def self.accept_key_auth(*actions) end
actions = actions.flatten.map(&:to_s)
write_inheritable_attribute('accept_key_auth_actions', actions)
end
def accept_key_auth_actions
self.class.read_inheritable_attribute('accept_key_auth_actions') || []
end
# qvalues http header parser
# code taken from webrick
def parse_qvalues(value)
tmp = []
if value
parts = value.split(/,\s*/)
parts.each {|part|
if m = %r{^([^\s,]+?)(?:;\s*q=(\d+(?:\.\d+)?))?$}.match(part)
val = m[1]
q = (m[2] or 1).to_f
tmp.push([val, q])
end
}
tmp = tmp.sort_by{|val, q| -q}
tmp.collect!{|val, q| val}
end
return tmp
end
end
-39
View File
@@ -1,39 +0,0 @@
# redMine - project management software
# Copyright (C) 2006-2007 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 AttachmentsController < ApplicationController
layout 'base'
before_filter :find_project, :check_project_privacy
def download
# images are sent inline
send_file @attachment.diskfile, :filename => @attachment.filename,
:type => @attachment.content_type,
:disposition => (@attachment.image? ? 'inline' : 'attachment')
rescue
# in case the disk file was deleted
render_404
end
private
def find_project
@attachment = Attachment.find(params[:id])
@project = @attachment.project
rescue
render_404
end
end
+2 -2
View File
@@ -65,10 +65,10 @@ class AuthSourcesController < ApplicationController
@auth_method = AuthSource.find(params[:id]) @auth_method = AuthSource.find(params[:id])
begin begin
@auth_method.test_connection @auth_method.test_connection
flash[:notice] = l(:notice_successful_connection)
rescue => text rescue => text
flash[:error] = "Unable to connect (#{text})" flash[:notice] = text
end end
flash[:notice] ||= l(:notice_successful_connection)
redirect_to :action => 'list' redirect_to :action => 'list'
end end
-87
View File
@@ -1,87 +0,0 @@
# redMine - project management software
# Copyright (C) 2006-2007 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 BoardsController < ApplicationController
layout 'base'
before_filter :find_project, :authorize
helper :messages
include MessagesHelper
helper :sort
include SortHelper
helper :watchers
include WatchersHelper
def index
@boards = @project.boards
# show the board if there is only one
if @boards.size == 1
@board = @boards.first
show
render :action => 'show'
end
end
def show
sort_init "#{Message.table_name}.updated_on", "desc"
sort_update
@topic_count = @board.topics.count
@topic_pages = Paginator.new self, @topic_count, 25, params['page']
@topics = @board.topics.find :all, :order => sort_clause,
:include => [:author, {:last_reply => :author}],
:limit => @topic_pages.items_per_page,
:offset => @topic_pages.current.offset
render :action => 'show', :layout => false if request.xhr?
end
verify :method => :post, :only => [ :destroy ], :redirect_to => { :action => :index }
def new
@board = Board.new(params[:board])
@board.project = @project
if request.post? && @board.save
flash[:notice] = l(:notice_successful_create)
redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'boards'
end
end
def edit
if request.post? && @board.update_attributes(params[:board])
case params[:position]
when 'highest'; @board.move_to_top
when 'higher'; @board.move_higher
when 'lower'; @board.move_lower
when 'lowest'; @board.move_to_bottom
end if params[:position]
redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'boards'
end
end
def destroy
@board.destroy
redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'boards'
end
private
def find_project
@project = Project.find(params[:project_id])
@board = @project.boards.find(params[:id]) if params[:id]
rescue ActiveRecord::RecordNotFound
render_404
end
end
+59 -59
View File
@@ -1,23 +1,23 @@
# redMine - project management software # redMine - project management software
# Copyright (C) 2006 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# 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 CustomFieldsController < ApplicationController class CustomFieldsController < ApplicationController
layout 'base' layout 'base'
before_filter :require_admin before_filter :require_admin
def index def index
list list
@@ -25,48 +25,48 @@ class CustomFieldsController < ApplicationController
end end
def list def list
@custom_fields_by_type = CustomField.find(:all).group_by {|f| f.class.name } @custom_fields_by_type = CustomField.find(:all).group_by {|f| f.type.to_s }
@tab = params[:tab] || 'IssueCustomField' @tab = params[:tab] || 'IssueCustomField'
render :action => "list", :layout => false if request.xhr? render :action => "list", :layout => false if request.xhr?
end end
def new def new
case params[:type] case params[:type]
when "IssueCustomField" when "IssueCustomField"
@custom_field = IssueCustomField.new(params[:custom_field]) @custom_field = IssueCustomField.new(params[:custom_field])
@custom_field.trackers = Tracker.find(params[:tracker_ids]) if params[:tracker_ids] @custom_field.trackers = Tracker.find(params[:tracker_ids]) if params[:tracker_ids]
when "UserCustomField" when "UserCustomField"
@custom_field = UserCustomField.new(params[:custom_field]) @custom_field = UserCustomField.new(params[:custom_field])
when "ProjectCustomField" when "ProjectCustomField"
@custom_field = ProjectCustomField.new(params[:custom_field]) @custom_field = ProjectCustomField.new(params[:custom_field])
else else
redirect_to :action => 'list' redirect_to :action => 'list'
return return
end end
if request.post? and @custom_field.save if request.post? and @custom_field.save
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
redirect_to :action => 'list', :tab => @custom_field.type redirect_to :action => 'list', :tab => @custom_field.type
end end
@trackers = Tracker.find(:all, :order => 'position') @trackers = Tracker.find(:all, :order => 'position')
end end
def edit def edit
@custom_field = CustomField.find(params[:id]) @custom_field = CustomField.find(params[:id])
if request.post? and @custom_field.update_attributes(params[:custom_field]) if request.post? and @custom_field.update_attributes(params[:custom_field])
if @custom_field.is_a? IssueCustomField if @custom_field.is_a? IssueCustomField
@custom_field.trackers = params[:tracker_ids] ? Tracker.find(params[:tracker_ids]) : [] @custom_field.trackers = params[:tracker_ids] ? Tracker.find(params[:tracker_ids]) : []
end end
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'list', :tab => @custom_field.type redirect_to :action => 'list', :tab => @custom_field.type
end end
@trackers = Tracker.find(:all, :order => 'position') @trackers = Tracker.find(:all, :order => 'position')
end end
def destroy def destroy
@custom_field = CustomField.find(params[:id]).destroy @custom_field = CustomField.find(params[:id]).destroy
redirect_to :action => 'list', :tab => @custom_field.type redirect_to :action => 'list', :tab => @custom_field.type
rescue rescue
flash[:error] = "Unable to delete custom field" flash[:notice] = "Unable to delete custom field"
redirect_to :action => 'list' redirect_to :action => 'list'
end end
end end
+55 -55
View File
@@ -1,71 +1,71 @@
# 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
# 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
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# 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 DocumentsController < ApplicationController
layout 'base'
before_filter :find_project, :authorize
class DocumentsController < ApplicationController def show
layout 'base'
before_filter :find_project, :authorize
def show
@attachments = @document.attachments.find(:all, :order => "created_on DESC") @attachments = @document.attachments.find(:all, :order => "created_on DESC")
end end
def edit def edit
@categories = Enumeration::get_values('DCAT') @categories = Enumeration::get_values('DCAT')
if request.post? and @document.update_attributes(params[:document]) if request.post? and @document.update_attributes(params[:document])
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'show', :id => @document redirect_to :action => 'show', :id => @document
end end
end end
def destroy def destroy
@document.destroy @document.destroy
redirect_to :controller => 'projects', :action => 'list_documents', :id => @project redirect_to :controller => 'projects', :action => 'list_documents', :id => @project
end
def download
@attachment = @document.attachments.find(params[:attachment_id])
@attachment.increment_download
send_file @attachment.diskfile, :filename => @attachment.filename
rescue
render_404
end
def add_attachment
# Save the attachments
@attachments = []
params[:attachments].each { |file|
next unless file.size > 0
a = Attachment.create(:container => @document, :file => file, :author => logged_in_user)
@attachments << a unless a.new_record?
} if params[:attachments] and params[:attachments].is_a? Array
Mailer.deliver_attachments_add(@attachments) if !@attachments.empty? and Permission.find_by_controller_and_action(params[:controller], params[:action]).mail_enabled?
redirect_to :action => 'show', :id => @document
end
def destroy_attachment
@document.attachments.find(params[:attachment_id]).destroy
redirect_to :action => 'show', :id => @document
end end
def download private
@attachment = @document.attachments.find(params[:attachment_id]) def find_project
@attachment.increment_download @document = Document.find(params[:id])
send_file @attachment.diskfile, :filename => @attachment.filename @project = @document.project
rescue rescue ActiveRecord::RecordNotFound
render_404 render_404
end
def add_attachment
# Save the attachments
@attachments = []
params[:attachments].each { |file|
next unless file.size > 0
a = Attachment.create(:container => @document, :file => file, :author => logged_in_user)
@attachments << a unless a.new_record?
} if params[:attachments] and params[:attachments].is_a? Array
Mailer.deliver_attachments_added(@attachments) if !@attachments.empty? && Setting.notified_events.include?('document_added')
redirect_to :action => 'show', :id => @document
end
def destroy_attachment
@document.attachments.find(params[:attachment_id]).destroy
redirect_to :action => 'show', :id => @document
end
private
def find_project
@document = Document.find(params[:id])
@project = @document.project
rescue ActiveRecord::RecordNotFound
render_404
end end
end end
+24 -39
View File
@@ -1,23 +1,23 @@
# redMine - project management software # redMine - project management software
# Copyright (C) 2006 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# 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 EnumerationsController < ApplicationController class EnumerationsController < ApplicationController
layout 'base' layout 'base'
before_filter :require_admin before_filter :require_admin
def index def index
list list
@@ -59,27 +59,12 @@ class EnumerationsController < ApplicationController
end end
end end
def move
@enumeration = Enumeration.find(params[:id])
case params[:position]
when 'highest'
@enumeration.move_to_top
when 'higher'
@enumeration.move_higher
when 'lower'
@enumeration.move_lower
when 'lowest'
@enumeration.move_to_bottom
end if params[:position]
redirect_to :action => 'index'
end
def destroy def destroy
Enumeration.find(params[:id]).destroy Enumeration.find(params[:id]).destroy
flash[:notice] = l(:notice_successful_delete) flash[:notice] = l(:notice_successful_delete)
redirect_to :action => 'list' redirect_to :action => 'list'
rescue rescue
flash[:error] = "Unable to delete enumeration" flash[:notice] = "Unable to delete enumeration"
redirect_to :action => 'list' redirect_to :action => 'list'
end end
end end
+3 -76
View File
@@ -1,5 +1,5 @@
# redMine - project management software # redMine - project management software
# Copyright (C) 2006-2007 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,83 +16,10 @@
# 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 FeedsController < ApplicationController class FeedsController < ApplicationController
before_filter :find_scope
session :off session :off
helper :issues
include IssuesHelper
helper :custom_fields
include CustomFieldsHelper
# news feeds
def news def news
News.with_scope(:find => @find_options) do @news = News.find :all, :order => 'news.created_on DESC', :limit => 10, :include => [ :author, :project ]
@news = News.find :all, :order => "#{News.table_name}.created_on DESC", :include => [ :author, :project ]
end
headers["Content-Type"] = "application/rss+xml" headers["Content-Type"] = "application/rss+xml"
render :action => 'news_atom' if 'atom' == params[:format]
end
# issue feeds
def issues
if @project && params[:query_id]
query = Query.find(params[:query_id])
query.executed_by = @user
# ignore query if it's not valid
query = nil unless query.valid?
# override with query conditions
@find_options[:conditions] = query.statement if query.valid? and @project == query.project
end
Issue.with_scope(:find => @find_options) do
@issues = Issue.find :all, :include => [:project, :author, :tracker, :status],
:order => "#{Issue.table_name}.created_on DESC"
end
@title = (@project ? @project.name : Setting.app_title) + ": " + (query ? query.name : l(:label_reported_issues))
headers["Content-Type"] = "application/rss+xml"
render :action => 'issues_atom' if 'atom' == params[:format]
end
# issue changes feeds
def history
if @project && params[:query_id]
query = Query.find(params[:query_id])
query.executed_by = @user
# ignore query if it's not valid
query = nil unless query.valid?
# override with query conditions
@find_options[:conditions] = query.statement if query.valid? and @project == query.project
end
Journal.with_scope(:find => @find_options) do
@journals = Journal.find :all, :include => [ :details, :user, {:issue => [:project, :author, :tracker, :status]} ],
:order => "#{Journal.table_name}.created_on DESC"
end
@title = (@project ? @project.name : Setting.app_title) + ": " + (query ? query.name : l(:label_changes_details))
headers["Content-Type"] = "application/rss+xml"
render :action => 'history_atom' if 'atom' == params[:format]
end
private
# override for feeds specific authentication
def check_if_login_required
@user = User.find_by_rss_key(params[:key])
render(:nothing => true, :status => 403) and return false if !@user && Setting.login_required?
end
def find_scope
if params[:project_id]
# project feed
# check if project is public or if the user is a member
@project = Project.find(params[:project_id])
render(:nothing => true, :status => 403) and return false unless @project.is_public? || (@user && @user.role_for_project(@project))
scope = ["#{Project.table_name}.id=?", params[:project_id].to_i]
else
# global feed
scope = ["#{Project.table_name}.is_public=?", true]
end
@find_options = {:conditions => scope, :limit => Setting.feeds_limit.to_i}
return true
end end
end end
+47
View File
@@ -0,0 +1,47 @@
# redMine - project management software
# Copyright (C) 2006 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 HelpController < ApplicationController
skip_before_filter :check_if_login_required
before_filter :load_help_config
# displays help page for the requested controller/action
def index
# select help page to display
if params[:ctrl] and @help_config['pages'][params[:ctrl]]
if params[:page] and @help_config['pages'][params[:ctrl]][params[:page]]
template = @help_config['pages'][params[:ctrl]][params[:page]]
else
template = @help_config['pages'][params[:ctrl]]['index']
end
end
# choose language according to available help translations
lang = (@help_config['langs'].include? current_language.to_s) ? current_language.to_s : @help_config['langs'].first
if template
redirect_to "/manual/#{lang}/#{template}"
else
redirect_to "/manual/#{lang}/index.html"
end
end
private
def load_help_config
@help_config = YAML::load(File.open("#{RAILS_ROOT}/config/help.yml"))
end
end
+33 -41
View File
@@ -1,26 +1,24 @@
# redMine - project management software # redMine - project management software
# Copyright (C) 2006 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# 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 IssueCategoriesController < ApplicationController class IssueCategoriesController < ApplicationController
layout 'base' layout 'base'
before_filter :find_project, :authorize before_filter :find_project, :authorize
verify :method => :post, :only => :destroy
def edit def edit
if request.post? and @category.update_attributes(params[:category]) if request.post? and @category.update_attributes(params[:category])
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
@@ -29,24 +27,18 @@ class IssueCategoriesController < ApplicationController
end end
def destroy def destroy
@issue_count = @category.issues.size @category.destroy
if @issue_count == 0 redirect_to :controller => 'projects', :action => 'settings', :tab => 'categories', :id => @project
# No issue assigned to this category rescue
@category.destroy flash[:notice] = "Categorie can't be deleted"
redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'categories' redirect_to :controller => 'projects', :action => 'settings', :tab => 'categories', :id => @project
elsif params[:todo] end
reassign_to = @project.issue_categories.find_by_id(params[:reassign_to_id]) if params[:todo] == 'reassign'
@category.destroy(reassign_to)
redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'categories'
end
@categories = @project.issue_categories - [@category]
end
private private
def find_project def find_project
@category = IssueCategory.find(params[:id]) @category = IssueCategory.find(params[:id])
@project = @category.project @project = @category.project
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
render_404 render_404
end end
end end
@@ -1,59 +0,0 @@
# redMine - project management software
# Copyright (C) 2006-2007 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 IssueRelationsController < ApplicationController
layout 'base'
before_filter :find_project, :authorize
def new
@relation = IssueRelation.new(params[:relation])
@relation.issue_from = @issue
@relation.save if request.post?
respond_to do |format|
format.html { redirect_to :controller => 'issues', :action => 'show', :id => @issue }
format.js do
render :update do |page|
page.replace_html "relations", :partial => 'issues/relations'
if @relation.errors.empty?
page << "$('relation_delay').value = ''"
page << "$('relation_issue_to_id').value = ''"
end
end
end
end
end
def destroy
relation = IssueRelation.find(params[:id])
if request.post? && @issue.relations.include?(relation)
relation.destroy
@issue.reload
end
respond_to do |format|
format.html { redirect_to :controller => 'issues', :action => 'show', :id => @issue }
format.js { render(:update) {|page| page.replace_html "relations", :partial => 'issues/relations'} }
end
end
private
def find_project
@issue = Issue.find(params[:issue_id])
@project = @issue.project
rescue ActiveRecord::RecordNotFound
render_404
end
end
+42 -42
View File
@@ -1,34 +1,34 @@
# redMine - project management software # redMine - project management software
# Copyright (C) 2006 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# 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 IssueStatusesController < ApplicationController class IssueStatusesController < ApplicationController
layout 'base' layout 'base'
before_filter :require_admin before_filter :require_admin
verify :method => :post, :only => [ :destroy, :create, :update, :move ], verify :method => :post, :only => [ :destroy, :create, :update, :move ],
:redirect_to => { :action => :list } :redirect_to => { :action => :list }
def index def index
list list
render :action => 'list' unless request.xhr? render :action => 'list' unless request.xhr?
end end
def list def list
@issue_status_pages, @issue_statuses = paginate :issue_statuses, :per_page => 25, :order => "position" @issue_status_pages, @issue_statuses = paginate :issue_statuses, :per_page => 25, :order => "position"
render :action => "list", :layout => false if request.xhr? render :action => "list", :layout => false if request.xhr?
end end
@@ -58,28 +58,28 @@ class IssueStatusesController < ApplicationController
else else
render :action => 'edit' render :action => 'edit'
end end
end end
def move def move
@issue_status = IssueStatus.find(params[:id]) @issue_status = IssueStatus.find(params[:id])
case params[:position] case params[:position]
when 'highest' when 'highest'
@issue_status.move_to_top @issue_status.move_to_top
when 'higher' when 'higher'
@issue_status.move_higher @issue_status.move_higher
when 'lower' when 'lower'
@issue_status.move_lower @issue_status.move_lower
when 'lowest' when 'lowest'
@issue_status.move_to_bottom @issue_status.move_to_bottom
end if params[:position] end if params[:position]
redirect_to :action => 'list' redirect_to :action => 'list'
end end
def destroy def destroy
IssueStatus.find(params[:id]).destroy IssueStatus.find(params[:id]).destroy
redirect_to :action => 'list' redirect_to :action => 'list'
rescue rescue
flash[:error] = "Unable to delete issue status" flash[:notice] = "Unable to delete issue status"
redirect_to :action => 'list' redirect_to :action => 'list'
end end
end end
+137 -205
View File
@@ -1,217 +1,149 @@
# 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
# 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
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# 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 IssuesController < ApplicationController
layout 'base', :except => :export_pdf
before_filter :find_project, :authorize
helper :custom_fields
include CustomFieldsHelper
helper :ifpdf
include IfpdfHelper
class IssuesController < ApplicationController
layout 'base', :except => :export_pdf
before_filter :find_project, :authorize, :except => [:index, :preview]
accept_key_auth :index
cache_sweeper :issue_sweeper, :only => [ :edit, :change_status, :destroy ]
helper :projects
include ProjectsHelper
helper :custom_fields
include CustomFieldsHelper
helper :ifpdf
include IfpdfHelper
helper :issue_relations
include IssueRelationsHelper
helper :watchers
include WatchersHelper
helper :attachments
include AttachmentsHelper
helper :queries
helper :sort
include SortHelper
def index
sort_init "#{Issue.table_name}.id", "desc"
sort_update
retrieve_query
if @query.valid?
@issue_count = Issue.count(:include => [:status, :project], :conditions => @query.statement)
@issue_pages = Paginator.new self, @issue_count, 25, params['page']
@issues = Issue.find :all, :order => sort_clause,
:include => [ :assigned_to, :status, :tracker, :project, :priority, :category ],
:conditions => @query.statement,
:limit => @issue_pages.items_per_page,
:offset => @issue_pages.current.offset
end
respond_to do |format|
format.html { render :layout => false if request.xhr? }
format.atom { render_feed(@issues, :title => l(:label_issue_plural)) }
end
end
def show def show
@custom_values = @issue.custom_values.find(:all, :include => :custom_field) @status_options = @issue.status.workflows.find(:all, :order => 'position', :include => :new_status, :conditions => ["role_id=? and tracker_id=?", self.logged_in_user.role_for_project(@project.id), @issue.tracker.id]).collect{ |w| w.new_status } if self.logged_in_user
@journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC") @custom_values = @issue.custom_values.find(:all, :include => :custom_field)
@journals_count = @issue.journals.count
if params[:format]=='pdf' @journals = @issue.journals.find(:all, :include => [:user, :details], :limit => 15, :order => "journals.created_on desc")
@options_for_rfpdf ||= {} end
@options_for_rfpdf[:file_name] = "#{@project.identifier}-#{@issue.id}.pdf"
render :template => 'issues/show.rfpdf', :layout => false def history
else @journals = @issue.journals.find(:all, :include => [:user, :details], :order => "journals.created_on desc")
@status_options = @issue.status.find_new_statuses_allowed_to(logged_in_user.role_for_project(@project), @issue.tracker) if logged_in_user @journals_count = @journals.length
render :template => 'issues/show.rhtml' end
end
def export_pdf
@custom_values = @issue.custom_values.find(:all, :include => :custom_field)
@options_for_rfpdf ||= {}
@options_for_rfpdf[:file_name] = "#{@project.name}_#{@issue.long_id}.pdf"
end end
def edit def edit
@priorities = Enumeration::get_values('IPRI') @priorities = Enumeration::get_values('IPRI')
if request.get? if request.get?
@custom_values = @project.custom_fields_for_issues(@issue.tracker).collect { |x| @issue.custom_values.find_by_custom_field_id(x.id) || CustomValue.new(:custom_field => x, :customized => @issue) } @custom_values = @project.custom_fields_for_issues(@issue.tracker).collect { |x| @issue.custom_values.find_by_custom_field_id(x.id) || CustomValue.new(:custom_field => x, :customized => @issue) }
else else
begin begin
@issue.init_journal(self.logged_in_user) @issue.init_journal(self.logged_in_user)
# Retrieve custom fields and values # Retrieve custom fields and values
if params["custom_fields"] @custom_values = @project.custom_fields_for_issues(@issue.tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue, :value => params["custom_fields"][x.id.to_s]) }
@custom_values = @project.custom_fields_for_issues(@issue.tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue, :value => params["custom_fields"][x.id.to_s]) } @issue.custom_values = @custom_values
@issue.custom_values = @custom_values @issue.attributes = params[:issue]
end
@issue.attributes = params[:issue]
if @issue.save if @issue.save
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to(params[:back_to] || {:action => 'show', :id => @issue})
end
rescue ActiveRecord::StaleObjectError
# Optimistic locking exception
flash[:error] = l(:notice_locking_conflict)
end
end
end
def add_note
journal = @issue.init_journal(User.current, params[:notes])
params[:attachments].each { |file|
next unless file.size > 0
a = Attachment.create(:container => @issue, :file => file, :author => logged_in_user)
journal.details << JournalDetail.new(:property => 'attachment',
:prop_key => a.id,
:value => a.filename) unless a.new_record?
} if params[:attachments] and params[:attachments].is_a? Array
if journal.save
flash[:notice] = l(:notice_successful_update)
Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated')
redirect_to :action => 'show', :id => @issue
return
end
show
end
def change_status
@status_options = @issue.status.find_new_statuses_allowed_to(logged_in_user.role_for_project(@project), @issue.tracker) if logged_in_user
@new_status = IssueStatus.find(params[:new_status_id])
if params[:confirm]
begin
journal = @issue.init_journal(self.logged_in_user, params[:notes])
@issue.status = @new_status
if @issue.update_attributes(params[:issue])
# Save attachments
params[:attachments].each { |file|
next unless file.size > 0
a = Attachment.create(:container => @issue, :file => file, :author => logged_in_user)
journal.details << JournalDetail.new(:property => 'attachment',
:prop_key => a.id,
:value => a.filename) unless a.new_record?
} if params[:attachments] and params[:attachments].is_a? Array
# Log time
if current_role.allowed_to?(:log_time)
@time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => logged_in_user, :spent_on => Date.today)
@time_entry.attributes = params[:time_entry]
@time_entry.save
end
flash[:notice] = l(:notice_successful_update)
Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated')
redirect_to :action => 'show', :id => @issue redirect_to :action => 'show', :id => @issue
end end
rescue ActiveRecord::StaleObjectError rescue ActiveRecord::StaleObjectError
# Optimistic locking exception # Optimistic locking exception
flash[:error] = l(:notice_locking_conflict) flash[:notice] = l(:notice_locking_conflict)
end end
end end
@assignable_to = @project.members.find(:all, :include => :user).collect{ |m| m.user } end
@activities = Enumeration::get_values('ACTI')
def add_note
unless params[:notes].empty?
journal = @issue.init_journal(self.logged_in_user, params[:notes])
#@history = @issue.histories.build(params[:history])
#@history.author_id = self.logged_in_user.id if self.logged_in_user
#@history.status = @issue.status
if @issue.save
flash[:notice] = l(:notice_successful_update)
Mailer.deliver_issue_edit(journal) if Permission.find_by_controller_and_action(params[:controller], params[:action]).mail_enabled?
redirect_to :action => 'show', :id => @issue
return
end
end
show
render :action => 'show'
end
def change_status
#@history = @issue.histories.build(params[:history])
@status_options = @issue.status.workflows.find(:all, :order => 'position', :include => :new_status, :conditions => ["role_id=? and tracker_id=?", self.logged_in_user.role_for_project(@project.id), @issue.tracker.id]).collect{ |w| w.new_status } if self.logged_in_user
@new_status = IssueStatus.find(params[:new_status_id])
if params[:confirm]
begin
#@history.author_id = self.logged_in_user.id if self.logged_in_user
#@issue.status = @history.status
#@issue.fixed_version_id = (params[:issue][:fixed_version_id])
#@issue.assigned_to_id = (params[:issue][:assigned_to_id])
#@issue.done_ratio = (params[:issue][:done_ratio])
#@issue.lock_version = (params[:issue][:lock_version])
journal = @issue.init_journal(self.logged_in_user, params[:notes])
@issue.status = @new_status
if @issue.update_attributes(params[:issue])
flash[:notice] = l(:notice_successful_update)
Mailer.deliver_issue_edit(journal) if Permission.find_by_controller_and_action(params[:controller], params[:action]).mail_enabled?
redirect_to :action => 'show', :id => @issue
end
rescue ActiveRecord::StaleObjectError
# Optimistic locking exception
flash[:notice] = l(:notice_locking_conflict)
end
end
@assignable_to = @project.members.find(:all, :include => :user).collect{ |m| m.user }
end end
def destroy def destroy
@issue.destroy @issue.destroy
redirect_to :controller => 'projects', :action => 'list_issues', :id => @project redirect_to :controller => 'projects', :action => 'list_issues', :id => @project
end end
def destroy_attachment def add_attachment
a = @issue.attachments.find(params[:attachment_id]) # Save the attachments
a.destroy @attachments = []
journal = @issue.init_journal(self.logged_in_user) params[:attachments].each { |file|
journal.details << JournalDetail.new(:property => 'attachment', next unless file.size > 0
:prop_key => a.id, a = Attachment.create(:container => @issue, :file => file, :author => logged_in_user)
:old_value => a.filename) @attachments << a unless a.new_record?
journal.save } if params[:attachments] and params[:attachments].is_a? Array
redirect_to :action => 'show', :id => @issue Mailer.deliver_attachments_add(@attachments) if !@attachments.empty? and Permission.find_by_controller_and_action(params[:controller], params[:action]).mail_enabled?
end redirect_to :action => 'show', :id => @issue
end
def context_menu
@priorities = Enumeration.get_values('IPRI').reverse def destroy_attachment
@statuses = IssueStatus.find(:all, :order => 'position') @issue.attachments.find(params[:attachment_id]).destroy
@allowed_statuses = @issue.status.find_new_statuses_allowed_to(User.current.role_for_project(@project), @issue.tracker) redirect_to :action => 'show', :id => @issue
@assignables = @issue.assignable_users end
@assignables << @issue.assigned_to if @issue.assigned_to && !@assignables.include?(@issue.assigned_to)
@can = {:edit => User.current.allowed_to?(:edit_issues, @project), # Send the file in stream mode
:change_status => User.current.allowed_to?(:change_issue_status, @project), def download
:add => User.current.allowed_to?(:add_issues, @project), @attachment = @issue.attachments.find(params[:attachment_id])
:move => User.current.allowed_to?(:move_issues, @project), send_file @attachment.diskfile, :filename => @attachment.filename
:delete => User.current.allowed_to?(:delete_issues, @project)} rescue
render :layout => false render_404
end end
def preview private
issue = Issue.find_by_id(params[:id]) def find_project
@attachements = issue.attachments if issue @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
@text = params[:issue][:description] @project = @issue.project
render :partial => 'common/preview' @html_title = "#{@project.name} - #{@issue.tracker.name} ##{@issue.id}"
end rescue ActiveRecord::RecordNotFound
render_404
private end
def find_project
@issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
@project = @issue.project
rescue ActiveRecord::RecordNotFound
render_404
end
# Retrieve query from session or build a new query
def retrieve_query
if params[:set_filter] or !session[:query] or session[:query].project_id
# Give it a name, required to be valid
@query = Query.new(:name => "_", :executed_by => logged_in_user)
if params[:fields] and params[:fields].is_a? Array
params[:fields].each do |field|
@query.add_filter(field, params[:operators][field], params[:values][field])
end
else
@query.available_filters.keys.each do |field|
@query.add_short_filter(field, params[field]) if params[field]
end
end
session[:query] = @query
else
@query = session[:query]
end
end
end end
+34 -54
View File
@@ -1,62 +1,42 @@
# redMine - project management software # redMine - project management software
# Copyright (C) 2006 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# 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 MembersController < ApplicationController class MembersController < ApplicationController
layout 'base' layout 'base'
before_filter :find_member, :except => :new before_filter :find_project, :authorize
before_filter :find_project, :only => :new
before_filter :authorize
def new
@project.members << Member.new(params[:member]) if request.post?
respond_to do |format|
format.html { redirect_to :action => 'settings', :tab => 'members', :id => @project }
format.js { render(:update) {|page| page.replace_html "tab-content-members", :partial => 'projects/settings/members'} }
end
end
def edit def edit
if request.post? and @member.update_attributes(params[:member]) if request.post? and @member.update_attributes(params[:member])
respond_to do |format| flash[:notice] = l(:notice_successful_update)
format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'members', :id => @project } redirect_to :controller => 'projects', :action => 'settings', :tab => 'members', :id => @project
format.js { render(:update) {|page| page.replace_html "tab-content-members", :partial => 'projects/settings/members'} }
end
end end
end end
def destroy def destroy
@member.destroy @member.destroy
respond_to do |format| flash[:notice] = l(:notice_successful_delete)
format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'members', :id => @project } redirect_to :controller => 'projects', :action => 'settings', :tab => 'members', :id => @project
format.js { render(:update) {|page| page.replace_html "tab-content-members", :partial => 'projects/settings/members'} } end
end
end private
def find_project
private @member = Member.find(params[:id])
def find_project @project = @member.project
@project = Project.find(params[:id]) rescue ActiveRecord::RecordNotFound
rescue ActiveRecord::RecordNotFound render_404
render_404
end
def find_member
@member = Member.find(params[:id])
@project = @member.project
rescue ActiveRecord::RecordNotFound
render_404
end end
end end
-61
View File
@@ -1,61 +0,0 @@
# redMine - project management software
# Copyright (C) 2006-2007 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 MessagesController < ApplicationController
layout 'base'
before_filter :find_project, :authorize
verify :method => :post, :only => [ :reply, :destroy ], :redirect_to => { :action => :show }
helper :attachments
include AttachmentsHelper
def show
@reply = Message.new(:subject => "RE: #{@message.subject}")
render :action => "show", :layout => false if request.xhr?
end
def new
@message = Message.new(params[:message])
@message.author = logged_in_user
@message.board = @board
if request.post? && @message.save
params[:attachments].each { |file|
next unless file.size > 0
Attachment.create(:container => @message, :file => file, :author => logged_in_user)
} if params[:attachments] and params[:attachments].is_a? Array
redirect_to :action => 'show', :id => @message
end
end
def reply
@reply = Message.new(params[:reply])
@reply.author = logged_in_user
@reply.board = @board
@message.children << @reply
redirect_to :action => 'show', :id => @message
end
private
def find_project
@board = Board.find(params[:board_id], :include => :project)
@project = @board.project
@message = @board.topics.find(params[:id]) if params[:id]
rescue ActiveRecord::RecordNotFound
render_404
end
end
+19 -43
View File
@@ -16,14 +16,11 @@
# 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 MyController < ApplicationController class MyController < ApplicationController
helper :issues
layout 'base' layout 'base'
before_filter :require_login before_filter :require_login
BLOCKS = { 'issuesassignedtome' => :label_assigned_to_me_issues, BLOCKS = { 'issuesassignedtome' => :label_assigned_to_me_issues,
'issuesreportedbyme' => :label_reported_issues, 'issuesreportedbyme' => :label_reported_issues,
'issueswatched' => :label_watched_issues,
'news' => :label_news_latest, 'news' => :label_news_latest,
'calendar' => :label_calendar, 'calendar' => :label_calendar,
'documents' => :label_document_plural 'documents' => :label_document_plural
@@ -50,51 +47,31 @@ class MyController < ApplicationController
# Edit user's account # Edit user's account
def account def account
@user = User.current @user = self.logged_in_user
@pref = @user.pref @pref = @user.pref
if request.post? @user.attributes = params[:user]
@user.attributes = params[:user] @user.pref.attributes = params[:pref]
@user.mail_notification = (params[:notification_option] == 'all') if request.post? and @user.save and @user.pref.save
@user.pref.attributes = params[:pref] set_localization
if @user.save flash.now[:notice] = l(:notice_account_updated)
@user.pref.save self.logged_in_user.reload
@user.notified_project_ids = (params[:notification_option] == 'selected' ? params[:notified_project_ids] : [])
set_language_if_valid @user.language
flash[:notice] = l(:notice_account_updated)
redirect_to :action => 'account'
return
end
end end
@notification_options = [[l(:label_user_mail_option_all), 'all'],
[l(:label_user_mail_option_none), 'none']]
# Only users that belong to more than 1 project can select projects for which they are notified
# Note that @user.membership.size would fail since AR ignores :include association option when doing a count
@notification_options.insert 1, [l(:label_user_mail_option_selected), 'selected'] if @user.memberships.length > 1
@notification_option = @user.mail_notification? ? 'all' : (@user.notified_projects_ids.empty? ? 'none' : 'selected')
end end
# Manage user's password # Change user's password
def password def change_password
@user = self.logged_in_user @user = self.logged_in_user
flash[:error] = l(:notice_can_t_change_password) and redirect_to :action => 'account' and return if @user.auth_source_id flash[:notice] = l(:notice_can_t_change_password) and redirect_to :action => 'account' and return if @user.auth_source_id
if request.post? if @user.check_password?(params[:password])
if @user.check_password?(params[:password]) @user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation]
@user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation] if @user.save
if @user.save flash[:notice] = l(:notice_account_password_updated)
flash[:notice] = l(:notice_account_password_updated)
redirect_to :action => 'account'
end
else else
flash[:error] = l(:notice_account_wrong_password) render :action => 'account'
return
end end
end else
end flash[:notice] = l(:notice_account_wrong_password)
# Create a new feeds key
def reset_rss_key
if request.post? && User.current.rss_token
User.current.rss_token.destroy
flash[:notice] = l(:notice_feeds_access_key_reseted)
end end
redirect_to :action => 'account' redirect_to :action => 'account'
end end
@@ -113,9 +90,8 @@ class MyController < ApplicationController
# The block is added on top of the page # The block is added on top of the page
# params[:block] : id of the block to add # params[:block] : id of the block to add
def add_block def add_block
block = params[:block]
render(:nothing => true) and return unless block && (BLOCKS.keys.include? block)
@user = self.logged_in_user @user = self.logged_in_user
block = params[:block]
# remove if already present in a group # remove if already present in a group
%w(top left right).each {|f| (session[:page_layout][f] ||= []).delete block } %w(top left right).each {|f| (session[:page_layout][f] ||= []).delete block }
# add it on top # add it on top
+44 -44
View File
@@ -1,23 +1,23 @@
# redMine - project management software # redMine - project management software
# Copyright (C) 2006 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# 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 NewsController < ApplicationController class NewsController < ApplicationController
layout 'base' layout 'base'
before_filter :find_project, :authorize before_filter :find_project, :authorize
def show def show
end end
@@ -27,34 +27,34 @@ class NewsController < ApplicationController
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'show', :id => @news redirect_to :action => 'show', :id => @news
end end
end
def add_comment
@comment = Comment.new(params[:comment])
@comment.author = logged_in_user
if @news.comments << @comment
flash[:notice] = l(:label_comment_added)
redirect_to :action => 'show', :id => @news
else
render :action => 'show'
end
end end
def add_comment def destroy_comment
@comment = Comment.new(params[:comment]) @news.comments.find(params[:comment_id]).destroy
@comment.author = logged_in_user redirect_to :action => 'show', :id => @news
if @news.comments << @comment end
flash[:notice] = l(:label_comment_added)
redirect_to :action => 'show', :id => @news
else
render :action => 'show'
end
end
def destroy_comment
@news.comments.find(params[:comment_id]).destroy
redirect_to :action => 'show', :id => @news
end
def destroy def destroy
@news.destroy @news.destroy
redirect_to :controller => 'projects', :action => 'list_news', :id => @project redirect_to :controller => 'projects', :action => 'list_news', :id => @project
end end
private private
def find_project def find_project
@news = News.find(params[:id]) @news = News.find(params[:id])
@project = @news.project @project = @news.project
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
render_404 render_404
end end
end end
File diff suppressed because it is too large Load Diff
+9 -41
View File
@@ -1,5 +1,5 @@
# redMine - project management software # redMine - project management software
# Copyright (C) 2006-2007 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,34 +17,8 @@
class QueriesController < ApplicationController class QueriesController < ApplicationController
layout 'base' layout 'base'
before_filter :find_project, :authorize before_filter :require_login, :find_query
def index
@queries = @project.queries.find(:all,
:order => "name ASC",
:conditions => ["is_public = ? or user_id = ?", true, (logged_in_user ? logged_in_user.id : 0)])
end
def new
@query = Query.new(params[:query])
@query.project = @project
@query.user = logged_in_user
@query.executed_by = logged_in_user
@query.is_public = false unless current_role.allowed_to?(:manage_public_queries)
@query.column_names = nil if params[:default_columns]
params[:fields].each do |field|
@query.add_filter(field, params[:operators][field], params[:values][field])
end if params[:fields]
if request.post? && params[:confirm] && @query.save
flash[:notice] = l(:notice_successful_create)
redirect_to :controller => 'projects', :action => 'list_issues', :id => @project, :query_id => @query
return
end
render :layout => false if request.xhr?
end
def edit def edit
if request.post? if request.post?
@query.filters = {} @query.filters = {}
@@ -52,9 +26,7 @@ class QueriesController < ApplicationController
@query.add_filter(field, params[:operators][field], params[:values][field]) @query.add_filter(field, params[:operators][field], params[:values][field])
end if params[:fields] end if params[:fields]
@query.attributes = params[:query] @query.attributes = params[:query]
@query.is_public = false unless current_role.allowed_to?(:manage_public_queries)
@query.column_names = nil if params[:default_columns]
if @query.save if @query.save
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to :controller => 'projects', :action => 'list_issues', :id => @project, :query_id => @query redirect_to :controller => 'projects', :action => 'list_issues', :id => @project, :query_id => @query
@@ -64,19 +36,15 @@ class QueriesController < ApplicationController
def destroy def destroy
@query.destroy if request.post? @query.destroy if request.post?
redirect_to :controller => 'queries', :project_id => @project redirect_to :controller => 'reports', :action => 'issue_report', :id => @project
end end
private private
def find_project def find_query
if params[:id] @query = Query.find(params[:id])
@query = Query.find(params[:id]) @project = @query.project
@query.executed_by = logged_in_user # check if user is allowed to manage queries (same permission as add_query)
@project = @query.project authorize('projects', 'add_query')
render_403 unless @query.editable_by?(logged_in_user)
else
@project = Project.find(params[:project_id])
end
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
render_404 render_404
end end
+165 -211
View File
@@ -1,213 +1,167 @@
# redMine - project management software # redMine - project management software
# Copyright (C) 2006 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# 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 ReportsController < ApplicationController class ReportsController < ApplicationController
layout 'base' layout 'base'
before_filter :find_project, :authorize before_filter :find_project, :authorize
def issue_report def issue_report
@statuses = IssueStatus.find(:all, :order => 'position') @statuses = IssueStatus.find(:all, :order => 'position')
case params[:detail] case params[:detail]
when "tracker" when "tracker"
@field = "tracker_id" @field = "tracker_id"
@rows = Tracker.find :all, :order => 'position' @rows = Tracker.find :all, :order => 'position'
@data = issues_by_tracker @data = issues_by_tracker
@report_title = l(:field_tracker) @report_title = l(:field_tracker)
render :template => "reports/issue_report_details" render :template => "reports/issue_report_details"
when "version" when "priority"
@field = "fixed_version_id" @field = "priority_id"
@rows = @project.versions.sort @rows = Enumeration::get_values('IPRI')
@data = issues_by_version @data = issues_by_priority
@report_title = l(:field_version) @report_title = l(:field_priority)
render :template => "reports/issue_report_details" render :template => "reports/issue_report_details"
when "priority" when "category"
@field = "priority_id" @field = "category_id"
@rows = Enumeration::get_values('IPRI') @rows = @project.issue_categories
@data = issues_by_priority @data = issues_by_category
@report_title = l(:field_priority) @report_title = l(:field_category)
render :template => "reports/issue_report_details" render :template => "reports/issue_report_details"
when "category" when "author"
@field = "category_id" @field = "author_id"
@rows = @project.issue_categories @rows = @project.members.collect { |m| m.user }
@data = issues_by_category @data = issues_by_author
@report_title = l(:field_category) @report_title = l(:field_author)
render :template => "reports/issue_report_details" render :template => "reports/issue_report_details"
when "author" else
@field = "author_id" @queries = @project.queries.find :all, :conditions => ["is_public=? or user_id=?", true, (logged_in_user ? logged_in_user.id : 0)]
@rows = @project.members.collect { |m| m.user } @trackers = Tracker.find(:all, :order => 'position')
@data = issues_by_author @priorities = Enumeration::get_values('IPRI')
@report_title = l(:field_author) @categories = @project.issue_categories
render :template => "reports/issue_report_details" @authors = @project.members.collect { |m| m.user }
when "subproject" issues_by_tracker
@field = "project_id" issues_by_priority
@rows = @project.active_children issues_by_category
@data = issues_by_subproject issues_by_author
@report_title = l(:field_subproject) render :template => "reports/issue_report"
render :template => "reports/issue_report_details" end
else end
@trackers = Tracker.find(:all, :order => 'position')
@versions = @project.versions.sort def delays
@priorities = Enumeration::get_values('IPRI') @trackers = Tracker.find(:all)
@categories = @project.issue_categories if request.get?
@authors = @project.members.collect { |m| m.user } @selected_tracker_ids = @trackers.collect {|t| t.id.to_s }
@subprojects = @project.active_children else
issues_by_tracker @selected_tracker_ids = params[:tracker_ids].collect { |id| id.to_i.to_s } if params[:tracker_ids] and params[:tracker_ids].is_a? Array
issues_by_version end
issues_by_priority @selected_tracker_ids ||= []
issues_by_category @raw =
issues_by_author ActiveRecord::Base.connection.select_all("SELECT datediff( a.created_on, b.created_on ) as delay, count(a.id) as total
issues_by_subproject FROM issue_histories a, issue_histories b, issues i
WHERE a.status_id =5
render :template => "reports/issue_report" AND a.issue_id = b.issue_id
end AND a.issue_id = i.id
end AND i.tracker_id in (#{@selected_tracker_ids.join(',')})
AND b.id = (
def delays SELECT min( c.id )
@trackers = Tracker.find(:all) FROM issue_histories c
if request.get? WHERE b.issue_id = c.issue_id )
@selected_tracker_ids = @trackers.collect {|t| t.id.to_s } GROUP BY delay") unless @selected_tracker_ids.empty?
else @raw ||=[]
@selected_tracker_ids = params[:tracker_ids].collect { |id| id.to_i.to_s } if params[:tracker_ids] and params[:tracker_ids].is_a? Array
end @x_from = 0
@selected_tracker_ids ||= [] @x_to = 0
@raw = @y_from = 0
ActiveRecord::Base.connection.select_all("SELECT datediff( a.created_on, b.created_on ) as delay, count(a.id) as total @y_to = 0
FROM issue_histories a, issue_histories b, issues i @sum_total = 0
WHERE a.status_id =5 @sum_delay = 0
AND a.issue_id = b.issue_id @raw.each do |r|
AND a.issue_id = i.id @x_to = [r['delay'].to_i, @x_to].max
AND i.tracker_id in (#{@selected_tracker_ids.join(',')}) @y_to = [r['total'].to_i, @y_to].max
AND b.id = ( @sum_total = @sum_total + r['total'].to_i
SELECT min( c.id ) @sum_delay = @sum_delay + r['total'].to_i * r['delay'].to_i
FROM issue_histories c end
WHERE b.issue_id = c.issue_id ) end
GROUP BY delay") unless @selected_tracker_ids.empty?
@raw ||=[] private
# Find project of id params[:id]
@x_from = 0 def find_project
@x_to = 0 @project = Project.find(params[:id])
@y_from = 0 rescue ActiveRecord::RecordNotFound
@y_to = 0 render_404
@sum_total = 0 end
@sum_delay = 0
@raw.each do |r| def issues_by_tracker
@x_to = [r['delay'].to_i, @x_to].max @issues_by_tracker ||=
@y_to = [r['total'].to_i, @y_to].max ActiveRecord::Base.connection.select_all("select s.id as status_id,
@sum_total = @sum_total + r['total'].to_i s.is_closed as closed,
@sum_delay = @sum_delay + r['total'].to_i * r['delay'].to_i t.id as tracker_id,
end count(i.id) as total
end from
issues i, issue_statuses s, trackers t
private where
# Find project of id params[:id] i.status_id=s.id
def find_project and i.tracker_id=t.id
@project = Project.find(params[:id]) and i.project_id=#{@project.id}
rescue ActiveRecord::RecordNotFound group by s.id, s.is_closed, t.id")
render_404 end
end
def issues_by_priority
def issues_by_tracker @issues_by_priority ||=
@issues_by_tracker ||= 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, p.id as priority_id,
t.id as tracker_id, count(i.id) as total
count(i.id) as total from
from issues i, issue_statuses s, enumerations p
#{Issue.table_name} i, #{IssueStatus.table_name} s, #{Tracker.table_name} t where
where i.status_id=s.id
i.status_id=s.id and i.priority_id=p.id
and i.tracker_id=t.id and i.project_id=#{@project.id}
and i.project_id=#{@project.id} group by s.id, s.is_closed, p.id")
group by s.id, s.is_closed, t.id") end
end
def issues_by_category
def issues_by_version @issues_by_category ||=
@issues_by_version ||= 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, c.id as category_id,
v.id as fixed_version_id, count(i.id) as total
count(i.id) as total from
from issues i, issue_statuses s, issue_categories c
#{Issue.table_name} i, #{IssueStatus.table_name} s, #{Version.table_name} v where
where i.status_id=s.id
i.status_id=s.id and i.category_id=c.id
and i.fixed_version_id=v.id and i.project_id=#{@project.id}
and i.project_id=#{@project.id} group by s.id, s.is_closed, c.id")
group by s.id, s.is_closed, v.id") end
end
def issues_by_author
def issues_by_priority @issues_by_author ||=
@issues_by_priority ||= 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, a.id as author_id,
p.id as priority_id, count(i.id) as total
count(i.id) as total from
from issues i, issue_statuses s, users a
#{Issue.table_name} i, #{IssueStatus.table_name} s, #{Enumeration.table_name} p where
where i.status_id=s.id
i.status_id=s.id and i.author_id=a.id
and i.priority_id=p.id and i.project_id=#{@project.id}
and i.project_id=#{@project.id} group by s.id, s.is_closed, a.id")
group by s.id, s.is_closed, p.id")
end
def issues_by_category
@issues_by_category ||=
ActiveRecord::Base.connection.select_all("select s.id as status_id,
s.is_closed as closed,
c.id as category_id,
count(i.id) as total
from
#{Issue.table_name} i, #{IssueStatus.table_name} s, #{IssueCategory.table_name} c
where
i.status_id=s.id
and i.category_id=c.id
and i.project_id=#{@project.id}
group by s.id, s.is_closed, c.id")
end
def issues_by_author
@issues_by_author ||=
ActiveRecord::Base.connection.select_all("select s.id as status_id,
s.is_closed as closed,
a.id as author_id,
count(i.id) as total
from
#{Issue.table_name} i, #{IssueStatus.table_name} s, #{User.table_name} a
where
i.status_id=s.id
and i.author_id=a.id
and i.project_id=#{@project.id}
group by s.id, s.is_closed, a.id")
end
def issues_by_subproject
@issues_by_subproject ||=
ActiveRecord::Base.connection.select_all("select s.id as status_id,
s.is_closed as closed,
i.project_id as project_id,
count(i.id) as total
from
#{Issue.table_name} i, #{IssueStatus.table_name} s
where
i.status_id=s.id
and i.project_id IN (#{@project.active_children.collect{|p| p.id}.join(',')})
group by s.id, s.is_closed, i.project_id") if @project.active_children.any?
@issues_by_subproject ||= []
end end
end end
+24 -207
View File
@@ -1,5 +1,5 @@
# redMine - project management software # redMine - project management software
# Copyright (C) 2006-2007 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
@@ -15,243 +15,60 @@
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# 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 'SVG/Graph/Bar'
require 'SVG/Graph/BarHorizontal'
require 'digest/sha1'
class RepositoriesController < ApplicationController class RepositoriesController < ApplicationController
layout 'base' layout 'base'
before_filter :find_repository, :except => :edit before_filter :find_project, :authorize
before_filter :find_project, :only => :edit
before_filter :authorize
accept_key_auth :revisions
def edit
@repository = @project.repository
if !@repository
@repository = Repository.factory(params[:repository_scm])
@repository.project = @project
end
if request.post?
@repository.attributes = params[:repository]
@repository.save
end
render(:update) {|page| page.replace_html "tab-content-repository", :partial => 'projects/settings/repository'}
end
def destroy
@repository.destroy
redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'repository'
end
def show def show
# check if new revisions have been committed in the repository @entries = @repository.scm.entries('')
@repository.fetch_changesets if Setting.autofetch_changesets? show_error and return unless @entries
# get entries for the browse frame @latest_revision = @entries.revisions.latest
@entries = @repository.entries('')
# latest changesets
@changesets = @repository.changesets.find(:all, :limit => 10, :order => "committed_on DESC")
show_error and return unless @entries || @changesets.any?
end end
def browse def browse
@entries = @repository.entries(@path, @rev) @entries = @repository.scm.entries(@path, @rev)
if request.xhr? show_error and return unless @entries
@entries ? render(:partial => 'dir_list_content') : render(:nothing => true)
else
show_error unless @entries
end
end
def changes
@entry = @repository.scm.entry(@path, @rev)
show_error and return unless @entry
@changesets = @repository.changesets_for_path(@path)
end end
def revisions def revisions
@changeset_count = @repository.changesets.count @entry = @repository.scm.entry(@path, @rev)
@changeset_pages = Paginator.new self, @changeset_count, @revisions = @repository.scm.revisions(@path, @rev)
25, show_error and return unless @entry && @revisions
params['page']
@changesets = @repository.changesets.find(:all,
:limit => @changeset_pages.items_per_page,
:offset => @changeset_pages.current.offset)
respond_to do |format|
format.html { render :layout => false if request.xhr? }
format.atom { render_feed(@changesets, :title => "#{@project.name}: #{l(:label_revision_plural)}") }
end
end end
def entry def entry
@content = @repository.scm.cat(@path, @rev) if 'raw' == params[:format]
show_error and return unless @content content = @repository.scm.cat(@path, @rev)
if 'raw' == params[:format] show_error and return unless content
send_data @content, :filename => @path.split('/').last send_data content, :filename => @path.split('/').last
end end
end end
def revision def revision
@changeset = @repository.changesets.find_by_revision(@rev) @revisions = @repository.scm.revisions '', @rev, @rev, :with_paths => true
show_error and return unless @changeset show_error and return unless @revisions
@changes_count = @changeset.changes.size @revision = @revisions.first
@changes_pages = Paginator.new self, @changes_count, 150, params['page']
@changes = @changeset.changes.find(:all,
:limit => @changes_pages.items_per_page,
:offset => @changes_pages.current.offset)
render :action => "revision", :layout => false if request.xhr?
end end
def diff def diff
@rev_to = params[:rev_to] ? params[:rev_to].to_i : (@rev - 1) @rev_to = params[:rev_to] || (@rev-1)
@diff_type = ('sbs' == params[:type]) ? 'sbs' : 'inline' @diff = @repository.scm.diff(params[:path], @rev, @rev_to)
show_error and return unless @diff
@cache_key = "repositories/diff/#{@repository.id}/" + Digest::MD5.hexdigest("#{@path}-#{@rev}-#{@rev_to}-#{@diff_type}")
unless read_fragment(@cache_key)
@diff = @repository.diff(@path, @rev, @rev_to, @diff_type)
show_error and return unless @diff
end
end
def stats
end
def graph
data = nil
case params[:graph]
when "commits_per_month"
data = graph_commits_per_month(@repository)
when "commits_per_author"
data = graph_commits_per_author(@repository)
end
if data
headers["Content-Type"] = "image/svg+xml"
send_data(data, :type => "image/svg+xml", :disposition => "inline")
else
render_404
end
end end
private private
def find_project def find_project
@project = Project.find(params[:id]) @project = Project.find(params[:id])
rescue ActiveRecord::RecordNotFound
render_404
end
def find_repository
@project = Project.find(params[:id])
@repository = @project.repository @repository = @project.repository
render_404 and return false unless @repository @path = params[:path].squeeze('/').gsub(/^\//, '') if params[:path]
@path = params[:path].join('/') unless params[:path].nil?
@path ||= '' @path ||= ''
@rev = params[:rev].to_i if params[:rev] @rev = params[:rev].to_i if params[:rev] and params[:rev].to_i > 0
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
render_404 render_404
end end
def show_error def show_error
flash.now[:error] = l(:notice_scm_error) flash.now[:notice] = l(:notice_scm_error)
render :nothing => true, :layout => true render :nothing => true, :layout => true
end end
def graph_commits_per_month(repository)
@date_to = Date.today
@date_from = @date_to << 11
@date_from = Date.civil(@date_from.year, @date_from.month, 1)
commits_by_day = repository.changesets.count(:all, :group => :commit_date, :conditions => ["commit_date BETWEEN ? AND ?", @date_from, @date_to])
commits_by_month = [0] * 12
commits_by_day.each {|c| commits_by_month[c.first.to_date.months_ago] += c.last }
changes_by_day = repository.changes.count(:all, :group => :commit_date, :conditions => ["commit_date BETWEEN ? AND ?", @date_from, @date_to])
changes_by_month = [0] * 12
changes_by_day.each {|c| changes_by_month[c.first.to_date.months_ago] += c.last }
fields = []
month_names = l(:actionview_datehelper_select_month_names_abbr).split(',')
12.times {|m| fields << month_names[((Date.today.month - 1 - m) % 12)]}
graph = SVG::Graph::Bar.new(
:height => 300,
:width => 500,
:fields => fields.reverse,
:stack => :side,
:scale_integers => true,
:step_x_labels => 2,
:show_data_values => false,
:graph_title => l(:label_commits_per_month),
:show_graph_title => true
)
graph.add_data(
:data => commits_by_month[0..11].reverse,
:title => l(:label_revision_plural)
)
graph.add_data(
:data => changes_by_month[0..11].reverse,
:title => l(:label_change_plural)
)
graph.burn
end
def graph_commits_per_author(repository)
commits_by_author = repository.changesets.count(:all, :group => :committer)
commits_by_author.sort! {|x, y| x.last <=> y.last}
changes_by_author = repository.changes.count(:all, :group => :committer)
h = changes_by_author.inject({}) {|o, i| o[i.first] = i.last; o}
fields = commits_by_author.collect {|r| r.first}
commits_data = commits_by_author.collect {|r| r.last}
changes_data = commits_by_author.collect {|r| h[r.first] || 0}
fields = fields + [""]*(10 - fields.length) if fields.length<10
commits_data = commits_data + [0]*(10 - commits_data.length) if commits_data.length<10
changes_data = changes_data + [0]*(10 - changes_data.length) if changes_data.length<10
graph = SVG::Graph::BarHorizontal.new(
:height => 300,
:width => 500,
:fields => fields,
:stack => :side,
:scale_integers => true,
:show_data_values => false,
:rotate_y_labels => false,
:graph_title => l(:label_commits_per_author),
:show_graph_title => true
)
graph.add_data(
:data => commits_data,
:title => l(:label_revision_plural)
)
graph.add_data(
:data => changes_data,
:title => l(:label_change_plural)
)
graph.burn
end
end
class Date
def months_ago(date = Date.today)
(date.year - self.year)*12 + (date.month - self.month)
end
def weeks_ago(date = Date.today)
(date.year - self.year)*52 + (date.cweek - self.cweek)
end
end
class String
def with_leading_slash
starts_with?('/') ? self : "/#{self}"
end
end end
+77 -86
View File
@@ -1,26 +1,26 @@
# redMine - project management software # redMine - project management software
# Copyright (C) 2006 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# 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 RolesController < ApplicationController class RolesController < ApplicationController
layout 'base' layout 'base'
before_filter :require_admin before_filter :require_admin
verify :method => :post, :only => [ :destroy, :move ], verify :method => :post, :only => [ :destroy, :move ],
:redirect_to => { :action => :list } :redirect_to => { :action => :list }
def index def index
list list
@@ -28,84 +28,75 @@ class RolesController < ApplicationController
end end
def list def list
@role_pages, @roles = paginate :roles, :per_page => 25, :order => 'builtin, position' @role_pages, @roles = paginate :roles, :per_page => 10, :order => "position"
render :action => "list", :layout => false if request.xhr? render :action => "list", :layout => false if request.xhr?
end end
def new def new
@role = Role.new(params[:role]) @role = Role.new(params[:role])
if request.post? && @role.save if request.post?
flash[:notice] = l(:notice_successful_create) @role.permissions = Permission.find(params[:permission_ids]) if params[:permission_ids]
redirect_to :action => 'list' if @role.save
flash[:notice] = l(:notice_successful_create)
redirect_to :action => 'list'
end
end end
@permissions = @role.setable_permissions @permissions = Permission.find(:all, :conditions => ["is_public=?", false], :order => 'sort ASC')
end end
def edit def edit
@role = Role.find(params[:id]) @role = Role.find(params[:id])
if request.post? and @role.update_attributes(params[:role]) if request.post? and @role.update_attributes(params[:role])
@role.permissions = Permission.find(params[:permission_ids] || [])
Permission.allowed_to_role_expired
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'list' redirect_to :action => 'list'
end end
@permissions = @role.setable_permissions @permissions = Permission.find(:all, :conditions => ["is_public=?", false], :order => 'sort ASC')
end end
def destroy def destroy
@role = Role.find(params[:id]) @role = Role.find(params[:id])
#unless @role.members.empty? unless @role.members.empty?
# flash[:error] = 'Some members have this role. Can\'t delete it.' flash[:notice] = 'Some members have this role. Can\'t delete it.'
#else else
@role.destroy @role.destroy
#end
redirect_to :action => 'list'
end
def move
@role = Role.find(params[:id])
case params[:position]
when 'highest'
@role.move_to_top
when 'higher'
@role.move_higher
when 'lower'
@role.move_lower
when 'lowest'
@role.move_to_bottom
end if params[:position]
redirect_to :action => 'list'
end
def workflow
@role = Role.find_by_id(params[:role_id])
@tracker = Tracker.find_by_id(params[:tracker_id])
if request.post?
Workflow.destroy_all( ["role_id=? and tracker_id=?", @role.id, @tracker.id])
(params[:issue_status] || []).each { |old, news|
news.each { |new|
@role.workflows.build(:tracker_id => @tracker.id, :old_status_id => old, :new_status_id => new)
}
}
if @role.save
flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'workflow', :role_id => @role, :tracker_id => @tracker
end
end
@roles = Role.find(:all, :order => 'builtin, position')
@trackers = Tracker.find(:all, :order => 'position')
@statuses = IssueStatus.find(:all, :include => :workflows, :order => 'position')
end
def report
@roles = Role.find(:all, :order => 'builtin, position')
@permissions = Redmine::AccessControl.permissions.select { |p| !p.public? }
if request.post?
@roles.each do |role|
role.permissions = params[:permissions][role.id.to_s]
role.save
end
flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'list'
end end
redirect_to :action => 'list'
end
def move
@role = Role.find(params[:id])
case params[:position]
when 'highest'
@role.move_to_top
when 'higher'
@role.move_higher
when 'lower'
@role.move_lower
when 'lowest'
@role.move_to_bottom
end if params[:position]
redirect_to :action => 'list'
end
def workflow
@role = Role.find_by_id(params[:role_id])
@tracker = Tracker.find_by_id(params[:tracker_id])
if request.post?
Workflow.destroy_all( ["role_id=? and tracker_id=?", @role.id, @tracker.id])
(params[:issue_status] || []).each { |old, news|
news.each { |new|
@role.workflows.build(:tracker_id => @tracker.id, :old_status_id => old, :new_status_id => new)
}
}
if @role.save
flash[:notice] = l(:notice_successful_update)
end
end
@roles = Role.find(:all, :order => 'position')
@trackers = Tracker.find(:all, :order => 'position')
@statuses = IssueStatus.find(:all, :include => :workflows, :order => 'position')
end end
end end
-109
View File
@@ -1,109 +0,0 @@
# redMine - project management software
# Copyright (C) 2006 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 SearchController < ApplicationController
layout 'base'
helper :messages
include MessagesHelper
def index
@question = params[:q] || ""
@question.strip!
@all_words = params[:all_words] || (params[:submit] ? false : true)
@titles_only = !params[:titles_only].nil?
offset = nil
begin; offset = params[:offset].to_time if params[:offset]; rescue; end
# quick jump to an issue
if @question.match(/^#?(\d+)$/) && Issue.find_by_id($1, :include => :project, :conditions => Project.visible_by(logged_in_user))
redirect_to :controller => "issues", :action => "show", :id => $1
return
end
if params[:id]
find_project
return unless check_project_privacy
end
if @project
# only show what the user is allowed to view
@object_types = %w(issues news documents changesets wiki_pages messages)
@object_types = @object_types.select {|o| User.current.allowed_to?("view_#{o}".to_sym, @project)}
@scope = @object_types.select {|t| params[t]}
@scope = @object_types if @scope.empty?
else
@object_types = @scope = %w(projects)
end
# tokens must be at least 3 character long
@tokens = @question.split.uniq.select {|w| w.length > 2 }
if !@tokens.empty?
# no more than 5 tokens to search for
@tokens.slice! 5..-1 if @tokens.size > 5
# strings used in sql like statement
like_tokens = @tokens.collect {|w| "%#{w.downcase}%"}
@results = []
limit = 10
if @project
@scope.each do |s|
@results += s.singularize.camelcase.constantize.search(like_tokens, @project,
:all_words => @all_words,
:titles_only => @titles_only,
:limit => (limit+1),
:offset => offset,
:before => params[:previous].nil?)
end
@results = @results.sort {|a,b| b.event_datetime <=> a.event_datetime}
if params[:previous].nil?
@pagination_previous_date = @results[0].event_datetime if offset && @results[0]
if @results.size > limit
@pagination_next_date = @results[limit-1].event_datetime
@results = @results[0, limit]
end
else
@pagination_next_date = @results[-1].event_datetime if offset && @results[-1]
if @results.size > limit
@pagination_previous_date = @results[-(limit)].event_datetime
@results = @results[-(limit), limit]
end
end
else
operator = @all_words ? ' AND ' : ' OR '
Project.with_scope(:find => {:conditions => Project.visible_by(logged_in_user)}) do
@results += Project.find(:all, :limit => limit, :conditions => [ (["(LOWER(name) like ? OR LOWER(description) like ?)"] * like_tokens.size).join(operator), * (like_tokens * 2).sort] ) if @scope.include? 'projects'
end
# if only one project is found, user is redirected to its overview
redirect_to :controller => 'projects', :action => 'show', :id => @results.first and return if @results.size == 1
end
@question = @tokens.join(" ")
else
@question = ""
end
render :layout => false if request.xhr?
end
private
def find_project
@project = Project.find(params[:id])
rescue ActiveRecord::RecordNotFound
render_404
end
end
-12
View File
@@ -30,16 +30,4 @@ class SettingsController < ApplicationController
redirect_to :action => 'edit' and return redirect_to :action => 'edit' and return
end end
end end
def plugin
plugin_id = params[:id].to_sym
@plugin = Redmine::Plugin.registered_plugins[plugin_id]
if request.post?
Setting["plugin_#{plugin_id}"] = params[:settings]
flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'plugin', :id => params[:id]
end
@partial = "../../vendor/plugins/#{plugin_id}/app/views/" + @plugin.settings[:partial]
@settings = Setting["plugin_#{plugin_id}"]
end
end end
-47
View File
@@ -1,47 +0,0 @@
# redMine - project management software
# Copyright (C) 2006-2007 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 SysController < ActionController::Base
wsdl_service_name 'Sys'
web_service_api SysApi
web_service_scaffold :invoke
before_invocation :check_enabled
# Returns the projects list, with their repositories
def projects
Project.find(:all, :include => :repository)
end
# Registers a repository for the given project identifier
# (Subversion specific)
def repository_created(identifier, url)
project = Project.find_by_identifier(identifier)
# Do not create the repository if the project has already one
return 0 unless project && project.repository.nil?
logger.debug "Repository for #{project.name} was created"
repository = Repository.factory('Subversion', :project => project, :url => url)
repository.save
repository.id || 0
end
protected
def check_enabled(name, args)
Setting.sys_api_enabled?
end
end
-172
View File
@@ -1,172 +0,0 @@
# redMine - project management software
# Copyright (C) 2006-2007 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 TimelogController < ApplicationController
layout 'base'
before_filter :find_project, :authorize
helper :sort
include SortHelper
helper :issues
def report
@available_criterias = { 'version' => {:sql => "#{Issue.table_name}.fixed_version_id",
:values => @project.versions,
:label => :label_version},
'category' => {:sql => "#{Issue.table_name}.category_id",
:values => @project.issue_categories,
:label => :field_category},
'member' => {:sql => "#{TimeEntry.table_name}.user_id",
:values => @project.users,
:label => :label_member},
'tracker' => {:sql => "#{Issue.table_name}.tracker_id",
:values => Tracker.find(:all),
:label => :label_tracker},
'activity' => {:sql => "#{TimeEntry.table_name}.activity_id",
:values => Enumeration::get_values('ACTI'),
:label => :label_activity}
}
@criterias = params[:criterias] || []
@criterias = @criterias.select{|criteria| @available_criterias.has_key? criteria}
@criterias.uniq!
@columns = (params[:period] && %w(year month week).include?(params[:period])) ? params[:period] : 'month'
if params[:date_from]
begin; @date_from = params[:date_from].to_date; rescue; end
end
if params[:date_to]
begin; @date_to = params[:date_to].to_date; rescue; end
end
@date_from ||= Date.civil(Date.today.year, 1, 1)
@date_to ||= Date.civil(Date.today.year, Date.today.month+1, 1) - 1
unless @criterias.empty?
sql_select = @criterias.collect{|criteria| @available_criterias[criteria][:sql] + " AS " + criteria}.join(', ')
sql_group_by = @criterias.collect{|criteria| @available_criterias[criteria][:sql]}.join(', ')
sql = "SELECT #{sql_select}, tyear, tmonth, tweek, SUM(hours) AS hours"
sql << " FROM #{TimeEntry.table_name} LEFT JOIN #{Issue.table_name} ON #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id"
sql << " WHERE #{TimeEntry.table_name}.project_id = %s" % @project.id
sql << " AND spent_on BETWEEN '%s' AND '%s'" % [ActiveRecord::Base.connection.quoted_date(@date_from.to_time), ActiveRecord::Base.connection.quoted_date(@date_to.to_time)]
sql << " GROUP BY #{sql_group_by}, tyear, tmonth, tweek"
@hours = ActiveRecord::Base.connection.select_all(sql)
@hours.each do |row|
case @columns
when 'year'
row['year'] = row['tyear']
when 'month'
row['month'] = "#{row['tyear']}-#{row['tmonth']}"
when 'week'
row['week'] = "#{row['tyear']}-#{row['tweek']}"
end
end
end
@periods = []
date_from = @date_from
# 100 columns max
while date_from < @date_to && @periods.length < 100
case @columns
when 'year'
@periods << "#{date_from.year}"
date_from = date_from >> 12
when 'month'
@periods << "#{date_from.year}-#{date_from.month}"
date_from = date_from >> 1
when 'week'
@periods << "#{date_from.year}-#{date_from.cweek}"
date_from = date_from + 7
end
end
render :layout => false if request.xhr?
end
def details
sort_init 'spent_on', 'desc'
sort_update
@entries = (@issue ? @issue : @project).time_entries.find(:all, :include => [:activity, :user, {:issue => [:tracker, :assigned_to, :priority]}], :order => sort_clause)
@total_hours = @entries.inject(0) { |sum,entry| sum + entry.hours }
@owner_id = logged_in_user ? logged_in_user.id : 0
send_csv and return if 'csv' == params[:export]
render :action => 'details', :layout => false if request.xhr?
end
def edit
render_404 and return if @time_entry && @time_entry.user != logged_in_user
@time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => logged_in_user, :spent_on => Date.today)
@time_entry.attributes = params[:time_entry]
if request.post? and @time_entry.save
flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'details', :project_id => @time_entry.project, :issue_id => @time_entry.issue
return
end
@activities = Enumeration::get_values('ACTI')
end
private
def find_project
if params[:id]
@time_entry = TimeEntry.find(params[:id])
@project = @time_entry.project
elsif params[:issue_id]
@issue = Issue.find(params[:issue_id])
@project = @issue.project
elsif params[:project_id]
@project = Project.find(params[:project_id])
else
render_404
return false
end
end
def send_csv
ic = Iconv.new(l(:general_csv_encoding), 'UTF-8')
export = StringIO.new
CSV::Writer.generate(export, l(:general_csv_separator)) do |csv|
# csv header fields
headers = [l(:field_spent_on),
l(:field_user),
l(:field_activity),
l(:field_issue),
l(:field_hours),
l(:field_comments)
]
csv << headers.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
# csv lines
@entries.each do |entry|
fields = [l_date(entry.spent_on),
entry.user.name,
entry.activity.name,
(entry.issue ? entry.issue.id : nil),
entry.hours,
entry.comments
]
csv << fields.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
end
end
export.rewind
send_data(export.read, :type => 'text/csv; header=present', :filename => 'export.csv')
end
end
+41 -48
View File
@@ -1,23 +1,23 @@
# redMine - project management software # redMine - project management software
# Copyright (C) 2006 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# 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 TrackersController < ApplicationController class TrackersController < ApplicationController
layout 'base' layout 'base'
before_filter :require_admin before_filter :require_admin
def index def index
list list
@@ -28,23 +28,16 @@ class TrackersController < ApplicationController
verify :method => :post, :only => [ :destroy, :move ], :redirect_to => { :action => :list } verify :method => :post, :only => [ :destroy, :move ], :redirect_to => { :action => :list }
def list def list
@tracker_pages, @trackers = paginate :trackers, :per_page => 10, :order => 'position' @tracker_pages, @trackers = paginate :trackers, :per_page => 10, :order => 'position'
render :action => "list", :layout => false if request.xhr? render :action => "list", :layout => false if request.xhr?
end end
def new def new
@tracker = Tracker.new(params[:tracker]) @tracker = Tracker.new(params[:tracker])
if request.post? and @tracker.save if request.post? and @tracker.save
# workflow copy
if !params[:copy_workflow_from].blank? && (copy_from = Tracker.find_by_id(params[:copy_workflow_from]))
copy_from.workflows.each do |w|
@tracker.workflows << w.clone
end
end
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
redirect_to :action => 'list' redirect_to :action => 'list'
end end
@trackers = Tracker.find :all
end end
def edit def edit
@@ -54,28 +47,28 @@ class TrackersController < ApplicationController
redirect_to :action => 'list' redirect_to :action => 'list'
end end
end end
def move def move
@tracker = Tracker.find(params[:id]) @tracker = Tracker.find(params[:id])
case params[:position] case params[:position]
when 'highest' when 'highest'
@tracker.move_to_top @tracker.move_to_top
when 'higher' when 'higher'
@tracker.move_higher @tracker.move_higher
when 'lower' when 'lower'
@tracker.move_lower @tracker.move_lower
when 'lowest' when 'lowest'
@tracker.move_to_bottom @tracker.move_to_bottom
end if params[:position] end if params[:position]
redirect_to :action => 'list' redirect_to :action => 'list'
end end
def destroy def destroy
@tracker = Tracker.find(params[:id]) @tracker = Tracker.find(params[:id])
unless @tracker.issues.empty? unless @tracker.issues.empty?
flash[:error] = "This tracker contains issues and can\'t be deleted." flash[:notice] = "This tracker contains issues and can\'t be deleted."
else else
@tracker.destroy @tracker.destroy
end end
redirect_to :action => 'list' redirect_to :action => 'list'
end end
+84 -91
View File
@@ -1,120 +1,113 @@
# 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
# 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
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# 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 UsersController < ApplicationController class UsersController < ApplicationController
layout 'base' layout 'base'
before_filter :require_admin before_filter :require_admin
helper :sort helper :sort
include SortHelper include SortHelper
helper :custom_fields helper :custom_fields
include CustomFieldsHelper include CustomFieldsHelper
def index def index
list list
render :action => 'list' unless request.xhr? render :action => 'list' unless request.xhr?
end end
def list def list
sort_init 'login', 'asc' sort_init 'login', 'asc'
sort_update sort_update
@user_count = User.count
@status = params[:status] ? params[:status].to_i : 1 @user_pages = Paginator.new self, @user_count,
conditions = nil 15,
conditions = ["status=?", @status] unless @status == 0 params['page']
@users = User.find :all,:order => sort_clause,
@user_count = User.count(:conditions => conditions) :limit => @user_pages.items_per_page,
@user_pages = Paginator.new self, @user_count, :offset => @user_pages.current.offset
15,
params['page']
@users = User.find :all,:order => sort_clause,
:conditions => conditions,
:limit => @user_pages.items_per_page,
:offset => @user_pages.current.offset
render :action => "list", :layout => false if request.xhr? render :action => "list", :layout => false if request.xhr?
end end
def add def add
if request.get? if request.get?
@user = User.new(:language => Setting.default_language) @user = User.new(:language => Setting.default_language)
@custom_values = UserCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x, :customized => @user) } @custom_values = UserCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x, :customized => @user) }
else else
@user = User.new(params[:user]) @user = User.new(params[:user])
@user.admin = params[:user][:admin] || false @user.admin = params[:user][:admin] || false
@user.login = params[:user][:login] @user.login = params[:user][:login]
@user.password, @user.password_confirmation = params[:password], params[:password_confirmation] unless @user.auth_source_id @user.password, @user.password_confirmation = params[:password], params[:password_confirmation] unless @user.auth_source_id
@custom_values = UserCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x, :customized => @user, :value => (params[:custom_fields] ? params["custom_fields"][x.id.to_s] : nil)) } @custom_values = UserCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x, :customized => @user, :value => params["custom_fields"][x.id.to_s]) }
@user.custom_values = @custom_values @user.custom_values = @custom_values
if @user.save if @user.save
Mailer.deliver_account_information(@user, params[:password]) if params[:send_information]
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
redirect_to :action => 'list' redirect_to :action => 'list'
end end
end end
@auth_sources = AuthSource.find(:all) @auth_sources = AuthSource.find(:all)
end end
def edit def edit
@user = User.find(params[:id]) @user = User.find(params[:id])
if request.get? if request.get?
@custom_values = UserCustomField.find(:all).collect { |x| @user.custom_values.find_by_custom_field_id(x.id) || CustomValue.new(:custom_field => x) } @custom_values = UserCustomField.find(:all).collect { |x| @user.custom_values.find_by_custom_field_id(x.id) || CustomValue.new(:custom_field => x) }
else else
@user.admin = params[:user][:admin] if params[:user][:admin] @user.admin = params[:user][:admin] if params[:user][:admin]
@user.login = params[:user][:login] if params[:user][:login] @user.login = params[:user][:login] if params[:user][:login]
@user.password, @user.password_confirmation = params[:password], params[:password_confirmation] unless params[:password].nil? or params[:password].empty? or @user.auth_source_id @user.password, @user.password_confirmation = params[:password], params[:password_confirmation] unless params[:password].nil? or params[:password].empty? or @user.auth_source_id
if params[:custom_fields] if params[:custom_fields]
@custom_values = UserCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x, :customized => @user, :value => params["custom_fields"][x.id.to_s]) } @custom_values = UserCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x, :customized => @user, :value => params["custom_fields"][x.id.to_s]) }
@user.custom_values = @custom_values @user.custom_values = @custom_values
end end
if @user.update_attributes(params[:user]) if @user.update_attributes(params[:user])
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'list' redirect_to :action => 'list'
end end
end end
@auth_sources = AuthSource.find(:all) @auth_sources = AuthSource.find(:all)
@roles = Role.find_all_givable @roles = Role.find(:all, :order => 'position')
@projects = Project.find(:all, :order => 'name', :conditions => "status=#{Project::STATUS_ACTIVE}") - @user.projects @projects = Project.find(:all) - @user.projects
@membership ||= Member.new @membership ||= Member.new
end end
def edit_membership def edit_membership
@user = User.find(params[:id]) @user = User.find(params[:id])
@membership = params[:membership_id] ? Member.find(params[:membership_id]) : Member.new(:user => @user) @membership = params[:membership_id] ? Member.find(params[:membership_id]) : Member.new(:user => @user)
@membership.attributes = params[:membership] @membership.attributes = params[:membership]
if request.post? and @membership.save if request.post? and @membership.save
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
end end
redirect_to :action => 'edit', :id => @user and return redirect_to :action => 'edit', :id => @user and return
end end
def destroy_membership def destroy_membership
@user = User.find(params[:id]) @user = User.find(params[:id])
if request.post? and Member.find(params[:membership_id]).destroy if request.post? and Member.find(params[:membership_id]).destroy
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
end end
redirect_to :action => 'edit', :id => @user and return redirect_to :action => 'edit', :id => @user and return
end end
def destroy def destroy
User.find(params[:id]).destroy User.find(params[:id]).destroy
redirect_to :action => 'list'
rescue
flash[:notice] = "Unable to delete user"
redirect_to :action => 'list' redirect_to :action => 'list'
rescue end
flash[:error] = "Unable to delete user"
redirect_to :action => 'list'
end
end end
+47 -49
View File
@@ -1,26 +1,24 @@
# redMine - project management software # redMine - project management software
# Copyright (C) 2006 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# 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 VersionsController < ApplicationController
layout 'base'
before_filter :find_project, :authorize
class VersionsController < ApplicationController
layout 'base'
before_filter :find_project, :authorize
cache_sweeper :version_sweeper, :only => [ :edit, :destroy ]
def edit def edit
if request.post? and @version.update_attributes(params[:version]) if request.post? and @version.update_attributes(params[:version])
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
@@ -28,33 +26,33 @@ class VersionsController < ApplicationController
end end
end end
def destroy def destroy
@version.destroy @version.destroy
redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
rescue rescue
flash[:error] = "Unable to delete version" flash[:notice] = "Unable to delete version"
redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
end end
def download def download
@attachment = @version.attachments.find(params[:attachment_id]) @attachment = @version.attachments.find(params[:attachment_id])
@attachment.increment_download @attachment.increment_download
send_file @attachment.diskfile, :filename => @attachment.filename send_file @attachment.diskfile, :filename => @attachment.filename
rescue rescue
render_404 render_404
end end
def destroy_file def destroy_file
@version.attachments.find(params[:attachment_id]).destroy @version.attachments.find(params[:attachment_id]).destroy
flash[:notice] = l(:notice_successful_delete) flash[:notice] = l(:notice_successful_delete)
redirect_to :controller => 'projects', :action => 'list_files', :id => @project redirect_to :controller => 'projects', :action => 'list_files', :id => @project
end end
private private
def find_project def find_project
@version = Version.find(params[:id]) @version = Version.find(params[:id])
@project = @version.project @project = @version.project
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
render_404 render_404
end end
end end
-49
View File
@@ -1,49 +0,0 @@
# redMine - project management software
# Copyright (C) 2006-2007 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 WatchersController < ApplicationController
layout 'base'
before_filter :require_login, :find_project, :check_project_privacy
def add
user = User.current
@watched.add_watcher(user)
respond_to do |format|
format.html { render :text => 'Watcher added.', :layout => true }
format.js { render(:update) {|page| page.replace_html 'watcher', watcher_link(@watched, user)} }
end
end
def remove
user = User.current
@watched.remove_watcher(user)
respond_to do |format|
format.html { render :text => 'Watcher removed.', :layout => true }
format.js { render(:update) {|page| page.replace_html 'watcher', watcher_link(@watched, user)} }
end
end
private
def find_project
klass = Object.const_get(params[:object_type].camelcase)
return false unless klass.respond_to?('watched_by')
@watched = klass.find(params[:object_id])
@project = @watched.project
rescue
render_404
end
end
+23 -23
View File
@@ -1,25 +1,25 @@
# redMine - project management software # redMine - project management software
# Copyright (C) 2006 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# 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 WelcomeController < ApplicationController class WelcomeController < ApplicationController
layout 'base' layout 'base'
def index def index
@news = News.latest logged_in_user @news = News.latest logged_in_user
@projects = Project.latest logged_in_user @projects = Project.latest logged_in_user
end end
end end
-180
View File
@@ -1,180 +0,0 @@
# redMine - project management software
# Copyright (C) 2006-2007 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.
require 'diff'
class WikiController < ApplicationController
layout 'base'
before_filter :find_wiki, :authorize
verify :method => :post, :only => [:destroy, :destroy_attachment], :redirect_to => { :action => :index }
helper :attachments
include AttachmentsHelper
# display a page (in editing mode if it doesn't exist)
def index
page_title = params[:page]
@page = @wiki.find_or_new_page(page_title)
if @page.new_record?
if User.current.allowed_to?(:edit_wiki_pages, @project)
edit
render :action => 'edit'
else
render_404
end
return
end
@content = @page.content_for_version(params[:version])
if params[:export] == 'html'
export = render_to_string :action => 'export', :layout => false
send_data(export, :type => 'text/html', :filename => "#{@page.title}.html")
return
elsif params[:export] == 'txt'
send_data(@content.text, :type => 'text/plain', :filename => "#{@page.title}.txt")
return
end
render :action => 'show'
end
# edit an existing page or a new one
def edit
@page = @wiki.find_or_new_page(params[:page])
@page.content = WikiContent.new(:page => @page) if @page.new_record?
@content = @page.content_for_version(params[:version])
@content.text = "h1. #{@page.pretty_title}" if @content.text.blank?
# don't keep previous comment
@content.comments = nil
if request.post?
if !@page.new_record? && @content.text == params[:content][:text]
# don't save if text wasn't changed
redirect_to :action => 'index', :id => @project, :page => @page.title
return
end
#@content.text = params[:content][:text]
#@content.comments = params[:content][:comments]
@content.attributes = params[:content]
@content.author = logged_in_user
# if page is new @page.save will also save content, but not if page isn't a new record
if (@page.new_record? ? @page.save : @content.save)
redirect_to :action => 'index', :id => @project, :page => @page.title
end
end
rescue ActiveRecord::StaleObjectError
# Optimistic locking exception
flash[:error] = l(:notice_locking_conflict)
end
# rename a page
def rename
@page = @wiki.find_page(params[:page])
@page.redirect_existing_links = true
# used to display the *original* title if some AR validation errors occur
@original_title = @page.pretty_title
if request.post? && @page.update_attributes(params[:wiki_page])
flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'index', :id => @project, :page => @page.title
end
end
# show page history
def history
@page = @wiki.find_page(params[:page])
@version_count = @page.content.versions.count
@version_pages = Paginator.new self, @version_count, 25, params['p']
# don't load text
@versions = @page.content.versions.find :all,
:select => "id, author_id, comments, updated_on, version",
:order => 'version DESC',
:limit => @version_pages.items_per_page + 1,
:offset => @version_pages.current.offset
render :layout => false if request.xhr?
end
def diff
@page = @wiki.find_page(params[:page])
@diff = @page.diff(params[:version], params[:version_from])
render_404 unless @diff
end
# remove a wiki page and its history
def destroy
@page = @wiki.find_page(params[:page])
@page.destroy if @page
redirect_to :action => 'special', :id => @project, :page => 'Page_index'
end
# display special pages
def special
page_title = params[:page].downcase
case page_title
# show pages index, sorted by title
when 'page_index', 'date_index'
# eager load information about last updates, without loading text
@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}
# export wiki to a single html file
when 'export'
@pages = @wiki.pages.find :all, :order => 'title'
export = render_to_string :action => 'export_multiple', :layout => false
send_data(export, :type => 'text/html', :filename => "wiki.html")
return
else
# requested special page doesn't exist, redirect to default page
redirect_to :action => 'index', :id => @project, :page => nil and return
end
render :action => "special_#{page_title}"
end
def preview
page = @wiki.find_page(params[:page])
@attachements = page.attachments if page
@text = params[:content][:text]
render :partial => 'common/preview'
end
def add_attachment
@page = @wiki.find_page(params[:page])
# Save the attachments
params[:attachments].each { |file|
next unless file.size > 0
a = Attachment.create(:container => @page, :file => file, :author => logged_in_user)
} if params[:attachments] and params[:attachments].is_a? Array
redirect_to :action => 'index', :page => @page.title
end
def destroy_attachment
@page = @wiki.find_page(params[:page])
@page.attachments.find(params[:attachment_id]).destroy
redirect_to :action => 'index', :page => @page.title
end
private
def find_wiki
@project = Project.find(params[:id])
@wiki = @project.wiki
render_404 unless @wiki
rescue ActiveRecord::RecordNotFound
render_404
end
end
-44
View File
@@ -1,44 +0,0 @@
# redMine - project management software
# Copyright (C) 2006-2007 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 WikisController < ApplicationController
layout 'base'
before_filter :find_project, :authorize
# Create or update a project's wiki
def edit
@wiki = @project.wiki || Wiki.new(:project => @project)
@wiki.attributes = params[:wiki]
@wiki.save if @request.post?
render(:update) {|page| page.replace_html "tab-content-wiki", :partial => 'projects/settings/wiki'}
end
# Delete a project's wiki
def destroy
if request.post? && params[:confirm] && @project.wiki
@project.wiki.destroy
redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'wiki'
end
end
private
def find_project
@project = Project.find(params[:id])
rescue ActiveRecord::RecordNotFound
render_404
end
end
+17 -17
View File
@@ -1,19 +1,19 @@
# redMine - project management software # redMine - project management software
# Copyright (C) 2006 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# 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 AccountHelper module AccountHelper
end end
+17 -21
View File
@@ -1,23 +1,19 @@
# redMine - project management software # redMine - project management software
# Copyright (C) 2006 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# 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 AdminHelper module AdminHelper
def project_status_options_for_select(selected)
options_for_select([[l(:label_all), "*"],
[l(:status_active), 1]], selected)
end
end end
+192 -368
View File
@@ -1,369 +1,193 @@
# 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
# 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
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# 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 ApplicationHelper module ApplicationHelper
def current_role # Return current logged in user or nil
@current_role ||= User.current.role_for_project(@project) def loggedin?
end @logged_in_user
end
# Return true if user is authorized for controller/action, otherwise false
def authorize_for(controller, action) # Return true if user is logged in and is admin, otherwise false
User.current.allowed_to?({:controller => controller, :action => action}, @project) def admin_loggedin?
end @logged_in_user and @logged_in_user.admin?
end
# Display a link if user is authorized
def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference) # Return true if user is authorized for controller/action, otherwise false
link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action]) def authorize_for(controller, action)
end # check if action is allowed on public projects
if @project.is_public? and Permission.allowed_to_public "%s/%s" % [ controller, action ]
# Display a link to user's account page return true
def link_to_user(user) end
link_to user.name, :controller => 'account', :action => 'show', :id => user # check if user is authorized
end if @logged_in_user and (@logged_in_user.admin? or Permission.allowed_to_role( "%s/%s" % [ controller, action ], @logged_in_user.role_for_project(@project.id) ) )
return true
def link_to_issue(issue) end
link_to "#{issue.tracker.name} ##{issue.id}", :controller => "issues", :action => "show", :id => issue return false
end end
def toggle_link(name, id, options={}) # Display a link if user is authorized
onclick = "Element.toggle('#{id}'); " def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ") link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller], options[:action])
onclick << "return false;" end
link_to(name, "#", :onclick => onclick)
end # Display a link to user's account page
def link_to_user(user)
def show_and_goto_link(name, id, options={}) link_to user.display_name, :controller => 'account', :action => 'show', :id => user
onclick = "Element.show('#{id}'); " end
onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
onclick << "location.href='##{id}-anchor'; " def image_to_function(name, function, html_options = {})
onclick << "return false;" html_options.symbolize_keys!
link_to(name, "#", options.merge(:onclick => onclick)) tag(:input, html_options.merge({
end :type => "image", :src => image_path(name),
:onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
def image_to_function(name, function, html_options = {}) }))
html_options.symbolize_keys! end
tag(:input, html_options.merge({
:type => "image", :src => image_path(name), def format_date(date)
:onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};" l_date(date) if date
})) end
end
def format_time(time)
def prompt_to_remote(name, text, param, url, html_options = {}) l_datetime(time) if time
html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;" end
link_to name, {}, html_options
end def day_name(day)
l(:general_day_names).split(',')[day-1]
def format_date(date) end
return nil unless date
@date_format ||= (Setting.date_format.to_i == 0 ? l(:general_fmt_date) : "%Y-%m-%d") def month_name(month)
date.strftime(@date_format) l(:actionview_datehelper_select_month_names).split(',')[month-1]
end end
def format_time(time) def pagination_links_full(paginator, options={}, html_options={})
return nil unless time html = ''
@date_format_setting ||= Setting.date_format.to_i html << link_to_remote(('&#171; ' + l(:label_previous)),
time = time.to_time if time.is_a?(String) {:update => "content", :url => { :page => paginator.current.previous }},
@date_format_setting == 0 ? l_datetime(time) : (time.strftime("%Y-%m-%d") + ' ' + l_time(time)) {:href => url_for(:action => 'list', :params => params.merge({:page => paginator.current.previous}))}) + ' ' if paginator.current.previous
end
html << (pagination_links_each(paginator, options) do |n|
def authoring(created, author) link_to_remote(n.to_s,
time_tag = content_tag('acronym', distance_of_time_in_words(Time.now, created), :title => format_time(created)) {:url => {:action => 'list', :params => params.merge({:page => n})}, :update => 'content'},
l(:label_added_time_by, author.name, time_tag) {:href => url_for(:action => 'list', :params => params.merge({:page => n}))})
end end || '')
def day_name(day) html << ' ' + link_to_remote((l(:label_next) + ' &#187;'),
l(:general_day_names).split(',')[day-1] {:update => "content", :url => { :page => paginator.current.next }},
end {:href => url_for(:action => 'list', :params => params.merge({:page => paginator.current.next}))}) if paginator.current.next
html
def month_name(month) end
l(:actionview_datehelper_select_month_names).split(',')[month-1]
end def textilizable(text)
(Setting.text_formatting == 'textile') && (ActionView::Helpers::TextHelper.method_defined? "textilize") ? RedCloth.new(h(text)).to_html : simple_format(auto_link(h(text)))
def pagination_links_full(paginator, options={}, html_options={}) end
page_param = options.delete(:page_param) || :page
def error_messages_for(object_name, options = {})
html = '' options = options.symbolize_keys
html << link_to_remote(('&#171; ' + l(:label_previous)), object = instance_variable_get("@#{object_name}")
{:update => "content", :url => options.merge(page_param => paginator.current.previous)}, if object && !object.errors.empty?
{:href => url_for(:params => options.merge(page_param => paginator.current.previous))}) + ' ' if paginator.current.previous # build full_messages here with controller current language
full_messages = []
html << (pagination_links_each(paginator, options) do |n| object.errors.each do |attr, msg|
link_to_remote(n.to_s, next if msg.nil?
{:url => {:params => options.merge(page_param => n)}, :update => 'content'}, if attr == "base"
{:href => url_for(:params => options.merge(page_param => n))}) full_messages << l(msg)
end || '') else
full_messages << "&#171; " + (l_has_string?("field_" + attr) ? l("field_" + attr) : object.class.human_attribute_name(attr)) + " &#187; " + l(msg) unless attr == "custom_values"
html << ' ' + link_to_remote((l(:label_next) + ' &#187;'), end
{:update => "content", :url => options.merge(page_param => paginator.current.next)}, end
{:href => url_for(:params => options.merge(page_param => paginator.current.next))}) if paginator.current.next # retrieve custom values error messages
html if object.errors[:custom_values]
end object.custom_values.each do |v|
v.errors.each do |attr, msg|
def set_html_title(text) next if msg.nil?
@html_header_title = text full_messages << "&#171; " + v.custom_field.name + " &#187; " + l(msg)
end end
end
def html_title end
title = [] content_tag("div",
title << @project.name if @project content_tag(
title << @html_header_title options[:header_tag] || "h2", lwr(:gui_validation_error, full_messages.length) + " :"
title << Setting.app_title ) +
title.compact.join(' - ') content_tag("ul", full_messages.collect { |msg| content_tag("li", msg) }),
end "id" => options[:id] || "errorExplanation", "class" => options[:class] || "errorExplanation"
)
ACCESSKEYS = {:edit => 'e', else
:preview => 'r', ""
:quick_search => 'f', end
:search => '4', end
}.freeze
def lang_options_for_select(blank=true)
def accesskey(s) (blank ? [["(auto)", ""]] : []) +
ACCESSKEYS[s] (GLoc.valid_languages.sort {|x,y| x.to_s <=> y.to_s }).collect {|lang| [ l_lang_name(lang.to_s, lang), lang.to_s]}
end end
# format text according to system settings def label_tag_for(name, option_tags = nil, options = {})
def textilizable(text, options = {}) label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
return "" if text.blank? content_tag("label", label_text)
end
# when using an image link, try to use an attachment, if possible
attachments = options[:attachments] def labelled_tabular_form_for(name, object, options, &proc)
if attachments options[:html] ||= {}
text = text.gsub(/!([<>=]*)(\S+\.(gif|jpg|jpeg|png))!/) do |m| options[:html].store :class, "tabular"
align = $1 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
filename = $2 end
rf = Regexp.new(filename, Regexp::IGNORECASE)
# search for the picture in attachments def check_all_links(form_name)
if found = attachments.detect { |att| att.filename =~ rf } link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
image_url = url_for :controller => 'attachments', :action => 'download', :id => found.id " | " +
"!#{align}#{image_url}!" link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
else end
"!#{align}#{filename}!"
end def calendar_for(field_id)
end image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
end javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
end
text = (Setting.text_formatting == 'textile') ?
Redmine::WikiFormatting.to_html(text) : simple_format(auto_link(h(text)))
# different methods for formatting wiki links
case options[:wiki_links]
when :local
# used for local links to html files
format_wiki_link = Proc.new {|project, title| "#{title}.html" }
when :anchor
# used for single-file wiki export
format_wiki_link = Proc.new {|project, title| "##{title}" }
else
format_wiki_link = Proc.new {|project, title| url_for :controller => 'wiki', :action => 'index', :id => project, :page => title }
end
project = options[:project] || @project
# turn wiki links into html links
# example:
# [[mypage]]
# [[mypage|mytext]]
# wiki links can refer other project wikis, using project name or identifier:
# [[project:]] -> wiki starting page
# [[project:|mytext]]
# [[project:mypage]]
# [[project:mypage|mytext]]
text = text.gsub(/\[\[([^\]\|]+)(\|([^\]\|]+))?\]\]/) do |m|
link_project = project
page = $1
title = $3
if page =~ /^([^\:]+)\:(.*)$/
link_project = Project.find_by_name($1) || Project.find_by_identifier($1)
page = title || $2
title = $1 if page.blank?
end
if link_project && link_project.wiki
# check if page exists
wiki_page = link_project.wiki.find_page(page)
link_to((title || page), format_wiki_link.call(link_project, Wiki.titleize(page)),
:class => ('wiki-page' + (wiki_page ? '' : ' new')))
else
# project or wiki doesn't exist
title || page
end
end
# turn issue and revision ids into links
# example:
# #52 -> <a href="/issues/show/52">#52</a>
# r52 -> <a href="/repositories/revision/6?rev=52">r52</a> (project.id is 6)
text = text.gsub(%r{([\s,-^])(#|r)(\d+)(?=[[:punct:]]|\s|<|$)}) do |m|
leading, otype, oid = $1, $2, $3
link = nil
if otype == 'r'
if project && (changeset = project.changesets.find_by_revision(oid))
link = link_to("r#{oid}", {:controller => 'repositories', :action => 'revision', :id => project.id, :rev => oid}, :class => 'changeset',
:title => truncate(changeset.comments, 100))
end
else
if issue = Issue.find_by_id(oid.to_i, :include => [:project, :status], :conditions => Project.visible_by(User.current))
link = link_to("##{oid}", {:controller => 'issues', :action => 'show', :id => oid}, :class => 'issue',
:title => "#{truncate(issue.subject, 100)} (#{issue.status.name})")
link = content_tag('del', link) if issue.closed?
end
end
leading + (link || "#{otype}#{oid}")
end
text
end
# Same as Rails' simple_format helper without using paragraphs
def simple_format_without_paragraph(text)
text.to_s.
gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
end
def error_messages_for(object_name, options = {})
options = options.symbolize_keys
object = instance_variable_get("@#{object_name}")
if object && !object.errors.empty?
# build full_messages here with controller current language
full_messages = []
object.errors.each do |attr, msg|
next if msg.nil?
msg = msg.first if msg.is_a? Array
if attr == "base"
full_messages << l(msg)
else
full_messages << "&#171; " + (l_has_string?("field_" + attr) ? l("field_" + attr) : object.class.human_attribute_name(attr)) + " &#187; " + l(msg) unless attr == "custom_values"
end
end
# retrieve custom values error messages
if object.errors[:custom_values]
object.custom_values.each do |v|
v.errors.each do |attr, msg|
next if msg.nil?
msg = msg.first if msg.is_a? Array
full_messages << "&#171; " + v.custom_field.name + " &#187; " + l(msg)
end
end
end
content_tag("div",
content_tag(
options[:header_tag] || "span", lwr(:gui_validation_error, full_messages.length) + ":"
) +
content_tag("ul", full_messages.collect { |msg| content_tag("li", msg) }),
"id" => options[:id] || "errorExplanation", "class" => options[:class] || "errorExplanation"
)
else
""
end
end
def lang_options_for_select(blank=true)
(blank ? [["(auto)", ""]] : []) +
GLoc.valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.first <=> y.first }
end
def label_tag_for(name, option_tags = nil, options = {})
label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
content_tag("label", label_text)
end
def labelled_tabular_form_for(name, object, options, &proc)
options[:html] ||= {}
options[:html].store :class, "tabular"
form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
end
def check_all_links(form_name)
link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
" | " +
link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
end
def context_menu_link(name, url, options={})
options[:class] ||= ''
if options.delete(:selected)
options[:class] << ' icon-checked disabled'
options[:disabled] = true
end
if options.delete(:disabled)
options.delete(:method)
options.delete(:confirm)
options.delete(:onclick)
options[:class] << ' disabled'
url = '#'
end
link_to name, url, options
end
def calendar_for(field_id)
image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
end
def wikitoolbar_for(field_id)
return '' unless Setting.text_formatting == 'textile'
javascript_include_tag('jstoolbar') + javascript_tag("var toolbar = new jsToolBar($('#{field_id}')); toolbar.draw();")
end
def content_for(name, content = nil, &block)
@has_content ||= {}
@has_content[name] = true
super(name, content, &block)
end
def has_content?(name)
(@has_content && @has_content[name]) || false
end
end end
class TabularFormBuilder < ActionView::Helpers::FormBuilder class TabularFormBuilder < ActionView::Helpers::FormBuilder
include GLoc include GLoc
def initialize(object_name, object, template, options, proc) def initialize(object_name, object, template, options, proc)
set_language_if_valid options.delete(:lang) set_language_if_valid options.delete(:lang)
@object_name, @object, @template, @options, @proc = object_name, object, template, options, proc @object_name, @object, @template, @options, @proc = object_name, object, template, options, proc
end end
(field_helpers - %w(radio_button hidden_field) + %w(date_select)).each do |selector| (field_helpers - %w(radio_button hidden_field) + %w(date_select)).each do |selector|
src = <<-END_SRC src = <<-END_SRC
def #{selector}(field, options = {}) def #{selector}(field, options = {})
return super if options.delete :no_label return super if options.delete :no_label
label_text = l(options[:label]) if options[:label] label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
label_text ||= l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) label = @template.content_tag("label", label_text,
label_text << @template.content_tag("span", " *", :class => "required") if options.delete(:required) :class => (@object && @object.errors[field] ? "error" : nil),
label = @template.content_tag("label", label_text, :for => (@object_name.to_s + "_" + field.to_s))
:class => (@object && @object.errors[field] ? "error" : nil), label + super
:for => (@object_name.to_s + "_" + field.to_s)) end
label + super END_SRC
end class_eval src, __FILE__, __LINE__
END_SRC end
class_eval src, __FILE__, __LINE__
end def select(field, choices, options = {}, html_options = {})
label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
def select(field, choices, options = {}, html_options = {}) label = @template.content_tag("label", label_text,
label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "") :class => (@object && @object.errors[field] ? "error" : nil),
label = @template.content_tag("label", label_text, :for => (@object_name.to_s + "_" + field.to_s))
:class => (@object && @object.errors[field] ? "error" : nil), label + super
:for => (@object_name.to_s + "_" + field.to_s)) end
label + super
end end
end
-25
View File
@@ -1,25 +0,0 @@
# redMine - project management software
# Copyright (C) 2006-2007 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.
module AttachmentsHelper
# displays the links to a collection of attachments
def link_to_attachments(attachments, options = {})
if attachments.any?
render :partial => 'attachments/links', :locals => {:attachments => attachments, :options => options}
end
end
end
+75 -75
View File
@@ -1,77 +1,77 @@
# redMine - project management software # redMine - project management software
# Copyright (C) 2006 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# 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 CustomFieldsHelper module CustomFieldsHelper
# Return custom field html tag corresponding to its format # Return custom field html tag corresponding to its format
def custom_field_tag(custom_value) def custom_field_tag(custom_value)
custom_field = custom_value.custom_field custom_field = custom_value.custom_field
field_name = "custom_fields[#{custom_field.id}]" field_name = "custom_fields[#{custom_field.id}]"
field_id = "custom_fields_#{custom_field.id}" field_id = "custom_fields_#{custom_field.id}"
case custom_field.field_format case custom_field.field_format
when "date" when "string", "int"
text_field('custom_value', 'value', :name => field_name, :id => field_id, :size => 10) + text_field 'custom_value', 'value', :name => field_name, :id => field_id
calendar_for(field_id) when "date"
when "text" text_field('custom_value', 'value', :name => field_name, :id => field_id, :size => 10) +
text_area 'custom_value', 'value', :name => field_name, :id => field_id, :cols => 60, :rows => 3 calendar_for(field_id)
when "bool" when "text"
check_box 'custom_value', 'value', :name => field_name, :id => field_id text_area 'custom_value', 'value', :name => field_name, :id => field_id, :cols => 60, :rows => 3
when "list" when "bool"
select 'custom_value', 'value', custom_field.possible_values, { :include_blank => true }, :name => field_name, :id => field_id check_box 'custom_value', 'value', :name => field_name, :id => field_id
else when "list"
text_field 'custom_value', 'value', :name => field_name, :id => field_id select 'custom_value', 'value', custom_field.possible_values, { :include_blank => true }, :name => field_name, :id => field_id
end end
end end
# Return custom field label tag # Return custom field label tag
def custom_field_label_tag(custom_value) def custom_field_label_tag(custom_value)
content_tag "label", custom_value.custom_field.name + content_tag "label", custom_value.custom_field.name +
(custom_value.custom_field.is_required? ? " <span class=\"required\">*</span>" : ""), (custom_value.custom_field.is_required? ? " <span class=\"required\">*</span>" : ""),
:for => "custom_fields_#{custom_value.custom_field.id}", :for => "custom_fields_#{custom_value.custom_field.id}",
:class => (custom_value.errors.empty? ? nil : "error" ) :class => (custom_value.errors.empty? ? nil : "error" )
end end
# Return custom field tag with its label tag # Return custom field tag with its label tag
def custom_field_tag_with_label(custom_value) def custom_field_tag_with_label(custom_value)
custom_field_label_tag(custom_value) + custom_field_tag(custom_value) custom_field_label_tag(custom_value) + custom_field_tag(custom_value)
end end
# Return a string used to display a custom value # Return a string used to display a custom value
def show_value(custom_value) def show_value(custom_value)
return "" unless custom_value return "" unless custom_value
format_value(custom_value.value, custom_value.custom_field.field_format) format_value(custom_value.value, custom_value.custom_field.field_format)
end end
# Return a string used to display a custom value # Return a string used to display a custom value
def format_value(value, field_format) def format_value(value, field_format)
return "" unless value && !value.empty? return "" unless value && !value.empty?
case field_format case field_format
when "date" when "date"
begin; l_date(value.to_date); rescue; value end begin; l_date(value.to_date); rescue; value end
when "bool" when "bool"
l_YesNo(value == "1") l_YesNo(value == "1")
else else
value value
end end
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 def custom_field_formats_for_select
CustomField::FIELD_FORMATS.sort {|a,b| a[1][:order]<=>b[1][:order]}.collect { |k| [ l(k[1][:name]), k[0] ] } CustomField::FIELD_FORMATS.sort {|a,b| a[1][:order]<=>b[1][:order]}.collect { |k| [ l(k[1][:name]), k[0] ] }
end end
end end
+17 -17
View File
@@ -1,19 +1,19 @@
# redMine - project management software # redMine - project management software
# Copyright (C) 2006 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# 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 DocumentsHelper module DocumentsHelper
end end
+17 -17
View File
@@ -1,19 +1,19 @@
# redMine - project management software # redMine - project management software
# Copyright (C) 2006 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# 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 EnumerationsHelper module EnumerationsHelper
end end
@@ -1,19 +1,19 @@
# redMine - project management software # redMine - project management software
# Copyright (C) 2006-2007 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# 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 BoardsHelper module HelpHelper
end end
+52 -79
View File
@@ -1,80 +1,53 @@
# redMine - project management software # redMine - project management software
# Copyright (C) 2006 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# 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 'rfpdf/chinese'
module IfpdfHelper
module IfpdfHelper
class IFPDF < FPDF
class IFPDF < FPDF
include GLoc attr_accessor :footer_date
attr_accessor :footer_date
def initialize
def initialize(lang) super
super() SetCreator("redMine #{Redmine::VERSION}")
set_language_if_valid lang end
case current_language
when :ja def Cell(w,h=0,txt='',border=0,ln=0,align='',fill=0,link='')
extend(PDF_Japanese) @ic ||= Iconv.new('ISO-8859-1', 'UTF-8')
AddSJISFont() txt = begin
@font_for_content = 'SJIS' @ic.iconv(txt)
@font_for_footer = 'SJIS' rescue
when :zh txt
extend(PDF_Chinese) end
AddBig5Font() super w,h,txt,border,ln,align,fill,link
@font_for_content = 'Big5' end
@font_for_footer = 'Big5'
else def Footer
@font_for_content = 'Arial' SetFont('Helvetica', 'I', 8)
@font_for_footer = 'Helvetica' SetY(-15)
end SetX(15)
SetCreator("redMine #{Redmine::VERSION}") Cell(0, 5, @footer_date, 0, 0, 'L')
SetFont(@font_for_content) SetY(-15)
end SetX(-30)
Cell(0, 5, PageNo().to_s + '/{nb}', 0, 0, 'C')
def SetFontStyle(style, size) end
SetFont(@font_for_content, style, size)
end
def Cell(w,h=0,txt='',border=0,ln=0,align='',fill=0,link='')
@ic ||= Iconv.new(l(:general_pdf_encoding), 'UTF-8')
# these quotation marks are not correctly rendered in the pdf
txt = txt.gsub(/[“”]/, '"') if txt
txt = begin
# 0x5c char handling
txtar = txt.split('\\')
txtar << '' if txt[-1] == ?\\
txtar.collect {|x| @ic.iconv(x)}.join('\\').gsub(/\\/, "\\\\\\\\")
rescue
txt
end || ''
super w,h,txt,border,ln,align,fill,link
end
def Footer
SetFont(@font_for_footer, 'I', 8)
SetY(-15)
SetX(15)
Cell(0, 5, @footer_date, 0, 0, 'L')
SetY(-15)
SetX(-30)
Cell(0, 5, PageNo().to_s + '/{nb}', 0, 0, 'C')
end
end end
end end
+17 -17
View File
@@ -1,19 +1,19 @@
# redMine - project management software # redMine - project management software
# Copyright (C) 2006 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# 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 IssueCategoriesHelper module IssueCategoriesHelper
end end
-23
View File
@@ -1,23 +0,0 @@
# redMine - project management software
# Copyright (C) 2006-2007 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.
module IssueRelationsHelper
def collection_for_relation_type_select
values = IssueRelation::TYPES
values.keys.sort{|x,y| values[x][:order] <=> values[y][:order]}.collect{|k| [l(values[k][:name]), k]}
end
end
+17 -17
View File
@@ -1,19 +1,19 @@
# redMine - project management software # redMine - project management software
# Copyright (C) 2006 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# 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 IssueStatusesHelper module IssueStatusesHelper
end end
+72 -102
View File
@@ -1,104 +1,74 @@
# redMine - project management software # redMine - project management software
# Copyright (C) 2006 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# 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 IssuesHelper module IssuesHelper
def render_issue_tooltip(issue) def show_detail(detail, no_html=false)
@cached_label_start_date ||= l(:field_start_date) case detail.property
@cached_label_due_date ||= l(:field_due_date) when 'attr'
@cached_label_assigned_to ||= l(:field_assigned_to) label = l(("field_" + detail.prop_key.to_s.gsub(/\_id$/, "")).to_sym)
@cached_label_priority ||= l(:field_priority) case detail.prop_key
when 'due_date', 'start_date'
link_to_issue(issue) + ": #{h(issue.subject)}<br /><br />" + value = format_date(detail.value.to_date) if detail.value
"<strong>#{@cached_label_start_date}</strong>: #{format_date(issue.start_date)}<br />" + old_value = format_date(detail.old_value.to_date) if detail.old_value
"<strong>#{@cached_label_due_date}</strong>: #{format_date(issue.due_date)}<br />" + when 'status_id'
"<strong>#{@cached_label_assigned_to}</strong>: #{issue.assigned_to}<br />" + s = IssueStatus.find_by_id(detail.value) and value = s.name if detail.value
"<strong>#{@cached_label_priority}</strong>: #{issue.priority.name}" s = IssueStatus.find_by_id(detail.old_value) and old_value = s.name if detail.old_value
end when 'assigned_to_id'
u = User.find_by_id(detail.value) and value = u.name if detail.value
def show_detail(detail, no_html=false) u = User.find_by_id(detail.old_value) and old_value = u.name if detail.old_value
case detail.property when 'priority_id'
when 'attr' e = Enumeration.find_by_id(detail.value) and value = e.name if detail.value
label = l(("field_" + detail.prop_key.to_s.gsub(/\_id$/, "")).to_sym) e = Enumeration.find_by_id(detail.old_value) and old_value = e.name if detail.old_value
case detail.prop_key when 'category_id'
when 'due_date', 'start_date' c = IssueCategory.find_by_id(detail.value) and value = c.name if detail.value
value = format_date(detail.value.to_date) if detail.value c = IssueCategory.find_by_id(detail.old_value) and old_value = c.name if detail.old_value
old_value = format_date(detail.old_value.to_date) if detail.old_value when 'fixed_version_id'
when 'status_id' v = Version.find_by_id(detail.value) and value = v.name if detail.value
s = IssueStatus.find_by_id(detail.value) and value = s.name if detail.value v = Version.find_by_id(detail.old_value) and old_value = v.name if detail.old_value
s = IssueStatus.find_by_id(detail.old_value) and old_value = s.name if detail.old_value end
when 'assigned_to_id' when 'cf'
u = User.find_by_id(detail.value) and value = u.name if detail.value custom_field = CustomField.find_by_id(detail.prop_key)
u = User.find_by_id(detail.old_value) and old_value = u.name if detail.old_value if custom_field
when 'priority_id' label = custom_field.name
e = Enumeration.find_by_id(detail.value) and value = e.name if detail.value value = format_value(detail.value, custom_field.field_format) if detail.value
e = Enumeration.find_by_id(detail.old_value) and old_value = e.name if detail.old_value old_value = format_value(detail.old_value, custom_field.field_format) if detail.old_value
when 'category_id' end
c = IssueCategory.find_by_id(detail.value) and value = c.name if detail.value end
c = IssueCategory.find_by_id(detail.old_value) and old_value = c.name if detail.old_value
when 'fixed_version_id' label ||= detail.prop_key
v = Version.find_by_id(detail.value) and value = v.name if detail.value value ||= detail.value
v = Version.find_by_id(detail.old_value) and old_value = v.name if detail.old_value old_value ||= detail.old_value
end
when 'cf' unless no_html
custom_field = CustomField.find_by_id(detail.prop_key) label = content_tag('strong', label)
if custom_field old_value = content_tag("i", h(old_value)) if detail.old_value
label = custom_field.name old_value = content_tag("strike", old_value) if detail.old_value and (!detail.value or detail.value.empty?)
value = format_value(detail.value, custom_field.field_format) if detail.value value = content_tag("i", h(value)) if value
old_value = format_value(detail.old_value, custom_field.field_format) if detail.old_value end
end
when 'attachment' if detail.value and !detail.value.to_s.empty?
label = l(:label_attachment) if old_value
end label + " " + l(:text_journal_changed, old_value, value)
else
label ||= detail.prop_key label + " " + l(:text_journal_set_to, value)
value ||= detail.value end
old_value ||= detail.old_value else
label + " " + l(:text_journal_deleted) + " (#{old_value})"
unless no_html end
label = content_tag('strong', label)
old_value = content_tag("i", h(old_value)) if detail.old_value
old_value = content_tag("strike", old_value) if detail.old_value and (!detail.value or detail.value.empty?)
if detail.property == 'attachment' && !value.blank? && Attachment.find_by_id(detail.prop_key)
# Link to the attachment if it has not been removed
value = link_to(value, :controller => 'attachments', :action => 'download', :id => detail.prop_key)
else
value = content_tag("i", h(value)) if value
end
end
if !detail.value.blank?
case detail.property
when 'attr', 'cf'
if !detail.old_value.blank?
label + " " + l(:text_journal_changed, old_value, value)
else
label + " " + l(:text_journal_set_to, value)
end
when 'attachment'
"#{label} #{value} #{l(:label_added)}"
end
else
case detail.property
when 'attr', 'cf'
label + " " + l(:text_journal_deleted) + " (#{old_value})"
when 'attachment'
"#{label} #{old_value} #{l(:label_deleted)}"
end
end
end end
end end
+17 -17
View File
@@ -1,19 +1,19 @@
# redMine - project management software # redMine - project management software
# Copyright (C) 2006 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# 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 MembersHelper module MembersHelper
end end
-28
View File
@@ -1,28 +0,0 @@
# redMine - project management software
# Copyright (C) 2006-2007 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.
module MessagesHelper
def link_to_message(message)
return '' unless message
link_to h(truncate(message.subject, 60)), :controller => 'messages',
:action => 'show',
:board_id => message.board_id,
:id => message.root,
:anchor => (message.parent_id ? "message-#{message.id}" : nil)
end
end
+17 -17
View File
@@ -1,19 +1,19 @@
# redMine - project management software # redMine - project management software
# Copyright (C) 2006 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# 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 NewsHelper module NewsHelper
end end
+17 -197
View File
@@ -1,199 +1,19 @@
# redMine - project management software # redMine - project management software
# Copyright (C) 2006 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# 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 ProjectsHelper module ProjectsHelper
def link_to_version(version, options = {})
return '' unless version && version.is_a?(Version)
link_to version.name, {:controller => 'projects',
:action => 'roadmap',
:id => version.project_id,
:completed => (version.completed? ? 1 : nil),
:anchor => version.name
}, options
end
def project_settings_tabs
tabs = [{:name => 'info', :action => :edit_project, :partial => 'projects/edit', :label => :label_information_plural},
{:name => 'modules', :action => :select_project_modules, :partial => 'projects/settings/modules', :label => :label_module_plural},
{:name => 'members', :action => :manage_members, :partial => 'projects/settings/members', :label => :label_member_plural},
{:name => 'versions', :action => :manage_versions, :partial => 'projects/settings/versions', :label => :label_version_plural},
{:name => 'categories', :action => :manage_categories, :partial => 'projects/settings/issue_categories', :label => :label_issue_category_plural},
{:name => 'wiki', :action => :manage_wiki, :partial => 'projects/settings/wiki', :label => :label_wiki},
{:name => 'repository', :action => :manage_repository, :partial => 'projects/settings/repository', :label => :label_repository},
{:name => 'boards', :action => :manage_boards, :partial => 'projects/settings/boards', :label => :label_board_plural}
]
tabs.select {|tab| User.current.allowed_to?(tab[:action], @project)}
end
# Generates a gantt image
# Only defined if RMagick is avalaible
def gantt_image(events, date_from, months, zoom)
date_to = (date_from >> months)-1
show_weeks = zoom > 1
show_days = zoom > 2
subject_width = 320
header_heigth = 18
# width of one day in pixels
zoom = zoom*2
g_width = (date_to - date_from + 1)*zoom
g_height = 20 * events.length + 20
headers_heigth = (show_weeks ? 2*header_heigth : header_heigth)
height = g_height + headers_heigth
imgl = Magick::ImageList.new
imgl.new_image(subject_width+g_width+1, height)
gc = Magick::Draw.new
# Subjects
top = headers_heigth + 20
gc.fill('black')
gc.stroke('transparent')
gc.stroke_width(1)
events.each do |i|
gc.text(4, top + 2, (i.is_a?(Issue) ? i.subject : i.name))
top = top + 20
end
# Months headers
month_f = date_from
left = subject_width
months.times do
width = ((month_f >> 1) - month_f) * zoom
gc.fill('white')
gc.stroke('grey')
gc.stroke_width(1)
gc.rectangle(left, 0, left + width, height)
gc.fill('black')
gc.stroke('transparent')
gc.stroke_width(1)
gc.text(left.round + 8, 14, "#{month_f.year}-#{month_f.month}")
left = left + width
month_f = month_f >> 1
end
# Weeks headers
if show_weeks
left = subject_width
height = header_heigth
if date_from.cwday == 1
# date_from is monday
week_f = date_from
else
# find next monday after date_from
week_f = date_from + (7 - date_from.cwday + 1)
width = (7 - date_from.cwday + 1) * zoom
gc.fill('white')
gc.stroke('grey')
gc.stroke_width(1)
gc.rectangle(left, header_heigth, left + width, 2*header_heigth + g_height-1)
left = left + width
end
while week_f <= date_to
width = (week_f + 6 <= date_to) ? 7 * zoom : (date_to - week_f + 1) * zoom
gc.fill('white')
gc.stroke('grey')
gc.stroke_width(1)
gc.rectangle(left.round, header_heigth, left.round + width, 2*header_heigth + g_height-1)
gc.fill('black')
gc.stroke('transparent')
gc.stroke_width(1)
gc.text(left.round + 2, header_heigth + 14, week_f.cweek.to_s)
left = left + width
week_f = week_f+7
end
end
# Days details (week-end in grey)
if show_days
left = subject_width
height = g_height + header_heigth - 1
wday = date_from.cwday
(date_to - date_from + 1).to_i.times do
width = zoom
gc.fill(wday == 6 || wday == 7 ? '#eee' : 'white')
gc.stroke('grey')
gc.stroke_width(1)
gc.rectangle(left, 2*header_heigth, left + width, 2*header_heigth + g_height-1)
left = left + width
wday = wday + 1
wday = 1 if wday > 7
end
end
# border
gc.fill('transparent')
gc.stroke('grey')
gc.stroke_width(1)
gc.rectangle(0, 0, subject_width+g_width, headers_heigth)
gc.stroke('black')
gc.rectangle(0, 0, subject_width+g_width, g_height+ headers_heigth-1)
# content
top = headers_heigth + 20
gc.stroke('transparent')
events.each do |i|
if i.is_a?(Issue)
i_start_date = (i.start_date >= date_from ? i.start_date : date_from )
i_end_date = (i.due_date <= date_to ? i.due_date : date_to )
i_done_date = i.start_date + ((i.due_date - i.start_date+1)*i.done_ratio/100).floor
i_done_date = (i_done_date <= date_from ? date_from : i_done_date )
i_done_date = (i_done_date >= date_to ? date_to : i_done_date )
i_late_date = [i_end_date, Date.today].min if i_start_date < Date.today
i_left = subject_width + ((i_start_date - date_from)*zoom).floor
i_width = ((i_end_date - i_start_date + 1)*zoom).floor # total width of the issue
d_width = ((i_done_date - i_start_date)*zoom).floor # done width
l_width = i_late_date ? ((i_late_date - i_start_date+1)*zoom).floor : 0 # delay width
gc.fill('grey')
gc.rectangle(i_left, top, i_left + i_width, top - 6)
gc.fill('red')
gc.rectangle(i_left, top, i_left + l_width, top - 6) if l_width > 0
gc.fill('blue')
gc.rectangle(i_left, top, i_left + d_width, top - 6) if d_width > 0
gc.fill('black')
gc.text(i_left + i_width + 5,top + 1, "#{i.status.name} #{i.done_ratio}%")
else
i_left = subject_width + ((i.start_date - date_from)*zoom).floor
gc.fill('green')
gc.rectangle(i_left, top, i_left + 6, top - 6)
gc.fill('black')
gc.text(i_left + 11, top + 1, i.name)
end
top = top + 20
end
# today red line
if Date.today >= @date_from and Date.today <= @date_to
gc.stroke('red')
x = (Date.today-@date_from+1)*zoom + subject_width
gc.line(x, headers_heigth, x, headers_heigth + g_height-1)
end
gc.draw(imgl)
imgl
end if Object.const_defined?(:Magick)
def new_issue_selector
trackers = Tracker.find(:all, :order => 'position')
# can't use form tag inside helper
content_tag('form',
select_tag('tracker_id', '<option></option>' + options_from_collection_for_select(trackers, 'id', 'name'), :onchange => "if (this.value != '') {this.form.submit()}"),
:action => url_for(:controller => 'projects', :action => 'add_issue', :id => @project), :method => 'get')
end
end end
+1 -40
View File
@@ -1,45 +1,6 @@
# redMine - project management software
# Copyright (C) 2006-2007 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.
module QueriesHelper module QueriesHelper
def operators_for_select(filter_type) def operators_for_select(filter_type)
Query.operators_by_filter_type[filter_type].collect {|o| [l(Query.operators[o]), o]} Query.operators_by_filter_type[filter_type].collect {|o| [l(Query.operators[o]), o]}
end end
def column_header(column)
if column.sortable
sort_header_tag(column.sortable, :caption => l("field_#{column.name}"))
else
content_tag('th', l("field_#{column.name}"))
end
end
def column_content(column, issue)
value = issue.send(column.name)
if value.is_a?(Date)
format_date(value)
elsif value.is_a?(Time)
format_time(value)
elsif column.name == :subject
((@project.nil? || @project != issue.project) ? "#{issue.project.name} - " : '') +
link_to(h(value), :controller => 'issues', :action => 'show', :id => issue)
else
h(value)
end
end
end end
+30 -34
View File
@@ -1,36 +1,32 @@
# redMine - project management software # redMine - project management software
# Copyright (C) 2006 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# 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 ReportsHelper module ReportsHelper
def aggregate(data, criteria)
a = 0
data.each { |row|
match = 1
criteria.each { |k, v|
match = 0 unless row[k].to_s == v.to_s
} unless criteria.nil?
a = a + row["total"].to_i if match == 1
} unless data.nil?
a
end
def aggregate(data, criteria)
a = 0
data.each { |row|
match = 1
criteria.each { |k, v|
match = 0 unless (row[k].to_s == v.to_s) || (k == 'closed' && row[k] == (v == 0 ? "f" : "t"))
} unless criteria.nil?
a = a + row["total"].to_i if match == 1
} unless data.nil?
a
end
def aggregate_link(data, criteria, *args)
a = aggregate data, criteria
a > 0 ? link_to(a, *args) : '-'
end
end end
-61
View File
@@ -15,66 +15,5 @@
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# 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 'coderay'
require 'coderay/helpers/file_type'
require 'iconv'
module RepositoriesHelper module RepositoriesHelper
def syntax_highlight(name, content)
type = CodeRay::FileType[name]
type ? CodeRay.scan(content, type).html : h(content)
end
def to_utf8(str)
return str if /\A[\r\n\t\x20-\x7e]*\Z/n.match(str) # for us-ascii
@encodings ||= Setting.repositories_encodings.split(',').collect(&:strip)
@encodings.each do |encoding|
begin
return Iconv.conv('UTF-8', encoding, str)
rescue Iconv::Failure
# do nothing here and try the next encoding
end
end
str
end
def repository_field_tags(form, repository)
method = repository.class.name.demodulize.underscore + "_field_tags"
send(method, form, repository) if repository.is_a?(Repository) && respond_to?(method)
end
def scm_select_tag(repository)
container = [[]]
REDMINE_SUPPORTED_SCM.each {|scm| container << ["Repository::#{scm}".constantize.scm_name, scm]}
select_tag('repository_scm',
options_for_select(container, repository.class.name.demodulize),
:disabled => (repository && !repository.new_record?),
:onchange => remote_function(:url => { :controller => 'repositories', :action => 'edit', :id => @project }, :method => :get, :with => "Form.serialize(this.form)")
)
end
def with_leading_slash(path)
path ||= ''
path.starts_with?("/") ? "/#{path}" : path
end
def subversion_field_tags(form, repository)
content_tag('p', form.text_field(:url, :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?)) +
'<br />(http://, https://, svn://, file:///)') +
content_tag('p', form.text_field(:login, :size => 30)) +
content_tag('p', form.password_field(:password, :size => 30))
end
def darcs_field_tags(form, repository)
content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true, :disabled => (repository && !repository.new_record?)))
end
def mercurial_field_tags(form, repository)
content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?)))
end
def cvs_field_tags(form, repository)
content_tag('p', form.text_field(:root_url, :label => 'CVSROOT', :size => 60, :required => true, :disabled => !repository.new_record?)) +
content_tag('p', form.text_field(:url, :label => 'Module', :size => 30, :required => true, :disabled => !repository.new_record?))
end
end end
+17 -17
View File
@@ -1,19 +1,19 @@
# redMine - project management software # redMine - project management software
# Copyright (C) 2006 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# 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 RolesHelper module RolesHelper
end end
-38
View File
@@ -1,38 +0,0 @@
# redMine - project management software
# Copyright (C) 2006-2007 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.
module SearchHelper
def highlight_tokens(text, tokens)
return text unless text && tokens && !tokens.empty?
regexp = Regexp.new "(#{tokens.join('|')})", Regexp::IGNORECASE
result = ''
text.split(regexp).each_with_index do |words, i|
if result.length > 1200
# maximum length of the preview reached
result << '...'
break
end
if i.even?
result << h(words.length > 100 ? "#{words[0..44]} ... #{words[-45..-1]}" : words)
else
t = (tokens.index(words.downcase) || 0) % 4
result << content_tag('span', h(words), :class => "highlight token-#{t}")
end
end
result
end
end
+10 -7
View File
@@ -107,12 +107,10 @@ module SortHelper
order = 'desc' # changed for desc order by default order = 'desc' # changed for desc order by default
end end
caption = titleize(Inflector::humanize(column)) unless caption caption = titleize(Inflector::humanize(column)) unless caption
params = {:params => {:sort_key => column, :sort_order => order}}
url = {:sort_key => column, :sort_order => order, :issue_id => params[:issue_id], :project_id => params[:project_id]}
link_to_remote(caption, link_to_remote(caption,
{:update => "content", :url => url}, {:update => "content", :url => { :sort_key => column, :sort_order => order}},
{:href => url_for(url)}) + {:href => url_for(:params => { :sort_key => column, :sort_order => order})}) +
(icon ? nbsp(2) + image_tag(icon) : '') (icon ? nbsp(2) + image_tag(icon) : '')
end end
@@ -137,8 +135,13 @@ module SortHelper
# </th> # </th>
# #
def sort_header_tag(column, options = {}) def sort_header_tag(column, options = {})
caption = options.delete(:caption) || titleize(Inflector::humanize(column)) if options[:caption]
options[:title]= l(:label_sort_by, "\"#{caption}\"") unless options[:title] caption = options[:caption]
options.delete(:caption)
else
caption = titleize(Inflector::humanize(column))
end
options[:title]= "Sort by #{caption}" unless options[:title]
content_tag('th', sort_link(column, caption), options) content_tag('th', sort_link(column, caption), options)
end end
+17 -17
View File
@@ -1,19 +1,19 @@
# redMine - project management software # redMine - project management software
# Copyright (C) 2006 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# 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 TrackersHelper module TrackersHelper
end end
+17 -23
View File
@@ -1,25 +1,19 @@
# redMine - project management software # redMine - project management software
# Copyright (C) 2006 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# 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 UsersHelper module UsersHelper
def status_options_for_select(selected)
options_for_select([[l(:label_all), "*"],
[l(:status_active), 1],
[l(:status_registered), 2],
[l(:status_locked), 3]], selected)
end
end end
+17 -17
View File
@@ -1,19 +1,19 @@
# redMine - project management software # redMine - project management software
# Copyright (C) 2006 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# 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 VersionsHelper module VersionsHelper
end end
-36
View File
@@ -1,36 +0,0 @@
# redMine - project management software
# Copyright (C) 2006-2007 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.
module WatchersHelper
def watcher_tag(object, user)
content_tag("span", watcher_link(object, user), :id => 'watcher')
end
def watcher_link(object, user)
return '' unless user && user.logged? && object.respond_to?('watched_by?')
watched = object.watched_by?(user)
url = {:controller => 'watchers',
:action => (watched ? 'remove' : 'add'),
:object_type => object.class.to_s.underscore,
:object_id => object.id}
link_to_remote((watched ? l(:button_unwatch) : l(:button_watch)),
{:url => url},
:href => url_for(url),
:class => (watched ? 'icon icon-fav' : 'icon icon-fav-off'))
end
end
+17 -17
View File
@@ -1,19 +1,19 @@
# redMine - project management software # redMine - project management software
# Copyright (C) 2006 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# 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 WelcomeHelper module WelcomeHelper
end end
-56
View File
@@ -1,56 +0,0 @@
# redMine - project management software
# Copyright (C) 2006-2007 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.
module WikiHelper
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 << ' ' + 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
+90 -108
View File
@@ -1,110 +1,92 @@
# 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
# 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
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# 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 "digest/md5" require "digest/md5"
class Attachment < ActiveRecord::Base class Attachment < ActiveRecord::Base
belongs_to :container, :polymorphic => true belongs_to :container, :polymorphic => true
belongs_to :author, :class_name => "User", :foreign_key => "author_id" belongs_to :author, :class_name => "User", :foreign_key => "author_id"
validates_presence_of :container, :filename validates_presence_of :container, :filename
validates_length_of :filename, :maximum => 255
validates_length_of :disk_filename, :maximum => 255 cattr_accessor :storage_path
@@storage_path = "#{RAILS_ROOT}/files"
acts_as_event :title => :filename,
:description => :filename, def validate
:url => Proc.new {|o| {:controller => 'attachments', :action => 'download', :id => o.id}} errors.add_to_base :too_long if self.filesize > Setting.attachment_max_size.to_i.kilobytes
end
cattr_accessor :storage_path
@@storage_path = "#{RAILS_ROOT}/files" def file=(incomming_file)
unless incomming_file.nil?
def validate @temp_file = incomming_file
errors.add_to_base :too_long if self.filesize > Setting.attachment_max_size.to_i.kilobytes if @temp_file.size > 0
end self.filename = sanitize_filename(@temp_file.original_filename)
self.disk_filename = DateTime.now.strftime("%y%m%d%H%M%S") + "_" + self.filename
def file=(incomming_file) self.content_type = @temp_file.content_type
unless incomming_file.nil? self.filesize = @temp_file.size
@temp_file = incomming_file end
if @temp_file.size > 0 end
self.filename = sanitize_filename(@temp_file.original_filename) end
self.disk_filename = DateTime.now.strftime("%y%m%d%H%M%S") + "_" + self.filename
self.content_type = @temp_file.content_type.chomp def file
self.filesize = @temp_file.size nil
end end
end
end # Copy temp file to its final location
def before_save
def file if @temp_file && (@temp_file.size > 0)
nil logger.debug("saving '#{self.diskfile}'")
end File.open(diskfile, "wb") do |f|
f.write(@temp_file.read)
# Copy temp file to its final location end
def before_save self.digest = Digest::MD5.hexdigest(File.read(diskfile))
if @temp_file && (@temp_file.size > 0) end
logger.debug("saving '#{self.diskfile}'") end
File.open(diskfile, "wb") do |f|
f.write(@temp_file.read) # Deletes file on the disk
end def after_destroy
self.digest = Digest::MD5.hexdigest(File.read(diskfile)) if self.filename?
end File.delete(diskfile) if File.exist?(diskfile)
# Don't save the content type if it's longer than the authorized length end
if self.content_type && self.content_type.length > 255 end
self.content_type = nil
end # Returns file's location on disk
end def diskfile
"#{@@storage_path}/#{self.disk_filename}"
# Deletes file on the disk end
def after_destroy
if self.filename? def increment_download
File.delete(diskfile) if File.exist?(diskfile) increment!(:downloads)
end end
end
# returns last created projects
# Returns file's location on disk def self.most_downloaded
def diskfile find(:all, :limit => 5, :order => "downloads DESC")
"#{@@storage_path}/#{self.disk_filename}" end
end
private
def increment_download def sanitize_filename(value)
increment!(:downloads) # get only the filename, not the whole path
end just_filename = value.gsub(/^.*(\\|\/)/, '')
# NOTE: File.basename doesn't work right with Windows paths on Unix
# returns last created projects # INCORRECT: just_filename = File.basename(value.gsub('\\\\', '/'))
def self.most_downloaded
find(:all, :limit => 5, :order => "downloads DESC") # Finally, replace all non alphanumeric, underscore or periods with underscore
end @filename = just_filename.gsub(/[^\w\.\-]/,'_')
end
def project
container.is_a?(Project) ? container : container.project
end
def image?
self.filename =~ /\.(jpeg|jpg|gif|png)$/i
end
private
def sanitize_filename(value)
# get only the filename, not the whole path
just_filename = value.gsub(/^.*(\\|\/)/, '')
# NOTE: File.basename doesn't work right with Windows paths on Unix
# INCORRECT: just_filename = File.basename(value.gsub('\\\\', '/'))
# Finally, replace all non alphanumeric, underscore or periods with underscore
@filename = just_filename.gsub(/[^\w\.\-]/,'_')
end
end end
+77 -80
View File
@@ -1,82 +1,79 @@
# redMine - project management software # redMine - project management software
# Copyright (C) 2006 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# 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 'net/ldap' require 'net/ldap'
require 'iconv' require 'iconv'
class AuthSourceLdap < AuthSource class AuthSourceLdap < AuthSource
validates_presence_of :host, :port, :attr_login validates_presence_of :host, :port, :attr_login
validates_presence_of :attr_firstname, :attr_lastname, :attr_mail, :if => Proc.new { |a| a.onthefly_register? }
def after_initialize
def after_initialize self.port = 389 if self.port == 0
self.port = 389 if self.port == 0 end
end
def authenticate(login, password)
def authenticate(login, password) attrs = []
attrs = [] # get user's DN
# get user's DN ldap_con = initialize_ldap_con(self.account, self.account_password)
ldap_con = initialize_ldap_con(self.account, self.account_password) login_filter = Net::LDAP::Filter.eq( self.attr_login, login )
login_filter = Net::LDAP::Filter.eq( self.attr_login, login ) object_filter = Net::LDAP::Filter.eq( "objectClass", "*" )
object_filter = Net::LDAP::Filter.eq( "objectClass", "*" ) dn = String.new
dn = String.new ldap_con.search( :base => self.base_dn,
ldap_con.search( :base => self.base_dn, :filter => object_filter & login_filter,
:filter => object_filter & login_filter, :attributes=> ['dn', self.attr_firstname, self.attr_lastname, self.attr_mail]) do |entry|
# only ask for the DN if on-the-fly registration is disabled dn = entry.dn
:attributes=> (onthefly_register? ? ['dn', self.attr_firstname, self.attr_lastname, self.attr_mail] : ['dn'])) do |entry| attrs = [:firstname => AuthSourceLdap.get_attr(entry, self.attr_firstname),
dn = entry.dn :lastname => AuthSourceLdap.get_attr(entry, self.attr_lastname),
attrs = [:firstname => AuthSourceLdap.get_attr(entry, self.attr_firstname), :mail => AuthSourceLdap.get_attr(entry, self.attr_mail),
:lastname => AuthSourceLdap.get_attr(entry, self.attr_lastname), :auth_source_id => self.id ]
:mail => AuthSourceLdap.get_attr(entry, self.attr_mail), end
:auth_source_id => self.id ] if onthefly_register? return nil if dn.empty?
end logger.debug "DN found for #{login}: #{dn}" if logger && logger.debug?
return nil if dn.empty? # authenticate user
logger.debug "DN found for #{login}: #{dn}" if logger && logger.debug? ldap_con = initialize_ldap_con(dn, password)
# authenticate user return nil unless ldap_con.bind
ldap_con = initialize_ldap_con(dn, password) # return user's attributes
return nil unless ldap_con.bind logger.debug "Authentication successful for '#{login}'" if logger && logger.debug?
# return user's attributes attrs
logger.debug "Authentication successful for '#{login}'" if logger && logger.debug? rescue Net::LDAP::LdapError => text
attrs raise "LdapError: " + text
rescue Net::LDAP::LdapError => text end
raise "LdapError: " + text
end # test the connection to the LDAP
def test_connection
# test the connection to the LDAP ldap_con = initialize_ldap_con(self.account, self.account_password)
def test_connection ldap_con.open { }
ldap_con = initialize_ldap_con(self.account, self.account_password) rescue Net::LDAP::LdapError => text
ldap_con.open { } raise "LdapError: " + text
rescue Net::LDAP::LdapError => text end
raise "LdapError: " + text
end def auth_method_name
"LDAP"
def auth_method_name end
"LDAP"
end private
def initialize_ldap_con(ldap_user, ldap_password)
private Net::LDAP.new( {:host => self.host,
def initialize_ldap_con(ldap_user, ldap_password) :port => self.port,
Net::LDAP.new( {:host => self.host, :auth => { :method => :simple, :username => Iconv.new('iso-8859-15', 'utf-8').iconv(ldap_user), :password => Iconv.new('iso-8859-15', 'utf-8').iconv(ldap_password) }}
:port => self.port, )
:auth => { :method => :simple, :username => ldap_user, :password => ldap_password }, end
:encryption => (self.tls ? :simple_tls : nil)}
) def self.get_attr(entry, attr_name)
end entry[attr_name].is_a?(Array) ? entry[attr_name].first : entry[attr_name]
def self.get_attr(entry, attr_name)
entry[attr_name].is_a?(Array) ? entry[attr_name].first : entry[attr_name]
end end
end end
-29
View File
@@ -1,29 +0,0 @@
# redMine - project management software
# Copyright (C) 2006-2007 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 Board < ActiveRecord::Base
belongs_to :project
has_many :topics, :class_name => 'Message', :conditions => "#{Message.table_name}.parent_id IS NULL", :order => "#{Message.table_name}.created_on DESC"
has_many :messages, :dependent => :delete_all, :order => "#{Message.table_name}.created_on DESC"
belongs_to :last_message, :class_name => 'Message', :foreign_key => :last_message_id
acts_as_list :scope => :project_id
acts_as_watchable
validates_presence_of :name, :description
validates_length_of :name, :maximum => 30
validates_length_of :description, :maximum => 255
end
-22
View File
@@ -1,22 +0,0 @@
# redMine - project management software
# Copyright (C) 2006-2007 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 Change < ActiveRecord::Base
belongs_to :changeset
validates_presence_of :changeset_id, :action, :path
end
-85
View File
@@ -1,85 +0,0 @@
# redMine - project management software
# Copyright (C) 2006-2007 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 Changeset < ActiveRecord::Base
belongs_to :repository
has_many :changes, :dependent => :delete_all
has_and_belongs_to_many :issues
acts_as_event :title => Proc.new {|o| "#{l(:label_revision)} #{o.revision}" + (o.comments.blank? ? '' : (': ' + o.comments))},
:description => :comments,
:datetime => :committed_on,
:author => :committer,
:url => Proc.new {|o| {:controller => 'repositories', :action => 'revision', :id => o.repository.project_id, :rev => o.revision}}
acts_as_searchable :columns => 'comments',
:include => :repository,
:project_key => "#{Repository.table_name}.project_id",
:date_column => 'committed_on'
validates_presence_of :repository_id, :revision, :committed_on, :commit_date
validates_numericality_of :revision, :only_integer => true
validates_uniqueness_of :revision, :scope => :repository_id
validates_uniqueness_of :scmid, :scope => :repository_id, :allow_nil => true
def comments=(comment)
write_attribute(:comments, comment.strip)
end
def committed_on=(date)
self.commit_date = date
super
end
def after_create
scan_comment_for_issue_ids
end
def scan_comment_for_issue_ids
return if comments.blank?
# keywords used to reference issues
ref_keywords = Setting.commit_ref_keywords.downcase.split(",").collect(&:strip)
# keywords used to fix issues
fix_keywords = Setting.commit_fix_keywords.downcase.split(",").collect(&:strip)
# status and optional done ratio applied
fix_status = IssueStatus.find_by_id(Setting.commit_fix_status_id)
done_ratio = Setting.commit_fix_done_ratio.blank? ? nil : Setting.commit_fix_done_ratio.to_i
kw_regexp = (ref_keywords + fix_keywords).collect{|kw| Regexp.escape(kw)}.join("|")
return if kw_regexp.blank?
referenced_issues = []
comments.scan(Regexp.new("(#{kw_regexp})[\s:]+(([\s,;&]*#?\\d+)+)", Regexp::IGNORECASE)).each do |match|
action = match[0]
target_issue_ids = match[1].scan(/\d+/)
target_issues = repository.project.issues.find_all_by_id(target_issue_ids)
if fix_status && fix_keywords.include?(action.downcase)
# update status of issues
logger.debug "Issues fixed by changeset #{self.revision}: #{issue_ids.join(', ')}." if logger && logger.debug?
target_issues.each do |issue|
# don't change the status is the issue is already closed
next if issue.status.is_closed?
issue.status = fix_status
issue.done_ratio = done_ratio if done_ratio
issue.save
end
end
referenced_issues += target_issues
end
self.issues = referenced_issues.uniq
end
end
+1 -1
View File
@@ -19,5 +19,5 @@ class Comment < ActiveRecord::Base
belongs_to :commented, :polymorphic => true, :counter_cache => true belongs_to :commented, :polymorphic => true, :counter_cache => true
belongs_to :author, :class_name => 'User', :foreign_key => 'author_id' belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
validates_presence_of :commented, :author, :comments validates_presence_of :commented, :author, :comment
end end
+59 -61
View File
@@ -1,62 +1,60 @@
# redMine - project management software # redMine - project management software
# Copyright (C) 2006 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# 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 CustomField < ActiveRecord::Base class CustomField < ActiveRecord::Base
has_many :custom_values, :dependent => :delete_all has_many :custom_values, :dependent => :delete_all
serialize :possible_values serialize :possible_values
FIELD_FORMATS = { "string" => { :name => :label_string, :order => 1 }, FIELD_FORMATS = { "string" => { :name => :label_string, :order => 1 },
"text" => { :name => :label_text, :order => 2 }, "text" => { :name => :label_text, :order => 2 },
"int" => { :name => :label_integer, :order => 3 }, "int" => { :name => :label_integer, :order => 3 },
"float" => { :name => :label_float, :order => 4 }, "list" => { :name => :label_list, :order => 4 },
"list" => { :name => :label_list, :order => 5 }, "date" => { :name => :label_date, :order => 5 },
"date" => { :name => :label_date, :order => 6 }, "bool" => { :name => :label_boolean, :order => 6 }
"bool" => { :name => :label_boolean, :order => 7 } }.freeze
}.freeze
validates_presence_of :name, :field_format
validates_presence_of :name, :field_format validates_uniqueness_of :name
validates_uniqueness_of :name validates_format_of :name, :with => /^[\w\s\'\-]*$/i
validates_length_of :name, :maximum => 30 validates_inclusion_of :field_format, :in => FIELD_FORMATS.keys
validates_format_of :name, :with => /^[\w\s\'\-]*$/i
validates_inclusion_of :field_format, :in => FIELD_FORMATS.keys def initialize(attributes = nil)
super
def initialize(attributes = nil) self.possible_values ||= []
super end
self.possible_values ||= []
def before_validation
# remove empty values
self.possible_values = self.possible_values.collect{|v| v unless v.empty?}.compact
end
def validate
if self.field_format == "list"
errors.add(:possible_values, :activerecord_error_blank) if self.possible_values.nil? || self.possible_values.empty?
errors.add(:possible_values, :activerecord_error_invalid) unless self.possible_values.is_a? Array
end
end
# to move in project_custom_field
def self.for_all
find(:all, :conditions => ["is_for_all=?", true])
end
def type_name
nil
end end
end
def before_validation
# remove empty values
self.possible_values = self.possible_values.collect{|v| v unless v.empty?}.compact
end
def validate
if self.field_format == "list"
errors.add(:possible_values, :activerecord_error_blank) if self.possible_values.nil? || self.possible_values.empty?
errors.add(:possible_values, :activerecord_error_invalid) unless self.possible_values.is_a? Array
end
end
# to move in project_custom_field
def self.for_all
find(:all, :conditions => ["is_for_all=?", true])
end
def type_name
nil
end
end
+37 -38
View File
@@ -1,39 +1,38 @@
# redMine - project management software # redMine - project management software
# Copyright (C) 2006 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# 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 CustomValue < ActiveRecord::Base class CustomValue < ActiveRecord::Base
belongs_to :custom_field belongs_to :custom_field
belongs_to :customized, :polymorphic => true belongs_to :customized, :polymorphic => true
protected protected
def validate def validate
errors.add(:value, :activerecord_error_blank) and return if custom_field.is_required? and value.blank? errors.add(:value, :activerecord_error_blank) and return if custom_field.is_required? and value.empty?
errors.add(:value, :activerecord_error_invalid) unless custom_field.regexp.blank? or value =~ Regexp.new(custom_field.regexp) errors.add(:value, :activerecord_error_invalid) unless custom_field.regexp.empty? or value =~ Regexp.new(custom_field.regexp)
errors.add(:value, :activerecord_error_too_short) if custom_field.min_length > 0 and value.length < custom_field.min_length and value.length > 0 errors.add(:value, :activerecord_error_too_short) if custom_field.min_length > 0 and value.length < custom_field.min_length and value.length > 0
errors.add(:value, :activerecord_error_too_long) if custom_field.max_length > 0 and value.length > custom_field.max_length errors.add(:value, :activerecord_error_too_long) if custom_field.max_length > 0 and value.length > custom_field.max_length
case custom_field.field_format case custom_field.field_format
when 'int' when "int"
errors.add(:value, :activerecord_error_not_a_number) unless value.blank? || value =~ /^[+-]?\d+$/ errors.add(:value, :activerecord_error_not_a_number) unless value =~ /^[0-9]*$/
when 'float' when "date"
begin; !value.blank? && Kernel.Float(value); rescue; errors.add(:value, :activerecord_error_invalid) end errors.add(:value, :activerecord_error_not_a_date) unless value =~ /^\d{4}-\d{2}-\d{2}$/ or value.empty?
when 'date' when "list"
errors.add(:value, :activerecord_error_not_a_date) unless value =~ /^\d{4}-\d{2}-\d{2}$/ or value.empty? errors.add(:value, :activerecord_error_inclusion) unless custom_field.possible_values.include? value or value.empty?
when 'list' end
errors.add(:value, :activerecord_error_inclusion) unless custom_field.possible_values.include? value or value.empty?
end
end end
end end
+22 -27
View File
@@ -1,29 +1,24 @@
# redMine - project management software # redMine - project management software
# Copyright (C) 2006 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# 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 Document < ActiveRecord::Base class Document < ActiveRecord::Base
belongs_to :project belongs_to :project
belongs_to :category, :class_name => "Enumeration", :foreign_key => "category_id" belongs_to :category, :class_name => "Enumeration", :foreign_key => "category_id"
has_many :attachments, :as => :container, :dependent => :destroy has_many :attachments, :as => :container, :dependent => :destroy
acts_as_searchable :columns => ['title', 'description']
acts_as_event :title => Proc.new {|o| "#{l(:label_document)}: #{o.title}"},
:url => Proc.new {|o| {:controller => 'documents', :action => 'show', :id => o.id}}
validates_presence_of :project, :title, :category validates_presence_of :project, :title, :category
validates_length_of :title, :maximum => 60
end end
-23
View File
@@ -1,23 +0,0 @@
# redMine - project management software
# Copyright (C) 2006-2007 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 EnabledModule < ActiveRecord::Base
belongs_to :project
validates_presence_of :name
validates_uniqueness_of :name, :scope => :project_id
end
+45 -65
View File
@@ -1,67 +1,47 @@
# redMine - project management software # redMine - project management software
# Copyright (C) 2006 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# 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 Enumeration < ActiveRecord::Base class Enumeration < ActiveRecord::Base
acts_as_list :scope => 'opt = \'#{opt}\'' before_destroy :check_integrity
before_destroy :check_integrity validates_presence_of :opt, :name
validates_uniqueness_of :name, :scope => [:opt]
validates_presence_of :opt, :name validates_format_of :name, :with => /^[\w\s\'\-]*$/i
validates_uniqueness_of :name, :scope => [:opt]
validates_length_of :name, :maximum => 30 OPTIONS = {
validates_format_of :name, :with => /^[\w\s\'\-]*$/i "IPRI" => :enumeration_issue_priorities,
"DCAT" => :enumeration_doc_categories
OPTIONS = { }.freeze
"IPRI" => :enumeration_issue_priorities,
"DCAT" => :enumeration_doc_categories, def self.get_values(option)
"ACTI" => :enumeration_activities find(:all, :conditions => ['opt=?', option])
}.freeze end
def self.get_values(option) def option_name
find(:all, :conditions => {:opt => option}, :order => 'position') OPTIONS[self.opt]
end end
def self.default(option) private
find(:first, :conditions => {:opt => option, :is_default => true}, :order => 'position') def check_integrity
end case self.opt
when "IPRI"
def option_name raise "Can't delete enumeration" if Issue.find(:first, :conditions => ["priority_id=?", self.id])
OPTIONS[self.opt] when "DCAT"
end raise "Can't delete enumeration" if Document.find(:first, :conditions => ["category_id=?", self.id])
end
def before_save
Enumeration.update_all("is_default = #{connection.quoted_false}", {:opt => opt}) if is_default?
end
def <=>(enumeration)
position <=> enumeration.position
end
def to_s; name end
private
def check_integrity
case self.opt
when "IPRI"
raise "Can't delete enumeration" if Issue.find(:first, :conditions => ["priority_id=?", self.id])
when "DCAT"
raise "Can't delete enumeration" if Document.find(:first, :conditions => ["category_id=?", self.id])
when "ACTI"
raise "Can't delete enumeration" if TimeEntry.find(:first, :conditions => ["activity_id=?", self.id])
end
end end
end end
+101 -190
View File
@@ -1,191 +1,102 @@
# redMine - project management software # redMine - project management software
# Copyright (C) 2006-2007 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# 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 Issue < ActiveRecord::Base class Issue < ActiveRecord::Base
belongs_to :project
belongs_to :tracker belongs_to :project
belongs_to :status, :class_name => 'IssueStatus', :foreign_key => 'status_id' belongs_to :tracker
belongs_to :author, :class_name => 'User', :foreign_key => 'author_id' belongs_to :status, :class_name => 'IssueStatus', :foreign_key => 'status_id'
belongs_to :assigned_to, :class_name => 'User', :foreign_key => 'assigned_to_id' belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
belongs_to :fixed_version, :class_name => 'Version', :foreign_key => 'fixed_version_id' belongs_to :assigned_to, :class_name => 'User', :foreign_key => 'assigned_to_id'
belongs_to :priority, :class_name => 'Enumeration', :foreign_key => 'priority_id' belongs_to :fixed_version, :class_name => 'Version', :foreign_key => 'fixed_version_id'
belongs_to :category, :class_name => 'IssueCategory', :foreign_key => 'category_id' belongs_to :priority, :class_name => 'Enumeration', :foreign_key => 'priority_id'
belongs_to :category, :class_name => 'IssueCategory', :foreign_key => 'category_id'
has_many :journals, :as => :journalized, :dependent => :destroy
has_many :attachments, :as => :container, :dependent => :destroy has_many :journals, :as => :journalized, :dependent => :destroy
has_many :time_entries, :dependent => :nullify has_many :attachments, :as => :container, :dependent => :destroy
has_many :custom_values, :dependent => :delete_all, :as => :customized
has_many :custom_fields, :through => :custom_values has_many :custom_values, :dependent => :delete_all, :as => :customized
has_and_belongs_to_many :changesets, :order => "revision ASC" has_many :custom_fields, :through => :custom_values
has_many :relations_from, :class_name => 'IssueRelation', :foreign_key => 'issue_from_id', :dependent => :delete_all validates_presence_of :subject, :description, :priority, :tracker, :author, :status
has_many :relations_to, :class_name => 'IssueRelation', :foreign_key => 'issue_to_id', :dependent => :delete_all validates_inclusion_of :done_ratio, :in => 0..100
validates_associated :custom_values, :on => :update
acts_as_watchable
acts_as_searchable :columns => ['subject', 'description'], :with => {:journal => :issue} # set default status for new issues
acts_as_event :title => Proc.new {|o| "#{o.tracker.name} ##{o.id}: #{o.subject}"}, def before_validation
:url => Proc.new {|o| {:controller => 'issues', :action => 'show', :id => o.id}} self.status = IssueStatus.default if new_record?
end
validates_presence_of :subject, :description, :priority, :tracker, :author, :status
validates_length_of :subject, :maximum => 255 def validate
validates_inclusion_of :done_ratio, :in => 0..100 if self.due_date.nil? && @attributes['due_date'] && !@attributes['due_date'].empty?
validates_numericality_of :estimated_hours, :allow_nil => true errors.add :due_date, :activerecord_error_not_a_date
validates_associated :custom_values, :on => :update end
def after_initialize if self.due_date and self.start_date and self.due_date < self.start_date
if new_record? errors.add :due_date, :activerecord_error_greater_than_start_date
# set default values for new records only end
self.status ||= IssueStatus.default end
self.priority ||= Enumeration.default('IPRI')
end #def before_create
end # build_history
#end
def copy_from(arg)
issue = arg.is_a?(Issue) ? arg : Issue.find(arg) def before_save
self.attributes = issue.attributes.dup if @current_journal
self.custom_values = issue.custom_values.collect {|v| v.clone} # attributes changes
self (Issue.column_names - %w(id description)).each {|c|
end @current_journal.details << JournalDetail.new(:property => 'attr',
:prop_key => c,
def priority_id=(pid) :old_value => @issue_before_change.send(c),
self.priority = nil :value => send(c)) unless send(c)==@issue_before_change.send(c)
write_attribute(:priority_id, pid) }
end # custom fields changes
custom_values.each {|c|
def validate @current_journal.details << JournalDetail.new(:property => 'cf',
if self.due_date.nil? && @attributes['due_date'] && !@attributes['due_date'].empty? :prop_key => c.custom_field_id,
errors.add :due_date, :activerecord_error_not_a_date :old_value => @custom_values_before_change[c.custom_field_id],
end :value => c.value) unless @custom_values_before_change[c.custom_field_id]==c.value
}
if self.due_date and self.start_date and self.due_date < self.start_date @current_journal.save unless @current_journal.details.empty? and @current_journal.notes.empty?
errors.add :due_date, :activerecord_error_greater_than_start_date end
end end
if start_date && soonest_start && start_date < soonest_start def long_id
errors.add :start_date, :activerecord_error_invalid "%05d" % self.id
end end
end
def custom_value_for(custom_field)
def before_create self.custom_values.each {|v| return v if v.custom_field_id == custom_field.id }
# default assignment based on category return nil
if assigned_to.nil? && category && category.assigned_to end
self.assigned_to = category.assigned_to
end def init_journal(user, notes = "")
end @current_journal ||= Journal.new(:journalized => self, :user => user, :notes => notes)
@issue_before_change = self.clone
def before_save @custom_values_before_change = {}
if @current_journal self.custom_values.each {|c| @custom_values_before_change.store c.custom_field_id, c.value }
# attributes changes @current_journal
(Issue.column_names - %w(id description)).each {|c| end
@current_journal.details << JournalDetail.new(:property => 'attr',
:prop_key => c, private
:old_value => @issue_before_change.send(c), # Creates an history for the issue
:value => send(c)) unless send(c)==@issue_before_change.send(c) #def build_history
} # @history = self.histories.build
# custom fields changes # @history.status = self.status
custom_values.each {|c| # @history.author = self.author
next if (@custom_values_before_change[c.custom_field_id]==c.value || #end
(@custom_values_before_change[c.custom_field_id].blank? && c.value.blank?))
@current_journal.details << JournalDetail.new(:property => 'cf',
:prop_key => c.custom_field_id,
:old_value => @custom_values_before_change[c.custom_field_id],
:value => c.value)
}
@current_journal.save
end
# Save the issue even if the journal is not saved (because empty)
true
end
def after_save
# Update start/due dates of following issues
relations_from.each(&:set_issue_to_dates)
# Close duplicates if the issue was closed
if @issue_before_change && !@issue_before_change.closed? && self.closed?
duplicates.each do |duplicate|
# Don't re-close it if it's already closed
next if duplicate.closed?
# Same user and notes
duplicate.init_journal(@current_journal.user, @current_journal.notes)
duplicate.update_attribute :status, self.status
end
end
end
def custom_value_for(custom_field)
self.custom_values.each {|v| return v if v.custom_field_id == custom_field.id }
return nil
end
def init_journal(user, notes = "")
@current_journal ||= Journal.new(:journalized => self, :user => user, :notes => notes)
@issue_before_change = self.clone
@custom_values_before_change = {}
self.custom_values.each {|c| @custom_values_before_change.store c.custom_field_id, c.value }
@current_journal
end
# Return true if the issue is closed, otherwise false
def closed?
self.status.is_closed?
end
# Users the issue can be assigned to
def assignable_users
project.assignable_users
end
# Returns the mail adresses of users that should be notified for the issue
def recipients
recipients = project.recipients
# Author and assignee are always notified
recipients << author.mail if author
recipients << assigned_to.mail if assigned_to
recipients.compact.uniq
end
def spent_hours
@spent_hours ||= time_entries.sum(:hours) || 0
end
def relations
(relations_from + relations_to).sort
end
def all_dependent_issues
dependencies = []
relations_from.each do |relation|
dependencies << relation.issue_to
dependencies += relation.issue_to.all_dependent_issues
end
dependencies
end
# Returns an array of the duplicate issues
def duplicates
relations.select {|r| r.relation_type == IssueRelation::TYPE_DUPLICATES}.collect {|r| r.other_issue(self)}
end
def duration
(start_date && due_date) ? due_date - start_date : 0
end
def soonest_start
@soonest_start ||= relations_to.collect{|relation| relation.successor_soonest_start}.compact.min
end
end end
+27 -37
View File
@@ -1,39 +1,29 @@
# redMine - project management software # redMine - project management software
# Copyright (C) 2006 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# 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 IssueCategory < ActiveRecord::Base class IssueCategory < ActiveRecord::Base
belongs_to :project before_destroy :check_integrity
belongs_to :assigned_to, :class_name => 'User', :foreign_key => 'assigned_to_id' belongs_to :project
has_many :issues, :foreign_key => 'category_id', :dependent => :nullify
validates_presence_of :name
validates_presence_of :name validates_uniqueness_of :name, :scope => [:project_id]
validates_uniqueness_of :name, :scope => [:project_id]
validates_length_of :name, :maximum => 30 private
def check_integrity
alias :destroy_without_reassign :destroy raise "Can't delete category" if Issue.find(:first, :conditions => ["category_id=?", self.id])
# Destroy the category
# If a category is specified, issues are reassigned to this category
def destroy(reassign_to = nil)
if reassign_to && reassign_to.is_a?(IssueCategory) && reassign_to.project == self.project
Issue.update_all("category_id = #{reassign_to.id}", "category_id = #{id}")
end
destroy_without_reassign
end end
def to_s; name end
end end
+26 -26
View File
@@ -1,27 +1,27 @@
# redMine - project management software # redMine - project management software
# Copyright (C) 2006 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# 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 IssueCustomField < CustomField class IssueCustomField < CustomField
has_and_belongs_to_many :projects, :join_table => "#{table_name_prefix}custom_fields_projects#{table_name_suffix}", :foreign_key => "custom_field_id" has_and_belongs_to_many :projects, :join_table => "custom_fields_projects", :foreign_key => "custom_field_id"
has_and_belongs_to_many :trackers, :join_table => "#{table_name_prefix}custom_fields_trackers#{table_name_suffix}", :foreign_key => "custom_field_id" has_and_belongs_to_many :trackers, :join_table => "custom_fields_trackers", :foreign_key => "custom_field_id"
has_many :issues, :through => :issue_custom_values has_many :issues, :through => :issue_custom_values
def type_name def type_name
:label_issue_plural :label_issue_plural
end end
end end
@@ -1,30 +1,24 @@
# redMine - project management software # redMine - project management software
# Copyright (C) 2006 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# 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 TimelogHelper class IssueHistory < ActiveRecord::Base
def select_hours(data, criteria, value) belongs_to :status, :class_name => 'IssueStatus', :foreign_key => 'status_id'
data.select {|row| row[criteria] == value.to_s} belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
end belongs_to :issue
def sum_hours(data) validates_presence_of :status
sum = 0
data.each do |row|
sum += row['hours'].to_f
end
sum
end
end end
-79
View File
@@ -1,79 +0,0 @@
# redMine - project management software
# Copyright (C) 2006-2007 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 IssueRelation < ActiveRecord::Base
belongs_to :issue_from, :class_name => 'Issue', :foreign_key => 'issue_from_id'
belongs_to :issue_to, :class_name => 'Issue', :foreign_key => 'issue_to_id'
TYPE_RELATES = "relates"
TYPE_DUPLICATES = "duplicates"
TYPE_BLOCKS = "blocks"
TYPE_PRECEDES = "precedes"
TYPES = { TYPE_RELATES => { :name => :label_relates_to, :sym_name => :label_relates_to, :order => 1 },
TYPE_DUPLICATES => { :name => :label_duplicates, :sym_name => :label_duplicates, :order => 2 },
TYPE_BLOCKS => { :name => :label_blocks, :sym_name => :label_blocked_by, :order => 3 },
TYPE_PRECEDES => { :name => :label_precedes, :sym_name => :label_follows, :order => 4 },
}.freeze
validates_presence_of :issue_from, :issue_to, :relation_type
validates_inclusion_of :relation_type, :in => TYPES.keys
validates_numericality_of :delay, :allow_nil => true
validates_uniqueness_of :issue_to_id, :scope => :issue_from_id
def validate
if issue_from && issue_to
errors.add :issue_to_id, :activerecord_error_invalid if issue_from_id == issue_to_id
errors.add :issue_to_id, :activerecord_error_not_same_project unless issue_from.project_id == issue_to.project_id || Setting.cross_project_issue_relations?
errors.add_to_base :activerecord_error_circular_dependency if issue_to.all_dependent_issues.include? issue_from
end
end
def other_issue(issue)
(self.issue_from_id == issue.id) ? issue_to : issue_from
end
def label_for(issue)
TYPES[relation_type] ? TYPES[relation_type][(self.issue_from_id == issue.id) ? :name : :sym_name] : :unknow
end
def before_save
if TYPE_PRECEDES == relation_type
self.delay ||= 0
else
self.delay = nil
end
set_issue_to_dates
end
def set_issue_to_dates
soonest_start = self.successor_soonest_start
if soonest_start && (!issue_to.start_date || issue_to.start_date < soonest_start)
issue_to.start_date, issue_to.due_date = successor_soonest_start, successor_soonest_start + issue_to.duration
issue_to.save
end
end
def successor_soonest_start
return nil unless (TYPE_PRECEDES == self.relation_type) && (issue_from.start_date || issue_from.due_date)
(issue_from.due_date || issue_from.start_date) + 1 + delay
end
def <=>(relation)
TYPES[self.relation_type][:order] <=> TYPES[relation.relation_type][:order]
end
end
+49 -63
View File
@@ -1,65 +1,51 @@
# redMine - project management software # redMine - project management software
# Copyright (C) 2006 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# 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 IssueStatus < ActiveRecord::Base class IssueStatus < ActiveRecord::Base
before_destroy :check_integrity before_destroy :check_integrity
has_many :workflows, :foreign_key => "old_status_id", :dependent => :delete_all has_many :workflows, :foreign_key => "old_status_id"
acts_as_list acts_as_list
validates_presence_of :name validates_presence_of :name
validates_uniqueness_of :name validates_uniqueness_of :name
validates_length_of :name, :maximum => 30 validates_format_of :name, :with => /^[\w\s\'\-]*$/i
validates_format_of :name, :with => /^[\w\s\'\-]*$/i validates_length_of :html_color, :is => 6
validates_format_of :html_color, :with => /^[a-f0-9]*$/i
def before_save
IssueStatus.update_all "is_default=#{connection.quoted_false}" if self.is_default? def before_save
end IssueStatus.update_all "is_default=#{connection.quoted_false}" if self.is_default?
end
# Returns the default status for new issues
def self.default # Returns the default status for new issues
find(:first, :conditions =>["is_default=?", true]) def self.default
end find(:first, :conditions =>["is_default=?", true])
end
# Returns an array of all statuses the given role can switch to
# Uses association cache when called more than one time # Returns an array of all statuses the given role can switch to
def new_statuses_allowed_to(role, tracker) def new_statuses_allowed_to(role, tracker)
new_statuses = workflows.select {|w| w.role_id == role.id && w.tracker_id == tracker.id}.collect{|w| w.new_status} if role && tracker statuses = []
new_statuses ? new_statuses.compact.sort{|x, y| x.position <=> y.position } : [] for workflow in self.workflows
end statuses << workflow.new_status if workflow.role_id == role.id and workflow.tracker_id == tracker.id
end unless role.nil? or tracker.nil?
# Same thing as above but uses a database query statuses
# More efficient than the previous method if called just once end
def find_new_statuses_allowed_to(role, tracker)
new_statuses = workflows.find(:all, private
:include => :new_status, def check_integrity
:conditions => ["role_id=? and tracker_id=?", role.id, tracker.id]).collect{ |w| w.new_status }.compact if role && tracker raise "Can't delete status" if Issue.find(:first, :conditions => ["status_id=?", self.id])
new_statuses ? new_statuses.sort{|x, y| x.position <=> y.position } : []
end
def new_status_allowed_to?(status, role, tracker)
status && role && tracker ?
!workflows.find(:first, :conditions => {:new_status_id => status.id, :role_id => role.id, :tracker_id => tracker.id}).nil? :
false
end
def to_s; name end
private
def check_integrity
raise "Can't delete status" if Issue.find(:first, :conditions => ["status_id=?", self.id])
end end
end end
-14
View File
@@ -17,20 +17,6 @@
class Journal < ActiveRecord::Base class Journal < ActiveRecord::Base
belongs_to :journalized, :polymorphic => true belongs_to :journalized, :polymorphic => true
# added as a quick fix to allow eager loading of the polymorphic association
# since always associated to an issue, for now
belongs_to :issue, :foreign_key => :journalized_id
belongs_to :user belongs_to :user
has_many :details, :class_name => "JournalDetail", :dependent => :delete_all has_many :details, :class_name => "JournalDetail", :dependent => :delete_all
acts_as_searchable :columns => 'notes',
:include => :issue,
:project_key => "#{Issue.table_name}.project_id",
:date_column => "#{Issue.table_name}.created_on"
def save
# Do not save an empty journal
(details.empty? && notes.blank?) ? false : super
end
end end
-5
View File
@@ -17,9 +17,4 @@
class JournalDetail < ActiveRecord::Base class JournalDetail < ActiveRecord::Base
belongs_to :journal belongs_to :journal
def before_save
self.value = value[0..254] if value && value.is_a?(String)
self.old_value = old_value[0..254] if old_value && old_value.is_a?(String)
end
end end
-40
View File
@@ -1,40 +0,0 @@
# redMine - project management software
# Copyright (C) 2006-2007 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 MailHandler < ActionMailer::Base
# Processes incoming emails
# Currently, it only supports adding a note to an existing issue
# by replying to the initial notification message
def receive(email)
# find related issue by parsing the subject
m = email.subject.match %r{\[.*#(\d+)\]}
return unless m
issue = Issue.find_by_id(m[1])
return unless issue
# find user
user = User.find_active(:first, :conditions => {:mail => email.from.first})
return unless user
# check permission
return unless user.allowed_to?(:add_issue_notes, issue.project)
# add the note
issue.init_journal(user, email.body.chomp)
issue.save
end
end
+84 -126
View File
@@ -1,130 +1,88 @@
# 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
# 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
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# 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 Mailer < ActionMailer::Base class Mailer < ActionMailer::Base
helper ApplicationHelper
helper IssuesHelper helper IssuesHelper
helper CustomFieldsHelper
include ActionController::UrlWriter
def issue_add(issue)
recipients issue.recipients
subject "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] #{issue.status.name} - #{issue.subject}"
body :issue => issue,
:issue_url => url_for(:controller => 'issues', :action => 'show', :id => issue)
end
def issue_edit(journal) def issue_add(issue)
issue = journal.journalized set_language_if_valid(Setting.default_language)
recipients issue.recipients # Sends to all project members
# Watchers in cc @recipients = issue.project.members.collect { |m| m.user.mail if m.user.mail_notification }.compact
cc(issue.watcher_recipients - @recipients) @from = Setting.mail_from
subject "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] #{issue.status.name} - #{issue.subject}" @subject = "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] #{issue.status.name} - #{issue.subject}"
body :issue => issue, @body['issue'] = issue
:journal => journal, end
:issue_url => url_for(:controller => 'issues', :action => 'show', :id => issue)
end def issue_edit(journal)
set_language_if_valid(Setting.default_language)
def document_added(document) # Sends to all project members
recipients document.project.recipients issue = journal.journalized
subject "[#{document.project.name}] #{l(:label_document_new)}: #{document.title}" @recipients = issue.project.members.collect { |m| m.user.mail if m.user.mail_notification }.compact
body :document => document, @from = Setting.mail_from
:document_url => url_for(:controller => 'documents', :action => 'show', :id => document) @subject = "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] #{issue.status.name} - #{issue.subject}"
end @body['issue'] = issue
@body['journal']= journal
def attachments_added(attachments) end
container = attachments.first.container
added_to = '' def document_add(document)
added_to_url = '' set_language_if_valid(Setting.default_language)
case container.class.name @recipients = document.project.users.collect { |u| u.mail if u.mail_notification }.compact
when 'Version' @from = Setting.mail_from
added_to_url = url_for(:controller => 'projects', :action => 'list_files', :id => container.project_id) @subject = "[#{document.project.name}] #{l(:label_document_new)}: #{document.title}"
added_to = "#{l(:label_version)}: #{container.name}" @body['document'] = document
when 'Document' end
added_to_url = url_for(:controller => 'documents', :action => 'show', :id => container.id)
added_to = "#{l(:label_document)}: #{container.title}" def attachments_add(attachments)
end set_language_if_valid(Setting.default_language)
recipients container.project.recipients container = attachments.first.container
subject "[#{container.project.name}] #{l(:label_attachment_new)}" url = "http://#{Setting.host_name}/"
body :attachments => attachments, added_to = ""
:added_to => added_to, case container.class.to_s
:added_to_url => added_to_url when 'Version'
end url << "projects/list_files/#{container.project_id}"
added_to = "#{l(:label_version)}: #{container.name}"
def news_added(news) when 'Document'
recipients news.project.recipients url << "documents/show/#{container.id}"
subject "[#{news.project.name}] #{l(:label_news)}: #{news.title}" added_to = "#{l(:label_document)}: #{container.title}"
body :news => news, when 'Issue'
:news_url => url_for(:controller => 'news', :action => 'show', :id => news) url << "issues/show/#{container.id}"
end added_to = "#{container.tracker.name} ##{container.id}: #{container.subject}"
end
def message_posted(message, recipients) @recipients = container.project.users.collect { |u| u.mail if u.mail_notification }.compact
recipients(recipients) @from = Setting.mail_from
subject "[#{message.board.project.name} - #{message.board.name}] #{message.subject}" @subject = "[#{container.project.name}] #{l(:label_attachment_new)}"
body :message => message, @body['attachments'] = attachments
:message_url => url_for(:controller => 'messages', :action => 'show', :board_id => message.board_id, :id => message.root) @body['url'] = url
end @body['added_to'] = added_to
end
def account_information(user, password)
set_language_if_valid user.language def lost_password(token)
recipients user.mail set_language_if_valid(token.user.language)
subject l(:mail_subject_register) @recipients = token.user.mail
body :user => user, @from = Setting.mail_from
:password => password, @subject = l(:mail_subject_lost_password)
:login_url => url_for(:controller => 'account', :action => 'login') @body['token'] = token
end
def lost_password(token)
set_language_if_valid(token.user.language)
recipients token.user.mail
subject l(:mail_subject_lost_password)
body :token => token,
:url => url_for(:controller => 'account', :action => 'lost_password', :token => token.value)
end end
def register(token) def register(token)
set_language_if_valid(token.user.language) set_language_if_valid(token.user.language)
recipients token.user.mail @recipients = token.user.mail
subject l(:mail_subject_register) @from = Setting.mail_from
body :token => token, @subject = l(:mail_subject_register)
:url => url_for(:controller => 'account', :action => 'register', :token => token.value) @body['token'] = token
end end
def test(user)
set_language_if_valid(user.language)
recipients user.mail
subject 'Redmine test'
body :url => url_for(:controller => 'welcome')
end
private
def initialize_defaults(method_name)
super
set_language_if_valid Setting.default_language
from Setting.mail_from
default_url_options[:host] = Setting.host_name
default_url_options[:protocol] = Setting.protocol
end
# Renders a message with the corresponding layout
def render_message(method_name, body)
layout = method_name.match(%r{text\.html\.(rhtml|rxml)}) ? 'layout.text.html.rhtml' : 'layout.text.plain.rhtml'
body[:content_for_layout] = render(:file => method_name, :body => body)
ActionView::Base.new(File.join(template_root, 'mailer'), body, self).render(:file => layout)
end
end end
+27 -36
View File
@@ -1,38 +1,29 @@
# redMine - project management software # redMine - project management software
# Copyright (C) 2006 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# 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 Member < ActiveRecord::Base class Member < ActiveRecord::Base
belongs_to :user belongs_to :user
belongs_to :role belongs_to :role
belongs_to :project belongs_to :project
validates_presence_of :role, :user, :project validates_presence_of :role, :user, :project
validates_uniqueness_of :user_id, :scope => :project_id validates_uniqueness_of :user_id, :scope => :project_id
def validate def name
errors.add :role_id, :activerecord_error_invalid if role && !role.member? self.user.display_name
end
def name
self.user.name
end
def before_destroy
# remove category based auto assignments for this member
project.issue_categories.update_all "assigned_to_id = NULL", ["assigned_to_id = ?", self.user.id]
end end
end end
-49
View File
@@ -1,49 +0,0 @@
# redMine - project management software
# Copyright (C) 2006-2007 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 Message < ActiveRecord::Base
belongs_to :board
belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
acts_as_tree :counter_cache => :replies_count, :order => "#{Message.table_name}.created_on ASC"
has_many :attachments, :as => :container, :dependent => :destroy
belongs_to :last_reply, :class_name => 'Message', :foreign_key => 'last_reply_id'
acts_as_searchable :columns => ['subject', 'content'],
:include => :board,
:project_key => 'project_id',
:date_column => 'created_on'
acts_as_event :title => Proc.new {|o| "#{o.board.name}: #{o.subject}"},
:description => :content,
:url => Proc.new {|o| {:controller => 'messages', :action => 'show', :board_id => o.board_id, :id => o.id}}
validates_presence_of :subject, :content
validates_length_of :subject, :maximum => 255
def after_create
board.update_attribute(:last_message_id, self.id)
board.increment! :messages_count
if parent
parent.reload.update_attribute(:last_reply_id, self.id)
else
board.increment! :topics_count
end
end
def project
board.project
end
end
-27
View File
@@ -1,27 +0,0 @@
# redMine - project management software
# Copyright (C) 2006-2007 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 MessageObserver < ActiveRecord::Observer
def after_create(message)
# send notification to the authors of the thread
recipients = ([message.root] + message.root.children).collect {|m| m.author.mail if m.author}
# send notification to the board watchers
recipients += message.board.watcher_recipients
recipients = recipients.compact.uniq
Mailer.deliver_message_posted(message, recipients) if !recipients.empty? && Setting.notified_events.include?('message_posted')
end
end
+27 -32
View File
@@ -1,34 +1,29 @@
# redMine - project management software # redMine - project management software
# Copyright (C) 2006 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# 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 News < ActiveRecord::Base class News < ActiveRecord::Base
belongs_to :project belongs_to :project
belongs_to :author, :class_name => 'User', :foreign_key => 'author_id' belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
has_many :comments, :as => :commented, :dependent => :delete_all, :order => "created_on" has_many :comments, :as => :commented, :dependent => :delete_all, :order => "created_on"
validates_presence_of :title, :description validates_presence_of :title, :description
validates_length_of :title, :maximum => 60
validates_length_of :summary, :maximum => 255 # returns latest news for projects visible by user
def self.latest(user=nil, count=5)
acts_as_searchable :columns => ['title', 'description'] find(:all, :limit => count, :conditions => Project.visible_by(user), :include => [ :author, :project ], :order => "news.created_on DESC")
acts_as_event :url => Proc.new {|o| {:controller => 'news', :action => 'show', :id => o.id}}
# returns latest news for projects visible by user
def self.latest(user=nil, count=5)
find(:all, :limit => count, :conditions => Project.visible_by(user), :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC")
end end
end end
+65
View File
@@ -0,0 +1,65 @@
# redMine - project management software
# Copyright (C) 2006 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 Permission < ActiveRecord::Base
has_and_belongs_to_many :roles
validates_presence_of :controller, :action, :description
GROUPS = {
100 => :label_project,
200 => :label_member_plural,
300 => :label_version_plural,
400 => :label_issue_category_plural,
600 => :label_query_plural,
1000 => :label_issue_plural,
1100 => :label_news_plural,
1200 => :label_document_plural,
1300 => :label_attachment_plural,
1400 => :label_repository
}.freeze
@@cached_perms_for_public = nil
@@cached_perms_for_roles = nil
def name
self.controller + "/" + self.action
end
def group_id
(self.sort / 100)*100
end
def self.allowed_to_public(action)
@@cached_perms_for_public ||= find(:all, :conditions => ["is_public=?", true]).collect {|p| "#{p.controller}/#{p.action}"}
@@cached_perms_for_public.include? action
end
def self.allowed_to_role(action, role)
@@cached_perms_for_roles ||=
begin
perms = {}
find(:all, :include => :roles).each {|p| perms.store "#{p.controller}/#{p.action}", p.roles.collect {|r| r.id } }
perms
end
allowed_to_public(action) or (@@cached_perms_for_roles[action] and @@cached_perms_for_roles[action].include? role)
end
def self.allowed_to_role_expired
@@cached_perms_for_roles = nil
end
end
+68 -176
View File
@@ -1,178 +1,70 @@
# redMine - project management software # redMine - project management software
# Copyright (C) 2006 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# 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 Project < ActiveRecord::Base class Project < ActiveRecord::Base
# Project statuses has_many :versions, :dependent => :destroy, :order => "versions.effective_date DESC, versions.name DESC"
STATUS_ACTIVE = 1 has_many :members, :dependent => :delete_all, :include => :user, :conditions => "users.status=#{User::STATUS_ACTIVE}"
STATUS_ARCHIVED = 9 has_many :users, :through => :members
has_many :custom_values, :dependent => :delete_all, :as => :customized
has_many :members, :dependent => :delete_all, :include => :user, :conditions => "#{User.table_name}.status=#{User::STATUS_ACTIVE}" has_many :issues, :dependent => :destroy, :order => "issues.created_on DESC", :include => [:status, :tracker]
has_many :users, :through => :members has_many :queries, :dependent => :delete_all
has_many :custom_values, :dependent => :delete_all, :as => :customized has_many :documents, :dependent => :destroy
has_many :enabled_modules, :dependent => :delete_all has_many :news, :dependent => :delete_all, :include => :author
has_many :issues, :dependent => :destroy, :order => "#{Issue.table_name}.created_on DESC", :include => [:status, :tracker] has_many :issue_categories, :dependent => :delete_all, :order => "issue_categories.name"
has_many :issue_changes, :through => :issues, :source => :journals has_one :repository, :dependent => :destroy
has_many :versions, :dependent => :destroy, :order => "#{Version.table_name}.effective_date DESC, #{Version.table_name}.name DESC" has_and_belongs_to_many :custom_fields, :class_name => 'IssueCustomField', :join_table => 'custom_fields_projects', :association_foreign_key => 'custom_field_id'
has_many :time_entries, :dependent => :delete_all acts_as_tree :order => "name", :counter_cache => true
has_many :queries, :dependent => :delete_all
has_many :documents, :dependent => :destroy validates_presence_of :name, :description
has_many :news, :dependent => :delete_all, :include => :author validates_uniqueness_of :name
has_many :issue_categories, :dependent => :delete_all, :order => "#{IssueCategory.table_name}.name" validates_associated :custom_values, :on => :update
has_many :boards, :order => "position ASC" validates_associated :repository
has_one :repository, :dependent => :destroy validates_format_of :name, :with => /^[\w\s\'\-]*$/i
has_many :changesets, :through => :repository
has_one :wiki, :dependent => :destroy # returns latest created projects
has_and_belongs_to_many :custom_fields, :class_name => 'IssueCustomField', :join_table => "#{table_name_prefix}custom_fields_projects#{table_name_suffix}", :association_foreign_key => 'custom_field_id' # non public projects will be returned only if user is a member of those
acts_as_tree :order => "name", :counter_cache => true def self.latest(user=nil, count=5)
find(:all, :limit => count, :conditions => visible_by(user), :order => "projects.created_on DESC")
acts_as_searchable :columns => ['name', 'description'], :project_key => 'id' end
acts_as_event :title => Proc.new {|o| "#{l(:label_project)}: #{o.name}"},
:url => Proc.new {|o| {:controller => 'projects', :action => 'show', :id => o.id}} def self.visible_by(user=nil)
if user && !user.memberships.empty?
attr_protected :status, :enabled_module_names return ["projects.is_public = ? or projects.id IN (#{user.memberships.collect{|m| m.project_id}.join(',')})", true]
else
validates_presence_of :name, :description, :identifier return ["projects.is_public = ?", true]
validates_uniqueness_of :name, :identifier end
validates_associated :custom_values, :on => :update end
validates_associated :repository, :wiki
validates_length_of :name, :maximum => 30 # Returns an array of all custom fields enabled for project issues
validates_format_of :name, :with => /^[\w\s\'\-]*$/i # (explictly associated custom fields and custom fields enabled for all projects)
validates_length_of :description, :maximum => 255 def custom_fields_for_issues(tracker)
validates_length_of :homepage, :maximum => 60 tracker.custom_fields.find(:all, :include => :projects,
validates_length_of :identifier, :in => 3..12 :conditions => ["is_for_all=? or project_id=?", true, self.id])
validates_format_of :identifier, :with => /^[a-z0-9\-]*$/ #(CustomField.for_all + custom_fields).uniq
end
def identifier=(identifier)
super unless identifier_frozen? def all_custom_fields
end @all_custom_fields ||= IssueCustomField.find(:all, :include => :projects,
:conditions => ["is_for_all=? or project_id=?", true, self.id])
def identifier_frozen? end
errors[:identifier].nil? && !(new_record? || identifier.blank?)
end protected
def validate
def issues_with_subprojects(include_subprojects=false) errors.add(parent_id, " must be a root project") if parent and parent.parent
conditions = nil errors.add_to_base("A project with subprojects can't be a subproject") if parent and children.size > 0
if include_subprojects && !active_children.empty?
ids = [id] + active_children.collect {|c| c.id}
conditions = ["#{Issue.table_name}.project_id IN (#{ids.join(',')})"]
end
conditions ||= ["#{Issue.table_name}.project_id = ?", id]
Issue.with_scope :find => { :conditions => conditions } do
yield
end
end
# returns latest created projects
# non public projects will be returned only if user is a member of those
def self.latest(user=nil, count=5)
find(:all, :limit => count, :conditions => visible_by(user), :order => "created_on DESC")
end
def self.visible_by(user=nil)
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
def active?
self.status == STATUS_ACTIVE
end
def archive
# Archive subprojects if any
children.each do |subproject|
subproject.archive
end
update_attribute :status, STATUS_ARCHIVED
end
def unarchive
return false if parent && !parent.active?
update_attribute :status, STATUS_ACTIVE
end
def active_children
children.select {|child| child.active?}
end
# Users issues can be assigned to
def assignable_users
members.select {|m| m.role.assignable?}.collect {|m| m.user}
end
# Returns the mail adresses of users that should be always notified on project events
def recipients
members.select {|m| m.mail_notification? || m.user.mail_notification?}.collect {|m| m.user.mail}
end
# Returns an array of all custom fields enabled for project issues
# (explictly associated custom fields and custom fields enabled for all projects)
def custom_fields_for_issues(tracker)
all_custom_fields.select {|c| tracker.custom_fields.include? c }
end
def all_custom_fields
@all_custom_fields ||= (IssueCustomField.for_all + custom_fields).uniq
end
def <=>(project)
name <=> project.name
end
def allows_to?(action)
if action.is_a? Hash
allowed_actions.include? "#{action[:controller]}/#{action[:action]}"
else
allowed_permissions.include? action
end
end
def module_enabled?(module_name)
module_name = module_name.to_s
enabled_modules.detect {|m| m.name == module_name}
end
def enabled_module_names=(module_names)
enabled_modules.clear
module_names = [] unless module_names && module_names.is_a?(Array)
module_names.each do |name|
enabled_modules << EnabledModule.new(:name => name.to_s)
end
end
protected
def validate
errors.add(parent_id, " must be a root project") if parent and parent.parent
errors.add_to_base("A project with subprojects can't be a subproject") if parent and children.size > 0
end
private
def allowed_permissions
@allowed_permissions ||= begin
module_names = enabled_modules.collect {|m| m.name}
Redmine::AccessControl.modules_permissions(module_names).collect {|p| p.name}
end
end
def allowed_actions
@actions_allowed ||= allowed_permissions.inject([]) { |actions, permission| actions += Redmine::AccessControl.allowed_actions(permission) }.flatten
end end
end end
+22 -22
View File
@@ -1,22 +1,22 @@
# redMine - project management software # redMine - project management software
# Copyright (C) 2006 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# 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 ProjectCustomField < CustomField class ProjectCustomField < CustomField
def type_name def type_name
:label_project_plural :label_project_plural
end end
end end
+34 -187
View File
@@ -1,5 +1,5 @@
# redMine - project management software # redMine - project management software
# Copyright (C) 2006-2007 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
@@ -15,28 +15,14 @@
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# 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 QueryColumn
attr_accessor :name, :sortable
def initialize(name, options={})
self.name = name
self.sortable = options[:sortable]
end
def default?; default end
end
class Query < ActiveRecord::Base class Query < ActiveRecord::Base
belongs_to :project belongs_to :project
belongs_to :user belongs_to :user
serialize :filters serialize :filters
serialize :column_names
attr_protected :project, :user attr_protected :project, :user
attr_accessor :executed_by
validates_presence_of :name, :on => :save validates_presence_of :name, :on => :save
validates_length_of :name, :maximum => 255
@@operators = { "=" => :label_equals, @@operators = { "=" => :label_equals,
"!" => :label_not_equals, "!" => :label_not_equals,
@@ -44,13 +30,10 @@ class Query < ActiveRecord::Base
"c" => :label_closed_issues, "c" => :label_closed_issues,
"!*" => :label_none, "!*" => :label_none,
"*" => :label_all, "*" => :label_all,
">=" => '>=',
"<=" => '<=',
"<t+" => :label_in_less_than, "<t+" => :label_in_less_than,
">t+" => :label_in_more_than, ">t+" => :label_in_more_than,
"t+" => :label_in, "t+" => :label_in,
"t" => :label_today, "t" => :label_today,
"w" => :label_this_week,
">t-" => :label_less_than_ago, ">t-" => :label_less_than_ago,
"<t-" => :label_more_than_ago, "<t-" => :label_more_than_ago,
"t-" => :label_ago, "t-" => :label_ago,
@@ -62,57 +45,28 @@ class Query < ActiveRecord::Base
@@operators_by_filter_type = { :list => [ "=", "!" ], @@operators_by_filter_type = { :list => [ "=", "!" ],
:list_status => [ "o", "=", "!", "c", "*" ], :list_status => [ "o", "=", "!", "c", "*" ],
:list_optional => [ "=", "!", "!*", "*" ], :list_optional => [ "=", "!", "!*", "*" ],
:list_one_or_more => [ "*", "=" ], :date => [ "<t+", ">t+", "t+", "t", ">t-", "<t-", "t-" ],
:date => [ "<t+", ">t+", "t+", "t", "w", ">t-", "<t-", "t-" ], :date_past => [ ">t-", "<t-", "t-", "t" ],
:date_past => [ ">t-", "<t-", "t-", "t", "w" ], :text => [ "~", "!~" ] }
:string => [ "=", "~", "!", "!~" ],
:text => [ "~", "!~" ],
:integer => [ "=", ">=", "<=" ] }
cattr_reader :operators_by_filter_type cattr_reader :operators_by_filter_type
@@available_columns = [
QueryColumn.new(:tracker, :sortable => "#{Tracker.table_name}.position"),
QueryColumn.new(:status, :sortable => "#{IssueStatus.table_name}.position"),
QueryColumn.new(:priority, :sortable => "#{Enumeration.table_name}.position"),
QueryColumn.new(:subject),
QueryColumn.new(:assigned_to, :sortable => "#{User.table_name}.lastname"),
QueryColumn.new(:updated_on, :sortable => "#{Issue.table_name}.updated_on"),
QueryColumn.new(:category, :sortable => "#{IssueCategory.table_name}.name"),
QueryColumn.new(:start_date, :sortable => "#{Issue.table_name}.start_date"),
QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date"),
QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours"),
QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio"),
QueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on"),
]
cattr_reader :available_columns
def initialize(attributes = nil) def initialize(attributes = nil)
super attributes super attributes
self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} } self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} }
end self.is_public = true
def executed_by=(user)
@executed_by = user
set_language_if_valid(user.language) if user
end end
def validate def validate
filters.each_key do |field| filters.each_key do |field|
errors.add label_for(field), :activerecord_error_blank unless errors.add field.gsub(/\_id$/, ""), :activerecord_error_blank unless
# filter requires one or more values # filter requires one or more values
(values_for(field) and !values_for(field).first.empty?) or (values_for(field) and !values_for(field).first.empty?) or
# filter doesn't require any value # filter doesn't require any value
["o", "c", "!*", "*", "t", "w"].include? operator_for(field) ["o", "c", "!*", "*", "t"].include? operator_for(field)
end if filters end if filters
end end
def editable_by?(user)
return false unless user
return true if !is_public && self.user_id == user.id
is_public && user.allowed_to?(:manage_public_queries, project)
end
def available_filters def available_filters
return @available_filters if @available_filters return @available_filters if @available_filters
@available_filters = { "status_id" => { :type => :list_status, :order => 1, :values => IssueStatus.find(:all, :order => 'position').collect{|s| [s.name, s.id.to_s] } }, @available_filters = { "status_id" => { :type => :list_status, :order => 1, :values => IssueStatus.find(:all, :order => 'position').collect{|s| [s.name, s.id.to_s] } },
@@ -122,42 +76,13 @@ class Query < ActiveRecord::Base
"created_on" => { :type => :date_past, :order => 9 }, "created_on" => { :type => :date_past, :order => 9 },
"updated_on" => { :type => :date_past, :order => 10 }, "updated_on" => { :type => :date_past, :order => 10 },
"start_date" => { :type => :date, :order => 11 }, "start_date" => { :type => :date, :order => 11 },
"due_date" => { :type => :date, :order => 12 }, "due_date" => { :type => :date, :order => 12 } }
"done_ratio" => { :type => :integer, :order => 13 }} unless project.nil?
# project specific filters
user_values = [] @available_filters["assigned_to_id"] = { :type => :list_optional, :order => 4, :values => @project.users.collect{|s| [s.name, s.id.to_s] } }
if project @available_filters["author_id"] = { :type => :list, :order => 5, :values => @project.users.collect{|s| [s.name, s.id.to_s] } }
user_values += project.users.collect{|s| [s.name, s.id.to_s] }
elsif executed_by
user_values << ["<< #{l(:label_me)} >>", "me"] if executed_by
# members of the user's projects
user_values += executed_by.projects.collect(&:users).flatten.uniq.sort.collect{|s| [s.name, s.id.to_s] }
end
@available_filters["assigned_to_id"] = { :type => :list_optional, :order => 4, :values => user_values } unless user_values.empty?
@available_filters["author_id"] = { :type => :list, :order => 5, :values => user_values } unless user_values.empty?
if project
# project specific filters
@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 => @project.issue_categories.collect{|s| [s.name, s.id.to_s] } }
@available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => @project.versions.sort.collect{|s| [s.name, s.id.to_s] } } @available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => @project.versions.collect{|s| [s.name, s.id.to_s] } }
unless @project.active_children.empty?
@available_filters["subproject_id"] = { :type => :list_one_or_more, :order => 13, :values => @project.active_children.collect{|s| [s.name, s.id.to_s] } }
end
@project.all_custom_fields.select(&:is_filter?).each do |field|
case field.field_format
when "string", "int"
options = { :type => :string, :order => 20 }
when "text"
options = { :type => :text, :order => 20 }
when "list"
options = { :type => :list_optional, :values => field.possible_values, :order => 20}
when "date"
options = { :type => :date, :order => 20 }
when "bool"
options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]], :order => 20 }
end
@available_filters["cf_#{field.id}"] = options.merge({ :name => field.name })
end
# remove category filter if no category defined # remove category filter if no category defined
@available_filters.delete "category_id" if @available_filters["category_id"][:values].empty? @available_filters.delete "category_id" if @available_filters["category_id"][:values].empty?
end end
@@ -197,124 +122,46 @@ class Query < ActiveRecord::Base
has_filter?(field) ? filters[field][:values] : nil has_filter?(field) ? filters[field][:values] : nil
end end
def label_for(field)
label = @available_filters[field][:name] if @available_filters.has_key?(field)
label ||= field.gsub(/\_id$/, "")
end
def available_columns
cols = Query.available_columns
end
def columns
if has_default_columns?
available_columns.select {|c| Setting.issue_list_default_columns.include?(c.name.to_s) }
else
# preserve the column_names order
column_names.collect {|name| available_columns.find {|col| col.name == name}}.compact
end
end
def column_names=(names)
names = names.select {|n| n.is_a?(Symbol) || !n.blank? } if names
names = names.collect {|n| n.is_a?(Symbol) ? n : n.to_sym } if names
write_attribute(:column_names, names)
end
def has_column?(column)
column_names && column_names.include?(column.name)
end
def has_default_columns?
column_names.nil? || column_names.empty?
end
def statement def statement
# project/subprojects clause sql = "1=1"
clause = '' sql << " AND issues.project_id=%d" % project.id if project
if project && has_filter?("subproject_id")
subproject_ids = []
if operator_for("subproject_id") == "="
subproject_ids = values_for("subproject_id").each(&:to_i)
else
subproject_ids = project.active_children.collect{|p| p.id}
end
clause << "#{Issue.table_name}.project_id IN (%d,%s)" % [project.id, subproject_ids.join(",")] if project
elsif project
clause << "#{Issue.table_name}.project_id=%d" % project.id
else
clause << Project.visible_by(executed_by)
end
# filters clauses
filters_clauses = []
filters.each_key do |field| filters.each_key do |field|
next if field == "subproject_id" v = values_for field
v = values_for(field).clone next unless v and !v.empty?
next unless v and !v.empty? sql = sql + " AND " unless sql.empty?
sql = ''
if field =~ /^cf_(\d+)$/
# custom field
db_table = CustomValue.table_name
db_field = 'value'
sql << "#{Issue.table_name}.id IN (SELECT #{db_table}.customized_id FROM #{db_table} where #{db_table}.customized_type='Issue' AND #{db_table}.customized_id=#{Issue.table_name}.id AND #{db_table}.custom_field_id=#{$1} AND "
else
# regular field
db_table = Issue.table_name
db_field = field
sql << '('
end
# "me" value subsitution
if %w(assigned_to_id author_id).include?(field)
v.push(executed_by ? executed_by.id.to_s : "0") if v.delete("me")
end
case operator_for field case operator_for field
when "=" when "="
sql = sql + "#{db_table}.#{db_field} IN (" + v.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")" sql = sql + "issues.#{field} IN (" + v.each(&:to_i).join(",") + ")"
when "!" when "!"
sql = sql + "#{db_table}.#{db_field} NOT IN (" + v.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")" sql = sql + "issues.#{field} NOT IN (" + v.each(&:to_i).join(",") + ")"
when "!*" when "!*"
sql = sql + "#{db_table}.#{db_field} IS NULL" sql = sql + "issues.#{field} IS NULL"
when "*" when "*"
sql = sql + "#{db_table}.#{db_field} IS NOT NULL" sql = sql + "issues.#{field} IS NOT NULL"
when ">="
sql = sql + "#{db_table}.#{db_field} >= #{v.first.to_i}"
when "<="
sql = sql + "#{db_table}.#{db_field} <= #{v.first.to_i}"
when "o" when "o"
sql = sql + "#{IssueStatus.table_name}.is_closed=#{connection.quoted_false}" if field == "status_id" sql = sql + "issue_statuses.is_closed=#{connection.quoted_false}" if field == "status_id"
when "c" when "c"
sql = sql + "#{IssueStatus.table_name}.is_closed=#{connection.quoted_true}" if field == "status_id" sql = sql + "issue_statuses.is_closed=#{connection.quoted_true}" if field == "status_id"
when ">t-" when ">t-"
sql = sql + "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date((Date.today - v.first.to_i).to_time), connection.quoted_date((Date.today + 1).to_time)] sql = sql + "issues.#{field} >= '%s'" % connection.quoted_date(Date.today - v.first.to_i)
when "<t-" when "<t-"
sql = sql + "#{db_table}.#{db_field} <= '%s'" % connection.quoted_date((Date.today - v.first.to_i).to_time) sql = sql + "issues.#{field} <= '" + (Date.today - v.first.to_i).strftime("%Y-%m-%d") + "'"
when "t-" when "t-"
sql = sql + "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date((Date.today - v.first.to_i).to_time), connection.quoted_date((Date.today - v.first.to_i + 1).to_time)] sql = sql + "issues.#{field} = '" + (Date.today - v.first.to_i).strftime("%Y-%m-%d") + "'"
when ">t+" when ">t+"
sql = sql + "#{db_table}.#{db_field} >= '%s'" % connection.quoted_date((Date.today + v.first.to_i).to_time) sql = sql + "issues.#{field} >= '" + (Date.today + v.first.to_i).strftime("%Y-%m-%d") + "'"
when "<t+" when "<t+"
sql = sql + "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date(Date.today.to_time), connection.quoted_date((Date.today + v.first.to_i + 1).to_time)] sql = sql + "issues.#{field} <= '" + (Date.today + v.first.to_i).strftime("%Y-%m-%d") + "'"
when "t+" when "t+"
sql = sql + "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date((Date.today + v.first.to_i).to_time), connection.quoted_date((Date.today + v.first.to_i + 1).to_time)] sql = sql + "issues.#{field} = '" + (Date.today + v.first.to_i).strftime("%Y-%m-%d") + "'"
when "t" when "t"
sql = sql + "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date(Date.today.to_time), connection.quoted_date((Date.today+1).to_time)] sql = sql + "issues.#{field} = '%s'" % connection.quoted_date(Date.today)
when "w"
sql = sql + "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date(Time.now.at_beginning_of_week), connection.quoted_date(Time.now.next_week.yesterday)]
when "~" when "~"
sql = sql + "#{db_table}.#{db_field} LIKE '%#{connection.quote_string(v.first)}%'" sql = sql + "issues.#{field} LIKE '%#{connection.quote_string(v.first)}%'"
when "!~" when "!~"
sql = sql + "#{db_table}.#{db_field} NOT LIKE '%#{connection.quote_string(v.first)}%'" sql = sql + "issues.#{field} NOT LIKE '%#{connection.quote_string(v.first)}%'"
end end
sql << ')'
filters_clauses << sql
end if filters and valid? end if filters and valid?
sql
clause << ' AND ' unless clause.empty?
clause << filters_clauses.join(' AND ') unless filters_clauses.empty?
clause
end end
end end
+6 -65
View File
@@ -1,5 +1,5 @@
# redMine - project management software # redMine - project management software
# Copyright (C) 2006-2007 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,71 +17,12 @@
class Repository < ActiveRecord::Base class Repository < ActiveRecord::Base
belongs_to :project belongs_to :project
has_many :changesets, :dependent => :destroy, :order => "#{Changeset.table_name}.revision DESC" validates_presence_of :url
has_many :changes, :through => :changesets validates_format_of :url, :with => /^(http|https|svn|file):\/\/.+/i
@scm = nil
def scm def scm
@scm ||= self.scm_adapter.new url, root_url, login, password @scm ||= SvnRepos::Base.new url
update_attribute(:root_url, @scm.root_url) if root_url.blank?
@scm
end
def scm_name
self.class.scm_name
end
def supports_cat?
scm.supports_cat?
end
def entries(path=nil, identifier=nil)
scm.entries(path, identifier)
end
def diff(path, rev, rev_to, type)
scm.diff(path, rev, rev_to, type)
end
# Default behaviour: we search in cached changesets
def changesets_for_path(path)
path = "/#{path}" unless path.starts_with?('/')
Change.find(:all, :include => :changeset,
:conditions => ["repository_id = ? AND path = ?", id, path],
:order => "committed_on DESC, #{Changeset.table_name}.revision DESC").collect(&:changeset)
end
def latest_changeset
@latest_changeset ||= changesets.find(:first)
end
def scan_changesets_for_issue_ids
self.changesets.each(&:scan_comment_for_issue_ids)
end
# fetch new changesets for all repositories
# can be called periodically by an external script
# eg. ruby script/runner "Repository.fetch_changesets"
def self.fetch_changesets
find(:all).each(&:fetch_changesets)
end
# scan changeset comments to find related and fixed issues for all repositories
def self.scan_changesets_for_issue_ids
find(:all).each(&:scan_changesets_for_issue_ids)
end
def self.scm_name
'Abstract'
end
def self.available_scm
subclasses.collect {|klass| [klass.scm_name, klass.name]}
end
def self.factory(klass_name, *args)
klass = "Repository::#{klass_name}".constantize
klass.new(*args)
rescue
nil
end end
end end
-150
View File
@@ -1,150 +0,0 @@
# redMine - project management software
# Copyright (C) 2006-2007 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.
require 'redmine/scm/adapters/cvs_adapter'
require 'digest/sha1'
class Repository::Cvs < Repository
validates_presence_of :url, :root_url
def scm_adapter
Redmine::Scm::Adapters::CvsAdapter
end
def self.scm_name
'CVS'
end
def entry(path, identifier)
e = entries(path, identifier)
e ? e.first : nil
end
def entries(path=nil, identifier=nil)
entries=scm.entries(path, identifier)
if entries
entries.each() do |entry|
unless entry.lastrev.nil? || entry.lastrev.identifier
change=changes.find_by_revision_and_path( entry.lastrev.revision, scm.with_leading_slash(entry.path) )
if change
entry.lastrev.identifier=change.changeset.revision
entry.lastrev.author=change.changeset.committer
entry.lastrev.revision=change.revision
entry.lastrev.branch=change.branch
end
end
end
end
entries
end
def diff(path, rev, rev_to, type)
#convert rev to revision. CVS can't handle changesets here
diff=[]
changeset_from=changesets.find_by_revision(rev)
if rev_to.to_i > 0
changeset_to=changesets.find_by_revision(rev_to)
end
changeset_from.changes.each() do |change_from|
revision_from=nil
revision_to=nil
revision_from=change_from.revision if path.nil? || (change_from.path.starts_with? scm.with_leading_slash(path))
if revision_from
if changeset_to
changeset_to.changes.each() do |change_to|
revision_to=change_to.revision if change_to.path==change_from.path
end
end
unless revision_to
revision_to=scm.get_previous_revision(revision_from)
end
diff=diff+scm.diff(change_from.path, revision_from, revision_to, type)
end
end
return diff
end
def fetch_changesets
#not the preferred way with CVS. maybe we should introduce always a cron-job for this
last_commit = changesets.maximum(:committed_on)
# some nifty bits to introduce a commit-id with cvs
# natively cvs doesn't provide any kind of changesets, there is only a revision per file.
# 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
# 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
time_delta=10.seconds
transaction do
scm.revisions('', last_commit, nil, :with_paths => true) do |revision|
# only add the change to the database, if it doen't exists. the cvs log
# is not exclusive at all.
unless changes.find_by_path_and_revision(scm.with_leading_slash(revision.paths[0][:path]), revision.paths[0][:revision])
revision
cs = changesets.find(:first, :conditions=>{
:committed_on=>revision.time-time_delta..revision.time+time_delta,
:committer=>revision.author,
:comments=>revision.message
})
# create a new changeset....
unless cs
# we use a negative changeset-number here (just for inserting)
# later on, we calculate a continous positive number
next_rev = changesets.minimum(:revision)
next_rev = 0 if next_rev.nil? or next_rev > 0
next_rev = next_rev - 1
cs=Changeset.create(:repository => self,
:revision => next_rev,
:committer => revision.author,
:committed_on => revision.time,
:comments => revision.message)
end
#convert CVS-File-States to internal Action-abbrevations
#default action is (M)odified
action="M"
if revision.paths[0][:action]=="Exp" && revision.paths[0][:revision]=="1.1"
action="A" #add-action always at first revision (= 1.1)
elsif revision.paths[0][:action]=="dead"
action="D" #dead-state is similar to Delete
end
Change.create(:changeset => cs,
:action => action,
:path => scm.with_leading_slash(revision.paths[0][:path]),
:revision => revision.paths[0][:revision],
:branch => revision.paths[0][:branch]
)
end
end
next_rev = [changesets.maximum(:revision) || 0, 0].max
changesets.find(:all, :conditions=>["revision < 0"], :order=>"committed_on ASC").each() do |changeset|
next_rev = next_rev + 1
changeset.revision = next_rev
changeset.save!
end
end
end
end
-90
View File
@@ -1,90 +0,0 @@
# redMine - project management software
# Copyright (C) 2006-2007 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.
require 'redmine/scm/adapters/darcs_adapter'
class Repository::Darcs < Repository
validates_presence_of :url
def scm_adapter
Redmine::Scm::Adapters::DarcsAdapter
end
def self.scm_name
'Darcs'
end
def entries(path=nil, identifier=nil)
entries=scm.entries(path, identifier)
if entries
entries.each do |entry|
# Search the DB for the entry's last change
changeset = changesets.find_by_scmid(entry.lastrev.scmid) if entry.lastrev && !entry.lastrev.scmid.blank?
if changeset
entry.lastrev.identifier = changeset.revision
entry.lastrev.name = changeset.revision
entry.lastrev.time = changeset.committed_on
entry.lastrev.author = changeset.committer
end
end
end
entries
end
def diff(path, rev, rev_to, type)
patch_from = changesets.find_by_revision(rev)
patch_to = changesets.find_by_revision(rev_to) if rev_to
if path.blank?
path = patch_from.changes.collect{|change| change.path}.join(' ')
end
scm.diff(path, patch_from.scmid, patch_to.scmid, type)
end
def fetch_changesets
scm_info = scm.info
if scm_info
db_last_id = latest_changeset ? latest_changeset.scmid : nil
next_rev = latest_changeset ? latest_changeset.revision + 1 : 1
# latest revision in the repository
scm_revision = scm_info.lastrev.scmid
unless changesets.find_by_scmid(scm_revision)
revisions = scm.revisions('', db_last_id, nil, :with_path => true)
transaction do
revisions.reverse_each do |revision|
changeset = Changeset.create(:repository => self,
:revision => next_rev,
:scmid => revision.scmid,
:committer => revision.author,
:committed_on => revision.time,
:comments => revision.message)
next if changeset.new_record?
revision.paths.each do |change|
Change.create(:changeset => changeset,
:action => change[:action],
:path => change[:path],
:from_path => change[:from_path],
:from_revision => change[:from_revision])
end
next_rev += 1
end if revisions
end
end
end
end
end
-81
View File
@@ -1,81 +0,0 @@
# redMine - project management software
# Copyright (C) 2006-2007 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.
require 'redmine/scm/adapters/mercurial_adapter'
class Repository::Mercurial < Repository
attr_protected :root_url
validates_presence_of :url
def scm_adapter
Redmine::Scm::Adapters::MercurialAdapter
end
def self.scm_name
'Mercurial'
end
def entries(path=nil, identifier=nil)
entries=scm.entries(path, identifier)
if entries
entries.each do |entry|
next unless entry.is_file?
# Search the DB for the entry's last change
change = changes.find(:first, :conditions => ["path = ?", scm.with_leading_slash(entry.path)], :order => "#{Changeset.table_name}.committed_on DESC")
if change
entry.lastrev.identifier = change.changeset.revision
entry.lastrev.name = change.changeset.revision
entry.lastrev.author = change.changeset.committer
entry.lastrev.revision = change.revision
end
end
end
entries
end
def fetch_changesets
scm_info = scm.info
if scm_info
# latest revision found in database
db_revision = latest_changeset ? latest_changeset.revision : nil
# latest revision in the repository
scm_revision = scm_info.lastrev.identifier.to_i
unless changesets.find_by_revision(scm_revision)
revisions = scm.revisions('', db_revision, nil)
transaction do
revisions.reverse_each do |revision|
changeset = Changeset.create(:repository => self,
:revision => revision.identifier,
:scmid => revision.scmid,
:committer => revision.author,
:committed_on => revision.time,
:comments => revision.message)
revision.paths.each do |change|
Change.create(:changeset => changeset,
:action => change[:action],
:path => change[:path],
:from_path => change[:from_path],
:from_revision => change[:from_revision])
end
end
end
end
end
end
end
-74
View File
@@ -1,74 +0,0 @@
# redMine - project management software
# Copyright (C) 2006-2007 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.
require 'redmine/scm/adapters/subversion_adapter'
class Repository::Subversion < Repository
attr_protected :root_url
validates_presence_of :url
validates_format_of :url, :with => /^(http|https|svn|svn\+ssh|file):\/\/.+/i
def scm_adapter
Redmine::Scm::Adapters::SubversionAdapter
end
def self.scm_name
'Subversion'
end
def changesets_for_path(path)
revisions = scm.revisions(path)
revisions ? changesets.find_all_by_revision(revisions.collect(&:identifier), :order => "committed_on DESC") : []
end
def fetch_changesets
scm_info = scm.info
if scm_info
# latest revision found in database
db_revision = latest_changeset ? latest_changeset.revision : 0
# latest revision in the repository
scm_revision = scm_info.lastrev.identifier.to_i
if db_revision < scm_revision
logger.debug "Fetching changesets for repository #{url}" if logger && logger.debug?
identifier_from = db_revision + 1
while (identifier_from <= scm_revision)
# loads changesets by batches of 200
identifier_to = [identifier_from + 199, scm_revision].min
revisions = scm.revisions('', identifier_to, identifier_from, :with_paths => true)
transaction do
revisions.reverse_each do |revision|
changeset = Changeset.create(:repository => self,
:revision => revision.identifier,
:committer => revision.author,
:committed_on => revision.time,
:comments => revision.message)
revision.paths.each do |change|
Change.create(:changeset => changeset,
:action => change[:action],
:path => change[:path],
:from_path => change[:from_path],
:from_revision => change[:from_revision])
end
end
end unless revisions.nil?
identifier_from = identifier_to + 1
end
end
end
end
end
+31 -106
View File
@@ -1,108 +1,33 @@
# redMine - project management software # redMine - project management software
# Copyright (C) 2006 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
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# 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 Role < ActiveRecord::Base class Role < ActiveRecord::Base
# Built-in roles before_destroy :check_integrity
BUILTIN_NON_MEMBER = 1 has_and_belongs_to_many :permissions
BUILTIN_ANONYMOUS = 2 has_many :workflows, :dependent => :delete_all
has_many :members
before_destroy :check_deletable acts_as_list
has_many :workflows, :dependent => :delete_all
has_many :members validates_presence_of :name
acts_as_list validates_uniqueness_of :name
validates_format_of :name, :with => /^[\w\s\'\-]*$/i
serialize :permissions
attr_protected :builtin private
def check_integrity
validates_presence_of :name raise "Can't delete role" if Member.find(:first, :conditions =>["role_id=?", self.id])
validates_uniqueness_of :name
validates_length_of :name, :maximum => 30
validates_format_of :name, :with => /^[\w\s\'\-]*$/i
def permissions
read_attribute(:permissions) || []
end
def permissions=(perms)
perms = perms.collect {|p| p.to_sym unless p.blank? }.compact if perms
write_attribute(:permissions, perms)
end
def <=>(role)
position <=> role.position
end
# Return true if the role is a builtin role
def builtin?
self.builtin != 0
end
# Return true if the role is a project member role
def member?
!self.builtin?
end
# Return true if role is allowed to do the specified action
# action can be:
# * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
# * a permission Symbol (eg. :edit_project)
def allowed_to?(action)
if action.is_a? Hash
allowed_actions.include? "#{action[:controller]}/#{action[:action]}"
else
allowed_permissions.include? action
end
end
# Return all the permissions that can be given to the role
def setable_permissions
setable_permissions = Redmine::AccessControl.permissions - Redmine::AccessControl.public_permissions
setable_permissions -= Redmine::AccessControl.members_only_permissions if self.builtin == BUILTIN_NON_MEMBER
setable_permissions -= Redmine::AccessControl.loggedin_only_permissions if self.builtin == BUILTIN_ANONYMOUS
setable_permissions
end
# Find all the roles that can be given to a project member
def self.find_all_givable
find(:all, :conditions => {:builtin => 0}, :order => 'position')
end
# Return the builtin 'non member' role
def self.non_member
find(:first, :conditions => {:builtin => BUILTIN_NON_MEMBER}) || raise('Missing non-member builtin role.')
end
# Return the builtin 'anonymous' role
def self.anonymous
find(:first, :conditions => {:builtin => BUILTIN_ANONYMOUS}) || raise('Missing anonymous builtin role.')
end
private
def allowed_permissions
@allowed_permissions ||= permissions + Redmine::AccessControl.public_permissions.collect {|p| p.name}
end
def allowed_actions
@actions_allowed ||= allowed_permissions.inject([]) { |actions, permission| actions += Redmine::AccessControl.allowed_actions(permission) }.flatten
end
def check_deletable
raise "Can't delete role" if members.any?
raise "Can't delete builtin role" if builtin?
end end
end end

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