Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3351ef22b2 | ||
|
|
e56dd42e64 | ||
|
|
a0c34ab916 | ||
|
|
c8a6b01e9f | ||
|
|
ff02412e75 | ||
|
|
166c3c15dd | ||
|
|
65a7c7e96c | ||
|
|
9a8cf5fa60 | ||
|
|
bf0008cd4e | ||
|
|
b849b9a47e | ||
|
|
011d9587a5 | ||
|
|
b7d813047b | ||
|
|
08784f551e | ||
|
|
faad143c03 | ||
|
|
08b6d7b654 | ||
|
|
18a30fd942 | ||
|
|
2813b93370 | ||
|
|
930cf0b487 | ||
|
|
27ac3193e3 | ||
|
|
80f5d3d041 | ||
|
|
a3f8fbaee6 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -18,11 +18,9 @@
|
||||
/public/plugin_assets
|
||||
/tmp/*
|
||||
/tmp/cache/*
|
||||
/tmp/pdf/*
|
||||
/tmp/sessions/*
|
||||
/tmp/sockets/*
|
||||
/tmp/test/*
|
||||
/vendor/cache
|
||||
/vendor/rails
|
||||
*.rbc
|
||||
|
||||
|
||||
@@ -20,11 +20,9 @@ public/dispatch.*
|
||||
public/plugin_assets
|
||||
tmp/*
|
||||
tmp/cache/*
|
||||
tmp/pdf/*
|
||||
tmp/sessions/*
|
||||
tmp/sockets/*
|
||||
tmp/test/*
|
||||
vendor/cache
|
||||
vendor/rails
|
||||
*.rbc
|
||||
|
||||
|
||||
90
Gemfile
90
Gemfile
@@ -1,90 +0,0 @@
|
||||
source :rubygems
|
||||
|
||||
gem "rails", "2.3.15"
|
||||
gem "i18n", "~> 0.4.2"
|
||||
gem "coderay", "~> 1.0.6"
|
||||
gem "fastercsv", "~> 1.5.0", :platforms => [:mri_18, :mingw_18, :jruby]
|
||||
gem "tzinfo", "~> 0.3.31"
|
||||
|
||||
# Optional gem for LDAP authentication
|
||||
group :ldap do
|
||||
gem "net-ldap", "~> 0.3.1"
|
||||
end
|
||||
|
||||
# Optional gem for OpenID authentication
|
||||
group :openid do
|
||||
gem "ruby-openid", "~> 2.1.4", :require => "openid"
|
||||
end
|
||||
|
||||
# Optional gem for exporting the gantt to a PNG file, not supported with jruby
|
||||
platforms :mri, :mingw do
|
||||
group :rmagick do
|
||||
# RMagick 2 supports ruby 1.9
|
||||
# RMagick 1 would be fine for ruby 1.8 but Bundler does not support
|
||||
# different requirements for the same gem on different platforms
|
||||
gem "rmagick", ">= 2.0.0"
|
||||
end
|
||||
end
|
||||
|
||||
# Database gems
|
||||
platforms :mri, :mingw do
|
||||
group :postgresql do
|
||||
gem "pg", ">= 0.11.0"
|
||||
end
|
||||
|
||||
group :sqlite do
|
||||
gem "sqlite3"
|
||||
end
|
||||
end
|
||||
|
||||
platforms :mri_18, :mingw_18 do
|
||||
group :mysql do
|
||||
gem "mysql", "~> 2.8.1"
|
||||
end
|
||||
end
|
||||
|
||||
platforms :mri_19, :mingw_19 do
|
||||
group :mysql do
|
||||
gem "mysql2", "~> 0.2.7"
|
||||
end
|
||||
end
|
||||
|
||||
platforms :jruby do
|
||||
gem "jruby-openssl"
|
||||
|
||||
group :mysql do
|
||||
gem "activerecord-jdbcmysql-adapter"
|
||||
end
|
||||
|
||||
group :postgresql do
|
||||
gem "activerecord-jdbcpostgresql-adapter"
|
||||
end
|
||||
|
||||
group :sqlite do
|
||||
gem "activerecord-jdbcsqlite3-adapter"
|
||||
end
|
||||
end
|
||||
|
||||
group :development do
|
||||
gem "rdoc", ">= 2.4.2"
|
||||
end
|
||||
|
||||
group :test do
|
||||
gem "shoulda", "~> 2.10.3"
|
||||
# Shoulda does not work nice on Ruby 1.9.3 and seems to need test-unit explicitely.
|
||||
gem "test-unit", :platforms => [:mri_19]
|
||||
gem "edavis10-object_daddy", :require => "object_daddy"
|
||||
gem "mocha", "0.12.3"
|
||||
end
|
||||
|
||||
local_gemfile = File.join(File.dirname(__FILE__), "Gemfile.local")
|
||||
if File.exists?(local_gemfile)
|
||||
puts "Loading Gemfile.local ..." if $DEBUG # `ruby -d` or `bundle -v`
|
||||
instance_eval File.read(local_gemfile)
|
||||
end
|
||||
|
||||
# Load plugins' Gemfiles
|
||||
Dir.glob File.expand_path("../vendor/plugins/*/Gemfile", __FILE__) do |file|
|
||||
puts "Loading #{file} ..." if $DEBUG # `ruby -d` or `bundle -v`
|
||||
instance_eval File.read(file)
|
||||
end
|
||||
@@ -29,9 +29,6 @@ class AccountController < ApplicationController
|
||||
else
|
||||
authenticate_user
|
||||
end
|
||||
rescue AuthSourceException => e
|
||||
logger.error "An error occured when authenticating #{params[:username]}: #{e.message}"
|
||||
render_error :message => e.message
|
||||
end
|
||||
|
||||
# Log out current user and redirect to welcome page
|
||||
@@ -84,8 +81,7 @@ class AccountController < ApplicationController
|
||||
session[:auth_source_registration] = nil
|
||||
@user = User.new(:language => Setting.default_language)
|
||||
else
|
||||
@user = User.new
|
||||
@user.safe_attributes = params[:user]
|
||||
@user = User.new(params[:user])
|
||||
@user.admin = false
|
||||
@user.register
|
||||
if session[:auth_source_registration]
|
||||
@@ -100,7 +96,7 @@ class AccountController < ApplicationController
|
||||
end
|
||||
else
|
||||
@user.login = params[:user][:login]
|
||||
@user.password, @user.password_confirmation = params[:user][:password], params[:user][:password_confirmation]
|
||||
@user.password, @user.password_confirmation = params[:password], params[:password_confirmation]
|
||||
|
||||
case Setting.self_registration
|
||||
when '1'
|
||||
@@ -131,6 +127,14 @@ class AccountController < ApplicationController
|
||||
|
||||
private
|
||||
|
||||
def logout_user
|
||||
if User.current.logged?
|
||||
cookies.delete :autologin
|
||||
Token.delete_all(["user_id = ? AND action = ?", User.current.id, 'autologin'])
|
||||
self.logged_user = nil
|
||||
end
|
||||
end
|
||||
|
||||
def authenticate_user
|
||||
if Setting.openid? && using_open_id?
|
||||
open_id_authenticate(params[:openid_url])
|
||||
|
||||
@@ -17,10 +17,6 @@
|
||||
|
||||
class AdminController < ApplicationController
|
||||
layout 'admin'
|
||||
menu_item :projects, :only => :projects
|
||||
menu_item :plugins, :only => :plugins
|
||||
menu_item :info, :only => :info
|
||||
|
||||
before_filter :require_admin
|
||||
helper :sort
|
||||
include SortHelper
|
||||
@@ -30,12 +26,14 @@ class AdminController < ApplicationController
|
||||
end
|
||||
|
||||
def projects
|
||||
@status = params[:status] || 1
|
||||
|
||||
scope = Project.status(@status)
|
||||
scope = scope.like(params[:name]) if params[:name].present?
|
||||
|
||||
@projects = scope.all(:order => 'lft')
|
||||
@status = params[:status] ? params[:status].to_i : 1
|
||||
c = ARCondition.new(@status == 0 ? "status <> 0" : ["status = ?", @status])
|
||||
unless params[:name].blank?
|
||||
name = "%#{params[:name].strip.downcase}%"
|
||||
c << ["LOWER(identifier) LIKE ? OR LOWER(name) LIKE ?", name, name]
|
||||
end
|
||||
@projects = Project.find :all, :order => 'lft',
|
||||
:conditions => c.conditions
|
||||
|
||||
render :action => "projects", :layout => false if request.xhr?
|
||||
end
|
||||
@@ -63,7 +61,7 @@ class AdminController < ApplicationController
|
||||
# Force ActionMailer to raise delivery errors so we can catch it
|
||||
ActionMailer::Base.raise_delivery_errors = true
|
||||
begin
|
||||
@test = Mailer.deliver_test_email(User.current)
|
||||
@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)
|
||||
@@ -75,9 +73,11 @@ class AdminController < ApplicationController
|
||||
def info
|
||||
@db_adapter_name = ActiveRecord::Base.connection.adapter_name
|
||||
@checklist = [
|
||||
[:text_default_administrator_account_changed, User.default_admin_account_changed?],
|
||||
[:text_default_administrator_account_changed,
|
||||
User.find(:first,
|
||||
:conditions => ["login=? and hashed_password=?", 'admin', User.hash_password('admin')]).nil?],
|
||||
[:text_file_repository_writable, File.writable?(Attachment.storage_path)],
|
||||
[:text_plugin_assets_writable, File.writable?(Redmine::Plugin.public_directory)],
|
||||
[:text_plugin_assets_writable, File.writable?(Engines.public_directory)],
|
||||
[:text_rmagick_available, Object.const_defined?(:Magick)]
|
||||
]
|
||||
end
|
||||
|
||||
@@ -31,32 +31,19 @@ class ApplicationController < ActionController::Base
|
||||
super
|
||||
cookies.delete(:autologin)
|
||||
end
|
||||
|
||||
# FIXME: Remove this when all of Rack and Rails have learned how to
|
||||
# properly use encodings
|
||||
before_filter :params_filter
|
||||
|
||||
def params_filter
|
||||
if RUBY_VERSION >= '1.9' && defined?(Rails) && Rails::VERSION::MAJOR < 3
|
||||
self.utf8nize!(params)
|
||||
# Remove broken cookie after upgrade from 0.8.x (#4292)
|
||||
# See https://rails.lighthouseapp.com/projects/8994/tickets/3360
|
||||
# TODO: remove it when Rails is fixed
|
||||
before_filter :delete_broken_cookies
|
||||
def delete_broken_cookies
|
||||
if cookies['_redmine_session'] && cookies['_redmine_session'] !~ /--/
|
||||
cookies.delete '_redmine_session'
|
||||
redirect_to home_path
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
def utf8nize!(obj)
|
||||
if obj.frozen?
|
||||
obj
|
||||
elsif obj.is_a? String
|
||||
obj.respond_to?(:force_encoding) ? obj.force_encoding("UTF-8") : obj
|
||||
elsif obj.is_a? Hash
|
||||
obj.each {|k, v| obj[k] = self.utf8nize!(v)}
|
||||
elsif obj.is_a? Array
|
||||
obj.each {|v| self.utf8nize!(v)}
|
||||
else
|
||||
obj
|
||||
end
|
||||
end
|
||||
|
||||
before_filter :session_expiration, :user_setup, :check_if_login_required, :set_localization
|
||||
before_filter :user_setup, :check_if_login_required, :set_localization
|
||||
filter_parameter_logging :password
|
||||
|
||||
rescue_from ActionController::InvalidAuthenticityToken, :with => :invalid_authenticity_token
|
||||
@@ -70,38 +57,6 @@ class ApplicationController < ActionController::Base
|
||||
require_dependency "repository/#{scm.underscore}"
|
||||
end
|
||||
|
||||
def session_expiration
|
||||
if session[:user_id]
|
||||
if session_expired? && !try_to_autologin
|
||||
reset_session
|
||||
flash[:error] = l(:error_session_expired)
|
||||
redirect_to signin_url
|
||||
else
|
||||
session[:atime] = Time.now.utc.to_i
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def session_expired?
|
||||
if Setting.session_lifetime?
|
||||
unless session[:ctime] && (Time.now.utc.to_i - session[:ctime].to_i <= Setting.session_lifetime.to_i * 60)
|
||||
return true
|
||||
end
|
||||
end
|
||||
if Setting.session_timeout?
|
||||
unless session[:atime] && (Time.now.utc.to_i - session[:atime].to_i <= Setting.session_timeout.to_i * 60)
|
||||
return true
|
||||
end
|
||||
end
|
||||
false
|
||||
end
|
||||
|
||||
def start_user_session(user)
|
||||
session[:user_id] = user.id
|
||||
session[:ctime] = Time.now.utc.to_i
|
||||
session[:atime] = Time.now.utc.to_i
|
||||
end
|
||||
|
||||
def user_setup
|
||||
# Check the settings cache for each request
|
||||
Setting.check_cache
|
||||
@@ -115,7 +70,10 @@ class ApplicationController < ActionController::Base
|
||||
if session[:user_id]
|
||||
# existing session
|
||||
(User.active.find(session[:user_id]) rescue nil)
|
||||
elsif user = try_to_autologin
|
||||
elsif cookies[:autologin] && Setting.autologin?
|
||||
# auto-login feature starts a new session
|
||||
user = User.try_to_autologin(cookies[:autologin])
|
||||
session[:user_id] = user.id if user
|
||||
user
|
||||
elsif params[:format] == 'atom' && params[:key] && request.get? && accept_rss_auth?
|
||||
# RSS key authentication does not start a session
|
||||
@@ -133,38 +91,17 @@ class ApplicationController < ActionController::Base
|
||||
end
|
||||
end
|
||||
|
||||
def try_to_autologin
|
||||
if cookies[:autologin] && Setting.autologin?
|
||||
# auto-login feature starts a new session
|
||||
user = User.try_to_autologin(cookies[:autologin])
|
||||
if user
|
||||
reset_session
|
||||
start_user_session(user)
|
||||
end
|
||||
user
|
||||
end
|
||||
end
|
||||
|
||||
# Sets the logged in user
|
||||
def logged_user=(user)
|
||||
reset_session
|
||||
if user && user.is_a?(User)
|
||||
User.current = user
|
||||
start_user_session(user)
|
||||
session[:user_id] = user.id
|
||||
else
|
||||
User.current = User.anonymous
|
||||
end
|
||||
end
|
||||
|
||||
# Logs out current user
|
||||
def logout_user
|
||||
if User.current.logged?
|
||||
cookies.delete :autologin
|
||||
Token.delete_all(["user_id = ? AND action = ?", User.current.id, 'autologin'])
|
||||
self.logged_user = nil
|
||||
end
|
||||
end
|
||||
|
||||
# check if login is globally required to access the application
|
||||
def check_if_login_required
|
||||
# no check needed if user is already logged in
|
||||
@@ -299,11 +236,20 @@ class ApplicationController < ActionController::Base
|
||||
render_404
|
||||
end
|
||||
|
||||
# Check if project is unique before bulk operations
|
||||
def check_project_uniqueness
|
||||
unless @project
|
||||
# TODO: let users bulk edit/move/destroy issues from different projects
|
||||
render_error 'Can not bulk edit/move/destroy issues from different projects'
|
||||
return false
|
||||
end
|
||||
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
|
||||
if @project && @project.active?
|
||||
if @project.visible?
|
||||
if @project.is_public? || User.current.member_of?(@project) || User.current.admin?
|
||||
true
|
||||
else
|
||||
deny_access
|
||||
@@ -403,6 +349,18 @@ class ApplicationController < ActionController::Base
|
||||
:content_type => 'application/atom+xml'
|
||||
end
|
||||
|
||||
# TODO: remove in Redmine 1.4
|
||||
def self.accept_key_auth(*actions)
|
||||
ActiveSupport::Deprecation.warn "ApplicationController.accept_key_auth is deprecated and will be removed in Redmine 1.4. Use accept_rss_auth (or accept_api_auth) instead."
|
||||
accept_rss_auth(*actions)
|
||||
end
|
||||
|
||||
# TODO: remove in Redmine 1.4
|
||||
def accept_key_auth_actions
|
||||
ActiveSupport::Deprecation.warn "ApplicationController.accept_key_auth_actions is deprecated and will be removed in Redmine 1.4. Use accept_rss_auth (or accept_api_auth) instead."
|
||||
self.class.accept_rss_auth
|
||||
end
|
||||
|
||||
def self.accept_rss_auth(*actions)
|
||||
if actions.any?
|
||||
write_inheritable_attribute('accept_rss_auth_actions', actions)
|
||||
@@ -499,9 +457,9 @@ class ApplicationController < ActionController::Base
|
||||
# Returns the API key present in the request
|
||||
def api_key_from_request
|
||||
if params[:key].present?
|
||||
params[:key].to_s
|
||||
params[:key]
|
||||
elsif request.headers["X-Redmine-API-Key"].present?
|
||||
request.headers["X-Redmine-API-Key"].to_s
|
||||
request.headers["X-Redmine-API-Key"]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -534,13 +492,16 @@ class ApplicationController < ActionController::Base
|
||||
end
|
||||
|
||||
# Renders API response on validation failure
|
||||
def render_validation_errors(objects)
|
||||
if objects.is_a?(Array)
|
||||
@error_messages = objects.map {|object| object.errors.full_messages}.flatten
|
||||
else
|
||||
@error_messages = objects.errors.full_messages
|
||||
end
|
||||
render :template => 'common/error_messages.api', :status => :unprocessable_entity, :layout => false
|
||||
def render_validation_errors(object)
|
||||
options = { :status => :unprocessable_entity, :layout => false }
|
||||
options.merge!(case params[:format]
|
||||
when 'xml'; { :xml => object.errors }
|
||||
when 'json'; { :json => {'errors' => object.errors} } # ActiveResource client compliance
|
||||
else
|
||||
raise "Unknown format #{params[:format]} in #render_validation_errors"
|
||||
end
|
||||
)
|
||||
render options
|
||||
end
|
||||
|
||||
# Overrides #default_template so that the api template
|
||||
|
||||
@@ -16,12 +16,11 @@
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
class AttachmentsController < ApplicationController
|
||||
before_filter :find_project, :except => :upload
|
||||
before_filter :file_readable, :read_authorize, :only => [:show, :download]
|
||||
before_filter :find_project
|
||||
before_filter :file_readable, :read_authorize, :except => :destroy
|
||||
before_filter :delete_authorize, :only => :destroy
|
||||
before_filter :authorize_global, :only => :upload
|
||||
|
||||
accept_api_auth :show, :download, :upload
|
||||
accept_api_auth :show, :download
|
||||
|
||||
def show
|
||||
respond_to do |format|
|
||||
@@ -30,11 +29,6 @@ class AttachmentsController < ApplicationController
|
||||
@diff = File.new(@attachment.diskfile, "rb").read
|
||||
@diff_type = params[:type] || User.current.pref[:diff_type] || 'inline'
|
||||
@diff_type = 'inline' unless %w(inline sbs).include?(@diff_type)
|
||||
# Save diff type as user preference
|
||||
if User.current.logged? && @diff_type != User.current.pref[:diff_type]
|
||||
User.current.pref[:diff_type] = @diff_type
|
||||
User.current.preference.save
|
||||
end
|
||||
render :action => 'diff'
|
||||
elsif @attachment.is_text? && @attachment.filesize <= Setting.file_max_size_displayed.to_i.kilobyte
|
||||
@content = File.new(@attachment.diskfile, "rb").read
|
||||
@@ -59,33 +53,8 @@ class AttachmentsController < ApplicationController
|
||||
|
||||
end
|
||||
|
||||
def upload
|
||||
# Make sure that API users get used to set this content type
|
||||
# as it won't trigger Rails' automatic parsing of the request body for parameters
|
||||
unless request.content_type == 'application/octet-stream'
|
||||
render :nothing => true, :status => 406
|
||||
return
|
||||
end
|
||||
|
||||
@attachment = Attachment.new(:file => request.raw_post)
|
||||
@attachment.author = User.current
|
||||
@attachment.filename = Redmine::Utils.random_hex(16)
|
||||
|
||||
if @attachment.save
|
||||
respond_to do |format|
|
||||
format.api { render :action => 'upload', :status => :created }
|
||||
end
|
||||
else
|
||||
respond_to do |format|
|
||||
format.api { render_validation_errors(@attachment) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
verify :method => :delete, :only => :destroy
|
||||
def destroy
|
||||
if @attachment.container.respond_to?(:init_journal)
|
||||
@attachment.container.init_journal(User.current)
|
||||
end
|
||||
# Make sure association callbacks are called
|
||||
@attachment.container.attachments.delete(@attachment)
|
||||
redirect_to :back
|
||||
|
||||
@@ -17,31 +17,36 @@
|
||||
|
||||
class AuthSourcesController < ApplicationController
|
||||
layout 'admin'
|
||||
menu_item :ldap_authentication
|
||||
|
||||
before_filter :require_admin
|
||||
|
||||
# GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html)
|
||||
verify :method => :post, :only => [ :destroy, :create, :update ],
|
||||
:redirect_to => { :template => :index }
|
||||
|
||||
def index
|
||||
@auth_source_pages, @auth_sources = paginate AuthSource, :per_page => 10
|
||||
@auth_source_pages, @auth_sources = paginate auth_source_class.name.tableize, :per_page => 10
|
||||
render "auth_sources/index"
|
||||
end
|
||||
|
||||
def new
|
||||
klass_name = params[:type] || 'AuthSourceLdap'
|
||||
@auth_source = AuthSource.new_subclass_instance(klass_name, params[:auth_source])
|
||||
@auth_source = auth_source_class.new
|
||||
render 'auth_sources/new'
|
||||
end
|
||||
|
||||
def create
|
||||
@auth_source = AuthSource.new_subclass_instance(params[:type], params[:auth_source])
|
||||
@auth_source = auth_source_class.new(params[:auth_source])
|
||||
if @auth_source.save
|
||||
flash[:notice] = l(:notice_successful_create)
|
||||
redirect_to :action => 'index'
|
||||
else
|
||||
render :action => 'new'
|
||||
render 'auth_sources/new'
|
||||
end
|
||||
end
|
||||
|
||||
def edit
|
||||
@auth_source = AuthSource.find(params[:id])
|
||||
render 'auth_sources/edit'
|
||||
end
|
||||
|
||||
def update
|
||||
@@ -50,17 +55,17 @@ class AuthSourcesController < ApplicationController
|
||||
flash[:notice] = l(:notice_successful_update)
|
||||
redirect_to :action => 'index'
|
||||
else
|
||||
render :action => 'edit'
|
||||
render 'auth_sources/edit'
|
||||
end
|
||||
end
|
||||
|
||||
def test_connection
|
||||
@auth_source = AuthSource.find(params[:id])
|
||||
@auth_method = AuthSource.find(params[:id])
|
||||
begin
|
||||
@auth_source.test_connection
|
||||
@auth_method.test_connection
|
||||
flash[:notice] = l(:notice_successful_connection)
|
||||
rescue Exception => e
|
||||
flash[:error] = l(:error_unable_to_connect, e.message)
|
||||
rescue => text
|
||||
flash[:error] = l(:error_unable_to_connect, text.message)
|
||||
end
|
||||
redirect_to :action => 'index'
|
||||
end
|
||||
@@ -73,4 +78,10 @@ class AuthSourcesController < ApplicationController
|
||||
end
|
||||
redirect_to :action => 'index'
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def auth_source_class
|
||||
AuthSource
|
||||
end
|
||||
end
|
||||
|
||||
@@ -17,12 +17,15 @@
|
||||
|
||||
class BoardsController < ApplicationController
|
||||
default_search_scope :messages
|
||||
before_filter :find_project_by_project_id, :find_board_if_available, :authorize
|
||||
before_filter :find_project, :find_board_if_available, :authorize
|
||||
accept_rss_auth :index, :show
|
||||
|
||||
helper :messages
|
||||
include MessagesHelper
|
||||
helper :sort
|
||||
include SortHelper
|
||||
helper :watchers
|
||||
include WatchersHelper
|
||||
|
||||
def index
|
||||
@boards = @project.boards
|
||||
@@ -47,7 +50,7 @@ class BoardsController < ApplicationController
|
||||
:include => [:author, {:last_reply => :author}],
|
||||
:limit => @topic_pages.items_per_page,
|
||||
:offset => @topic_pages.current.offset
|
||||
@message = Message.new(:board => @board)
|
||||
@message = Message.new
|
||||
render :action => 'show', :layout => !request.xhr?
|
||||
}
|
||||
format.atom {
|
||||
@@ -59,31 +62,20 @@ class BoardsController < ApplicationController
|
||||
end
|
||||
end
|
||||
|
||||
def new
|
||||
@board = @project.boards.build
|
||||
@board.safe_attributes = params[:board]
|
||||
end
|
||||
verify :method => :post, :only => [ :destroy ], :redirect_to => { :action => :index }
|
||||
|
||||
def create
|
||||
@board = @project.boards.build
|
||||
@board.safe_attributes = params[:board]
|
||||
if @board.save
|
||||
def new
|
||||
@board = Board.new(params[:board])
|
||||
@board.project = @project
|
||||
if request.post? && @board.save
|
||||
flash[:notice] = l(:notice_successful_create)
|
||||
redirect_to_settings_in_projects
|
||||
else
|
||||
render :action => 'new'
|
||||
end
|
||||
end
|
||||
|
||||
def edit
|
||||
end
|
||||
|
||||
def update
|
||||
@board.safe_attributes = params[:board]
|
||||
if @board.save
|
||||
if request.post? && @board.update_attributes(params[:board])
|
||||
redirect_to_settings_in_projects
|
||||
else
|
||||
render :action => 'edit'
|
||||
end
|
||||
end
|
||||
|
||||
@@ -97,6 +89,12 @@ private
|
||||
redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'boards'
|
||||
end
|
||||
|
||||
def find_project
|
||||
@project = Project.find(params[:project_id])
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
end
|
||||
|
||||
def find_board_if_available
|
||||
@board = @project.boards.find(params[:id]) if params[:id]
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
|
||||
@@ -1,20 +1,3 @@
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2012 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 CommentsController < ApplicationController
|
||||
default_search_scope :news
|
||||
model_object News
|
||||
@@ -22,11 +5,9 @@ class CommentsController < ApplicationController
|
||||
before_filter :find_project_from_association
|
||||
before_filter :authorize
|
||||
|
||||
verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed }
|
||||
def create
|
||||
raise Unauthorized unless @news.commentable?
|
||||
|
||||
@comment = Comment.new
|
||||
@comment.safe_attributes = params[:comment]
|
||||
@comment = Comment.new(params[:comment])
|
||||
@comment.author = User.current
|
||||
if @news.comments << @comment
|
||||
flash[:notice] = l(:label_comment_added)
|
||||
@@ -35,6 +16,7 @@ class CommentsController < ApplicationController
|
||||
redirect_to :controller => 'news', :action => 'show', :id => @news
|
||||
end
|
||||
|
||||
verify :method => :delete, :only => :destroy, :render => {:nothing => true, :status => :method_not_allowed }
|
||||
def destroy
|
||||
@news.comments.find(params[:comment_id]).destroy
|
||||
redirect_to :controller => 'news', :action => 'show', :id => @news
|
||||
@@ -50,4 +32,5 @@ class CommentsController < ApplicationController
|
||||
@comment = nil
|
||||
@news
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -4,11 +4,17 @@ class ContextMenusController < ApplicationController
|
||||
|
||||
def issues
|
||||
@issues = Issue.visible.all(:conditions => {:id => params[:ids]}, :include => :project)
|
||||
|
||||
if (@issues.size == 1)
|
||||
@issue = @issues.first
|
||||
@allowed_statuses = @issue.new_statuses_allowed_to(User.current)
|
||||
else
|
||||
@allowed_statuses = @issues.map do |i|
|
||||
i.new_statuses_allowed_to(User.current)
|
||||
end.inject do |memo,s|
|
||||
memo & s
|
||||
end
|
||||
end
|
||||
|
||||
@allowed_statuses = @issues.map(&:new_statuses_allowed_to).reduce(:&)
|
||||
@projects = @issues.collect(&:project).compact.uniq
|
||||
@project = @projects.first if @projects.size == 1
|
||||
|
||||
@@ -28,26 +34,14 @@ class ContextMenusController < ApplicationController
|
||||
@trackers = @project.trackers
|
||||
else
|
||||
#when multiple projects, we only keep the intersection of each set
|
||||
@assignables = @projects.map(&:assignable_users).reduce(:&)
|
||||
@trackers = @projects.map(&:trackers).reduce(:&)
|
||||
@assignables = @projects.map(&:assignable_users).inject{|memo,a| memo & a}
|
||||
@trackers = @projects.map(&:trackers).inject{|memo,t| memo & t}
|
||||
end
|
||||
|
||||
@priorities = IssuePriority.active.reverse
|
||||
@statuses = IssueStatus.find(:all, :order => 'position')
|
||||
@back = back_url
|
||||
|
||||
@options_by_custom_field = {}
|
||||
if @can[:edit]
|
||||
custom_fields = @issues.map(&:available_custom_fields).reduce(:&).select do |f|
|
||||
%w(bool list user version).include?(f.field_format) && !f.multiple?
|
||||
end
|
||||
custom_fields.each do |field|
|
||||
values = field.possible_values_options(@projects)
|
||||
if values.any?
|
||||
@options_by_custom_field[field] = values
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
render :layout => false
|
||||
end
|
||||
|
||||
|
||||
@@ -19,8 +19,6 @@ class CustomFieldsController < ApplicationController
|
||||
layout 'admin'
|
||||
|
||||
before_filter :require_admin
|
||||
before_filter :build_new_custom_field, :only => [:new, :create]
|
||||
before_filter :find_custom_field, :only => [:edit, :update, :destroy]
|
||||
|
||||
def index
|
||||
@custom_fields_by_type = CustomField.find(:all).group_by {|f| f.class.name }
|
||||
@@ -28,51 +26,39 @@ class CustomFieldsController < ApplicationController
|
||||
end
|
||||
|
||||
def new
|
||||
end
|
||||
@custom_field = begin
|
||||
if params[:type].to_s.match(/.+CustomField$/)
|
||||
params[:type].to_s.constantize.new(params[:custom_field])
|
||||
end
|
||||
rescue
|
||||
end
|
||||
(redirect_to(:action => 'index'); return) unless @custom_field.is_a?(CustomField)
|
||||
|
||||
def create
|
||||
if request.post? and @custom_field.save
|
||||
flash[:notice] = l(:notice_successful_create)
|
||||
call_hook(:controller_custom_fields_new_after_save, :params => params, :custom_field => @custom_field)
|
||||
redirect_to :action => 'index', :tab => @custom_field.class.name
|
||||
else
|
||||
render :action => 'new'
|
||||
@trackers = Tracker.find(:all, :order => 'position')
|
||||
end
|
||||
end
|
||||
|
||||
def edit
|
||||
end
|
||||
|
||||
def update
|
||||
if request.put? and @custom_field.update_attributes(params[:custom_field])
|
||||
@custom_field = CustomField.find(params[:id])
|
||||
if request.post? and @custom_field.update_attributes(params[:custom_field])
|
||||
flash[:notice] = l(:notice_successful_update)
|
||||
call_hook(:controller_custom_fields_edit_after_save, :params => params, :custom_field => @custom_field)
|
||||
redirect_to :action => 'index', :tab => @custom_field.class.name
|
||||
else
|
||||
render :action => 'edit'
|
||||
@trackers = Tracker.find(:all, :order => 'position')
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
@custom_field.destroy
|
||||
@custom_field = CustomField.find(params[:id]).destroy
|
||||
redirect_to :action => 'index', :tab => @custom_field.class.name
|
||||
rescue
|
||||
flash[:error] = l(:error_can_not_delete_custom_field)
|
||||
redirect_to :action => 'index'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def build_new_custom_field
|
||||
@custom_field = CustomField.new_subclass_instance(params[:type], params[:custom_field])
|
||||
if @custom_field.nil?
|
||||
render_404
|
||||
end
|
||||
end
|
||||
|
||||
def find_custom_field
|
||||
@custom_field = CustomField.find(params[:id])
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
end
|
||||
end
|
||||
|
||||
@@ -18,9 +18,9 @@
|
||||
class DocumentsController < ApplicationController
|
||||
default_search_scope :documents
|
||||
model_object Document
|
||||
before_filter :find_project_by_project_id, :only => [:index, :new, :create]
|
||||
before_filter :find_model_object, :except => [:index, :new, :create]
|
||||
before_filter :find_project_from_association, :except => [:index, :new, :create]
|
||||
before_filter :find_project, :only => [:index, :new]
|
||||
before_filter :find_model_object, :except => [:index, :new]
|
||||
before_filter :find_project_from_association, :except => [:index, :new]
|
||||
before_filter :authorize
|
||||
|
||||
helper :attachments
|
||||
@@ -47,38 +47,25 @@ class DocumentsController < ApplicationController
|
||||
end
|
||||
|
||||
def new
|
||||
@document = @project.documents.build
|
||||
@document.safe_attributes = params[:document]
|
||||
end
|
||||
|
||||
def create
|
||||
@document = @project.documents.build
|
||||
@document.safe_attributes = params[:document]
|
||||
@document.save_attachments(params[:attachments])
|
||||
if @document.save
|
||||
@document = @project.documents.build(params[:document])
|
||||
if request.post? and @document.save
|
||||
attachments = Attachment.attach_files(@document, params[:attachments])
|
||||
render_attachment_warning_if_needed(@document)
|
||||
flash[:notice] = l(:notice_successful_create)
|
||||
redirect_to :action => 'index', :project_id => @project
|
||||
else
|
||||
render :action => 'new'
|
||||
end
|
||||
end
|
||||
|
||||
def edit
|
||||
end
|
||||
|
||||
def update
|
||||
@document.safe_attributes = params[:document]
|
||||
if request.put? and @document.save
|
||||
@categories = DocumentCategory.active #TODO: use it in the views
|
||||
if request.post? and @document.update_attributes(params[:document])
|
||||
flash[:notice] = l(:notice_successful_update)
|
||||
redirect_to :action => 'show', :id => @document
|
||||
else
|
||||
render :action => 'edit'
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
@document.destroy if request.delete?
|
||||
@document.destroy
|
||||
redirect_to :controller => 'documents', :action => 'index', :project_id => @project
|
||||
end
|
||||
|
||||
@@ -89,4 +76,11 @@ class DocumentsController < ApplicationController
|
||||
Mailer.deliver_attachments_added(attachments[:files]) if attachments.present? && attachments[:files].present? && Setting.notified_events.include?('document_added')
|
||||
redirect_to :action => 'show', :id => @document
|
||||
end
|
||||
|
||||
private
|
||||
def find_project
|
||||
@project = Project.find(params[:project_id])
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
end
|
||||
end
|
||||
|
||||
@@ -19,19 +19,28 @@ class EnumerationsController < ApplicationController
|
||||
layout 'admin'
|
||||
|
||||
before_filter :require_admin
|
||||
before_filter :build_new_enumeration, :only => [:new, :create]
|
||||
before_filter :find_enumeration, :only => [:edit, :update, :destroy]
|
||||
|
||||
helper :custom_fields
|
||||
include CustomFieldsHelper
|
||||
|
||||
def index
|
||||
end
|
||||
|
||||
verify :method => :post, :only => [ :destroy, :create, :update ],
|
||||
:redirect_to => { :action => :index }
|
||||
|
||||
def new
|
||||
begin
|
||||
@enumeration = params[:type].constantize.new
|
||||
rescue NameError
|
||||
@enumeration = Enumeration.new
|
||||
end
|
||||
end
|
||||
|
||||
def create
|
||||
if request.post? && @enumeration.save
|
||||
@enumeration = Enumeration.new(params[:enumeration])
|
||||
@enumeration.type = params[:enumeration][:type]
|
||||
if @enumeration.save
|
||||
flash[:notice] = l(:notice_successful_create)
|
||||
redirect_to :action => 'index', :type => @enumeration.type
|
||||
else
|
||||
@@ -40,10 +49,13 @@ class EnumerationsController < ApplicationController
|
||||
end
|
||||
|
||||
def edit
|
||||
@enumeration = Enumeration.find(params[:id])
|
||||
end
|
||||
|
||||
def update
|
||||
if request.put? && @enumeration.update_attributes(params[:enumeration])
|
||||
@enumeration = Enumeration.find(params[:id])
|
||||
@enumeration.type = params[:enumeration][:type] if params[:enumeration][:type]
|
||||
if @enumeration.update_attributes(params[:enumeration])
|
||||
flash[:notice] = l(:notice_successful_update)
|
||||
redirect_to :action => 'index', :type => @enumeration.type
|
||||
else
|
||||
@@ -52,6 +64,7 @@ class EnumerationsController < ApplicationController
|
||||
end
|
||||
|
||||
def destroy
|
||||
@enumeration = Enumeration.find(params[:id])
|
||||
if !@enumeration.in_use?
|
||||
# No associated objects
|
||||
@enumeration.destroy
|
||||
@@ -64,22 +77,9 @@ class EnumerationsController < ApplicationController
|
||||
return
|
||||
end
|
||||
end
|
||||
@enumerations = @enumeration.class.all - [@enumeration]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def build_new_enumeration
|
||||
class_name = params[:enumeration] && params[:enumeration][:type] || params[:type]
|
||||
@enumeration = Enumeration.new_subclass_instance(class_name, params[:enumeration])
|
||||
if @enumeration.nil?
|
||||
render_404
|
||||
end
|
||||
end
|
||||
|
||||
def find_enumeration
|
||||
@enumeration = Enumeration.find(params[:id])
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
@enumerations = @enumeration.class.find(:all) - [@enumeration]
|
||||
#rescue
|
||||
# flash[:error] = 'Unable to delete enumeration'
|
||||
# redirect_to :action => 'index'
|
||||
end
|
||||
end
|
||||
|
||||
@@ -20,7 +20,7 @@ class IssueCategoriesController < ApplicationController
|
||||
model_object IssueCategory
|
||||
before_filter :find_model_object, :except => [:index, :new, :create]
|
||||
before_filter :find_project_from_association, :except => [:index, :new, :create]
|
||||
before_filter :find_project_by_project_id, :only => [:index, :new, :create]
|
||||
before_filter :find_project, :only => [:index, :new, :create]
|
||||
before_filter :authorize
|
||||
accept_api_auth :index, :show, :create, :update, :destroy
|
||||
|
||||
@@ -39,13 +39,12 @@ class IssueCategoriesController < ApplicationController
|
||||
end
|
||||
|
||||
def new
|
||||
@category = @project.issue_categories.build
|
||||
@category.safe_attributes = params[:issue_category]
|
||||
@category = @project.issue_categories.build(params[:issue_category])
|
||||
end
|
||||
|
||||
verify :method => :post, :only => :create
|
||||
def create
|
||||
@category = @project.issue_categories.build
|
||||
@category.safe_attributes = params[:issue_category]
|
||||
@category = @project.issue_categories.build(params[:issue_category])
|
||||
if @category.save
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
@@ -55,7 +54,7 @@ class IssueCategoriesController < ApplicationController
|
||||
format.js do
|
||||
# IE doesn't support the replace_html rjs method for select box options
|
||||
render(:update) {|page| page.replace "issue_category_id",
|
||||
content_tag('select', content_tag('option') + options_from_collection_for_select(@project.issue_categories, 'id', 'name', @category.id), :id => 'issue_category_id', :name => 'issue[category_id]')
|
||||
content_tag('select', '<option></option>' + options_from_collection_for_select(@project.issue_categories, 'id', 'name', @category.id), :id => 'issue_category_id', :name => 'issue[category_id]')
|
||||
}
|
||||
end
|
||||
format.api { render :action => 'show', :status => :created, :location => issue_category_path(@category) }
|
||||
@@ -74,9 +73,9 @@ class IssueCategoriesController < ApplicationController
|
||||
def edit
|
||||
end
|
||||
|
||||
verify :method => :put, :only => :update
|
||||
def update
|
||||
@category.safe_attributes = params[:issue_category]
|
||||
if @category.save
|
||||
if @category.update_attributes(params[:issue_category])
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
flash[:notice] = l(:notice_successful_update)
|
||||
@@ -92,6 +91,7 @@ class IssueCategoriesController < ApplicationController
|
||||
end
|
||||
end
|
||||
|
||||
verify :method => :delete, :only => :destroy
|
||||
def destroy
|
||||
@issue_count = @category.issues.size
|
||||
if @issue_count == 0 || params[:todo] || api_request?
|
||||
@@ -116,4 +116,10 @@ private
|
||||
super
|
||||
@category = @object
|
||||
end
|
||||
|
||||
def find_project
|
||||
@project = Project.find(params[:project_id])
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
end
|
||||
end
|
||||
|
||||
87
app/controllers/issue_moves_controller.rb
Normal file
87
app/controllers/issue_moves_controller.rb
Normal file
@@ -0,0 +1,87 @@
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2011 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
class IssueMovesController < ApplicationController
|
||||
menu_item :issues
|
||||
|
||||
default_search_scope :issues
|
||||
before_filter :find_issues, :check_project_uniqueness
|
||||
before_filter :authorize
|
||||
|
||||
def new
|
||||
prepare_for_issue_move
|
||||
render :layout => false if request.xhr?
|
||||
end
|
||||
|
||||
def create
|
||||
prepare_for_issue_move
|
||||
|
||||
if request.post?
|
||||
new_tracker = params[:new_tracker_id].blank? ? nil : @target_project.trackers.find_by_id(params[:new_tracker_id])
|
||||
unsaved_issue_ids = []
|
||||
moved_issues = []
|
||||
@issues.each do |issue|
|
||||
issue.reload
|
||||
issue.init_journal(User.current)
|
||||
issue.current_journal.notes = @notes if @notes.present?
|
||||
call_hook(:controller_issues_move_before_save, { :params => params, :issue => issue, :target_project => @target_project, :copy => !!@copy })
|
||||
if r = issue.move_to_project(@target_project, new_tracker, {:copy => @copy, :attributes => extract_changed_attributes_for_move(params)})
|
||||
moved_issues << r
|
||||
else
|
||||
unsaved_issue_ids << issue.id
|
||||
end
|
||||
end
|
||||
set_flash_from_bulk_issue_save(@issues, unsaved_issue_ids)
|
||||
|
||||
if params[:follow]
|
||||
if @issues.size == 1 && moved_issues.size == 1
|
||||
redirect_to :controller => 'issues', :action => 'show', :id => moved_issues.first
|
||||
else
|
||||
redirect_to :controller => 'issues', :action => 'index', :project_id => (@target_project || @project)
|
||||
end
|
||||
else
|
||||
redirect_to :controller => 'issues', :action => 'index', :project_id => @project
|
||||
end
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def prepare_for_issue_move
|
||||
@issues.sort!
|
||||
@copy = params[:copy_options] && params[:copy_options][:copy]
|
||||
@allowed_projects = Issue.allowed_target_projects_on_move
|
||||
@target_project = @allowed_projects.detect {|p| p.id.to_s == params[:new_project_id]} if params[:new_project_id]
|
||||
@target_project ||= @project
|
||||
@trackers = @target_project.trackers
|
||||
@available_statuses = Workflow.available_statuses(@project)
|
||||
@notes = params[:notes]
|
||||
@notes ||= ''
|
||||
end
|
||||
|
||||
def extract_changed_attributes_for_move(params)
|
||||
changed_attributes = {}
|
||||
[:assigned_to_id, :status_id, :start_date, :due_date, :priority_id].each do |valid_attribute|
|
||||
unless params[valid_attribute].blank?
|
||||
changed_attributes[valid_attribute] = (params[valid_attribute] == 'none' ? nil : params[valid_attribute])
|
||||
end
|
||||
end
|
||||
changed_attributes
|
||||
end
|
||||
|
||||
end
|
||||
@@ -39,10 +39,11 @@ class IssueRelationsController < ApplicationController
|
||||
end
|
||||
end
|
||||
|
||||
verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed }
|
||||
def create
|
||||
@relation = IssueRelation.new(params[:relation])
|
||||
@relation.issue_from = @issue
|
||||
if params[:relation] && m = params[:relation][:issue_to_id].to_s.strip.match(/^#?(\d+)$/)
|
||||
if params[:relation] && m = params[:relation][:issue_to_id].to_s.match(/^#?(\d+)$/)
|
||||
@relation.issue_to = Issue.visible.find_by_id(m[1].to_i)
|
||||
end
|
||||
saved = @relation.save
|
||||
@@ -69,12 +70,13 @@ class IssueRelationsController < ApplicationController
|
||||
end
|
||||
end
|
||||
|
||||
verify :method => :delete, :only => :destroy, :render => {:nothing => true, :status => :method_not_allowed }
|
||||
def destroy
|
||||
raise Unauthorized unless @relation.deletable?
|
||||
@relation.destroy
|
||||
|
||||
respond_to do |format|
|
||||
format.html { redirect_to issue_path } # TODO : does this really work since @issue is always nil? What is it useful to?
|
||||
format.html { redirect_to :controller => 'issues', :action => 'show', :id => @issue }
|
||||
format.js { render(:update) {|page| page.remove "relation-#{@relation.id}"} }
|
||||
format.api { head :ok }
|
||||
end
|
||||
|
||||
@@ -62,6 +62,7 @@ class IssueStatusesController < ApplicationController
|
||||
end
|
||||
end
|
||||
|
||||
verify :method => :delete, :only => :destroy, :redirect_to => { :action => :index }
|
||||
def destroy
|
||||
IssueStatus.find(params[:id]).destroy
|
||||
redirect_to :action => 'index'
|
||||
|
||||
@@ -20,7 +20,8 @@ class IssuesController < ApplicationController
|
||||
default_search_scope :issues
|
||||
|
||||
before_filter :find_issue, :only => [:show, :edit, :update]
|
||||
before_filter :find_issues, :only => [:bulk_edit, :bulk_update, :destroy]
|
||||
before_filter :find_issues, :only => [:bulk_edit, :bulk_update, :move, :perform_move, :destroy]
|
||||
before_filter :check_project_uniqueness, :only => [:move, :perform_move]
|
||||
before_filter :find_project, :only => [:new, :create]
|
||||
before_filter :authorize, :except => [:index]
|
||||
before_filter :find_optional_project, :only => [:index]
|
||||
@@ -53,6 +54,14 @@ class IssuesController < ApplicationController
|
||||
helper :gantt
|
||||
include Redmine::Export::PDF
|
||||
|
||||
verify :method => [:post, :delete],
|
||||
:only => :destroy,
|
||||
:render => { :nothing => true, :status => :method_not_allowed }
|
||||
|
||||
verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed }
|
||||
verify :method => :post, :only => :bulk_update, :render => {:nothing => true, :status => :method_not_allowed }
|
||||
verify :method => :put, :only => :update, :render => {:nothing => true, :status => :method_not_allowed }
|
||||
|
||||
def index
|
||||
retrieve_query
|
||||
sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
|
||||
@@ -104,8 +113,10 @@ class IssuesController < ApplicationController
|
||||
@journals.each_with_index {|j,i| j.indice = i+1}
|
||||
@journals.reverse! if User.current.wants_comments_in_reverse_order?
|
||||
|
||||
@changesets = @issue.changesets.visible.all
|
||||
@changesets.reverse! if User.current.wants_comments_in_reverse_order?
|
||||
if User.current.allowed_to?(:view_changesets, @project)
|
||||
@changesets = @issue.changesets.visible.all
|
||||
@changesets.reverse! if User.current.wants_comments_in_reverse_order?
|
||||
end
|
||||
|
||||
@relations = @issue.relations.select {|r| r.other_issue(@issue) && r.other_issue(@issue).visible? }
|
||||
@allowed_statuses = @issue.new_statuses_allowed_to(User.current)
|
||||
@@ -113,10 +124,7 @@ class IssuesController < ApplicationController
|
||||
@priorities = IssuePriority.active
|
||||
@time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
retrieve_previous_and_next_issue_ids
|
||||
render :template => 'issues/show'
|
||||
}
|
||||
format.html { render :template => 'issues/show' }
|
||||
format.api
|
||||
format.atom { render :template => 'journals/index', :layout => false, :content_type => 'application/atom+xml' }
|
||||
format.pdf { send_data(issue_to_pdf(@issue), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") }
|
||||
@@ -128,30 +136,20 @@ class IssuesController < ApplicationController
|
||||
def new
|
||||
respond_to do |format|
|
||||
format.html { render :action => 'new', :layout => !request.xhr? }
|
||||
format.js {
|
||||
render(:update) { |page|
|
||||
if params[:project_change]
|
||||
page.replace_html 'all_attributes', :partial => 'form'
|
||||
else
|
||||
page.replace_html 'attributes', :partial => 'attributes'
|
||||
end
|
||||
m = User.current.allowed_to?(:log_time, @issue.project) ? 'show' : 'hide'
|
||||
page << "if ($('log_time')) {Element.#{m}('log_time');}"
|
||||
}
|
||||
}
|
||||
format.js { render :partial => 'attributes' }
|
||||
end
|
||||
end
|
||||
|
||||
def create
|
||||
call_hook(:controller_issues_new_before_save, { :params => params, :issue => @issue })
|
||||
@issue.save_attachments(params[:attachments] || (params[:issue] && params[:issue][:uploads]))
|
||||
if @issue.save
|
||||
attachments = Attachment.attach_files(@issue, params[:attachments])
|
||||
call_hook(:controller_issues_new_after_save, { :params => params, :issue => @issue})
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
render_attachment_warning_if_needed(@issue)
|
||||
flash[:notice] = l(:notice_issue_successful_create, :id => "<a href='#{issue_path(@issue)}'>##{@issue.id}</a>")
|
||||
redirect_to(params[:continue] ? { :action => 'new', :project_id => @issue.project, :issue => {:tracker_id => @issue.tracker, :parent_issue_id => @issue.parent_issue_id}.reject {|k,v| v.nil?} } :
|
||||
redirect_to(params[:continue] ? { :action => 'new', :project_id => @project, :issue => {:tracker_id => @issue.tracker, :parent_issue_id => @issue.parent_issue_id}.reject {|k,v| v.nil?} } :
|
||||
{ :action => 'show', :id => @issue })
|
||||
}
|
||||
format.api { render :action => 'show', :status => :created, :location => issue_url(@issue) }
|
||||
@@ -166,7 +164,9 @@ class IssuesController < ApplicationController
|
||||
end
|
||||
|
||||
def edit
|
||||
return unless update_issue_from_params
|
||||
update_issue_from_params
|
||||
|
||||
@journal = @issue.current_journal
|
||||
|
||||
respond_to do |format|
|
||||
format.html { }
|
||||
@@ -175,24 +175,9 @@ class IssuesController < ApplicationController
|
||||
end
|
||||
|
||||
def update
|
||||
return unless update_issue_from_params
|
||||
@issue.save_attachments(params[:attachments] || (params[:issue] && params[:issue][:uploads]))
|
||||
saved = false
|
||||
begin
|
||||
saved = @issue.save_issue_with_child_records(params, @time_entry)
|
||||
rescue ActiveRecord::StaleObjectError
|
||||
@conflict = true
|
||||
if params[:last_journal_id]
|
||||
if params[:last_journal_id].present?
|
||||
last_journal_id = params[:last_journal_id].to_i
|
||||
@conflict_journals = @issue.journals.all(:conditions => ["#{Journal.table_name}.id > ?", last_journal_id])
|
||||
else
|
||||
@conflict_journals = @issue.journals.all
|
||||
end
|
||||
end
|
||||
end
|
||||
update_issue_from_params
|
||||
|
||||
if saved
|
||||
if @issue.save_issue_with_child_records(params, @time_entry)
|
||||
render_attachment_warning_if_needed(@issue)
|
||||
flash[:notice] = l(:notice_successful_update) unless @issue.current_journal.new_record?
|
||||
|
||||
@@ -201,6 +186,10 @@ class IssuesController < ApplicationController
|
||||
format.api { head :ok }
|
||||
end
|
||||
else
|
||||
render_attachment_warning_if_needed(@issue)
|
||||
flash[:notice] = l(:notice_successful_update) unless @issue.current_journal.new_record?
|
||||
@journal = @issue.current_journal
|
||||
|
||||
respond_to do |format|
|
||||
format.html { render :action => 'edit' }
|
||||
format.api { render_validation_errors(@issue) }
|
||||
@@ -208,74 +197,32 @@ class IssuesController < ApplicationController
|
||||
end
|
||||
end
|
||||
|
||||
# Bulk edit/copy a set of issues
|
||||
# Bulk edit a set of issues
|
||||
def bulk_edit
|
||||
@issues.sort!
|
||||
@copy = params[:copy].present?
|
||||
@notes = params[:notes]
|
||||
|
||||
if User.current.allowed_to?(:move_issues, @projects)
|
||||
@allowed_projects = Issue.allowed_target_projects_on_move
|
||||
if params[:issue]
|
||||
@target_project = @allowed_projects.detect {|p| p.id.to_s == params[:issue][:project_id].to_s}
|
||||
if @target_project
|
||||
target_projects = [@target_project]
|
||||
end
|
||||
end
|
||||
end
|
||||
target_projects ||= @projects
|
||||
|
||||
if @copy
|
||||
@available_statuses = [IssueStatus.default]
|
||||
else
|
||||
@available_statuses = @issues.map(&:new_statuses_allowed_to).reduce(:&)
|
||||
end
|
||||
@custom_fields = target_projects.map{|p|p.all_issue_custom_fields}.reduce(:&)
|
||||
@assignables = target_projects.map(&:assignable_users).reduce(:&)
|
||||
@trackers = target_projects.map(&:trackers).reduce(:&)
|
||||
@versions = target_projects.map {|p| p.shared_versions.open}.reduce(:&)
|
||||
@categories = target_projects.map {|p| p.issue_categories}.reduce(:&)
|
||||
if @copy
|
||||
@attachments_present = @issues.detect {|i| i.attachments.any?}.present?
|
||||
end
|
||||
|
||||
@safe_attributes = @issues.map(&:safe_attribute_names).reduce(:&)
|
||||
render :layout => false if request.xhr?
|
||||
@available_statuses = @projects.map{|p|Workflow.available_statuses(p)}.inject{|memo,w|memo & w}
|
||||
@custom_fields = @projects.map{|p|p.all_issue_custom_fields}.inject{|memo,c|memo & c}
|
||||
@assignables = @projects.map(&:assignable_users).inject{|memo,a| memo & a}
|
||||
@trackers = @projects.map(&:trackers).inject{|memo,t| memo & t}
|
||||
end
|
||||
|
||||
def bulk_update
|
||||
@issues.sort!
|
||||
@copy = params[:copy].present?
|
||||
attributes = parse_params_for_bulk_issue_attributes(params)
|
||||
|
||||
unsaved_issue_ids = []
|
||||
moved_issues = []
|
||||
@issues.each do |issue|
|
||||
issue.reload
|
||||
if @copy
|
||||
issue = issue.copy({}, :attachments => params[:copy_attachments].present?)
|
||||
end
|
||||
journal = issue.init_journal(User.current, params[:notes])
|
||||
issue.safe_attributes = attributes
|
||||
call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue })
|
||||
if issue.save
|
||||
moved_issues << issue
|
||||
else
|
||||
unless issue.save
|
||||
# Keep unsaved issue ids to display them in flash error
|
||||
unsaved_issue_ids << issue.id
|
||||
end
|
||||
end
|
||||
set_flash_from_bulk_issue_save(@issues, unsaved_issue_ids)
|
||||
|
||||
if params[:follow]
|
||||
if @issues.size == 1 && moved_issues.size == 1
|
||||
redirect_to :controller => 'issues', :action => 'show', :id => moved_issues.first
|
||||
elsif moved_issues.map(&:project).uniq.size == 1
|
||||
redirect_to :controller => 'issues', :action => 'index', :project_id => moved_issues.map(&:project).first
|
||||
end
|
||||
else
|
||||
redirect_back_or_default({:controller => 'issues', :action => 'index', :project_id => @project})
|
||||
end
|
||||
redirect_back_or_default({:controller => 'issues', :action => 'index', :project_id => @project})
|
||||
end
|
||||
|
||||
def destroy
|
||||
@@ -327,58 +274,25 @@ private
|
||||
end
|
||||
|
||||
def find_project
|
||||
project_id = params[:project_id] || (params[:issue] && params[:issue][:project_id])
|
||||
project_id = (params[:issue] && params[:issue][:project_id]) || params[:project_id]
|
||||
@project = Project.find(project_id)
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
end
|
||||
|
||||
def retrieve_previous_and_next_issue_ids
|
||||
retrieve_query_from_session
|
||||
if @query
|
||||
sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
|
||||
sort_update(@query.sortable_columns, 'issues_index_sort')
|
||||
limit = 500
|
||||
issue_ids = @query.issue_ids(:order => sort_clause, :limit => (limit + 1), :include => [:assigned_to, :tracker, :priority, :category, :fixed_version])
|
||||
if (idx = issue_ids.index(@issue.id)) && idx < limit
|
||||
if issue_ids.size < 500
|
||||
@issue_position = idx + 1
|
||||
@issue_count = issue_ids.size
|
||||
end
|
||||
@prev_issue_id = issue_ids[idx - 1] if idx > 0
|
||||
@next_issue_id = issue_ids[idx + 1] if idx < (issue_ids.size - 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Used by #edit and #update to set some common instance variables
|
||||
# from the params
|
||||
# TODO: Refactor, not everything in here is needed by #edit
|
||||
def update_issue_from_params
|
||||
@allowed_statuses = @issue.new_statuses_allowed_to(User.current)
|
||||
@priorities = IssuePriority.active
|
||||
@edit_allowed = User.current.allowed_to?(:edit_issues, @project)
|
||||
@time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
|
||||
@time_entry.attributes = params[:time_entry]
|
||||
|
||||
@notes = params[:notes] || (params[:issue].present? ? params[:issue][:notes] : nil)
|
||||
@issue.init_journal(User.current, @notes)
|
||||
|
||||
issue_attributes = params[:issue]
|
||||
if issue_attributes && params[:conflict_resolution]
|
||||
case params[:conflict_resolution]
|
||||
when 'overwrite'
|
||||
issue_attributes = issue_attributes.dup
|
||||
issue_attributes.delete(:lock_version)
|
||||
when 'add_notes'
|
||||
issue_attributes = {}
|
||||
when 'cancel'
|
||||
redirect_to issue_path(@issue)
|
||||
return false
|
||||
end
|
||||
end
|
||||
@issue.safe_attributes = issue_attributes
|
||||
@priorities = IssuePriority.active
|
||||
@allowed_statuses = @issue.new_statuses_allowed_to(User.current)
|
||||
true
|
||||
@issue.safe_attributes = params[:issue]
|
||||
end
|
||||
|
||||
# TODO: Refactor, lots of extra code in here
|
||||
@@ -386,16 +300,7 @@ private
|
||||
def build_new_issue_from_params
|
||||
if params[:id].blank?
|
||||
@issue = Issue.new
|
||||
if params[:copy_from]
|
||||
begin
|
||||
@copy_from = Issue.visible.find(params[:copy_from])
|
||||
@copy_attachments = params[:copy_attachments].present? || request.get?
|
||||
@issue.copy_from(@copy_from, :attachments => @copy_attachments)
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
return
|
||||
end
|
||||
end
|
||||
@issue.copy_from(params[:copy_from]) if params[:copy_from]
|
||||
@issue.project = @project
|
||||
else
|
||||
@issue = @project.issues.visible.find(params[:id])
|
||||
@@ -410,11 +315,14 @@ private
|
||||
return false
|
||||
end
|
||||
@issue.start_date ||= Date.today if Setting.default_issue_start_date_to_creation_date?
|
||||
@issue.safe_attributes = params[:issue]
|
||||
|
||||
if params[:issue].is_a?(Hash)
|
||||
@issue.safe_attributes = params[:issue]
|
||||
if User.current.allowed_to?(:add_issue_watchers, @project) && @issue.new_record?
|
||||
@issue.watcher_user_ids = params[:issue]['watcher_user_ids']
|
||||
end
|
||||
end
|
||||
@priorities = IssuePriority.active
|
||||
@allowed_statuses = @issue.new_statuses_allowed_to(User.current, true)
|
||||
@available_watchers = (@issue.project.users.sort + @issue.watcher_users).uniq
|
||||
end
|
||||
|
||||
def check_for_default_issue_status
|
||||
@@ -427,16 +335,7 @@ private
|
||||
def parse_params_for_bulk_issue_attributes(params)
|
||||
attributes = (params[:issue] || {}).reject {|k,v| v.blank?}
|
||||
attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'}
|
||||
if custom = attributes[:custom_field_values]
|
||||
custom.reject! {|k,v| v.blank?}
|
||||
custom.keys.each do |k|
|
||||
if custom[k].is_a?(Array)
|
||||
custom[k] << '' if custom[k].delete('__none__')
|
||||
else
|
||||
custom[k] = '' if custom[k] == '__none__'
|
||||
end
|
||||
end
|
||||
end
|
||||
attributes[:custom_field_values].reject! {|k,v| v.blank?} if attributes[:custom_field_values]
|
||||
attributes
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,26 +1,25 @@
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2012 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2011 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
desc 'Removes uploaded files left unattached after one day.'
|
||||
class LdapAuthSourcesController < AuthSourcesController
|
||||
|
||||
namespace :redmine do
|
||||
namespace :attachments do
|
||||
task :prune => :environment do
|
||||
Attachment.prune
|
||||
end
|
||||
protected
|
||||
|
||||
def auth_source_class
|
||||
AuthSourceLdap
|
||||
end
|
||||
end
|
||||
@@ -18,6 +18,10 @@
|
||||
class MailHandlerController < ActionController::Base
|
||||
before_filter :check_credential
|
||||
|
||||
verify :method => :post,
|
||||
:only => :index,
|
||||
:render => { :nothing => true, :status => 405 }
|
||||
|
||||
# Submits an incoming email to MailHandler
|
||||
def index
|
||||
options = params.dup
|
||||
|
||||
@@ -17,54 +17,29 @@
|
||||
|
||||
class MembersController < ApplicationController
|
||||
model_object Member
|
||||
before_filter :find_model_object, :except => [:index, :create, :autocomplete]
|
||||
before_filter :find_project_from_association, :except => [:index, :create, :autocomplete]
|
||||
before_filter :find_project_by_project_id, :only => [:index, :create, :autocomplete]
|
||||
before_filter :find_model_object, :except => [:new, :autocomplete_for_member]
|
||||
before_filter :find_project_from_association, :except => [:new, :autocomplete_for_member]
|
||||
before_filter :find_project, :only => [:new, :autocomplete_for_member]
|
||||
before_filter :authorize
|
||||
accept_api_auth :index, :show, :create, :update, :destroy
|
||||
|
||||
def index
|
||||
@offset, @limit = api_offset_and_limit
|
||||
@member_count = @project.member_principals.count
|
||||
@member_pages = Paginator.new self, @member_count, @limit, params['page']
|
||||
@offset ||= @member_pages.current.offset
|
||||
@members = @project.member_principals.all(
|
||||
:order => "#{Member.table_name}.id",
|
||||
:limit => @limit,
|
||||
:offset => @offset
|
||||
)
|
||||
|
||||
respond_to do |format|
|
||||
format.html { head 406 }
|
||||
format.api
|
||||
end
|
||||
end
|
||||
|
||||
def show
|
||||
respond_to do |format|
|
||||
format.html { head 406 }
|
||||
format.api
|
||||
end
|
||||
end
|
||||
|
||||
def create
|
||||
def new
|
||||
members = []
|
||||
if params[:membership]
|
||||
if params[:membership][:user_ids]
|
||||
attrs = params[:membership].dup
|
||||
user_ids = attrs.delete(:user_ids)
|
||||
if params[:member] && request.post?
|
||||
attrs = params[:member].dup
|
||||
if (user_ids = attrs.delete(:user_ids))
|
||||
user_ids.each do |user_id|
|
||||
members << Member.new(:role_ids => params[:membership][:role_ids], :user_id => user_id)
|
||||
members << Member.new(attrs.merge(:user_id => user_id))
|
||||
end
|
||||
else
|
||||
members << Member.new(:role_ids => params[:membership][:role_ids], :user_id => params[:membership][:user_id])
|
||||
members << Member.new(attrs)
|
||||
end
|
||||
@project.members << members
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
if members.present? && members.all? {|m| m.valid? }
|
||||
|
||||
format.html { 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'
|
||||
@@ -72,11 +47,8 @@ class MembersController < ApplicationController
|
||||
members.each {|member| page.visual_effect(:highlight, "member-#{member.id}") }
|
||||
}
|
||||
}
|
||||
format.api {
|
||||
@member = members.first
|
||||
render :action => 'show', :status => :created, :location => membership_url(@member)
|
||||
}
|
||||
else
|
||||
|
||||
format.js {
|
||||
render(:update) {|page|
|
||||
errors = members.collect {|m|
|
||||
@@ -86,37 +58,28 @@ class MembersController < ApplicationController
|
||||
page.alert(l(:notice_failed_to_save_members, :errors => errors.join(', ')))
|
||||
}
|
||||
}
|
||||
format.api { render_validation_errors(members.first) }
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
if params[:membership]
|
||||
@member.role_ids = params[:membership][:role_ids]
|
||||
end
|
||||
saved = @member.save
|
||||
respond_to do |format|
|
||||
format.html { 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'
|
||||
page << 'hideOnLoad()'
|
||||
page.visual_effect(:highlight, "member-#{@member.id}")
|
||||
def edit
|
||||
if request.post? and @member.update_attributes(params[:member])
|
||||
respond_to do |format|
|
||||
format.html { 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'
|
||||
page << 'hideOnLoad()'
|
||||
page.visual_effect(:highlight, "member-#{@member.id}")
|
||||
}
|
||||
}
|
||||
}
|
||||
format.api {
|
||||
if saved
|
||||
head :ok
|
||||
else
|
||||
render_validation_errors(@member)
|
||||
end
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
if request.delete? && @member.deletable?
|
||||
if request.post? && @member.deletable?
|
||||
@member.destroy
|
||||
end
|
||||
respond_to do |format|
|
||||
@@ -126,18 +89,11 @@ class MembersController < ApplicationController
|
||||
page << 'hideOnLoad()'
|
||||
}
|
||||
}
|
||||
format.api {
|
||||
if @member.destroyed?
|
||||
head :ok
|
||||
else
|
||||
head :unprocessable_entity
|
||||
end
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def autocomplete
|
||||
@principals = Principal.active.not_member_of(@project).like(params[:q]).all(:limit => 100)
|
||||
def autocomplete_for_member
|
||||
@principals = Principal.active.like(params[:q]).find(:all, :limit => 100) - @project.principals
|
||||
render :layout => false
|
||||
end
|
||||
|
||||
|
||||
@@ -22,6 +22,9 @@ class MessagesController < ApplicationController
|
||||
before_filter :find_message, :except => [:new, :preview]
|
||||
before_filter :authorize, :except => [:preview, :edit, :destroy]
|
||||
|
||||
verify :method => :post, :only => [ :reply, :destroy ], :redirect_to => { :action => :show }
|
||||
verify :xhr => true, :only => :quote
|
||||
|
||||
helper :watchers
|
||||
helper :attachments
|
||||
include AttachmentsHelper
|
||||
@@ -50,26 +53,26 @@ class MessagesController < ApplicationController
|
||||
|
||||
# Create a new topic
|
||||
def new
|
||||
@message = Message.new
|
||||
@message = Message.new(params[:message])
|
||||
@message.author = User.current
|
||||
@message.board = @board
|
||||
@message.safe_attributes = params[:message]
|
||||
if request.post?
|
||||
@message.save_attachments(params[:attachments])
|
||||
if @message.save
|
||||
call_hook(:controller_messages_new_after_save, { :params => params, :message => @message})
|
||||
render_attachment_warning_if_needed(@message)
|
||||
redirect_to :action => 'show', :id => @message
|
||||
end
|
||||
if params[:message] && User.current.allowed_to?(:edit_messages, @project)
|
||||
@message.locked = params[:message]['locked']
|
||||
@message.sticky = params[:message]['sticky']
|
||||
end
|
||||
if request.post? && @message.save
|
||||
call_hook(:controller_messages_new_after_save, { :params => params, :message => @message})
|
||||
attachments = Attachment.attach_files(@message, params[:attachments])
|
||||
render_attachment_warning_if_needed(@message)
|
||||
redirect_to :action => 'show', :id => @message
|
||||
end
|
||||
end
|
||||
|
||||
# Reply to a topic
|
||||
def reply
|
||||
@reply = Message.new
|
||||
@reply = Message.new(params[:reply])
|
||||
@reply.author = User.current
|
||||
@reply.board = @board
|
||||
@reply.safe_attributes = params[:reply]
|
||||
@topic.children << @reply
|
||||
if !@reply.new_record?
|
||||
call_hook(:controller_messages_reply_after_save, { :params => params, :message => @reply})
|
||||
@@ -82,8 +85,11 @@ class MessagesController < ApplicationController
|
||||
# Edit a message
|
||||
def edit
|
||||
(render_403; return false) unless @message.editable_by?(User.current)
|
||||
@message.safe_attributes = params[:message]
|
||||
if request.post? && @message.save
|
||||
if params[:message]
|
||||
@message.locked = params[:message]['locked']
|
||||
@message.sticky = params[:message]['sticky']
|
||||
end
|
||||
if request.post? && @message.update_attributes(params[:message])
|
||||
attachments = Attachment.attach_files(@message, params[:attachments])
|
||||
render_attachment_warning_if_needed(@message)
|
||||
flash[:notice] = l(:notice_successful_update)
|
||||
|
||||
@@ -35,6 +35,9 @@ class MyController < ApplicationController
|
||||
'right' => ['issuesreportedbyme']
|
||||
}.freeze
|
||||
|
||||
verify :xhr => true,
|
||||
:only => [:add_block, :remove_block, :order_blocks]
|
||||
|
||||
def index
|
||||
page
|
||||
render :action => 'page'
|
||||
@@ -65,24 +68,6 @@ class MyController < ApplicationController
|
||||
end
|
||||
end
|
||||
|
||||
# Destroys user's account
|
||||
def destroy
|
||||
@user = User.current
|
||||
unless @user.own_account_deletable?
|
||||
redirect_to :action => 'account'
|
||||
return
|
||||
end
|
||||
|
||||
if request.post? && params[:confirm]
|
||||
@user.destroy
|
||||
if @user.destroyed?
|
||||
logout_user
|
||||
flash[:notice] = l(:notice_account_deleted)
|
||||
end
|
||||
redirect_to home_path
|
||||
end
|
||||
end
|
||||
|
||||
# Manage user's password
|
||||
def password
|
||||
@user = User.current
|
||||
|
||||
@@ -20,14 +20,13 @@ class NewsController < ApplicationController
|
||||
model_object News
|
||||
before_filter :find_model_object, :except => [:new, :create, :index]
|
||||
before_filter :find_project_from_association, :except => [:new, :create, :index]
|
||||
before_filter :find_project_by_project_id, :only => [:new, :create]
|
||||
before_filter :find_project, :only => [:new, :create]
|
||||
before_filter :authorize, :except => [:index]
|
||||
before_filter :find_optional_project, :only => :index
|
||||
accept_rss_auth :index
|
||||
accept_api_auth :index
|
||||
|
||||
helper :watchers
|
||||
helper :attachments
|
||||
|
||||
def index
|
||||
case params[:format]
|
||||
@@ -68,14 +67,14 @@ class NewsController < ApplicationController
|
||||
|
||||
def create
|
||||
@news = News.new(:project => @project, :author => User.current)
|
||||
@news.safe_attributes = params[:news]
|
||||
@news.save_attachments(params[:attachments])
|
||||
if @news.save
|
||||
render_attachment_warning_if_needed(@news)
|
||||
flash[:notice] = l(:notice_successful_create)
|
||||
redirect_to :controller => 'news', :action => 'index', :project_id => @project
|
||||
else
|
||||
render :action => 'new'
|
||||
if request.post?
|
||||
@news.attributes = params[:news]
|
||||
if @news.save
|
||||
flash[:notice] = l(:notice_successful_create)
|
||||
redirect_to :controller => 'news', :action => 'index', :project_id => @project
|
||||
else
|
||||
render :action => 'new'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -83,10 +82,7 @@ class NewsController < ApplicationController
|
||||
end
|
||||
|
||||
def update
|
||||
@news.safe_attributes = params[:news]
|
||||
@news.save_attachments(params[:attachments])
|
||||
if @news.save
|
||||
render_attachment_warning_if_needed(@news)
|
||||
if request.put? and @news.update_attributes(params[:news])
|
||||
flash[:notice] = l(:notice_successful_update)
|
||||
redirect_to :action => 'show', :id => @news
|
||||
else
|
||||
@@ -99,7 +95,12 @@ class NewsController < ApplicationController
|
||||
redirect_to :action => 'index', :project_id => @project
|
||||
end
|
||||
|
||||
private
|
||||
private
|
||||
def find_project
|
||||
@project = Project.find(params[:project_id])
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
end
|
||||
|
||||
def find_optional_project
|
||||
return true unless params[:project_id]
|
||||
|
||||
@@ -29,7 +29,7 @@ class ProjectsController < ApplicationController
|
||||
|
||||
after_filter :only => [:create, :edit, :update, :archive, :unarchive, :destroy] do |controller|
|
||||
if controller.request.post?
|
||||
controller.send :expire_action, :controller => 'welcome', :action => 'robots'
|
||||
controller.send :expire_action, :controller => 'welcome', :action => 'robots.txt'
|
||||
end
|
||||
end
|
||||
|
||||
@@ -66,10 +66,10 @@ class ProjectsController < ApplicationController
|
||||
def new
|
||||
@issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
|
||||
@trackers = Tracker.all
|
||||
@project = Project.new
|
||||
@project.safe_attributes = params[:project]
|
||||
@project = Project.new(params[:project])
|
||||
end
|
||||
|
||||
verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed }
|
||||
def create
|
||||
@issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
|
||||
@trackers = Tracker.all
|
||||
@@ -176,12 +176,15 @@ class ProjectsController < ApplicationController
|
||||
@issue_category ||= IssueCategory.new
|
||||
@member ||= @project.members.new
|
||||
@trackers = Tracker.all
|
||||
@repository ||= @project.repository
|
||||
@wiki ||= @project.wiki
|
||||
end
|
||||
|
||||
def edit
|
||||
end
|
||||
|
||||
# TODO: convert to PUT only
|
||||
verify :method => [:post, :put], :only => :update, :render => {:nothing => true, :status => :method_not_allowed }
|
||||
def update
|
||||
@project.safe_attributes = params[:project]
|
||||
if validate_parent_id && @project.save
|
||||
@@ -204,6 +207,7 @@ class ProjectsController < ApplicationController
|
||||
end
|
||||
end
|
||||
|
||||
verify :method => :post, :only => :modules, :render => {:nothing => true, :status => :method_not_allowed }
|
||||
def modules
|
||||
@project.enabled_module_names = params[:enabled_module_names]
|
||||
flash[:notice] = l(:notice_successful_update)
|
||||
@@ -227,18 +231,29 @@ class ProjectsController < ApplicationController
|
||||
# Delete @project
|
||||
def destroy
|
||||
@project_to_destroy = @project
|
||||
if api_request? || params[:confirm]
|
||||
@project_to_destroy.destroy
|
||||
respond_to do |format|
|
||||
format.html { redirect_to :controller => 'admin', :action => 'projects' }
|
||||
format.api { head :ok }
|
||||
if request.get?
|
||||
# display confirmation view
|
||||
else
|
||||
if api_request? || params[:confirm]
|
||||
@project_to_destroy.destroy
|
||||
respond_to do |format|
|
||||
format.html { redirect_to :controller => 'admin', :action => 'projects' }
|
||||
format.api { head :ok }
|
||||
end
|
||||
end
|
||||
end
|
||||
# hide project in layout
|
||||
@project = nil
|
||||
end
|
||||
|
||||
private
|
||||
private
|
||||
def find_optional_project
|
||||
return true unless params[:id]
|
||||
@project = Project.find(params[:id])
|
||||
authorize
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
end
|
||||
|
||||
# Validates parent_id param according to user's permissions
|
||||
# TODO: move it to Project model in a validation that depends on User.current
|
||||
|
||||
@@ -50,6 +50,7 @@ class QueriesController < ApplicationController
|
||||
build_query_from_params
|
||||
end
|
||||
|
||||
verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed }
|
||||
def create
|
||||
@query = Query.new(params[:query])
|
||||
@query.user = User.current
|
||||
@@ -69,6 +70,7 @@ class QueriesController < ApplicationController
|
||||
def edit
|
||||
end
|
||||
|
||||
verify :method => :put, :only => :update, :render => {:nothing => true, :status => :method_not_allowed }
|
||||
def update
|
||||
@query.attributes = params[:query]
|
||||
@query.project = nil if params[:query_is_for_all]
|
||||
@@ -84,6 +86,7 @@ class QueriesController < ApplicationController
|
||||
end
|
||||
end
|
||||
|
||||
verify :method => :delete, :only => :destroy, :render => {:nothing => true, :status => :method_not_allowed }
|
||||
def destroy
|
||||
@query.destroy
|
||||
redirect_to :controller => 'issues', :action => 'index', :project_id => @project, :set_filter => 1
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2012 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2011 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
@@ -24,46 +24,44 @@ class InvalidRevisionParam < Exception; end
|
||||
|
||||
class RepositoriesController < ApplicationController
|
||||
menu_item :repository
|
||||
menu_item :settings, :only => [:new, :create, :edit, :update, :destroy, :committers]
|
||||
menu_item :settings, :only => :edit
|
||||
default_search_scope :changesets
|
||||
|
||||
before_filter :find_project_by_project_id, :only => [:new, :create]
|
||||
before_filter :find_repository, :only => [:edit, :update, :destroy, :committers]
|
||||
before_filter :find_project_repository, :except => [:new, :create, :edit, :update, :destroy, :committers]
|
||||
before_filter :find_changeset, :only => [:revision, :add_related_issue, :remove_related_issue]
|
||||
before_filter :find_repository, :except => :edit
|
||||
before_filter :find_project, :only => :edit
|
||||
before_filter :authorize
|
||||
accept_rss_auth :revisions
|
||||
|
||||
rescue_from Redmine::Scm::Adapters::CommandFailed, :with => :show_error_command_failed
|
||||
|
||||
def new
|
||||
scm = params[:repository_scm] || (Redmine::Scm::Base.all & Setting.enabled_scm).first
|
||||
@repository = Repository.factory(scm)
|
||||
@repository.is_default = @project.repository.nil?
|
||||
@repository.project = @project
|
||||
render :layout => !request.xhr?
|
||||
end
|
||||
|
||||
def create
|
||||
@repository = Repository.factory(params[:repository_scm], params[:repository])
|
||||
@repository.project = @project
|
||||
if request.post? && @repository.save
|
||||
redirect_to settings_project_path(@project, :tab => 'repositories')
|
||||
else
|
||||
render :action => 'new'
|
||||
end
|
||||
end
|
||||
|
||||
def edit
|
||||
end
|
||||
|
||||
def update
|
||||
@repository.attributes = params[:repository]
|
||||
@repository.project = @project
|
||||
if request.put? && @repository.save
|
||||
redirect_to settings_project_path(@project, :tab => 'repositories')
|
||||
else
|
||||
render :action => 'edit'
|
||||
@repository = @project.repository
|
||||
if !@repository && !params[:repository_scm].blank?
|
||||
@repository = Repository.factory(params[:repository_scm])
|
||||
@repository.project = @project if @repository
|
||||
end
|
||||
if request.post? && @repository
|
||||
p1 = params[:repository]
|
||||
p = {}
|
||||
p_extra = {}
|
||||
p1.each do |k, v|
|
||||
if k =~ /^extra_/
|
||||
p_extra[k] = v
|
||||
else
|
||||
p[k] = v
|
||||
end
|
||||
end
|
||||
@repository.attributes = p
|
||||
@repository.merge_extra_info(p_extra)
|
||||
@repository.save
|
||||
end
|
||||
render(:update) do |page|
|
||||
page.replace_html "tab-content-repository",
|
||||
:partial => 'projects/settings/repository'
|
||||
if @repository && !@project.repository
|
||||
@project.reload # needed to reload association
|
||||
page.replace_html "main-menu", render_main_menu(@project)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -78,13 +76,16 @@ class RepositoriesController < ApplicationController
|
||||
# Build a hash with repository usernames as keys and corresponding user ids as values
|
||||
@repository.committer_ids = params[:committers].values.inject({}) {|h, c| h[c.first] = c.last; h}
|
||||
flash[:notice] = l(:notice_successful_update)
|
||||
redirect_to settings_project_path(@project, :tab => 'repositories')
|
||||
redirect_to :action => 'committers', :id => @project
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
@repository.destroy if request.delete?
|
||||
redirect_to settings_project_path(@project, :tab => 'repositories')
|
||||
@repository.destroy
|
||||
redirect_to :controller => 'projects',
|
||||
:action => 'settings',
|
||||
:id => @project,
|
||||
:tab => 'repository'
|
||||
end
|
||||
|
||||
def show
|
||||
@@ -98,7 +99,6 @@ class RepositoriesController < ApplicationController
|
||||
(show_error_not_found; return) unless @entries
|
||||
@changesets = @repository.latest_changesets(@path, @rev)
|
||||
@properties = @repository.properties(@path, @rev)
|
||||
@repositories = @project.repositories
|
||||
render :action => 'show'
|
||||
end
|
||||
end
|
||||
@@ -186,56 +186,16 @@ class RepositoriesController < ApplicationController
|
||||
end
|
||||
|
||||
def revision
|
||||
raise ChangesetNotFound if @rev.blank?
|
||||
@changeset = @repository.find_changeset_by_name(@rev)
|
||||
raise ChangesetNotFound unless @changeset
|
||||
|
||||
respond_to do |format|
|
||||
format.html
|
||||
format.js {render :layout => false}
|
||||
end
|
||||
end
|
||||
|
||||
# Adds a related issue to a changeset
|
||||
# POST /projects/:project_id/repository/(:repository_id/)revisions/:rev/issues
|
||||
def add_related_issue
|
||||
@issue = @changeset.find_referenced_issue_by_id(params[:issue_id])
|
||||
if @issue && (!@issue.visible? || @changeset.issues.include?(@issue))
|
||||
@issue = nil
|
||||
end
|
||||
|
||||
if @issue
|
||||
@changeset.issues << @issue
|
||||
respond_to do |format|
|
||||
format.js {
|
||||
render :update do |page|
|
||||
page.replace_html "related-issues", :partial => "related_issues"
|
||||
page.visual_effect :highlight, "related-issue-#{@issue.id}"
|
||||
end
|
||||
}
|
||||
end
|
||||
else
|
||||
respond_to do |format|
|
||||
format.js {
|
||||
render :update do |page|
|
||||
page.alert(l(:label_issue) + ' ' + l('activerecord.errors.messages.invalid'))
|
||||
end
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Removes a related issue from a changeset
|
||||
# DELETE /projects/:project_id/repository/(:repository_id/)revisions/:rev/issues/:issue_id
|
||||
def remove_related_issue
|
||||
@issue = Issue.visible.find_by_id(params[:issue_id])
|
||||
if @issue
|
||||
@changeset.issues.delete(@issue)
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.js {
|
||||
render :update do |page|
|
||||
page.remove "related-issue-#{@issue.id}"
|
||||
end if @issue
|
||||
}
|
||||
end
|
||||
rescue ChangesetNotFound
|
||||
show_error_not_found
|
||||
end
|
||||
|
||||
def diff
|
||||
@@ -290,22 +250,11 @@ class RepositoriesController < ApplicationController
|
||||
|
||||
private
|
||||
|
||||
def find_repository
|
||||
@repository = Repository.find(params[:id])
|
||||
@project = @repository.project
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
end
|
||||
|
||||
REV_PARAM_RE = %r{\A[a-f0-9]*\Z}i
|
||||
|
||||
def find_project_repository
|
||||
def find_repository
|
||||
@project = Project.find(params[:id])
|
||||
if params[:repository_id].present?
|
||||
@repository = @project.repositories.find_by_identifier_param(params[:repository_id])
|
||||
else
|
||||
@repository = @project.repository
|
||||
end
|
||||
@repository = @project.repository
|
||||
(render_404; return false) unless @repository
|
||||
@path = params[:path].join('/') unless params[:path].nil?
|
||||
@path ||= ''
|
||||
@@ -323,13 +272,6 @@ class RepositoriesController < ApplicationController
|
||||
show_error_not_found
|
||||
end
|
||||
|
||||
def find_changeset
|
||||
if @rev.present?
|
||||
@changeset = @repository.find_changeset_by_name(@rev)
|
||||
end
|
||||
show_error_not_found unless @changeset
|
||||
end
|
||||
|
||||
def show_error_not_found
|
||||
render_error :message => l(:error_scm_not_found), :status => 404
|
||||
end
|
||||
@@ -424,3 +366,18 @@ class RepositoriesController < ApplicationController
|
||||
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
|
||||
|
||||
@@ -18,31 +18,19 @@
|
||||
class RolesController < ApplicationController
|
||||
layout 'admin'
|
||||
|
||||
before_filter :require_admin, :except => :index
|
||||
before_filter :require_admin_or_api_request, :only => :index
|
||||
before_filter :find_role, :only => [:edit, :update, :destroy]
|
||||
accept_api_auth :index
|
||||
before_filter :require_admin
|
||||
|
||||
verify :method => :post, :only => [ :destroy ],
|
||||
:redirect_to => { :action => :index }
|
||||
|
||||
def index
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
@role_pages, @roles = paginate :roles, :per_page => 25, :order => 'builtin, position'
|
||||
render :action => "index", :layout => false if request.xhr?
|
||||
}
|
||||
format.api {
|
||||
@roles = Role.givable.all
|
||||
}
|
||||
end
|
||||
@role_pages, @roles = paginate :roles, :per_page => 25, :order => 'builtin, position'
|
||||
render :action => "index", :layout => false if request.xhr?
|
||||
end
|
||||
|
||||
def new
|
||||
# Prefills the form with 'Non member' role permissions
|
||||
@role = Role.new(params[:role] || {:permissions => Role.non_member.permissions})
|
||||
@roles = Role.sorted.all
|
||||
end
|
||||
|
||||
def create
|
||||
@role = Role.new(params[:role])
|
||||
if request.post? && @role.save
|
||||
# workflow copy
|
||||
if !params[:copy_workflow_from].blank? && (copy_from = Role.find_by_id(params[:copy_workflow_from]))
|
||||
@@ -51,24 +39,23 @@ class RolesController < ApplicationController
|
||||
flash[:notice] = l(:notice_successful_create)
|
||||
redirect_to :action => 'index'
|
||||
else
|
||||
@roles = Role.sorted.all
|
||||
render :action => 'new'
|
||||
@permissions = @role.setable_permissions
|
||||
@roles = Role.find :all, :order => 'builtin, position'
|
||||
end
|
||||
end
|
||||
|
||||
def edit
|
||||
end
|
||||
|
||||
def update
|
||||
if request.put? and @role.update_attributes(params[:role])
|
||||
@role = Role.find(params[:id])
|
||||
if request.post? and @role.update_attributes(params[:role])
|
||||
flash[:notice] = l(:notice_successful_update)
|
||||
redirect_to :action => 'index'
|
||||
else
|
||||
render :action => 'edit'
|
||||
@permissions = @role.setable_permissions
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
@role = Role.find(params[:id])
|
||||
@role.destroy
|
||||
redirect_to :action => 'index'
|
||||
rescue
|
||||
@@ -76,8 +63,8 @@ class RolesController < ApplicationController
|
||||
redirect_to :action => 'index'
|
||||
end
|
||||
|
||||
def permissions
|
||||
@roles = Role.sorted.all
|
||||
def report
|
||||
@roles = Role.find(:all, :order => 'builtin, position')
|
||||
@permissions = Redmine::AccessControl.permissions.select { |p| !p.public? }
|
||||
if request.post?
|
||||
@roles.each do |role|
|
||||
@@ -88,12 +75,4 @@ class RolesController < ApplicationController
|
||||
redirect_to :action => 'index'
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def find_role
|
||||
@role = Role.find(params[:id])
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
end
|
||||
end
|
||||
|
||||
@@ -34,7 +34,7 @@ class SearchController < ApplicationController
|
||||
when 'my_projects'
|
||||
User.current.memberships.collect(&:project)
|
||||
when 'subprojects'
|
||||
@project ? (@project.self_and_descendants.active.all) : nil
|
||||
@project ? (@project.self_and_descendants.active) : nil
|
||||
else
|
||||
@project
|
||||
end
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
|
||||
class SettingsController < ApplicationController
|
||||
layout 'admin'
|
||||
menu_item :plugins, :only => :plugin
|
||||
|
||||
before_filter :require_admin
|
||||
|
||||
@@ -52,12 +51,12 @@ class SettingsController < ApplicationController
|
||||
def plugin
|
||||
@plugin = Redmine::Plugin.find(params[:id])
|
||||
if request.post?
|
||||
Setting.send "plugin_#{@plugin.id}=", params[:settings]
|
||||
Setting["plugin_#{@plugin.id}"] = params[:settings]
|
||||
flash[:notice] = l(:notice_successful_update)
|
||||
redirect_to :action => 'plugin', :id => @plugin.id
|
||||
else
|
||||
@partial = @plugin.settings[:partial]
|
||||
@settings = Setting.send "plugin_#{@plugin.id}"
|
||||
@settings = Setting["plugin_#{@plugin.id}"]
|
||||
end
|
||||
rescue Redmine::PluginNotFound
|
||||
render_404
|
||||
|
||||
@@ -19,16 +19,9 @@ class SysController < ActionController::Base
|
||||
before_filter :check_enabled
|
||||
|
||||
def projects
|
||||
p = Project.active.has_module(:repository).find(
|
||||
:all,
|
||||
:include => :repository,
|
||||
:order => "#{Project.table_name}.identifier"
|
||||
)
|
||||
p = Project.active.has_module(:repository).find(:all, :include => :repository, :order => 'identifier')
|
||||
# extra_info attribute from repository breaks activeresource client
|
||||
render :xml => p.to_xml(
|
||||
:only => [:id, :identifier, :name, :is_public, :status],
|
||||
:include => {:repository => {:only => [:id, :url]}}
|
||||
)
|
||||
render :xml => p.to_xml(:only => [:id, :identifier, :name, :is_public, :status], :include => {:repository => {:only => [:id, :url]}})
|
||||
end
|
||||
|
||||
def create_project_repository
|
||||
@@ -37,10 +30,9 @@ class SysController < ActionController::Base
|
||||
render :nothing => true, :status => 409
|
||||
else
|
||||
logger.info "Repository for #{project.name} was reported to be created by #{request.remote_ip}."
|
||||
repository = Repository.factory(params[:vendor], params[:repository])
|
||||
repository.project = project
|
||||
if repository.save
|
||||
render :xml => {repository.class.name.underscore.gsub('/', '-') => {:id => repository.id, :url => repository.url}}, :status => 201
|
||||
project.repository = Repository.factory(params[:vendor], params[:repository])
|
||||
if project.repository && project.repository.save
|
||||
render :xml => project.repository.to_xml(:only => [:id, :url]), :status => 201
|
||||
else
|
||||
render :nothing => true, :status => 422
|
||||
end
|
||||
@@ -49,22 +41,14 @@ class SysController < ActionController::Base
|
||||
|
||||
def fetch_changesets
|
||||
projects = []
|
||||
scope = Project.active.has_module(:repository)
|
||||
if params[:id]
|
||||
project = nil
|
||||
if params[:id].to_s =~ /^\d*$/
|
||||
project = scope.find(params[:id])
|
||||
else
|
||||
project = scope.find_by_identifier(params[:id])
|
||||
end
|
||||
raise ActiveRecord::RecordNotFound unless project
|
||||
projects << project
|
||||
projects << Project.active.has_module(:repository).find(params[:id])
|
||||
else
|
||||
projects = scope.all
|
||||
projects = Project.active.has_module(:repository).find(:all, :include => :repository)
|
||||
end
|
||||
projects.each do |project|
|
||||
project.repositories.each do |repository|
|
||||
repository.fetch_changesets
|
||||
if project.repository
|
||||
project.repository.fetch_changesets
|
||||
end
|
||||
end
|
||||
render :nothing => true, :status => 200
|
||||
|
||||
209
app/controllers/time_entry_reports_controller.rb
Normal file
209
app/controllers/time_entry_reports_controller.rb
Normal file
@@ -0,0 +1,209 @@
|
||||
class TimeEntryReportsController < ApplicationController
|
||||
menu_item :issues
|
||||
before_filter :find_optional_project
|
||||
before_filter :load_available_criterias
|
||||
|
||||
helper :sort
|
||||
include SortHelper
|
||||
helper :issues
|
||||
helper :timelog
|
||||
include TimelogHelper
|
||||
helper :custom_fields
|
||||
include CustomFieldsHelper
|
||||
|
||||
def report
|
||||
@criterias = params[:criterias] || []
|
||||
@criterias = @criterias.select{|criteria| @available_criterias.has_key? criteria}
|
||||
@criterias.uniq!
|
||||
@criterias = @criterias[0,3]
|
||||
|
||||
@columns = (params[:columns] && %w(year month week day).include?(params[:columns])) ? params[:columns] : 'month'
|
||||
|
||||
retrieve_date_range
|
||||
|
||||
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_condition = ''
|
||||
|
||||
if @project.nil?
|
||||
sql_condition = Project.allowed_to_condition(User.current, :view_time_entries)
|
||||
elsif @issue.nil?
|
||||
sql_condition = @project.project_condition(Setting.display_subprojects_issues?)
|
||||
else
|
||||
sql_condition = "#{Issue.table_name}.root_id = #{@issue.root_id} AND #{Issue.table_name}.lft >= #{@issue.lft} AND #{Issue.table_name}.rgt <= #{@issue.rgt}"
|
||||
end
|
||||
|
||||
sql = "SELECT #{sql_select}, tyear, tmonth, tweek, spent_on, SUM(hours) AS hours"
|
||||
sql << " FROM #{TimeEntry.table_name}"
|
||||
sql << time_report_joins
|
||||
sql << " WHERE"
|
||||
sql << " (%s) AND" % sql_condition
|
||||
sql << " (spent_on BETWEEN '%s' AND '%s')" % [ActiveRecord::Base.connection.quoted_date(@from), ActiveRecord::Base.connection.quoted_date(@to)]
|
||||
sql << " GROUP BY #{sql_group_by}, tyear, tmonth, tweek, spent_on"
|
||||
|
||||
@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']}"
|
||||
when 'day'
|
||||
row['day'] = "#{row['spent_on']}"
|
||||
end
|
||||
end
|
||||
|
||||
@total_hours = @hours.inject(0) {|s,k| s = s + k['hours'].to_f}
|
||||
|
||||
@periods = []
|
||||
# Date#at_beginning_of_ not supported in Rails 1.2.x
|
||||
date_from = @from.to_time
|
||||
# 100 columns max
|
||||
while date_from <= @to.to_time && @periods.length < 100
|
||||
case @columns
|
||||
when 'year'
|
||||
@periods << "#{date_from.year}"
|
||||
date_from = (date_from + 1.year).at_beginning_of_year
|
||||
when 'month'
|
||||
@periods << "#{date_from.year}-#{date_from.month}"
|
||||
date_from = (date_from + 1.month).at_beginning_of_month
|
||||
when 'week'
|
||||
@periods << "#{date_from.year}-#{date_from.to_date.cweek}"
|
||||
date_from = (date_from + 7.day).at_beginning_of_week
|
||||
when 'day'
|
||||
@periods << "#{date_from.to_date}"
|
||||
date_from = date_from + 1.day
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.html { render :layout => !request.xhr? }
|
||||
format.csv { send_data(report_to_csv(@criterias, @periods, @hours), :type => 'text/csv; header=present', :filename => 'timelog.csv') }
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# TODO: duplicated in TimelogController
|
||||
def find_optional_project
|
||||
if !params[:issue_id].blank?
|
||||
@issue = Issue.find(params[:issue_id])
|
||||
@project = @issue.project
|
||||
elsif !params[:project_id].blank?
|
||||
@project = Project.find(params[:project_id])
|
||||
end
|
||||
deny_access unless User.current.allowed_to?(:view_time_entries, @project, :global => true)
|
||||
end
|
||||
|
||||
# Retrieves the date range based on predefined ranges or specific from/to param dates
|
||||
# TODO: duplicated in TimelogController
|
||||
def retrieve_date_range
|
||||
@free_period = false
|
||||
@from, @to = nil, nil
|
||||
|
||||
if params[:period_type] == '1' || (params[:period_type].nil? && !params[:period].nil?)
|
||||
case params[:period].to_s
|
||||
when 'today'
|
||||
@from = @to = Date.today
|
||||
when 'yesterday'
|
||||
@from = @to = Date.today - 1
|
||||
when 'current_week'
|
||||
@from = Date.today - (Date.today.cwday - 1)%7
|
||||
@to = @from + 6
|
||||
when 'last_week'
|
||||
@from = Date.today - 7 - (Date.today.cwday - 1)%7
|
||||
@to = @from + 6
|
||||
when '7_days'
|
||||
@from = Date.today - 7
|
||||
@to = Date.today
|
||||
when 'current_month'
|
||||
@from = Date.civil(Date.today.year, Date.today.month, 1)
|
||||
@to = (@from >> 1) - 1
|
||||
when 'last_month'
|
||||
@from = Date.civil(Date.today.year, Date.today.month, 1) << 1
|
||||
@to = (@from >> 1) - 1
|
||||
when '30_days'
|
||||
@from = Date.today - 30
|
||||
@to = Date.today
|
||||
when 'current_year'
|
||||
@from = Date.civil(Date.today.year, 1, 1)
|
||||
@to = Date.civil(Date.today.year, 12, 31)
|
||||
end
|
||||
elsif params[:period_type] == '2' || (params[:period_type].nil? && (!params[:from].nil? || !params[:to].nil?))
|
||||
begin; @from = params[:from].to_s.to_date unless params[:from].blank?; rescue; end
|
||||
begin; @to = params[:to].to_s.to_date unless params[:to].blank?; rescue; end
|
||||
@free_period = true
|
||||
else
|
||||
# default
|
||||
end
|
||||
|
||||
@from, @to = @to, @from if @from && @to && @from > @to
|
||||
@from ||= (TimeEntry.earilest_date_for_project(@project) || Date.today)
|
||||
@to ||= (TimeEntry.latest_date_for_project(@project) || Date.today)
|
||||
end
|
||||
|
||||
def load_available_criterias
|
||||
@available_criterias = { 'project' => {:sql => "#{TimeEntry.table_name}.project_id",
|
||||
:klass => Project,
|
||||
:label => :label_project},
|
||||
'version' => {:sql => "#{Issue.table_name}.fixed_version_id",
|
||||
:klass => Version,
|
||||
:label => :label_version},
|
||||
'category' => {:sql => "#{Issue.table_name}.category_id",
|
||||
:klass => IssueCategory,
|
||||
:label => :field_category},
|
||||
'member' => {:sql => "#{TimeEntry.table_name}.user_id",
|
||||
:klass => User,
|
||||
:label => :label_member},
|
||||
'tracker' => {:sql => "#{Issue.table_name}.tracker_id",
|
||||
:klass => Tracker,
|
||||
:label => :label_tracker},
|
||||
'activity' => {:sql => "#{TimeEntry.table_name}.activity_id",
|
||||
:klass => TimeEntryActivity,
|
||||
:label => :label_activity},
|
||||
'issue' => {:sql => "#{TimeEntry.table_name}.issue_id",
|
||||
:klass => Issue,
|
||||
:label => :label_issue}
|
||||
}
|
||||
|
||||
# Add list and boolean custom fields as available criterias
|
||||
custom_fields = (@project.nil? ? IssueCustomField.for_all : @project.all_issue_custom_fields)
|
||||
custom_fields.select {|cf| %w(list bool).include? cf.field_format }.each do |cf|
|
||||
@available_criterias["cf_#{cf.id}"] = {:sql => "(SELECT c.value FROM #{CustomValue.table_name} c WHERE c.custom_field_id = #{cf.id} AND c.customized_type = 'Issue' AND c.customized_id = #{Issue.table_name}.id)",
|
||||
:format => cf.field_format,
|
||||
:label => cf.name}
|
||||
end if @project
|
||||
|
||||
# Add list and boolean time entry custom fields
|
||||
TimeEntryCustomField.find(:all).select {|cf| %w(list bool).include? cf.field_format }.each do |cf|
|
||||
@available_criterias["cf_#{cf.id}"] = {:sql => "(SELECT c.value FROM #{CustomValue.table_name} c WHERE c.custom_field_id = #{cf.id} AND c.customized_type = 'TimeEntry' AND c.customized_id = #{TimeEntry.table_name}.id)",
|
||||
:format => cf.field_format,
|
||||
:label => cf.name}
|
||||
end
|
||||
|
||||
# Add list and boolean time entry activity custom fields
|
||||
TimeEntryActivityCustomField.find(:all).select {|cf| %w(list bool).include? cf.field_format }.each do |cf|
|
||||
@available_criterias["cf_#{cf.id}"] = {:sql => "(SELECT c.value FROM #{CustomValue.table_name} c WHERE c.custom_field_id = #{cf.id} AND c.customized_type = 'Enumeration' AND c.customized_id = #{TimeEntry.table_name}.activity_id)",
|
||||
:format => cf.field_format,
|
||||
:label => cf.name}
|
||||
end
|
||||
|
||||
call_hook(:controller_timelog_available_criterias, { :available_criterias => @available_criterias, :project => @project })
|
||||
@available_criterias
|
||||
end
|
||||
|
||||
def time_report_joins
|
||||
sql = ''
|
||||
sql << " LEFT JOIN #{Issue.table_name} ON #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id"
|
||||
sql << " LEFT JOIN #{Project.table_name} ON #{TimeEntry.table_name}.project_id = #{Project.table_name}.id"
|
||||
# TODO: rename hook
|
||||
call_hook(:controller_timelog_time_report_joins, {:sql => sql} )
|
||||
sql
|
||||
end
|
||||
|
||||
end
|
||||
@@ -17,16 +17,11 @@
|
||||
|
||||
class TimelogController < ApplicationController
|
||||
menu_item :issues
|
||||
|
||||
before_filter :find_project_for_new_time_entry, :only => [:create]
|
||||
before_filter :find_project, :only => [:new, :create]
|
||||
before_filter :find_time_entry, :only => [:show, :edit, :update]
|
||||
before_filter :find_time_entries, :only => [:bulk_edit, :bulk_update, :destroy]
|
||||
before_filter :authorize, :except => [:new, :index, :report]
|
||||
|
||||
before_filter :find_optional_project, :only => [:index, :report]
|
||||
before_filter :find_optional_project_for_new_time_entry, :only => [:new]
|
||||
before_filter :authorize_global, :only => [:new, :index, :report]
|
||||
|
||||
before_filter :authorize, :except => [:index]
|
||||
before_filter :find_optional_project, :only => [:index]
|
||||
accept_rss_auth :index
|
||||
accept_api_auth :index, :show, :create, :update, :destroy
|
||||
|
||||
@@ -39,76 +34,67 @@ class TimelogController < ApplicationController
|
||||
|
||||
def index
|
||||
sort_init 'spent_on', 'desc'
|
||||
sort_update 'spent_on' => ['spent_on', "#{TimeEntry.table_name}.created_on"],
|
||||
sort_update 'spent_on' => 'spent_on',
|
||||
'user' => 'user_id',
|
||||
'activity' => 'activity_id',
|
||||
'project' => "#{Project.table_name}.name",
|
||||
'issue' => 'issue_id',
|
||||
'hours' => 'hours'
|
||||
|
||||
retrieve_date_range
|
||||
|
||||
scope = TimeEntry.visible.spent_between(@from, @to)
|
||||
cond = ARCondition.new
|
||||
if @issue
|
||||
scope = scope.on_issue(@issue)
|
||||
cond << "#{Issue.table_name}.root_id = #{@issue.root_id} AND #{Issue.table_name}.lft >= #{@issue.lft} AND #{Issue.table_name}.rgt <= #{@issue.rgt}"
|
||||
elsif @project
|
||||
scope = scope.on_project(@project, Setting.display_subprojects_issues?)
|
||||
cond << @project.project_condition(Setting.display_subprojects_issues?)
|
||||
end
|
||||
|
||||
retrieve_date_range
|
||||
cond << ['spent_on BETWEEN ? AND ?', @from, @to]
|
||||
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
# Paginate results
|
||||
@entry_count = scope.count
|
||||
@entry_count = TimeEntry.visible.count(:include => [:project, :issue], :conditions => cond.conditions)
|
||||
@entry_pages = Paginator.new self, @entry_count, per_page_option, params['page']
|
||||
@entries = scope.all(
|
||||
:include => [:project, :activity, :user, {:issue => :tracker}],
|
||||
:order => sort_clause,
|
||||
:limit => @entry_pages.items_per_page,
|
||||
:offset => @entry_pages.current.offset
|
||||
)
|
||||
@total_hours = scope.sum(:hours).to_f
|
||||
@entries = TimeEntry.visible.find(:all,
|
||||
:include => [:project, :activity, :user, {:issue => :tracker}],
|
||||
:conditions => cond.conditions,
|
||||
:order => sort_clause,
|
||||
:limit => @entry_pages.items_per_page,
|
||||
:offset => @entry_pages.current.offset)
|
||||
@total_hours = TimeEntry.visible.sum(:hours, :include => [:project, :issue], :conditions => cond.conditions).to_f
|
||||
|
||||
render :layout => !request.xhr?
|
||||
}
|
||||
format.api {
|
||||
@entry_count = scope.count
|
||||
@entry_count = TimeEntry.visible.count(:include => [:project, :issue], :conditions => cond.conditions)
|
||||
@offset, @limit = api_offset_and_limit
|
||||
@entries = scope.all(
|
||||
:include => [:project, :activity, :user, {:issue => :tracker}],
|
||||
:order => sort_clause,
|
||||
:limit => @limit,
|
||||
:offset => @offset
|
||||
)
|
||||
@entries = TimeEntry.visible.find(:all,
|
||||
:include => [:project, :activity, :user, {:issue => :tracker}],
|
||||
:conditions => cond.conditions,
|
||||
:order => sort_clause,
|
||||
:limit => @limit,
|
||||
:offset => @offset)
|
||||
}
|
||||
format.atom {
|
||||
entries = scope.all(
|
||||
:include => [:project, :activity, :user, {:issue => :tracker}],
|
||||
:order => "#{TimeEntry.table_name}.created_on DESC",
|
||||
:limit => Setting.feeds_limit.to_i
|
||||
)
|
||||
entries = TimeEntry.visible.find(:all,
|
||||
:include => [:project, :activity, :user, {:issue => :tracker}],
|
||||
:conditions => cond.conditions,
|
||||
:order => "#{TimeEntry.table_name}.created_on DESC",
|
||||
:limit => Setting.feeds_limit.to_i)
|
||||
render_feed(entries, :title => l(:label_spent_time))
|
||||
}
|
||||
format.csv {
|
||||
# Export all entries
|
||||
@entries = scope.all(
|
||||
:include => [:project, :activity, :user, {:issue => [:tracker, :assigned_to, :priority]}],
|
||||
:order => sort_clause
|
||||
)
|
||||
@entries = TimeEntry.visible.find(:all,
|
||||
:include => [:project, :activity, :user, {:issue => [:tracker, :assigned_to, :priority]}],
|
||||
:conditions => cond.conditions,
|
||||
:order => sort_clause)
|
||||
send_data(entries_to_csv(@entries), :type => 'text/csv; header=present', :filename => 'timelog.csv')
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def report
|
||||
retrieve_date_range
|
||||
@report = Redmine::Helpers::TimeReport.new(@project, @issue, params[:criteria], params[:columns], @from, @to)
|
||||
|
||||
respond_to do |format|
|
||||
format.html { render :layout => !request.xhr? }
|
||||
format.csv { send_data(report_to_csv(@report), :type => 'text/csv; header=present', :filename => 'timelog.csv') }
|
||||
end
|
||||
end
|
||||
|
||||
def show
|
||||
respond_to do |format|
|
||||
# TODO: Implement html response
|
||||
@@ -119,49 +105,44 @@ class TimelogController < ApplicationController
|
||||
|
||||
def new
|
||||
@time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => User.current.today)
|
||||
@time_entry.safe_attributes = params[:time_entry]
|
||||
@time_entry.attributes = params[:time_entry]
|
||||
|
||||
call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
|
||||
render :action => 'edit'
|
||||
end
|
||||
|
||||
verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed }
|
||||
def create
|
||||
@time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => User.current.today)
|
||||
@time_entry.safe_attributes = params[:time_entry]
|
||||
@time_entry.attributes = params[:time_entry]
|
||||
|
||||
call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
|
||||
|
||||
if @time_entry.save
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
flash[:notice] = l(:notice_successful_create)
|
||||
if params[:continue]
|
||||
if params[:project_id]
|
||||
redirect_to :action => 'new', :project_id => @time_entry.project, :issue_id => @time_entry.issue,
|
||||
:time_entry => {:issue_id => @time_entry.issue_id, :activity_id => @time_entry.activity_id},
|
||||
:back_url => params[:back_url]
|
||||
else
|
||||
redirect_to :action => 'new',
|
||||
:time_entry => {:project_id => @time_entry.project_id, :issue_id => @time_entry.issue_id, :activity_id => @time_entry.activity_id},
|
||||
:back_url => params[:back_url]
|
||||
end
|
||||
else
|
||||
redirect_back_or_default :action => 'index', :project_id => @time_entry.project
|
||||
end
|
||||
flash[:notice] = l(:notice_successful_update)
|
||||
redirect_back_or_default :action => 'index', :project_id => @time_entry.project
|
||||
}
|
||||
format.api { render :action => 'show', :status => :created, :location => time_entry_url(@time_entry) }
|
||||
end
|
||||
else
|
||||
respond_to do |format|
|
||||
format.html { render :action => 'new' }
|
||||
format.html { render :action => 'edit' }
|
||||
format.api { render_validation_errors(@time_entry) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def edit
|
||||
@time_entry.safe_attributes = params[:time_entry]
|
||||
@time_entry.attributes = params[:time_entry]
|
||||
|
||||
call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
|
||||
end
|
||||
|
||||
verify :method => :put, :only => :update, :render => {:nothing => true, :status => :method_not_allowed }
|
||||
def update
|
||||
@time_entry.safe_attributes = params[:time_entry]
|
||||
@time_entry.attributes = params[:time_entry]
|
||||
|
||||
call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
|
||||
|
||||
@@ -192,7 +173,7 @@ class TimelogController < ApplicationController
|
||||
unsaved_time_entry_ids = []
|
||||
@time_entries.each do |time_entry|
|
||||
time_entry.reload
|
||||
time_entry.safe_attributes = attributes
|
||||
time_entry.attributes = attributes
|
||||
call_hook(:controller_time_entries_bulk_edit_before_save, { :params => params, :time_entry => time_entry })
|
||||
unless time_entry.save
|
||||
# Keep unsaved time_entry ids to display them in flash error
|
||||
@@ -203,31 +184,32 @@ class TimelogController < ApplicationController
|
||||
redirect_back_or_default({:controller => 'timelog', :action => 'index', :project_id => @projects.first})
|
||||
end
|
||||
|
||||
verify :method => :delete, :only => :destroy, :render => {:nothing => true, :status => :method_not_allowed }
|
||||
def destroy
|
||||
destroyed = TimeEntry.transaction do
|
||||
@time_entries.each do |t|
|
||||
@time_entries.each do |t|
|
||||
begin
|
||||
unless t.destroy && t.destroyed?
|
||||
raise ActiveRecord::Rollback
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
flash[:error] = l(:notice_unable_delete_time_entry)
|
||||
redirect_to :back
|
||||
}
|
||||
format.api { render_validation_errors(t) }
|
||||
end
|
||||
return
|
||||
end
|
||||
rescue ::ActionController::RedirectBackError
|
||||
redirect_to :action => 'index', :project_id => @projects.first
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
if destroyed
|
||||
flash[:notice] = l(:notice_successful_delete)
|
||||
else
|
||||
flash[:error] = l(:notice_unable_delete_time_entry)
|
||||
end
|
||||
flash[:notice] = l(:notice_successful_delete)
|
||||
redirect_back_or_default(:action => 'index', :project_id => @projects.first)
|
||||
}
|
||||
format.api {
|
||||
if destroyed
|
||||
head :ok
|
||||
else
|
||||
render_validation_errors(@time_entries)
|
||||
end
|
||||
}
|
||||
format.api { head :ok }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -263,25 +245,20 @@ private
|
||||
end
|
||||
end
|
||||
|
||||
def find_optional_project_for_new_time_entry
|
||||
if (project_id = (params[:project_id] || params[:time_entry] && params[:time_entry][:project_id])).present?
|
||||
@project = Project.find(project_id)
|
||||
end
|
||||
def find_project
|
||||
if (issue_id = (params[:issue_id] || params[:time_entry] && params[:time_entry][:issue_id])).present?
|
||||
@issue = Issue.find(issue_id)
|
||||
@project ||= @issue.project
|
||||
@project = @issue.project
|
||||
elsif (project_id = (params[:project_id] || params[:time_entry] && params[:time_entry][:project_id])).present?
|
||||
@project = Project.find(project_id)
|
||||
else
|
||||
render_404
|
||||
return false
|
||||
end
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
end
|
||||
|
||||
def find_project_for_new_time_entry
|
||||
find_optional_project_for_new_time_entry
|
||||
if @project.nil?
|
||||
render_404
|
||||
end
|
||||
end
|
||||
|
||||
def find_optional_project
|
||||
if !params[:issue_id].blank?
|
||||
@issue = Issue.find(params[:issue_id])
|
||||
@@ -289,6 +266,7 @@ private
|
||||
elsif !params[:project_id].blank?
|
||||
@project = Project.find(params[:project_id])
|
||||
end
|
||||
deny_access unless User.current.allowed_to?(:view_time_entries, @project, :global => true)
|
||||
end
|
||||
|
||||
# Retrieves the date range based on predefined ranges or specific from/to param dates
|
||||
@@ -333,6 +311,8 @@ private
|
||||
end
|
||||
|
||||
@from, @to = @to, @from if @from && @to && @from > @to
|
||||
@from ||= (TimeEntry.earilest_date_for_project(@project) || Date.today)
|
||||
@to ||= (TimeEntry.latest_date_for_project(@project) || Date.today)
|
||||
end
|
||||
|
||||
def parse_params_for_bulk_time_entry_attributes(params)
|
||||
|
||||
@@ -71,6 +71,7 @@ class TrackersController < ApplicationController
|
||||
render :action => 'edit'
|
||||
end
|
||||
|
||||
verify :method => :delete, :only => :destroy, :redirect_to => { :action => :index }
|
||||
def destroy
|
||||
@tracker = Tracker.find(params[:id])
|
||||
unless @tracker.issues.empty?
|
||||
|
||||
@@ -38,17 +38,23 @@ class UsersController < ApplicationController
|
||||
@limit = per_page_option
|
||||
end
|
||||
|
||||
@status = params[:status] || 1
|
||||
scope = User
|
||||
scope = scope.in_group(params[:group_id].to_i) if params[:group_id].present?
|
||||
|
||||
scope = User.logged.status(@status)
|
||||
scope = scope.like(params[:name]) if params[:name].present?
|
||||
scope = scope.in_group(params[:group_id]) if params[:group_id].present?
|
||||
@status = params[:status] ? params[:status].to_i : 1
|
||||
c = ARCondition.new(@status == 0 ? "status <> 0" : ["status = ?", @status])
|
||||
|
||||
@user_count = scope.count
|
||||
unless params[:name].blank?
|
||||
name = "%#{params[:name].strip.downcase}%"
|
||||
c << ["LOWER(login) LIKE ? OR LOWER(firstname) LIKE ? OR LOWER(lastname) LIKE ? OR LOWER(mail) LIKE ?", name, name, name, name]
|
||||
end
|
||||
|
||||
@user_count = scope.count(:conditions => c.conditions)
|
||||
@user_pages = Paginator.new self, @user_count, @limit, params['page']
|
||||
@offset ||= @user_pages.current.offset
|
||||
@users = scope.find :all,
|
||||
:order => sort_clause,
|
||||
:conditions => c.conditions,
|
||||
:limit => @limit,
|
||||
:offset => @offset
|
||||
|
||||
@@ -86,6 +92,7 @@ class UsersController < ApplicationController
|
||||
@auth_sources = AuthSource.find(:all)
|
||||
end
|
||||
|
||||
verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed }
|
||||
def create
|
||||
@user = User.new(:language => Setting.default_language, :mail_notification => Setting.default_notification_option)
|
||||
@user.safe_attributes = params[:user]
|
||||
@@ -93,9 +100,11 @@ class UsersController < ApplicationController
|
||||
@user.login = params[:user][:login]
|
||||
@user.password, @user.password_confirmation = params[:user][:password], params[:user][:password_confirmation] unless @user.auth_source_id
|
||||
|
||||
# TODO: Similar to My#account
|
||||
@user.pref.attributes = params[:pref]
|
||||
@user.pref[:no_self_notified] = (params[:no_self_notified] == '1')
|
||||
|
||||
if @user.save
|
||||
@user.pref.attributes = params[:pref]
|
||||
@user.pref[:no_self_notified] = (params[:no_self_notified] == '1')
|
||||
@user.pref.save
|
||||
@user.notified_project_ids = (@user.mail_notification == 'selected' ? params[:notified_project_ids] : [])
|
||||
|
||||
@@ -128,6 +137,7 @@ class UsersController < ApplicationController
|
||||
@membership ||= Member.new
|
||||
end
|
||||
|
||||
verify :method => :put, :only => :update, :render => {:nothing => true, :status => :method_not_allowed }
|
||||
def update
|
||||
@user.admin = params[:user][:admin] if params[:user][:admin]
|
||||
@user.login = params[:user][:login] if params[:user][:login]
|
||||
@@ -173,19 +183,18 @@ class UsersController < ApplicationController
|
||||
redirect_to :controller => 'users', :action => 'edit', :id => @user
|
||||
end
|
||||
|
||||
verify :method => :delete, :only => :destroy, :render => {:nothing => true, :status => :method_not_allowed }
|
||||
def destroy
|
||||
@user.destroy
|
||||
respond_to do |format|
|
||||
format.html { redirect_to :back }
|
||||
format.html { redirect_to(users_url) }
|
||||
format.api { head :ok }
|
||||
end
|
||||
rescue ::ActionController::RedirectBackError
|
||||
redirect_to(users_url)
|
||||
end
|
||||
|
||||
def edit_membership
|
||||
@membership = Member.edit_membership(params[:membership_id], params[:membership], @user)
|
||||
@membership.save
|
||||
@membership.save if request.post?
|
||||
respond_to do |format|
|
||||
if @membership.valid?
|
||||
format.html { redirect_to :controller => 'users', :action => 'edit', :id => @user, :tab => 'memberships' }
|
||||
@@ -207,7 +216,7 @@ class UsersController < ApplicationController
|
||||
|
||||
def destroy_membership
|
||||
@membership = Member.find(params[:membership_id])
|
||||
if @membership.deletable?
|
||||
if request.post? && @membership.deletable?
|
||||
@membership.destroy
|
||||
end
|
||||
respond_to do |format|
|
||||
|
||||
@@ -23,7 +23,7 @@ class VersionsController < ApplicationController
|
||||
before_filter :find_project, :only => [:index, :new, :create, :close_completed]
|
||||
before_filter :authorize
|
||||
|
||||
accept_api_auth :index, :show, :create, :update, :destroy
|
||||
accept_api_auth :index, :create, :update, :destroy
|
||||
|
||||
helper :custom_fields
|
||||
helper :projects
|
||||
@@ -39,19 +39,17 @@ class VersionsController < ApplicationController
|
||||
@versions = @project.shared_versions || []
|
||||
@versions += @project.rolled_up_versions.visible if @with_subprojects
|
||||
@versions = @versions.uniq.sort
|
||||
unless params[:completed]
|
||||
@completed_versions = @versions.select {|version| version.closed? || version.completed? }
|
||||
@versions -= @completed_versions
|
||||
end
|
||||
@versions.reject! {|version| version.closed? || version.completed? } unless params[:completed]
|
||||
|
||||
@issues_by_version = {}
|
||||
if @selected_tracker_ids.any? && @versions.any?
|
||||
issues = Issue.visible.all(
|
||||
:include => [:project, :status, :tracker, :priority, :fixed_version],
|
||||
:conditions => {:tracker_id => @selected_tracker_ids, :project_id => project_ids, :fixed_version_id => @versions.map(&:id)},
|
||||
:order => "#{Project.table_name}.lft, #{Tracker.table_name}.position, #{Issue.table_name}.id"
|
||||
)
|
||||
@issues_by_version = issues.group_by(&:fixed_version)
|
||||
unless @selected_tracker_ids.empty?
|
||||
@versions.each do |version|
|
||||
issues = version.fixed_issues.visible.find(:all,
|
||||
:include => [:project, :status, :tracker, :priority],
|
||||
:conditions => {:tracker_id => @selected_tracker_ids, :project_id => project_ids},
|
||||
:order => "#{Project.table_name}.lft, #{Tracker.table_name}.position, #{Issue.table_name}.id")
|
||||
@issues_by_version[version] = issues
|
||||
end
|
||||
end
|
||||
@versions.reject! {|version| !project_ids.include?(version.project_id) && @issues_by_version[version].blank?}
|
||||
}
|
||||
@@ -74,26 +72,20 @@ class VersionsController < ApplicationController
|
||||
|
||||
def new
|
||||
@version = @project.versions.build
|
||||
@version.safe_attributes = params[:version]
|
||||
|
||||
respond_to do |format|
|
||||
format.html
|
||||
format.js do
|
||||
render :update do |page|
|
||||
page.replace_html 'ajax-modal', :partial => 'versions/new_modal'
|
||||
page << "showModal('ajax-modal', '600px');"
|
||||
page << "Form.Element.focus('version_name');"
|
||||
end
|
||||
end
|
||||
if params[:version]
|
||||
attributes = params[:version].dup
|
||||
attributes.delete('sharing') unless attributes.nil? || @version.allowed_sharings.include?(attributes['sharing'])
|
||||
@version.attributes = attributes
|
||||
end
|
||||
end
|
||||
|
||||
def create
|
||||
# TODO: refactor with code above in #new
|
||||
@version = @project.versions.build
|
||||
if params[:version]
|
||||
attributes = params[:version].dup
|
||||
attributes.delete('sharing') unless attributes.nil? || @version.allowed_sharings.include?(attributes['sharing'])
|
||||
@version.safe_attributes = attributes
|
||||
@version.attributes = attributes
|
||||
end
|
||||
|
||||
if request.post?
|
||||
@@ -104,11 +96,9 @@ class VersionsController < ApplicationController
|
||||
redirect_back_or_default :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
|
||||
end
|
||||
format.js do
|
||||
render(:update) {|page|
|
||||
page << 'hideModal();'
|
||||
# IE doesn't support the replace_html rjs method for select box options
|
||||
page.replace "issue_fixed_version_id",
|
||||
content_tag('select', content_tag('option') + version_options_for_select(@project.shared_versions.open, @version), :id => 'issue_fixed_version_id', :name => 'issue[fixed_version_id]')
|
||||
# IE doesn't support the replace_html rjs method for select box options
|
||||
render(:update) {|page| page.replace "issue_fixed_version_id",
|
||||
content_tag('select', '<option></option>' + version_options_for_select(@project.shared_versions.open, @version), :id => 'issue_fixed_version_id', :name => 'issue[fixed_version_id]')
|
||||
}
|
||||
end
|
||||
format.api do
|
||||
@@ -119,10 +109,7 @@ class VersionsController < ApplicationController
|
||||
respond_to do |format|
|
||||
format.html { render :action => 'new' }
|
||||
format.js do
|
||||
render :update do |page|
|
||||
page.replace_html 'ajax-modal', :partial => 'versions/new_modal'
|
||||
page << "Form.Element.focus('version_name');"
|
||||
end
|
||||
render(:update) {|page| page.alert(@version.errors.full_messages.join('\n')) }
|
||||
end
|
||||
format.api { render_validation_errors(@version) }
|
||||
end
|
||||
@@ -137,8 +124,7 @@ class VersionsController < ApplicationController
|
||||
if request.put? && params[:version]
|
||||
attributes = params[:version].dup
|
||||
attributes.delete('sharing') unless @version.allowed_sharings.include?(attributes['sharing'])
|
||||
@version.safe_attributes = attributes
|
||||
if @version.save
|
||||
if @version.update_attributes(attributes)
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
flash[:notice] = l(:notice_successful_update)
|
||||
@@ -162,6 +148,7 @@ class VersionsController < ApplicationController
|
||||
redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
|
||||
end
|
||||
|
||||
verify :method => :delete, :only => :destroy, :render => {:nothing => true, :status => :method_not_allowed }
|
||||
def destroy
|
||||
if @version.fixed_issues.empty?
|
||||
@version.destroy
|
||||
|
||||
@@ -20,6 +20,10 @@ class WatchersController < ApplicationController
|
||||
before_filter :require_login, :check_project_privacy, :only => [:watch, :unwatch]
|
||||
before_filter :authorize, :only => [:new, :destroy]
|
||||
|
||||
verify :method => :post,
|
||||
:only => [ :watch, :unwatch ],
|
||||
:render => { :nothing => true, :status => :method_not_allowed }
|
||||
|
||||
def watch
|
||||
if @watched.respond_to?(:visible?) && !@watched.visible?(User.current)
|
||||
render_403
|
||||
@@ -33,29 +37,13 @@ class WatchersController < ApplicationController
|
||||
end
|
||||
|
||||
def new
|
||||
respond_to do |format|
|
||||
format.js do
|
||||
render :update do |page|
|
||||
page.replace_html 'ajax-modal', :partial => 'watchers/new', :locals => {:watched => @watched}
|
||||
page << "showModal('ajax-modal', '400px');"
|
||||
page << "$('ajax-modal').addClassName('new-watcher');"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def create
|
||||
if params[:watcher].is_a?(Hash) && request.post?
|
||||
user_ids = params[:watcher][:user_ids] || [params[:watcher][:user_id]]
|
||||
user_ids.each do |user_id|
|
||||
Watcher.create(:watchable => @watched, :user_id => user_id)
|
||||
end
|
||||
end
|
||||
@watcher = Watcher.new(params[:watcher])
|
||||
@watcher.watchable = @watched
|
||||
@watcher.save if request.post?
|
||||
respond_to do |format|
|
||||
format.html { redirect_to :back }
|
||||
format.js do
|
||||
render :update do |page|
|
||||
page.replace_html 'ajax-modal', :partial => 'watchers/new', :locals => {:watched => @watched}
|
||||
page.replace_html 'watchers', :partial => 'watchers/watchers', :locals => {:watched => @watched}
|
||||
end
|
||||
end
|
||||
@@ -64,25 +52,6 @@ class WatchersController < ApplicationController
|
||||
render :text => 'Watcher added.', :layout => true
|
||||
end
|
||||
|
||||
def append
|
||||
if params[:watcher].is_a?(Hash)
|
||||
user_ids = params[:watcher][:user_ids] || [params[:watcher][:user_id]]
|
||||
users = User.active.find_all_by_id(user_ids)
|
||||
respond_to do |format|
|
||||
format.js do
|
||||
render :update do |page|
|
||||
users.each do |user|
|
||||
page.select("#issue_watcher_user_ids_#{user.id}").each do |item|
|
||||
page.remove item
|
||||
end
|
||||
end
|
||||
page.insert_html :bottom, 'watchers_inputs', :text => watchers_checkboxes(nil, users, true)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
@watched.set_watcher(User.find(params[:user_id]), false) if request.post?
|
||||
respond_to do |format|
|
||||
@@ -95,24 +64,12 @@ class WatchersController < ApplicationController
|
||||
end
|
||||
end
|
||||
|
||||
def autocomplete_for_user
|
||||
@users = User.active.like(params[:q]).find(:all, :limit => 100)
|
||||
if @watched
|
||||
@users -= @watched.watcher_users
|
||||
end
|
||||
render :layout => false
|
||||
end
|
||||
|
||||
private
|
||||
def find_project
|
||||
if params[:object_type] && params[:object_id]
|
||||
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
|
||||
elsif params[:project_id]
|
||||
@project = Project.visible.find(params[:project_id])
|
||||
end
|
||||
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
|
||||
|
||||
@@ -73,7 +73,7 @@ class WikiController < ApplicationController
|
||||
@content = @page.content_for_version(params[:version])
|
||||
if User.current.allowed_to?(:export_wiki_pages, @project)
|
||||
if params[:format] == 'pdf'
|
||||
send_data(wiki_page_to_pdf(@page, @project), :type => 'application/pdf', :filename => "#{@page.title}.pdf")
|
||||
send_data(wiki_to_pdf(@page, @project), :type => 'application/pdf', :filename => "#{@page.title}.pdf")
|
||||
return
|
||||
elsif params[:format] == 'html'
|
||||
export = render_to_string :action => 'export', :layout => false
|
||||
@@ -95,12 +95,7 @@ class WikiController < ApplicationController
|
||||
# edit an existing page or a new one
|
||||
def edit
|
||||
return render_403 unless editable?
|
||||
if @page.new_record?
|
||||
@page.content = WikiContent.new(:page => @page)
|
||||
if params[:parent].present?
|
||||
@page.parent = @page.wiki.find_page(params[:parent].to_s)
|
||||
end
|
||||
end
|
||||
@page.content = WikiContent.new(:page => @page) if @page.new_record?
|
||||
|
||||
@content = @page.content_for_version(params[:version])
|
||||
@content.text = initial_page_content(@page) if @content.text.blank?
|
||||
@@ -118,11 +113,11 @@ class WikiController < ApplicationController
|
||||
end
|
||||
end
|
||||
|
||||
verify :method => :put, :only => :update, :render => {:nothing => true, :status => :method_not_allowed }
|
||||
# Creates a new page or updates an existing one
|
||||
def update
|
||||
return render_403 unless editable?
|
||||
@page.content = WikiContent.new(:page => @page) if @page.new_record?
|
||||
@page.safe_attributes = params[:wiki_page]
|
||||
|
||||
@content = @page.content_for_version(params[:version])
|
||||
@content.text = initial_page_content(@page) if @content.text.blank?
|
||||
@@ -132,12 +127,11 @@ class WikiController < ApplicationController
|
||||
if !@page.new_record? && params[:content].present? && @content.text == params[:content][:text]
|
||||
attachments = Attachment.attach_files(@page, params[:attachments])
|
||||
render_attachment_warning_if_needed(@page)
|
||||
# don't save content if text wasn't changed
|
||||
@page.save
|
||||
# don't save if text wasn't changed
|
||||
redirect_to :action => 'show', :project_id => @project, :id => @page.title
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
@content.comments = params[:content][:comments]
|
||||
@text = params[:content][:text]
|
||||
if params[:section].present? && Redmine::WikiFormatting.supports_section_edit?
|
||||
@@ -149,8 +143,8 @@ class WikiController < ApplicationController
|
||||
@content.text = @text
|
||||
end
|
||||
@content.author = User.current
|
||||
@page.content = @content
|
||||
if @page.save
|
||||
# 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)
|
||||
attachments = Attachment.attach_files(@page, params[:attachments])
|
||||
render_attachment_warning_if_needed(@page)
|
||||
call_hook(:controller_wiki_edit_after_save, { :params => params, :page => @page})
|
||||
@@ -177,6 +171,7 @@ class WikiController < ApplicationController
|
||||
end
|
||||
end
|
||||
|
||||
verify :method => :post, :only => :protect, :redirect_to => { :action => :show }
|
||||
def protect
|
||||
@page.update_attribute :protected, params[:protected]
|
||||
redirect_to :action => 'show', :project_id => @project, :id => @page.title
|
||||
@@ -206,6 +201,7 @@ class WikiController < ApplicationController
|
||||
render_404 unless @annotate
|
||||
end
|
||||
|
||||
verify :method => :delete, :only => [:destroy], :redirect_to => { :action => :show }
|
||||
# Removes a wiki page and its history
|
||||
# Children can be either set as root pages, removed or reassigned to another parent page
|
||||
def destroy
|
||||
@@ -235,17 +231,14 @@ class WikiController < ApplicationController
|
||||
redirect_to :action => 'index', :project_id => @project
|
||||
end
|
||||
|
||||
# Export wiki to a single pdf or html file
|
||||
# Export wiki to a single html file
|
||||
def export
|
||||
@pages = @wiki.pages.all(:order => 'title', :include => [:content, :attachments], :limit => 75)
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
export = render_to_string :action => 'export_multiple', :layout => false
|
||||
send_data(export, :type => 'text/html', :filename => "wiki.html")
|
||||
}
|
||||
format.pdf {
|
||||
send_data(wiki_pages_to_pdf(@pages, @project), :type => 'application/pdf', :filename => "#{@project.identifier}.pdf")
|
||||
}
|
||||
if User.current.allowed_to?(:export_wiki_pages, @project)
|
||||
@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")
|
||||
else
|
||||
redirect_to :action => 'show', :project_id => @project, :id => nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ class WikisController < ApplicationController
|
||||
# Create or update a project's wiki
|
||||
def edit
|
||||
@wiki = @project.wiki || Wiki.new(:project => @project)
|
||||
@wiki.safe_attributes = params[:wiki]
|
||||
@wiki.attributes = params[:wiki]
|
||||
@wiki.save if request.post?
|
||||
render(:update) {|page| page.replace_html "tab-content-wiki", :partial => 'projects/settings/wiki'}
|
||||
end
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2011 Jean-Philippe Lang
|
||||
#
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2011 Jean-Philippe Lang
|
||||
#
|
||||
@@ -20,6 +18,6 @@
|
||||
module AdminHelper
|
||||
def project_status_options_for_select(selected)
|
||||
options_for_select([[l(:label_all), ''],
|
||||
[l(:status_active), '1']], selected.to_s)
|
||||
[l(:status_active), 1]], selected)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -85,8 +85,8 @@ module ApplicationHelper
|
||||
s = link_to "#{h(issue.tracker)} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue},
|
||||
:class => issue.css_classes,
|
||||
:title => title
|
||||
s << h(": #{subject}") if subject
|
||||
s = h("#{issue.project} - ") + s if options[:project]
|
||||
s << ": #{h subject}" if subject
|
||||
s = "#{h issue.project} - " + s if options[:project]
|
||||
s
|
||||
end
|
||||
|
||||
@@ -97,29 +97,21 @@ module ApplicationHelper
|
||||
def link_to_attachment(attachment, options={})
|
||||
text = options.delete(:text) || attachment.filename
|
||||
action = options.delete(:download) ? 'download' : 'show'
|
||||
opt_only_path = {}
|
||||
opt_only_path[:only_path] = (options[:only_path] == false ? false : true)
|
||||
options.delete(:only_path)
|
||||
link_to(h(text),
|
||||
{:controller => 'attachments', :action => action,
|
||||
:id => attachment, :filename => attachment.filename}.merge(opt_only_path),
|
||||
:id => attachment, :filename => attachment.filename },
|
||||
options)
|
||||
end
|
||||
|
||||
# Generates a link to a SCM revision
|
||||
# Options:
|
||||
# * :text - Link text (default to the formatted revision)
|
||||
def link_to_revision(revision, repository, options={})
|
||||
if repository.is_a?(Project)
|
||||
repository = repository.repository
|
||||
end
|
||||
def link_to_revision(revision, project, options={})
|
||||
text = options.delete(:text) || format_revision(revision)
|
||||
rev = revision.respond_to?(:identifier) ? revision.identifier : revision
|
||||
link_to(
|
||||
h(text),
|
||||
{:controller => 'repositories', :action => 'revision', :id => repository.project, :repository_id => repository.identifier_param, :rev => rev},
|
||||
:title => l(:label_revision_id, format_revision(revision))
|
||||
)
|
||||
|
||||
link_to(h(text), {:controller => 'repositories', :action => 'revision', :id => project, :rev => rev},
|
||||
:title => l(:label_revision_id, format_revision(revision)))
|
||||
end
|
||||
|
||||
# Generates a link to a message
|
||||
@@ -182,8 +174,7 @@ module ApplicationHelper
|
||||
end
|
||||
|
||||
def format_activity_description(text)
|
||||
h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')
|
||||
).gsub(/[\r\n]+/, "<br />").html_safe
|
||||
h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')).gsub(/[\r\n]+/, "<br />")
|
||||
end
|
||||
|
||||
def format_version_name(version)
|
||||
@@ -220,7 +211,7 @@ module ApplicationHelper
|
||||
def render_flash_messages
|
||||
s = ''
|
||||
flash.each do |k,v|
|
||||
s << (content_tag('div', v.html_safe, :class => "flash #{k}"))
|
||||
s << content_tag('div', v, :class => "flash #{k}")
|
||||
end
|
||||
s.html_safe
|
||||
end
|
||||
@@ -253,7 +244,7 @@ module ApplicationHelper
|
||||
def project_tree_options_for_select(projects, options = {})
|
||||
s = ''
|
||||
project_tree(projects) do |project, level|
|
||||
name_prefix = (level > 0 ? (' ' * 2 * level + '» ').html_safe : '')
|
||||
name_prefix = (level > 0 ? (' ' * 2 * level + '» ') : '')
|
||||
tag_options = {:value => project.id}
|
||||
if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project))
|
||||
tag_options[:selected] = 'selected'
|
||||
@@ -308,9 +299,6 @@ module ApplicationHelper
|
||||
# Returns a string for users/groups option tags
|
||||
def principals_options_for_select(collection, selected=nil)
|
||||
s = ''
|
||||
if collection.include?(User.current)
|
||||
s << content_tag('option', "<< #{l(:label_me)} >>".html_safe, :value => User.current.id)
|
||||
end
|
||||
groups = ''
|
||||
collection.sort.each do |element|
|
||||
selected_attribute = ' selected="selected"' if option_value_selected?(element, selected)
|
||||
@@ -337,10 +325,6 @@ module ApplicationHelper
|
||||
end
|
||||
end
|
||||
|
||||
def anchor(text)
|
||||
text.to_s.gsub(' ', '_')
|
||||
end
|
||||
|
||||
def html_hours(text)
|
||||
text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>').html_safe
|
||||
end
|
||||
@@ -358,12 +342,6 @@ module ApplicationHelper
|
||||
end
|
||||
end
|
||||
|
||||
def syntax_highlight_lines(name, content)
|
||||
lines = []
|
||||
syntax_highlight(name, content).each_line { |line| lines << line }
|
||||
lines
|
||||
end
|
||||
|
||||
def syntax_highlight(name, content)
|
||||
Redmine::SyntaxHighlighting.highlight_by_filename(content, name)
|
||||
end
|
||||
@@ -479,8 +457,8 @@ module ApplicationHelper
|
||||
css << 'theme-' + theme.name
|
||||
end
|
||||
|
||||
css << 'controller-' + controller_name
|
||||
css << 'action-' + action_name
|
||||
css << 'controller-' + params[:controller]
|
||||
css << 'action-' + params[:action]
|
||||
css.join(' ')
|
||||
end
|
||||
|
||||
@@ -512,22 +490,18 @@ module ApplicationHelper
|
||||
text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr)
|
||||
|
||||
@parsed_headings = []
|
||||
@heading_anchors = {}
|
||||
@current_section = 0 if options[:edit_section_links]
|
||||
|
||||
parse_sections(text, project, obj, attr, only_path, options)
|
||||
text = parse_non_pre_blocks(text) do |text|
|
||||
[:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links, :parse_macros].each do |method_name|
|
||||
[:parse_sections, :parse_inline_attachments, :parse_wiki_links, :parse_redmine_links, :parse_macros, :parse_headings].each do |method_name|
|
||||
send method_name, text, project, obj, attr, only_path, options
|
||||
end
|
||||
end
|
||||
parse_headings(text, project, obj, attr, only_path, options)
|
||||
|
||||
if @parsed_headings.any?
|
||||
replace_toc(text, @parsed_headings)
|
||||
end
|
||||
|
||||
text.html_safe
|
||||
text
|
||||
end
|
||||
|
||||
def parse_non_pre_blocks(text)
|
||||
@@ -556,7 +530,7 @@ module ApplicationHelper
|
||||
while tag = tags.pop
|
||||
parsed << "</#{tag}>"
|
||||
end
|
||||
parsed
|
||||
parsed.html_safe
|
||||
end
|
||||
|
||||
def parse_inline_attachments(text, project, obj, attr, only_path, options)
|
||||
@@ -573,9 +547,9 @@ module ApplicationHelper
|
||||
if !desc.blank? && alttext.blank?
|
||||
alt = " title=\"#{desc}\" alt=\"#{desc}\""
|
||||
end
|
||||
"src=\"#{image_url}\"#{alt}"
|
||||
"src=\"#{image_url}\"#{alt}".html_safe
|
||||
else
|
||||
m
|
||||
m.html_safe
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -619,18 +593,16 @@ module ApplicationHelper
|
||||
when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export
|
||||
else
|
||||
wiki_page_id = page.present? ? Wiki.titleize(page) : nil
|
||||
parent = wiki_page.nil? && obj.is_a?(WikiContent) && obj.page && project == link_project ? obj.page.title : nil
|
||||
url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project,
|
||||
:id => wiki_page_id, :anchor => anchor, :parent => parent)
|
||||
url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project, :id => wiki_page_id, :anchor => anchor)
|
||||
end
|
||||
end
|
||||
link_to(title.present? ? title.html_safe : h(page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
|
||||
else
|
||||
# project or wiki doesn't exist
|
||||
all
|
||||
all.html_safe
|
||||
end
|
||||
else
|
||||
all
|
||||
all.html_safe
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -668,35 +640,26 @@ module ApplicationHelper
|
||||
# identifier:version:1.0.0
|
||||
# identifier:source:some/file
|
||||
def parse_redmine_links(text, project, obj, attr, only_path, options)
|
||||
text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-_]+):)?(attachment|document|version|forum|news|message|project|commit|source|export)?(((#)|((([a-z0-9\-]+)\|)?(r)))((\d+)((#note)?-(\d+))?)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]][^A-Za-z0-9_/])|,|\s|\]|<|$)}) do |m|
|
||||
leading, esc, project_prefix, project_identifier, prefix, repo_prefix, repo_identifier, sep, identifier, comment_suffix, comment_id = $1, $2, $3, $4, $5, $10, $11, $8 || $12 || $18, $14 || $19, $15, $17
|
||||
text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-]+):)?(attachment|document|version|forum|news|commit|source|export|message|project)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|,|\s|\]|<|$)}) do |m|
|
||||
leading, esc, project_prefix, project_identifier, prefix, sep, identifier = $1, $2, $3, $4, $5, $7 || $9, $8 || $10
|
||||
link = nil
|
||||
if project_identifier
|
||||
project = Project.visible.find_by_identifier(project_identifier)
|
||||
end
|
||||
if esc.nil?
|
||||
if prefix.nil? && sep == 'r'
|
||||
if project
|
||||
repository = nil
|
||||
if repo_identifier
|
||||
repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
|
||||
else
|
||||
repository = project.repository
|
||||
end
|
||||
# project.changesets.visible raises an SQL error because of a double join on repositories
|
||||
if repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(repository.id, identifier))
|
||||
link = link_to(h("#{project_prefix}#{repo_prefix}r#{identifier}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :repository_id => repository.identifier_param, :rev => changeset.revision},
|
||||
:class => 'changeset',
|
||||
:title => truncate_single_line(changeset.comments, :length => 100))
|
||||
end
|
||||
# project.changesets.visible raises an SQL error because of a double join on repositories
|
||||
if project && project.repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(project.repository.id, identifier))
|
||||
link = link_to(h("#{project_prefix}r#{identifier}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
|
||||
:class => 'changeset',
|
||||
:title => truncate_single_line(changeset.comments, :length => 100))
|
||||
end
|
||||
elsif sep == '#'
|
||||
oid = identifier.to_i
|
||||
case prefix
|
||||
when nil
|
||||
if issue = Issue.visible.find_by_id(oid, :include => :status)
|
||||
anchor = comment_id ? "note-#{comment_id}" : nil
|
||||
link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid, :anchor => anchor},
|
||||
link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
|
||||
:class => issue.css_classes,
|
||||
:title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
|
||||
end
|
||||
@@ -753,34 +716,22 @@ module ApplicationHelper
|
||||
link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
|
||||
:class => 'news'
|
||||
end
|
||||
when 'commit', 'source', 'export'
|
||||
if project
|
||||
repository = nil
|
||||
if name =~ %r{^(([a-z0-9\-]+)\|)(.+)$}
|
||||
repo_prefix, repo_identifier, name = $1, $2, $3
|
||||
repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
|
||||
else
|
||||
repository = project.repository
|
||||
end
|
||||
if prefix == 'commit'
|
||||
if repository && (changeset = Changeset.visible.find(:first, :conditions => ["repository_id = ? AND scmid LIKE ?", repository.id, "#{name}%"]))
|
||||
link = link_to h("#{project_prefix}#{repo_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :repository_id => repository.identifier_param, :rev => changeset.identifier},
|
||||
:class => 'changeset',
|
||||
:title => truncate_single_line(h(changeset.comments), :length => 100)
|
||||
end
|
||||
else
|
||||
if repository && User.current.allowed_to?(:browse_repository, project)
|
||||
name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
|
||||
path, rev, anchor = $1, $3, $5
|
||||
link = link_to h("#{project_prefix}#{prefix}:#{repo_prefix}#{name}"), {:controller => 'repositories', :action => 'entry', :id => project, :repository_id => repository.identifier_param,
|
||||
:path => to_path_param(path),
|
||||
:rev => rev,
|
||||
:anchor => anchor,
|
||||
:format => (prefix == 'export' ? 'raw' : nil)},
|
||||
:class => (prefix == 'export' ? 'source download' : 'source')
|
||||
end
|
||||
end
|
||||
repo_prefix = nil
|
||||
when 'commit'
|
||||
if project && project.repository && (changeset = Changeset.visible.find(:first, :conditions => ["repository_id = ? AND scmid LIKE ?", project.repository.id, "#{name}%"]))
|
||||
link = link_to h("#{project_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.identifier},
|
||||
:class => 'changeset',
|
||||
:title => truncate_single_line(h(changeset.comments), :length => 100)
|
||||
end
|
||||
when 'source', 'export'
|
||||
if project && project.repository && User.current.allowed_to?(:browse_repository, project)
|
||||
name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
|
||||
path, rev, anchor = $1, $3, $5
|
||||
link = link_to h("#{project_prefix}#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project,
|
||||
:path => to_path_param(path),
|
||||
:rev => rev,
|
||||
:anchor => anchor,
|
||||
:format => (prefix == 'export' ? 'raw' : nil)},
|
||||
:class => (prefix == 'export' ? 'source download' : 'source')
|
||||
end
|
||||
when 'attachment'
|
||||
attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
|
||||
@@ -795,24 +746,23 @@ module ApplicationHelper
|
||||
end
|
||||
end
|
||||
end
|
||||
(leading + (link || "#{project_prefix}#{prefix}#{repo_prefix}#{sep}#{identifier}#{comment_suffix}"))
|
||||
(leading + (link || "#{project_prefix}#{prefix}#{sep}#{identifier}")).html_safe
|
||||
end
|
||||
end
|
||||
|
||||
HEADING_RE = /(<h(\d)( [^>]+)?>(.+?)<\/h(\d)>)/i unless const_defined?(:HEADING_RE)
|
||||
HEADING_RE = /(<h(1|2|3|4)( [^>]+)?>(.+?)<\/h(1|2|3|4)>)/i unless const_defined?(:HEADING_RE)
|
||||
|
||||
def parse_sections(text, project, obj, attr, only_path, options)
|
||||
return unless options[:edit_section_links]
|
||||
text.gsub!(HEADING_RE) do
|
||||
heading = $1
|
||||
@current_section += 1
|
||||
if @current_section > 1
|
||||
content_tag('div',
|
||||
link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)),
|
||||
:class => 'contextual',
|
||||
:title => l(:button_edit_section)) + heading.html_safe
|
||||
:title => l(:button_edit_section)) + $1
|
||||
else
|
||||
heading
|
||||
$1
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -828,11 +778,6 @@ module ApplicationHelper
|
||||
anchor = sanitize_anchor_name(item)
|
||||
# used for single-file wiki export
|
||||
anchor = "#{obj.page.title}_#{anchor}" if options[:wiki_links] == :anchor && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version))
|
||||
@heading_anchors[anchor] ||= 0
|
||||
idx = (@heading_anchors[anchor] += 1)
|
||||
if idx > 1
|
||||
anchor = "#{anchor}-#{idx}"
|
||||
end
|
||||
@parsed_headings << [level, anchor, item]
|
||||
"<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">¶</a></h#{level}>"
|
||||
end
|
||||
@@ -870,8 +815,6 @@ module ApplicationHelper
|
||||
# Renders the TOC with given headings
|
||||
def replace_toc(text, headings)
|
||||
text.gsub!(TOC_RE) do
|
||||
# Keep only the 4 first levels
|
||||
headings = headings.select{|level, anchor, item| level <= 4}
|
||||
if headings.empty?
|
||||
''
|
||||
else
|
||||
@@ -920,54 +863,25 @@ module ApplicationHelper
|
||||
end
|
||||
|
||||
def labelled_tabular_form_for(*args, &proc)
|
||||
ActiveSupport::Deprecation.warn "ApplicationHelper#labelled_tabular_form_for is deprecated and will be removed in Redmine 1.5. Use #labelled_form_for instead."
|
||||
args << {} unless args.last.is_a?(Hash)
|
||||
options = args.last
|
||||
options[:html] ||= {}
|
||||
options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
|
||||
options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
|
||||
options.merge!({:builder => TabularFormBuilder})
|
||||
form_for(*args, &proc)
|
||||
end
|
||||
|
||||
def labelled_form_for(*args, &proc)
|
||||
args << {} unless args.last.is_a?(Hash)
|
||||
options = args.last
|
||||
options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
|
||||
options.merge!({:builder => TabularFormBuilder})
|
||||
form_for(*args, &proc)
|
||||
end
|
||||
|
||||
def labelled_fields_for(*args, &proc)
|
||||
args << {} unless args.last.is_a?(Hash)
|
||||
options = args.last
|
||||
options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
|
||||
fields_for(*args, &proc)
|
||||
end
|
||||
|
||||
def labelled_remote_form_for(*args, &proc)
|
||||
args << {} unless args.last.is_a?(Hash)
|
||||
options = args.last
|
||||
options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
|
||||
remote_form_for(*args, &proc)
|
||||
end
|
||||
|
||||
def error_messages_for(*objects)
|
||||
html = ""
|
||||
objects = objects.map {|o| o.is_a?(String) ? instance_variable_get("@#{o}") : o}.compact
|
||||
errors = objects.map {|o| o.errors.full_messages}.flatten
|
||||
if errors.any?
|
||||
html << "<div id='errorExplanation'><ul>\n"
|
||||
errors.each do |error|
|
||||
html << "<li>#{h error}</li>\n"
|
||||
end
|
||||
html << "</ul></div>\n"
|
||||
end
|
||||
html.html_safe
|
||||
end
|
||||
|
||||
def back_url_hidden_field_tag
|
||||
back_url = params[:back_url] || request.env['HTTP_REFERER']
|
||||
back_url = CGI.unescape(back_url.to_s)
|
||||
hidden_field_tag('back_url', CGI.escape(back_url), :id => nil) unless back_url.blank?
|
||||
hidden_field_tag('back_url', CGI.escape(back_url)) unless back_url.blank?
|
||||
end
|
||||
|
||||
def check_all_links(form_name)
|
||||
@@ -1014,6 +928,22 @@ module ApplicationHelper
|
||||
javascript_tag "new ContextMenu('#{ url_for(url) }')"
|
||||
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 h(name), url, options
|
||||
end
|
||||
|
||||
def calendar_for(field_id)
|
||||
include_calendar_headers_tags
|
||||
image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2011 Jean-Philippe Lang
|
||||
#
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2011 Jean-Philippe Lang
|
||||
#
|
||||
@@ -18,7 +16,4 @@
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
module AuthSourcesHelper
|
||||
def auth_source_partial_name(auth_source)
|
||||
"form_#{auth_source.class.name.underscore}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2011 Jean-Philippe Lang
|
||||
#
|
||||
|
||||
@@ -1,22 +1,3 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2011 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
module CalendarsHelper
|
||||
def link_to_previous_month(year, month, options={})
|
||||
target_year, target_month = if month == 1
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2012 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 ContextMenusHelper
|
||||
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 h(name), url, options
|
||||
end
|
||||
|
||||
def bulk_update_custom_field_context_menu_link(field, text, value)
|
||||
context_menu_link h(text),
|
||||
{:controller => 'issues', :action => 'bulk_update', :ids => @issues.collect(&:id), :issue => {'custom_field_values' => {field.id => value}}, :back_url => @back},
|
||||
:method => :post,
|
||||
:selected => (@issue && @issue.custom_field_value(field) == value)
|
||||
end
|
||||
end
|
||||
@@ -1,7 +1,5 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2012 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2011 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
@@ -36,7 +34,6 @@ module CustomFieldsHelper
|
||||
def custom_field_tag(name, custom_value)
|
||||
custom_field = custom_value.custom_field
|
||||
field_name = "#{name}[custom_field_values][#{custom_field.id}]"
|
||||
field_name << "[]" if custom_field.multiple?
|
||||
field_id = "#{name}_custom_field_values_#{custom_field.id}"
|
||||
|
||||
field_format = Redmine::CustomFieldFormat.find_by_name(custom_field.field_format)
|
||||
@@ -49,22 +46,10 @@ module CustomFieldsHelper
|
||||
when "bool"
|
||||
hidden_field_tag(field_name, '0') + check_box_tag(field_name, '1', custom_value.true?, :id => field_id)
|
||||
when "list"
|
||||
blank_option = ''
|
||||
unless custom_field.multiple?
|
||||
if custom_field.is_required?
|
||||
unless custom_field.default_value.present?
|
||||
blank_option = "<option value=\"\">--- #{l(:actionview_instancetag_blank_option)} ---</option>"
|
||||
end
|
||||
else
|
||||
blank_option = '<option></option>'
|
||||
end
|
||||
end
|
||||
s = select_tag(field_name, blank_option.html_safe + options_for_select(custom_field.possible_values_options(custom_value.customized), custom_value.value),
|
||||
:id => field_id, :multiple => custom_field.multiple?)
|
||||
if custom_field.multiple?
|
||||
s << hidden_field_tag(field_name, '')
|
||||
end
|
||||
s
|
||||
blank_option = custom_field.is_required? ?
|
||||
(custom_field.default_value.blank? ? "<option value=\"\">--- #{l(:actionview_instancetag_blank_option)} ---</option>" : '') :
|
||||
'<option></option>'
|
||||
select_tag(field_name, blank_option + options_for_select(custom_field.possible_values_options(custom_value.customized), custom_value.value), :id => field_id)
|
||||
else
|
||||
text_field_tag(field_name, custom_value.value, :id => field_id)
|
||||
end
|
||||
@@ -74,7 +59,8 @@ module CustomFieldsHelper
|
||||
def custom_field_label_tag(name, custom_value)
|
||||
content_tag "label", h(custom_value.custom_field.name) +
|
||||
(custom_value.custom_field.is_required? ? " <span class=\"required\">*</span>".html_safe : ""),
|
||||
:for => "#{name}_custom_field_values_#{custom_value.custom_field.id}"
|
||||
:for => "#{name}_custom_field_values_#{custom_value.custom_field.id}",
|
||||
:class => (custom_value.errors.empty? ? nil : "error" )
|
||||
end
|
||||
|
||||
# Return custom field tag with its label tag
|
||||
@@ -84,7 +70,6 @@ module CustomFieldsHelper
|
||||
|
||||
def custom_field_tag_for_bulk_edit(name, custom_field, projects=nil)
|
||||
field_name = "#{name}[custom_field_values][#{custom_field.id}]"
|
||||
field_name << "[]" if custom_field.multiple?
|
||||
field_id = "#{name}_custom_field_values_#{custom_field.id}"
|
||||
field_format = Redmine::CustomFieldFormat.find_by_name(custom_field.field_format)
|
||||
case field_format.try(:edit_as)
|
||||
@@ -98,12 +83,7 @@ module CustomFieldsHelper
|
||||
[l(:general_text_yes), '1'],
|
||||
[l(:general_text_no), '0']]), :id => field_id)
|
||||
when "list"
|
||||
options = []
|
||||
options << [l(:label_no_change_option), ''] unless custom_field.multiple?
|
||||
options << [l(:label_none), '__none__'] unless custom_field.is_required?
|
||||
options += custom_field.possible_values_options(projects)
|
||||
select_tag(field_name, options_for_select(options),
|
||||
:id => field_id, :multiple => custom_field.multiple?)
|
||||
select_tag(field_name, options_for_select([[l(:label_no_change_option), '']] + custom_field.possible_values_options(projects)), :id => field_id)
|
||||
else
|
||||
text_field_tag(field_name, '', :id => field_id)
|
||||
end
|
||||
@@ -117,11 +97,7 @@ module CustomFieldsHelper
|
||||
|
||||
# Return a string used to display a custom value
|
||||
def format_value(value, field_format)
|
||||
if value.is_a?(Array)
|
||||
value.collect {|v| format_value(v, field_format)}.compact.sort.join(', ')
|
||||
else
|
||||
Redmine::CustomFieldFormat.format_value(value, field_format)
|
||||
end
|
||||
Redmine::CustomFieldFormat.format_value(value, field_format) # Proxy
|
||||
end
|
||||
|
||||
# Return an array of custom field formats which can be used in select_tag
|
||||
@@ -133,18 +109,8 @@ module CustomFieldsHelper
|
||||
def render_api_custom_values(custom_values, api)
|
||||
api.array :custom_fields do
|
||||
custom_values.each do |custom_value|
|
||||
attrs = {:id => custom_value.custom_field_id, :name => custom_value.custom_field.name}
|
||||
attrs.merge!(:multiple => true) if custom_value.custom_field.multiple?
|
||||
api.custom_field attrs do
|
||||
if custom_value.value.is_a?(Array)
|
||||
api.array :value do
|
||||
custom_value.value.each do |value|
|
||||
api.value value unless value.blank?
|
||||
end
|
||||
end
|
||||
else
|
||||
api.value custom_value.value
|
||||
end
|
||||
api.custom_field :id => custom_value.custom_field_id, :name => custom_value.custom_field.name do
|
||||
api.value custom_value.value
|
||||
end
|
||||
end
|
||||
end unless custom_values.empty?
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2011 Jean-Philippe Lang
|
||||
#
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2011 Jean-Philippe Lang
|
||||
#
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2011 Jean-Philippe Lang
|
||||
#
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2011 Jean-Philippe Lang
|
||||
#
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2011 Jean-Philippe Lang
|
||||
#
|
||||
|
||||
2
app/helpers/issue_moves_helper.rb
Normal file
2
app/helpers/issue_moves_helper.rb
Normal file
@@ -0,0 +1,2 @@
|
||||
module IssueMovesHelper
|
||||
end
|
||||
@@ -1,5 +1,3 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2011 Jean-Philippe Lang
|
||||
#
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2011 Jean-Philippe Lang
|
||||
#
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2011 Jean-Philippe Lang
|
||||
#
|
||||
@@ -48,13 +46,13 @@ module IssuesHelper
|
||||
@cached_label_priority ||= l(:field_priority)
|
||||
@cached_label_project ||= l(:field_project)
|
||||
|
||||
link_to_issue(issue) + "<br /><br />".html_safe +
|
||||
"<strong>#{@cached_label_project}</strong>: #{link_to_project(issue.project)}<br />".html_safe +
|
||||
"<strong>#{@cached_label_status}</strong>: #{h(issue.status.name)}<br />".html_safe +
|
||||
"<strong>#{@cached_label_start_date}</strong>: #{format_date(issue.start_date)}<br />".html_safe +
|
||||
"<strong>#{@cached_label_due_date}</strong>: #{format_date(issue.due_date)}<br />".html_safe +
|
||||
"<strong>#{@cached_label_assigned_to}</strong>: #{h(issue.assigned_to)}<br />".html_safe +
|
||||
"<strong>#{@cached_label_priority}</strong>: #{h(issue.priority.name)}".html_safe
|
||||
(link_to_issue(issue) + "<br /><br />" +
|
||||
"<strong>#{@cached_label_project}</strong>: #{link_to_project(issue.project)}<br />" +
|
||||
"<strong>#{@cached_label_status}</strong>: #{h(issue.status.name)}<br />" +
|
||||
"<strong>#{@cached_label_start_date}</strong>: #{format_date(issue.start_date)}<br />" +
|
||||
"<strong>#{@cached_label_due_date}</strong>: #{format_date(issue.due_date)}<br />" +
|
||||
"<strong>#{@cached_label_assigned_to}</strong>: #{h(issue.assigned_to)}<br />" +
|
||||
"<strong>#{@cached_label_priority}</strong>: #{h(issue.priority.name)}").html_safe
|
||||
end
|
||||
|
||||
def issue_heading(issue)
|
||||
@@ -88,7 +86,7 @@ module IssuesHelper
|
||||
content_tag('td', progress_bar(child.done_ratio, :width => '80px')),
|
||||
:class => "issue issue-#{child.id} hascontextmenu #{level > 0 ? "idnt idnt-#{level}" : nil}")
|
||||
end
|
||||
s << '</table></form>'
|
||||
s << '</form></table>'
|
||||
s.html_safe
|
||||
end
|
||||
|
||||
@@ -131,11 +129,14 @@ module IssuesHelper
|
||||
|
||||
def sidebar_queries
|
||||
unless @sidebar_queries
|
||||
@sidebar_queries = Query.visible.all(
|
||||
:order => "#{Query.table_name}.name ASC",
|
||||
# Project specific queries and global queries
|
||||
:conditions => (@project.nil? ? ["project_id IS NULL"] : ["project_id IS NULL OR project_id = ?", @project.id])
|
||||
)
|
||||
# User can see public queries and his own queries
|
||||
visible = ARCondition.new(["is_public = ? OR user_id = ?", true, (User.current.logged? ? User.current.id : 0)])
|
||||
# Project specific queries and global queries
|
||||
visible << (@project.nil? ? ["project_id IS NULL"] : ["project_id IS NULL OR project_id = ?", @project.id])
|
||||
@sidebar_queries = Query.find(:all,
|
||||
:select => 'id, name, is_public',
|
||||
:order => "name ASC",
|
||||
:conditions => visible.conditions)
|
||||
end
|
||||
@sidebar_queries
|
||||
end
|
||||
@@ -146,14 +147,12 @@ module IssuesHelper
|
||||
|
||||
content_tag('h3', h(title)) +
|
||||
queries.collect {|query|
|
||||
css = 'query'
|
||||
css << ' selected' if query == @query
|
||||
link_to(h(query.name), url_params.merge(:query_id => query), :class => css)
|
||||
}.join('<br />').html_safe
|
||||
link_to(h(query.name), url_params.merge(:query_id => query))
|
||||
}.join('<br />')
|
||||
end
|
||||
|
||||
def render_sidebar_queries
|
||||
out = ''.html_safe
|
||||
out = ''
|
||||
queries = sidebar_queries.select {|q| !q.is_public?}
|
||||
out << query_links(l(:label_my_queries), queries) if queries.any?
|
||||
queries = sidebar_queries.select {|q| q.is_public?}
|
||||
@@ -161,76 +160,36 @@ module IssuesHelper
|
||||
out
|
||||
end
|
||||
|
||||
# Returns the textual representation of a journal details
|
||||
# as an array of strings
|
||||
def details_to_strings(details, no_html=false, options={})
|
||||
options[:only_path] = (options[:only_path] == false ? false : true)
|
||||
strings = []
|
||||
values_by_field = {}
|
||||
details.each do |detail|
|
||||
if detail.property == 'cf'
|
||||
field_id = detail.prop_key
|
||||
field = CustomField.find_by_id(field_id)
|
||||
if field && field.multiple?
|
||||
values_by_field[field_id] ||= {:added => [], :deleted => []}
|
||||
if detail.old_value
|
||||
values_by_field[field_id][:deleted] << detail.old_value
|
||||
end
|
||||
if detail.value
|
||||
values_by_field[field_id][:added] << detail.value
|
||||
end
|
||||
next
|
||||
end
|
||||
end
|
||||
strings << show_detail(detail, no_html, options)
|
||||
end
|
||||
values_by_field.each do |field_id, changes|
|
||||
detail = JournalDetail.new(:property => 'cf', :prop_key => field_id)
|
||||
if changes[:added].any?
|
||||
detail.value = changes[:added]
|
||||
strings << show_detail(detail, no_html, options)
|
||||
elsif changes[:deleted].any?
|
||||
detail.old_value = changes[:deleted]
|
||||
strings << show_detail(detail, no_html, options)
|
||||
end
|
||||
end
|
||||
strings
|
||||
end
|
||||
|
||||
# Returns the textual representation of a single journal detail
|
||||
def show_detail(detail, no_html=false, options={})
|
||||
multiple = false
|
||||
def show_detail(detail, no_html=false)
|
||||
case detail.property
|
||||
when 'attr'
|
||||
field = detail.prop_key.to_s.gsub(/\_id$/, "")
|
||||
label = l(("field_" + field).to_sym)
|
||||
case detail.prop_key
|
||||
when 'due_date', 'start_date'
|
||||
case
|
||||
when ['due_date', 'start_date'].include?(detail.prop_key)
|
||||
value = format_date(detail.value.to_date) if detail.value
|
||||
old_value = format_date(detail.old_value.to_date) if detail.old_value
|
||||
|
||||
when 'project_id', 'status_id', 'tracker_id', 'assigned_to_id',
|
||||
'priority_id', 'category_id', 'fixed_version_id'
|
||||
when ['project_id', 'status_id', 'tracker_id', 'assigned_to_id', 'priority_id', 'category_id', 'fixed_version_id'].include?(detail.prop_key)
|
||||
value = find_name_by_reflection(field, detail.value)
|
||||
old_value = find_name_by_reflection(field, detail.old_value)
|
||||
|
||||
when 'estimated_hours'
|
||||
when detail.prop_key == 'estimated_hours'
|
||||
value = "%0.02f" % detail.value.to_f unless detail.value.blank?
|
||||
old_value = "%0.02f" % detail.old_value.to_f unless detail.old_value.blank?
|
||||
|
||||
when 'parent_id'
|
||||
when detail.prop_key == 'parent_id'
|
||||
label = l(:field_parent_issue)
|
||||
value = "##{detail.value}" unless detail.value.blank?
|
||||
old_value = "##{detail.old_value}" unless detail.old_value.blank?
|
||||
|
||||
when 'is_private'
|
||||
when detail.prop_key == 'is_private'
|
||||
value = l(detail.value == "0" ? :general_text_No : :general_text_Yes) unless detail.value.blank?
|
||||
old_value = l(detail.old_value == "0" ? :general_text_No : :general_text_Yes) unless detail.old_value.blank?
|
||||
end
|
||||
when 'cf'
|
||||
custom_field = CustomField.find_by_id(detail.prop_key)
|
||||
if custom_field
|
||||
multiple = custom_field.multiple?
|
||||
label = custom_field.name
|
||||
value = format_value(detail.value, custom_field.field_format) if detail.value
|
||||
old_value = format_value(detail.old_value, custom_field.field_format) if detail.old_value
|
||||
@@ -238,8 +197,7 @@ module IssuesHelper
|
||||
when 'attachment'
|
||||
label = l(:label_attachment)
|
||||
end
|
||||
call_hook(:helper_issues_show_detail_after_setting,
|
||||
{:detail => detail, :label => label, :value => value, :old_value => old_value })
|
||||
call_hook(:helper_issues_show_detail_after_setting, {:detail => detail, :label => label, :value => value, :old_value => old_value })
|
||||
|
||||
label ||= detail.prop_key
|
||||
value ||= detail.value
|
||||
@@ -249,16 +207,9 @@ module IssuesHelper
|
||||
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.blank?
|
||||
if detail.property == 'attachment' && !value.blank? && atta = Attachment.find_by_id(detail.prop_key)
|
||||
if detail.property == 'attachment' && !value.blank? && a = Attachment.find_by_id(detail.prop_key)
|
||||
# Link to the attachment if it has not been removed
|
||||
value = link_to_attachment(atta, :download => true, :only_path => options[:only_path])
|
||||
if options[:only_path] != false && atta.is_text?
|
||||
value += link_to(
|
||||
image_tag('magnifier.png'),
|
||||
:controller => 'attachments', :action => 'show',
|
||||
:id => atta, :filename => atta.filename
|
||||
)
|
||||
end
|
||||
value = link_to_attachment(a)
|
||||
else
|
||||
value = content_tag("i", h(value)) if value
|
||||
end
|
||||
@@ -268,27 +219,24 @@ module IssuesHelper
|
||||
s = l(:text_journal_changed_no_detail, :label => label)
|
||||
unless no_html
|
||||
diff_link = link_to 'diff',
|
||||
{:controller => 'journals', :action => 'diff', :id => detail.journal_id,
|
||||
:detail_id => detail.id, :only_path => options[:only_path]},
|
||||
{:controller => 'journals', :action => 'diff', :id => detail.journal_id, :detail_id => detail.id},
|
||||
:title => l(:label_view_diff)
|
||||
s << " (#{ diff_link })"
|
||||
end
|
||||
s.html_safe
|
||||
elsif detail.value.present?
|
||||
s
|
||||
elsif !detail.value.blank?
|
||||
case detail.property
|
||||
when 'attr', 'cf'
|
||||
if detail.old_value.present?
|
||||
l(:text_journal_changed, :label => label, :old => old_value, :new => value).html_safe
|
||||
elsif multiple
|
||||
l(:text_journal_added, :label => label, :value => value).html_safe
|
||||
if !detail.old_value.blank?
|
||||
l(:text_journal_changed, :label => label, :old => old_value, :new => value)
|
||||
else
|
||||
l(:text_journal_set_to, :label => label, :value => value).html_safe
|
||||
l(:text_journal_set_to, :label => label, :value => value)
|
||||
end
|
||||
when 'attachment'
|
||||
l(:text_journal_added, :label => label, :value => value).html_safe
|
||||
l(:text_journal_added, :label => label, :value => value)
|
||||
end
|
||||
else
|
||||
l(:text_journal_deleted, :label => label, :old => old_value).html_safe
|
||||
l(:text_journal_deleted, :label => label, :old => old_value)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -329,16 +277,16 @@ module IssuesHelper
|
||||
issues.each do |issue|
|
||||
col_values = columns.collect do |column|
|
||||
s = if column.is_a?(QueryCustomFieldColumn)
|
||||
cv = issue.custom_field_values.detect {|v| v.custom_field_id == column.custom_field.id}
|
||||
cv = issue.custom_values.detect {|v| v.custom_field_id == column.custom_field.id}
|
||||
show_value(cv)
|
||||
else
|
||||
value = column.value(issue)
|
||||
value = issue.send(column.name)
|
||||
if value.is_a?(Date)
|
||||
format_date(value)
|
||||
elsif value.is_a?(Time)
|
||||
format_time(value)
|
||||
elsif value.is_a?(Float)
|
||||
("%.2f" % value).gsub('.', decimal_separator)
|
||||
value.to_s.gsub('.', decimal_separator)
|
||||
else
|
||||
value
|
||||
end
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2011 Jean-Philippe Lang
|
||||
#
|
||||
@@ -30,11 +28,11 @@ module JournalsHelper
|
||||
{ :controller => 'journals', :action => 'edit', :id => journal },
|
||||
:title => l(:button_edit)) if editable
|
||||
end
|
||||
content << content_tag('div', links.join(' ').html_safe, :class => 'contextual') unless links.empty?
|
||||
content << content_tag('div', links.join(' '), :class => 'contextual') unless links.empty?
|
||||
content << textilizable(journal, :notes)
|
||||
css_classes = "wiki"
|
||||
css_classes << " editable" if editable
|
||||
content_tag('div', content.html_safe, :id => "journal-#{journal.id}-notes", :class => css_classes)
|
||||
content_tag('div', content, :id => "journal-#{journal.id}-notes", :class => css_classes)
|
||||
end
|
||||
|
||||
def link_to_in_place_notes_editor(text, field_id, url, options={})
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2011 Jean-Philippe Lang
|
||||
#
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2011 Jean-Philippe Lang
|
||||
#
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2011 Jean-Philippe Lang
|
||||
#
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2011 Jean-Philippe Lang
|
||||
#
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2011 Jean-Philippe Lang
|
||||
#
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2011 Jean-Philippe Lang
|
||||
#
|
||||
@@ -30,7 +28,7 @@ module ProjectsHelper
|
||||
{: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 => 'repositories', :action => :manage_repository, :partial => 'projects/settings/repositories', :label => :label_repository_plural},
|
||||
{: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},
|
||||
{:name => 'activities', :action => :manage_project_activities, :partial => 'projects/settings/activities', :label => :enumeration_activities}
|
||||
]
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2011 Jean-Philippe Lang
|
||||
#
|
||||
@@ -31,14 +29,7 @@ module QueriesHelper
|
||||
|
||||
def column_content(column, issue)
|
||||
value = column.value(issue)
|
||||
if value.is_a?(Array)
|
||||
value.collect {|v| column_value(column, issue, v)}.compact.sort.join(', ').html_safe
|
||||
else
|
||||
column_value(column, issue, value)
|
||||
end
|
||||
end
|
||||
|
||||
def column_value(column, issue, value)
|
||||
|
||||
case value.class.name
|
||||
when 'String'
|
||||
if column.name == :subject
|
||||
@@ -53,8 +44,6 @@ module QueriesHelper
|
||||
when 'Fixnum', 'Float'
|
||||
if column.name == :done_ratio
|
||||
progress_bar(value, :width => '80px')
|
||||
elsif column.name == :spent_hours
|
||||
sprintf "%.2f", value
|
||||
else
|
||||
h(value.to_s)
|
||||
end
|
||||
@@ -99,23 +88,6 @@ module QueriesHelper
|
||||
end
|
||||
end
|
||||
|
||||
def retrieve_query_from_session
|
||||
if session[:query]
|
||||
if session[:query][:id]
|
||||
@query = Query.find_by_id(session[:query][:id])
|
||||
return unless @query
|
||||
else
|
||||
@query = Query.new(:name => "_", :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names])
|
||||
end
|
||||
if session[:query].has_key?(:project_id)
|
||||
@query.project_id = session[:query][:project_id]
|
||||
else
|
||||
@query.project = @project
|
||||
end
|
||||
@query
|
||||
end
|
||||
end
|
||||
|
||||
def build_query_from_params
|
||||
if params[:fields] || params[:f]
|
||||
@query.filters = {}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2011 Jean-Philippe Lang
|
||||
#
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2011 Jean-Philippe Lang
|
||||
#
|
||||
@@ -92,26 +90,22 @@ module RepositoriesHelper
|
||||
text = link_to(h(text), :controller => 'repositories',
|
||||
:action => 'show',
|
||||
:id => @project,
|
||||
:repository_id => @repository.identifier_param,
|
||||
:path => path_param,
|
||||
:rev => @changeset.identifier)
|
||||
output << "<li class='#{style}'>#{text}"
|
||||
output << "<li class='#{style}'>#{text}</li>"
|
||||
output << render_changes_tree(s)
|
||||
output << "</li>"
|
||||
elsif c = tree[file][:c]
|
||||
style << " change-#{c.action}"
|
||||
path_param = to_path_param(@repository.relative_path(c.path))
|
||||
text = link_to(h(text), :controller => 'repositories',
|
||||
:action => 'entry',
|
||||
:id => @project,
|
||||
:repository_id => @repository.identifier_param,
|
||||
:path => path_param,
|
||||
:rev => @changeset.identifier) unless c.action == 'D'
|
||||
text << " - #{h(c.revision)}" unless c.revision.blank?
|
||||
text << ' ('.html_safe + link_to(l(:label_diff), :controller => 'repositories',
|
||||
:action => 'diff',
|
||||
:id => @project,
|
||||
:repository_id => @repository.identifier_param,
|
||||
:path => path_param,
|
||||
:rev => @changeset.identifier) + ') '.html_safe if c.action == 'M'
|
||||
text << ' '.html_safe + content_tag('span', h(c.from_path), :class => 'copied-from') unless c.from_path.blank?
|
||||
@@ -142,10 +136,13 @@ module RepositoriesHelper
|
||||
options_for_select(scm_options, repository.class.name.demodulize),
|
||||
:disabled => (repository && !repository.new_record?),
|
||||
:onchange => remote_function(
|
||||
:url => new_project_repository_path(@project),
|
||||
:method => :get,
|
||||
:update => 'content',
|
||||
:with => "Form.serialize(this.form)")
|
||||
:url => {
|
||||
:controller => 'repositories',
|
||||
:action => 'edit',
|
||||
:id => @project
|
||||
},
|
||||
:method => :get,
|
||||
:with => "Form.serialize(this.form)")
|
||||
)
|
||||
end
|
||||
|
||||
@@ -256,63 +253,59 @@ module RepositoriesHelper
|
||||
'<br />'.html_safe + l(:text_scm_path_encoding_note))
|
||||
end
|
||||
|
||||
def index_commits(commits, heads)
|
||||
def index_commits(commits, heads, href_proc = nil)
|
||||
return nil if commits.nil? or commits.first.parents.nil?
|
||||
|
||||
map = {}
|
||||
commit_hashes = []
|
||||
refs_map = {}
|
||||
heads.each do |head|
|
||||
refs_map[head.scmid] ||= []
|
||||
refs_map[head.scmid] << head
|
||||
href_proc ||= Proc.new {|x|x}
|
||||
heads.each{|r| refs_map[r.scmid] ||= []; refs_map[r.scmid] << r}
|
||||
commits.reverse.each_with_index do |c, i|
|
||||
h = {}
|
||||
h[:parents] = c.parents.collect do |p|
|
||||
[p.scmid, 0, 0]
|
||||
end
|
||||
h[:rdmid] = i
|
||||
h[:space] = 0
|
||||
h[:refs] = refs_map[c.scmid].join(" ") if refs_map.include? c.scmid
|
||||
h[:scmid] = c.scmid
|
||||
h[:href] = href_proc.call(c.scmid)
|
||||
commit_hashes << h
|
||||
map[c.scmid] = h
|
||||
end
|
||||
|
||||
commits_by_scmid = {}
|
||||
commits.reverse.each_with_index do |commit, commit_index|
|
||||
|
||||
commits_by_scmid[commit.scmid] = {
|
||||
:parent_scmids => commit.parents.collect { |parent| parent.scmid },
|
||||
:rdmid => commit_index,
|
||||
:refs => refs_map.include?(commit.scmid) ? refs_map[commit.scmid].join(" ") : nil,
|
||||
:scmid => commit.scmid,
|
||||
:href => block_given? ? yield(commit.scmid) : commit.scmid
|
||||
}
|
||||
heads.sort! do |a,b|
|
||||
a.to_s <=> b.to_s
|
||||
end
|
||||
|
||||
heads.sort! { |head1, head2| head1.to_s <=> head2.to_s }
|
||||
|
||||
space = nil
|
||||
heads.each do |head|
|
||||
if commits_by_scmid.include? head.scmid
|
||||
space = index_head((space || -1) + 1, head, commits_by_scmid)
|
||||
j = 0
|
||||
heads.each do |h|
|
||||
if map.include? h.scmid then
|
||||
j = mark_chain(j += 1, map[h.scmid], map)
|
||||
end
|
||||
end
|
||||
|
||||
# when no head matched anything use first commit
|
||||
space ||= index_head(0, commits.first, commits_by_scmid)
|
||||
|
||||
return commits_by_scmid, space
|
||||
if j == 0 then
|
||||
mark_chain(j += 1, map.values.first, map)
|
||||
end
|
||||
map
|
||||
end
|
||||
|
||||
def index_head(space, commit, commits_by_scmid)
|
||||
|
||||
stack = [[space, commits_by_scmid[commit.scmid]]]
|
||||
max_space = space
|
||||
|
||||
def mark_chain(mark, commit, map)
|
||||
stack = [[mark, commit]]
|
||||
markmax = mark
|
||||
until stack.empty?
|
||||
space, commit = stack.pop
|
||||
commit[:space] = space if commit[:space].nil?
|
||||
|
||||
space -= 1
|
||||
commit[:parent_scmids].each_with_index do |parent_scmid, parent_index|
|
||||
|
||||
parent_commit = commits_by_scmid[parent_scmid]
|
||||
|
||||
if parent_commit and parent_commit[:space].nil?
|
||||
|
||||
stack.unshift [space += 1, parent_commit]
|
||||
current = stack.pop
|
||||
m, commit = current
|
||||
commit[:space] = m if commit[:space] == 0
|
||||
m1 = m - 1
|
||||
commit[:parents].each_with_index do |p, i|
|
||||
psha = p[0]
|
||||
if map.include? psha and map[psha][:space] == 0 then
|
||||
stack << [m1 += 1, map[psha]] if i == 0
|
||||
stack = [[m1 += 1, map[psha]]] + stack if i > 0
|
||||
end
|
||||
end
|
||||
max_space = space if max_space < space
|
||||
markmax = m1 if markmax < m1
|
||||
end
|
||||
max_space
|
||||
markmax
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2011 Jean-Philippe Lang
|
||||
#
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2011 Jean-Philippe Lang
|
||||
#
|
||||
@@ -37,7 +35,7 @@ module SearchHelper
|
||||
result << content_tag('span', h(words), :class => "highlight token-#{t}")
|
||||
end
|
||||
end
|
||||
result.html_safe
|
||||
result
|
||||
end
|
||||
|
||||
def type_label(t)
|
||||
@@ -63,8 +61,6 @@ module SearchHelper
|
||||
links << link_to(h(text), :q => params[:q], :titles_only => params[:titles_only],
|
||||
:all_words => params[:all_words], :scope => params[:scope], t => 1)
|
||||
end
|
||||
('<ul>'.html_safe +
|
||||
links.map {|link| content_tag('li', link)}.join(' ').html_safe +
|
||||
'</ul>'.html_safe) unless links.empty?
|
||||
('<ul>' + links.map {|link| content_tag('li', link)}.join(' ') + '</ul>') unless links.empty?
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2011 Jean-Philippe Lang
|
||||
#
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Helpers to sort tables using clickable column headers.
|
||||
#
|
||||
# Author: Stuart Rackham <srackham@methods.co.nz>, March 2005.
|
||||
@@ -160,8 +158,7 @@ module SortHelper
|
||||
# sort_clause.
|
||||
# - criteria can be either an array or a hash of allowed keys
|
||||
#
|
||||
def sort_update(criteria, sort_name=nil)
|
||||
sort_name ||= self.sort_name
|
||||
def sort_update(criteria)
|
||||
@sort_criteria = SortCriteria.new
|
||||
@sort_criteria.available_criteria = criteria
|
||||
@sort_criteria.from_param(params[:sort] || session[sort_name])
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2011 Jean-Philippe Lang
|
||||
#
|
||||
@@ -118,7 +116,7 @@ module TimelogHelper
|
||||
entry.hours.to_s.gsub('.', decimal_separator),
|
||||
entry.comments
|
||||
]
|
||||
fields += custom_fields.collect {|f| show_value(entry.custom_field_values.detect {|v| v.custom_field_id == f.id}) }
|
||||
fields += custom_fields.collect {|f| show_value(entry.custom_value_for(f)) }
|
||||
|
||||
csv << fields.collect {|c| Redmine::CodesetUtil.from_utf8(
|
||||
c.to_s,
|
||||
@@ -128,10 +126,10 @@ module TimelogHelper
|
||||
export
|
||||
end
|
||||
|
||||
def format_criteria_value(criteria_options, value)
|
||||
def format_criteria_value(criteria, value)
|
||||
if value.blank?
|
||||
"[#{l(:label_none)}]"
|
||||
elsif k = criteria_options[:klass]
|
||||
l(:label_none)
|
||||
elsif k = @available_criterias[criteria][:klass]
|
||||
obj = k.find_by_id(value.to_i)
|
||||
if obj.is_a?(Issue)
|
||||
obj.visible? ? "#{obj.tracker} ##{obj.id}: #{obj.subject}" : "##{obj.id}"
|
||||
@@ -139,28 +137,28 @@ module TimelogHelper
|
||||
obj
|
||||
end
|
||||
else
|
||||
format_value(value, criteria_options[:format])
|
||||
format_value(value, @available_criterias[criteria][:format])
|
||||
end
|
||||
end
|
||||
|
||||
def report_to_csv(report)
|
||||
def report_to_csv(criterias, periods, hours)
|
||||
decimal_separator = l(:general_csv_decimal_separator)
|
||||
export = FCSV.generate(:col_sep => l(:general_csv_separator)) do |csv|
|
||||
# Column headers
|
||||
headers = report.criteria.collect {|criteria| l(report.available_criteria[criteria][:label]) }
|
||||
headers += report.periods
|
||||
headers = criterias.collect {|criteria| l(@available_criterias[criteria][:label]) }
|
||||
headers += periods
|
||||
headers << l(:label_total)
|
||||
csv << headers.collect {|c| Redmine::CodesetUtil.from_utf8(
|
||||
c.to_s,
|
||||
l(:general_csv_encoding) ) }
|
||||
# Content
|
||||
report_criteria_to_csv(csv, report.available_criteria, report.columns, report.criteria, report.periods, report.hours)
|
||||
report_criteria_to_csv(csv, criterias, periods, hours)
|
||||
# Total row
|
||||
str_total = Redmine::CodesetUtil.from_utf8(l(:label_total), l(:general_csv_encoding))
|
||||
row = [ str_total ] + [''] * (report.criteria.size - 1)
|
||||
row = [ str_total ] + [''] * (criterias.size - 1)
|
||||
total = 0
|
||||
report.periods.each do |period|
|
||||
sum = sum_hours(select_hours(report.hours, report.columns, period.to_s))
|
||||
periods.each do |period|
|
||||
sum = sum_hours(select_hours(hours, @columns, period.to_s))
|
||||
total += sum
|
||||
row << (sum > 0 ? ("%.2f" % sum).gsub('.',decimal_separator) : '')
|
||||
end
|
||||
@@ -170,26 +168,26 @@ module TimelogHelper
|
||||
export
|
||||
end
|
||||
|
||||
def report_criteria_to_csv(csv, available_criteria, columns, criteria, periods, hours, level=0)
|
||||
def report_criteria_to_csv(csv, criterias, periods, hours, level=0)
|
||||
decimal_separator = l(:general_csv_decimal_separator)
|
||||
hours.collect {|h| h[criteria[level]].to_s}.uniq.each do |value|
|
||||
hours_for_value = select_hours(hours, criteria[level], value)
|
||||
hours.collect {|h| h[criterias[level]].to_s}.uniq.each do |value|
|
||||
hours_for_value = select_hours(hours, criterias[level], value)
|
||||
next if hours_for_value.empty?
|
||||
row = [''] * level
|
||||
row << Redmine::CodesetUtil.from_utf8(
|
||||
format_criteria_value(available_criteria[criteria[level]], value).to_s,
|
||||
format_criteria_value(criterias[level], value).to_s,
|
||||
l(:general_csv_encoding) )
|
||||
row += [''] * (criteria.length - level - 1)
|
||||
row += [''] * (criterias.length - level - 1)
|
||||
total = 0
|
||||
periods.each do |period|
|
||||
sum = sum_hours(select_hours(hours_for_value, columns, period.to_s))
|
||||
sum = sum_hours(select_hours(hours_for_value, @columns, period.to_s))
|
||||
total += sum
|
||||
row << (sum > 0 ? ("%.2f" % sum).gsub('.',decimal_separator) : '')
|
||||
end
|
||||
row << ("%.2f" % total).gsub('.',decimal_separator)
|
||||
csv << row
|
||||
if criteria.length > level + 1
|
||||
report_criteria_to_csv(csv, available_criteria, columns, criteria, periods, hours_for_value, level + 1)
|
||||
if criterias.length > level + 1
|
||||
report_criteria_to_csv(csv, criterias, periods, hours_for_value, level + 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2011 Jean-Philippe Lang
|
||||
#
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2011 Jean-Philippe Lang
|
||||
#
|
||||
@@ -21,9 +19,9 @@ module UsersHelper
|
||||
def users_status_options_for_select(selected)
|
||||
user_count_by_status = User.count(:group => 'status').to_hash
|
||||
options_for_select([[l(:label_all), ''],
|
||||
["#{l(:status_active)} (#{user_count_by_status[1].to_i})", '1'],
|
||||
["#{l(:status_registered)} (#{user_count_by_status[2].to_i})", '2'],
|
||||
["#{l(:status_locked)} (#{user_count_by_status[3].to_i})", '3']], selected.to_s)
|
||||
["#{l(:status_active)} (#{user_count_by_status[1].to_i})", 1],
|
||||
["#{l(:status_registered)} (#{user_count_by_status[2].to_i})", 2],
|
||||
["#{l(:status_locked)} (#{user_count_by_status[3].to_i})", 3]], selected)
|
||||
end
|
||||
|
||||
# Options for the new membership projects combo-box
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2011 Jean-Philippe Lang
|
||||
#
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2011 Jean-Philippe Lang
|
||||
#
|
||||
@@ -59,16 +57,8 @@ module WatchersHelper
|
||||
:style => "vertical-align: middle",
|
||||
:class => "delete")
|
||||
end
|
||||
content_tag :li, s.html_safe
|
||||
"<li>#{ s }</li>"
|
||||
end
|
||||
(lis.empty? ? "" : "<ul>#{ lis.join("\n") }</ul>").html_safe
|
||||
end
|
||||
|
||||
def watchers_checkboxes(object, users, checked=nil)
|
||||
users.map do |user|
|
||||
c = checked.nil? ? object.watched_by?(user) : checked
|
||||
tag = check_box_tag 'issue[watcher_user_ids][]', user.id, c, :id => nil
|
||||
content_tag 'label', "#{tag} #{h(user)}", :id => "issue_watcher_user_ids_#{user.id}", :class => "floating"
|
||||
end.join
|
||||
lis.empty? ? "" : "<ul>#{ lis.join("\n") }</ul>"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2011 Jean-Philippe Lang
|
||||
#
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2011 Jean-Philippe Lang
|
||||
#
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2011 Jean-Philippe Lang
|
||||
#
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2012 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2011 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
@@ -21,10 +21,9 @@ class Attachment < ActiveRecord::Base
|
||||
belongs_to :container, :polymorphic => true
|
||||
belongs_to :author, :class_name => "User", :foreign_key => "author_id"
|
||||
|
||||
validates_presence_of :filename, :author
|
||||
validates_presence_of :container, :filename, :author
|
||||
validates_length_of :filename, :maximum => 255
|
||||
validates_length_of :disk_filename, :maximum => 255
|
||||
validates_length_of :description, :maximum => 255, :allow_blank => true
|
||||
validate :validate_max_file_size
|
||||
|
||||
acts_as_event :title => :filename,
|
||||
@@ -50,26 +49,9 @@ class Attachment < ActiveRecord::Base
|
||||
before_save :files_to_final_location
|
||||
after_destroy :delete_from_disk
|
||||
|
||||
def container_with_blank_type_check
|
||||
if container_type.blank?
|
||||
nil
|
||||
else
|
||||
container_without_blank_type_check
|
||||
end
|
||||
end
|
||||
alias_method_chain :container, :blank_type_check unless method_defined?(:container_without_blank_type_check)
|
||||
|
||||
# Returns an unsaved copy of the attachment
|
||||
def copy(attributes=nil)
|
||||
copy = self.class.new
|
||||
copy.attributes = self.attributes.dup.except("id", "downloads")
|
||||
copy.attributes = attributes if attributes
|
||||
copy
|
||||
end
|
||||
|
||||
def validate_max_file_size
|
||||
if @temp_file && self.filesize > Setting.attachment_max_size.to_i.kilobytes
|
||||
errors.add(:base, l(:error_attachment_too_big, :max_size => Setting.attachment_max_size.to_i.kilobytes))
|
||||
if self.filesize > Setting.attachment_max_size.to_i.kilobytes
|
||||
errors.add(:base, :too_long, :count => Setting.attachment_max_size.to_i.kilobytes)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -77,33 +59,21 @@ class Attachment < ActiveRecord::Base
|
||||
unless incoming_file.nil?
|
||||
@temp_file = incoming_file
|
||||
if @temp_file.size > 0
|
||||
if @temp_file.respond_to?(:original_filename)
|
||||
self.filename = @temp_file.original_filename
|
||||
self.filename.force_encoding("UTF-8") if filename.respond_to?(:force_encoding)
|
||||
end
|
||||
if @temp_file.respond_to?(:content_type)
|
||||
self.content_type = @temp_file.content_type.to_s.chomp
|
||||
end
|
||||
if content_type.blank? && filename.present?
|
||||
self.filename = sanitize_filename(@temp_file.original_filename)
|
||||
self.disk_filename = Attachment.disk_filename(filename)
|
||||
self.content_type = @temp_file.content_type.to_s.chomp
|
||||
if content_type.blank?
|
||||
self.content_type = Redmine::MimeType.of(filename)
|
||||
end
|
||||
self.filesize = @temp_file.size
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def file
|
||||
nil
|
||||
end
|
||||
|
||||
def filename=(arg)
|
||||
write_attribute :filename, sanitize_filename(arg.to_s)
|
||||
if new_record? && disk_filename.blank?
|
||||
self.disk_filename = Attachment.disk_filename(filename)
|
||||
end
|
||||
filename
|
||||
end
|
||||
|
||||
# Copies the temporary file to its final location
|
||||
# and computes its MD5 hash
|
||||
def files_to_final_location
|
||||
@@ -111,15 +81,10 @@ class Attachment < ActiveRecord::Base
|
||||
logger.info("Saving attachment '#{self.diskfile}' (#{@temp_file.size} bytes)")
|
||||
md5 = Digest::MD5.new
|
||||
File.open(diskfile, "wb") do |f|
|
||||
if @temp_file.respond_to?(:read)
|
||||
buffer = ""
|
||||
while (buffer = @temp_file.read(8192))
|
||||
f.write(buffer)
|
||||
md5.update(buffer)
|
||||
end
|
||||
else
|
||||
f.write(@temp_file)
|
||||
md5.update(@temp_file)
|
||||
buffer = ""
|
||||
while (buffer = @temp_file.read(8192))
|
||||
f.write(buffer)
|
||||
md5.update(buffer)
|
||||
end
|
||||
end
|
||||
self.digest = md5.hexdigest
|
||||
@@ -131,11 +96,9 @@ class Attachment < ActiveRecord::Base
|
||||
end
|
||||
end
|
||||
|
||||
# Deletes the file from the file system if it's not referenced by other attachments
|
||||
# Deletes file on the disk
|
||||
def delete_from_disk
|
||||
if Attachment.first(:conditions => ["disk_filename = ? AND id <> ?", disk_filename, id]).nil?
|
||||
delete_from_disk!
|
||||
end
|
||||
File.delete(diskfile) if !filename.blank? && File.exist?(diskfile)
|
||||
end
|
||||
|
||||
# Returns file's location on disk
|
||||
@@ -148,15 +111,15 @@ class Attachment < ActiveRecord::Base
|
||||
end
|
||||
|
||||
def project
|
||||
container.try(:project)
|
||||
container.project
|
||||
end
|
||||
|
||||
def visible?(user=User.current)
|
||||
container && container.attachments_visible?(user)
|
||||
container.attachments_visible?(user)
|
||||
end
|
||||
|
||||
def deletable?(user=User.current)
|
||||
container && container.attachments_deletable?(user)
|
||||
container.attachments_deletable?(user)
|
||||
end
|
||||
|
||||
def image?
|
||||
@@ -176,53 +139,41 @@ class Attachment < ActiveRecord::Base
|
||||
File.readable?(diskfile)
|
||||
end
|
||||
|
||||
# Returns the attachment token
|
||||
def token
|
||||
"#{id}.#{digest}"
|
||||
end
|
||||
|
||||
# Finds an attachment that matches the given token and that has no container
|
||||
def self.find_by_token(token)
|
||||
if token.to_s =~ /^(\d+)\.([0-9a-f]+)$/
|
||||
attachment_id, attachment_digest = $1, $2
|
||||
attachment = Attachment.first(:conditions => {:id => attachment_id, :digest => attachment_digest})
|
||||
if attachment && attachment.container.nil?
|
||||
attachment
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Bulk attaches a set of files to an object
|
||||
#
|
||||
# Returns a Hash of the results:
|
||||
# :files => array of the attached files
|
||||
# :unsaved => array of the files that could not be attached
|
||||
def self.attach_files(obj, attachments)
|
||||
result = obj.save_attachments(attachments, User.current)
|
||||
obj.attach_saved_attachments
|
||||
result
|
||||
attached = []
|
||||
if attachments && attachments.is_a?(Hash)
|
||||
attachments.each_value do |attachment|
|
||||
file = attachment['file']
|
||||
next unless file && file.size > 0
|
||||
a = Attachment.create(:container => obj,
|
||||
:file => file,
|
||||
:description => attachment['description'].to_s.strip,
|
||||
:author => User.current)
|
||||
obj.attachments << a
|
||||
|
||||
if a.new_record?
|
||||
obj.unsaved_attachments ||= []
|
||||
obj.unsaved_attachments << a
|
||||
else
|
||||
attached << a
|
||||
end
|
||||
end
|
||||
end
|
||||
{:files => attached, :unsaved => obj.unsaved_attachments}
|
||||
end
|
||||
|
||||
def self.latest_attach(attachments, filename)
|
||||
attachments.sort_by(&:created_on).reverse.detect {
|
||||
attachments.sort_by(&:created_on).reverse.detect {
|
||||
|att| att.filename.downcase == filename.downcase
|
||||
}
|
||||
end
|
||||
|
||||
def self.prune(age=1.day)
|
||||
attachments = Attachment.all(:conditions => ["created_on < ? AND (container_type IS NULL OR container_type = '')", Time.now - age])
|
||||
attachments.each(&:destroy)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Physically deletes the file from the file system
|
||||
def delete_from_disk!
|
||||
if disk_filename.present? && File.exist?(diskfile)
|
||||
File.delete(diskfile)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def sanitize_filename(value)
|
||||
# get only the filename, not the whole path
|
||||
just_filename = value.gsub(/^.*(\\|\/)/, '')
|
||||
|
||||
@@ -15,12 +15,7 @@
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
# Generic exception for when the AuthSource can not be reached
|
||||
# (eg. can not connect to the LDAP)
|
||||
class AuthSourceException < Exception; end
|
||||
|
||||
class AuthSource < ActiveRecord::Base
|
||||
include Redmine::SubclassFactory
|
||||
include Redmine::Ciphering
|
||||
|
||||
has_many :users
|
||||
|
||||
@@ -15,51 +15,40 @@
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
require 'iconv'
|
||||
require 'net/ldap'
|
||||
require 'net/ldap/dn'
|
||||
require 'iconv'
|
||||
|
||||
class AuthSourceLdap < AuthSource
|
||||
validates_presence_of :host, :port, :attr_login
|
||||
validates_length_of :name, :host, :maximum => 60, :allow_nil => true
|
||||
validates_length_of :account, :account_password, :base_dn, :filter, :maximum => 255, :allow_blank => true
|
||||
validates_length_of :account, :account_password, :base_dn, :maximum => 255, :allow_nil => true
|
||||
validates_length_of :attr_login, :attr_firstname, :attr_lastname, :attr_mail, :maximum => 30, :allow_nil => true
|
||||
validates_numericality_of :port, :only_integer => true
|
||||
validate :validate_filter
|
||||
|
||||
before_validation :strip_ldap_attributes
|
||||
|
||||
def self.human_attribute_name(attribute_key_name, *args)
|
||||
attr_name = attribute_key_name.to_s
|
||||
if attr_name == "filter"
|
||||
attr_name = "ldap_filter"
|
||||
end
|
||||
super(attr_name, *args)
|
||||
end
|
||||
|
||||
def initialize(attributes=nil, *args)
|
||||
super
|
||||
def after_initialize
|
||||
self.port = 389 if self.port == 0
|
||||
end
|
||||
|
||||
def authenticate(login, password)
|
||||
return nil if login.blank? || password.blank?
|
||||
attrs = get_user_dn(login, password)
|
||||
attrs = get_user_dn(login)
|
||||
|
||||
if attrs && attrs[:dn] && authenticate_dn(attrs[:dn], password)
|
||||
logger.debug "Authentication successful for '#{login}'" if logger && logger.debug?
|
||||
return attrs.except(:dn)
|
||||
end
|
||||
rescue Net::LDAP::LdapError => e
|
||||
raise AuthSourceException.new(e.message)
|
||||
rescue Net::LDAP::LdapError => text
|
||||
raise "LdapError: " + text
|
||||
end
|
||||
|
||||
# test the connection to the LDAP
|
||||
def test_connection
|
||||
ldap_con = initialize_ldap_con(self.account, self.account_password)
|
||||
ldap_con.open { }
|
||||
rescue Net::LDAP::LdapError => e
|
||||
raise "LdapError: " + e.message
|
||||
rescue Net::LDAP::LdapError => text
|
||||
raise "LdapError: " + text
|
||||
end
|
||||
|
||||
def auth_method_name
|
||||
@@ -68,20 +57,6 @@ class AuthSourceLdap < AuthSource
|
||||
|
||||
private
|
||||
|
||||
def ldap_filter
|
||||
if filter.present?
|
||||
Net::LDAP::Filter.construct(filter)
|
||||
end
|
||||
rescue Net::LDAP::LdapError
|
||||
nil
|
||||
end
|
||||
|
||||
def validate_filter
|
||||
if filter.present? && ldap_filter.nil?
|
||||
errors.add(:filter, :invalid)
|
||||
end
|
||||
end
|
||||
|
||||
def strip_ldap_attributes
|
||||
[:attr_login, :attr_firstname, :attr_lastname, :attr_mail].each do |attr|
|
||||
write_attribute(attr, read_attribute(attr).strip) unless read_attribute(attr).nil?
|
||||
@@ -125,24 +100,14 @@ class AuthSourceLdap < AuthSource
|
||||
end
|
||||
|
||||
# Get the user's dn and any attributes for them, given their login
|
||||
def get_user_dn(login, password)
|
||||
ldap_con = nil
|
||||
if self.account && self.account.include?("$login")
|
||||
ldap_con = initialize_ldap_con(self.account.sub("$login", Net::LDAP::DN.escape(login)), password)
|
||||
else
|
||||
ldap_con = initialize_ldap_con(self.account, self.account_password)
|
||||
end
|
||||
def get_user_dn(login)
|
||||
ldap_con = initialize_ldap_con(self.account, self.account_password)
|
||||
login_filter = Net::LDAP::Filter.eq( self.attr_login, login )
|
||||
object_filter = Net::LDAP::Filter.eq( "objectClass", "*" )
|
||||
attrs = {}
|
||||
|
||||
search_filter = object_filter & login_filter
|
||||
if f = ldap_filter
|
||||
search_filter = search_filter & f
|
||||
end
|
||||
|
||||
ldap_con.search( :base => self.base_dn,
|
||||
:filter => search_filter,
|
||||
:filter => object_filter & login_filter,
|
||||
:attributes=> search_attributes) do |entry|
|
||||
|
||||
if onthefly_register?
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
class Board < ActiveRecord::Base
|
||||
include Redmine::SafeAttributes
|
||||
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 => :destroy, :order => "#{Message.table_name}.created_on DESC"
|
||||
@@ -31,8 +30,6 @@ class Board < ActiveRecord::Base
|
||||
named_scope :visible, lambda {|*args| { :include => :project,
|
||||
:conditions => Project.allowed_to_condition(args.shift || User.current, :view_messages, *args) } }
|
||||
|
||||
safe_attributes 'name', 'description', 'move_to'
|
||||
|
||||
def visible?(user=User.current)
|
||||
!user.nil? && user.allowed_to?(:view_messages, project)
|
||||
end
|
||||
|
||||
@@ -31,10 +31,10 @@ class Changeset < ActiveRecord::Base
|
||||
:join_table => "#{table_name_prefix}changeset_parents#{table_name_suffix}",
|
||||
:association_foreign_key => 'changeset_id', :foreign_key => 'parent_id'
|
||||
|
||||
acts_as_event :title => Proc.new {|o| o.title},
|
||||
acts_as_event :title => Proc.new {|o| "#{l(:label_revision)} #{o.format_identifier}" + (o.short_comments.blank? ? '' : (': ' + o.short_comments))},
|
||||
:description => :long_comments,
|
||||
:datetime => :committed_on,
|
||||
:url => Proc.new {|o| {:controller => 'repositories', :action => 'revision', :id => o.repository.project, :repository_id => o.repository.identifier_param, :rev => o.identifier}}
|
||||
:url => Proc.new {|o| {:controller => 'repositories', :action => 'revision', :id => o.repository.project, :rev => o.identifier}}
|
||||
|
||||
acts_as_searchable :columns => 'comments',
|
||||
:include => {:repository => :project},
|
||||
@@ -151,26 +151,12 @@ class Changeset < ActiveRecord::Base
|
||||
@long_comments || split_comments.last
|
||||
end
|
||||
|
||||
def text_tag(ref_project=nil)
|
||||
tag = if scmid?
|
||||
def text_tag
|
||||
if scmid?
|
||||
"commit:#{scmid}"
|
||||
else
|
||||
"r#{revision}"
|
||||
end
|
||||
if repository && repository.identifier.present?
|
||||
tag = "#{repository.identifier}|#{tag}"
|
||||
end
|
||||
if ref_project && project && ref_project != project
|
||||
tag = "#{project.identifier}:#{tag}"
|
||||
end
|
||||
tag
|
||||
end
|
||||
|
||||
# Returns the title used for the changeset in the activity/search results
|
||||
def title
|
||||
repo = (repository && repository.identifier.present?) ? " (#{repository.identifier})" : ''
|
||||
comm = short_comments.blank? ? '' : (': ' + short_comments)
|
||||
"#{l(:label_revision)} #{format_identifier}#{repo}#{comm}"
|
||||
end
|
||||
|
||||
# Returns the previous changeset
|
||||
@@ -198,14 +184,14 @@ class Changeset < ActiveRecord::Base
|
||||
:from_revision => change[:from_revision])
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Finds an issue that can be referenced by the commit message
|
||||
# i.e. an issue that belong to the repository project, a subproject or a parent project
|
||||
def find_referenced_issue_by_id(id)
|
||||
return nil if id.blank?
|
||||
issue = Issue.find_by_id(id.to_i, :include => :project)
|
||||
if Setting.commit_cross_project_ref?
|
||||
# all issues can be referenced/fixed
|
||||
elsif issue
|
||||
# issue that belong to the repository project, a subproject or a parent project only
|
||||
if issue
|
||||
unless issue.project &&
|
||||
(project == issue.project || project.is_ancestor_of?(issue.project) ||
|
||||
project.is_descendant_of?(issue.project))
|
||||
@@ -215,8 +201,6 @@ class Changeset < ActiveRecord::Base
|
||||
issue
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def fix_issue(issue)
|
||||
status = IssueStatus.find_by_id(Setting.commit_fix_status_id.to_i)
|
||||
if status.nil?
|
||||
@@ -229,7 +213,7 @@ class Changeset < ActiveRecord::Base
|
||||
# don't change the status is the issue is closed
|
||||
return if issue.status && issue.status.is_closed?
|
||||
|
||||
journal = issue.init_journal(user || User.anonymous, ll(Setting.default_language, :text_status_changed_by_changeset, text_tag(issue.project)))
|
||||
journal = issue.init_journal(user || User.anonymous, ll(Setting.default_language, :text_status_changed_by_changeset, text_tag))
|
||||
issue.status = status
|
||||
unless Setting.commit_fix_done_ratio.blank?
|
||||
issue.done_ratio = Setting.commit_fix_done_ratio.to_i
|
||||
@@ -248,7 +232,7 @@ class Changeset < ActiveRecord::Base
|
||||
:hours => hours,
|
||||
:issue => issue,
|
||||
:spent_on => commit_date,
|
||||
:comments => l(:text_time_logged_by_changeset, :value => text_tag(issue.project),
|
||||
:comments => l(:text_time_logged_by_changeset, :value => text_tag,
|
||||
:locale => Setting.default_language)
|
||||
)
|
||||
time_entry.activity = log_time_activity unless log_time_activity.nil?
|
||||
|
||||
@@ -16,11 +16,8 @@
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
class Comment < ActiveRecord::Base
|
||||
include Redmine::SafeAttributes
|
||||
belongs_to :commented, :polymorphic => true, :counter_cache => true
|
||||
belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
|
||||
|
||||
validates_presence_of :commented, :author, :comments
|
||||
|
||||
safe_attributes 'comments'
|
||||
end
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2012 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2011 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
@@ -16,8 +16,6 @@
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
class CustomField < ActiveRecord::Base
|
||||
include Redmine::SubclassFactory
|
||||
|
||||
has_many :custom_values, :dependent => :delete_all
|
||||
acts_as_list :scope => 'type = \'#{self.class}\''
|
||||
serialize :possible_values
|
||||
@@ -27,23 +25,20 @@ class CustomField < ActiveRecord::Base
|
||||
validates_length_of :name, :maximum => 30
|
||||
validates_inclusion_of :field_format, :in => Redmine::CustomFieldFormat.available_formats
|
||||
|
||||
validate :validate_custom_field
|
||||
before_validation :set_searchable
|
||||
validate :validate_values
|
||||
|
||||
def initialize(attributes=nil, *args)
|
||||
def initialize(attributes = nil)
|
||||
super
|
||||
self.possible_values ||= []
|
||||
end
|
||||
|
||||
def set_searchable
|
||||
def before_validation
|
||||
# make sure these fields are not searchable
|
||||
self.searchable = false if %w(int float date bool).include?(field_format)
|
||||
# make sure only these fields can have multiple values
|
||||
self.multiple = false unless %w(list user version).include?(field_format)
|
||||
true
|
||||
end
|
||||
|
||||
def validate_custom_field
|
||||
def validate_values
|
||||
if self.field_format == "list"
|
||||
errors.add(:possible_values, :blank) if self.possible_values.nil? || self.possible_values.empty?
|
||||
errors.add(:possible_values, :invalid) unless self.possible_values.is_a? Array
|
||||
@@ -57,9 +52,10 @@ class CustomField < ActiveRecord::Base
|
||||
end
|
||||
end
|
||||
|
||||
if default_value.present? && !valid_field_value?(default_value)
|
||||
errors.add(:default_value, :invalid)
|
||||
end
|
||||
# validate default value
|
||||
v = CustomValue.new(:custom_field => self.clone, :value => default_value, :customized => nil)
|
||||
v.custom_field.is_required = false
|
||||
errors.add(:default_value, :invalid) unless v.valid?
|
||||
end
|
||||
|
||||
def possible_values_options(obj=nil)
|
||||
@@ -73,14 +69,12 @@ class CustomField < ActiveRecord::Base
|
||||
obj.project.shared_versions.sort.collect {|u| [u.to_s, u.id.to_s]}
|
||||
end
|
||||
elsif obj.is_a?(Array)
|
||||
obj.collect {|o| possible_values_options(o)}.reduce(:&)
|
||||
obj.collect {|o| possible_values_options(o)}.inject {|memo, v| memo & v}
|
||||
else
|
||||
[]
|
||||
end
|
||||
when 'bool'
|
||||
[[l(:general_text_Yes), '1'], [l(:general_text_No), '0']]
|
||||
else
|
||||
read_possible_values_utf8_encoded || []
|
||||
read_attribute :possible_values
|
||||
end
|
||||
end
|
||||
|
||||
@@ -88,10 +82,8 @@ class CustomField < ActiveRecord::Base
|
||||
case field_format
|
||||
when 'user', 'version'
|
||||
possible_values_options(obj).collect(&:last)
|
||||
when 'bool'
|
||||
['1', '0']
|
||||
else
|
||||
read_possible_values_utf8_encoded
|
||||
read_attribute :possible_values
|
||||
end
|
||||
end
|
||||
|
||||
@@ -129,7 +121,6 @@ class CustomField < ActiveRecord::Base
|
||||
# objects by their value of the custom field.
|
||||
# Returns false, if the custom field can not be used for sorting.
|
||||
def order_statement
|
||||
return nil if multiple?
|
||||
case field_format
|
||||
when 'string', 'text', 'list', 'date', 'bool'
|
||||
# COALESCE is here to make sure that blank and NULL values are sorted equally
|
||||
@@ -167,65 +158,4 @@ class CustomField < ActiveRecord::Base
|
||||
def type_name
|
||||
nil
|
||||
end
|
||||
|
||||
# Returns the error messages for the given value
|
||||
# or an empty array if value is a valid value for the custom field
|
||||
def validate_field_value(value)
|
||||
errs = []
|
||||
if value.is_a?(Array)
|
||||
if !multiple?
|
||||
errs << ::I18n.t('activerecord.errors.messages.invalid')
|
||||
end
|
||||
if is_required? && value.detect(&:present?).nil?
|
||||
errs << ::I18n.t('activerecord.errors.messages.blank')
|
||||
end
|
||||
value.each {|v| errs += validate_field_value_format(v)}
|
||||
else
|
||||
if is_required? && value.blank?
|
||||
errs << ::I18n.t('activerecord.errors.messages.blank')
|
||||
end
|
||||
errs += validate_field_value_format(value)
|
||||
end
|
||||
errs
|
||||
end
|
||||
|
||||
# Returns true if value is a valid value for the custom field
|
||||
def valid_field_value?(value)
|
||||
validate_field_value(value).empty?
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# Returns the error message for the given value regarding its format
|
||||
def validate_field_value_format(value)
|
||||
errs = []
|
||||
if value.present?
|
||||
errs << ::I18n.t('activerecord.errors.messages.invalid') unless regexp.blank? or value =~ Regexp.new(regexp)
|
||||
errs << ::I18n.t('activerecord.errors.messages.too_short', :count => min_length) if min_length > 0 and value.length < min_length
|
||||
errs << ::I18n.t('activerecord.errors.messages.too_long', :count => max_length) if max_length > 0 and value.length > max_length
|
||||
|
||||
# Format specific validations
|
||||
case field_format
|
||||
when 'int'
|
||||
errs << ::I18n.t('activerecord.errors.messages.not_a_number') unless value =~ /^[+-]?\d+$/
|
||||
when 'float'
|
||||
begin; Kernel.Float(value); rescue; errs << ::I18n.t('activerecord.errors.messages.invalid') end
|
||||
when 'date'
|
||||
errs << ::I18n.t('activerecord.errors.messages.not_a_date') unless value =~ /^\d{4}-\d{2}-\d{2}$/ && begin; value.to_date; rescue; false end
|
||||
when 'list'
|
||||
errs << ::I18n.t('activerecord.errors.messages.inclusion') unless possible_values.include?(value)
|
||||
end
|
||||
end
|
||||
errs
|
||||
end
|
||||
|
||||
def read_possible_values_utf8_encoded
|
||||
values = read_attribute(:possible_values)
|
||||
if values.is_a?(Array)
|
||||
values.each do |value|
|
||||
value.force_encoding('UTF-8') if value.respond_to?(:force_encoding)
|
||||
end
|
||||
end
|
||||
values
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2012 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 CustomFieldValue
|
||||
attr_accessor :custom_field, :customized, :value
|
||||
|
||||
def custom_field_id
|
||||
custom_field.id
|
||||
end
|
||||
|
||||
def true?
|
||||
self.value == '1'
|
||||
end
|
||||
|
||||
def editable?
|
||||
custom_field.editable?
|
||||
end
|
||||
|
||||
def visible?
|
||||
custom_field.visible?
|
||||
end
|
||||
|
||||
def required?
|
||||
custom_field.is_required?
|
||||
end
|
||||
|
||||
def to_s
|
||||
value.to_s
|
||||
end
|
||||
|
||||
def validate_value
|
||||
custom_field.validate_field_value(value).each do |message|
|
||||
customized.errors.add(:base, custom_field.name + ' ' + message)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,5 +1,5 @@
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2012 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2011 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
@@ -19,8 +19,9 @@ class CustomValue < ActiveRecord::Base
|
||||
belongs_to :custom_field
|
||||
belongs_to :customized, :polymorphic => true
|
||||
|
||||
def initialize(attributes=nil, *args)
|
||||
super
|
||||
validate :validate_custom_value
|
||||
|
||||
def after_initialize
|
||||
if new_record? && custom_field && (customized_type.blank? || (customized && customized.new_record?))
|
||||
self.value ||= custom_field.default_value
|
||||
end
|
||||
@@ -46,4 +47,27 @@ class CustomValue < ActiveRecord::Base
|
||||
def to_s
|
||||
value.to_s
|
||||
end
|
||||
|
||||
protected
|
||||
def validate_custom_value
|
||||
if value.blank?
|
||||
errors.add(:value, :blank) if custom_field.is_required? and value.blank?
|
||||
else
|
||||
errors.add(:value, :invalid) unless custom_field.regexp.blank? or value =~ Regexp.new(custom_field.regexp)
|
||||
errors.add(:value, :too_short, :count => custom_field.min_length) if custom_field.min_length > 0 and value.length < custom_field.min_length
|
||||
errors.add(:value, :too_long, :count => custom_field.max_length) if custom_field.max_length > 0 and value.length > custom_field.max_length
|
||||
|
||||
# Format specific validations
|
||||
case custom_field.field_format
|
||||
when 'int'
|
||||
errors.add(:value, :not_a_number) unless value =~ /^[+-]?\d+$/
|
||||
when 'float'
|
||||
begin; Kernel.Float(value); rescue; errors.add(:value, :invalid) end
|
||||
when 'date'
|
||||
errors.add(:value, :not_a_date) unless value =~ /^\d{4}-\d{2}-\d{2}$/ && begin; value.to_date; rescue; false end
|
||||
when 'list'
|
||||
errors.add(:value, :inclusion) unless custom_field.possible_values.include?(value)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
class Document < ActiveRecord::Base
|
||||
include Redmine::SafeAttributes
|
||||
belongs_to :project
|
||||
belongs_to :category, :class_name => "DocumentCategory", :foreign_key => "category_id"
|
||||
acts_as_attachable :delete_permission => :manage_documents
|
||||
@@ -33,24 +32,19 @@ class Document < ActiveRecord::Base
|
||||
named_scope :visible, lambda {|*args| { :include => :project,
|
||||
:conditions => Project.allowed_to_condition(args.shift || User.current, :view_documents, *args) } }
|
||||
|
||||
safe_attributes 'category_id', 'title', 'description'
|
||||
|
||||
def visible?(user=User.current)
|
||||
!user.nil? && user.allowed_to?(:view_documents, project)
|
||||
end
|
||||
|
||||
def initialize(attributes=nil, *args)
|
||||
super
|
||||
def after_initialize
|
||||
if new_record?
|
||||
# Rails3 use this instead
|
||||
# self.category ||= DocumentCategory.default
|
||||
self.category_id = DocumentCategory.default.id if self.category_id == 0
|
||||
self.category ||= DocumentCategory.default
|
||||
end
|
||||
end
|
||||
|
||||
def updated_on
|
||||
unless @updated_on
|
||||
a = attachments.last
|
||||
a = attachments.find(:first, :order => 'created_on DESC')
|
||||
@updated_on = (a && a.created_on) || created_on
|
||||
end
|
||||
@updated_on
|
||||
|
||||
@@ -31,12 +31,4 @@ class DocumentCategory < Enumeration
|
||||
def transfer_relations(to)
|
||||
documents.update_all("category_id = #{to.id}")
|
||||
end
|
||||
|
||||
def self.default
|
||||
d = super
|
||||
if d.nil?
|
||||
d = find(:first)
|
||||
end
|
||||
d
|
||||
end
|
||||
end
|
||||
|
||||
@@ -16,8 +16,6 @@
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
class Enumeration < ActiveRecord::Base
|
||||
include Redmine::SubclassFactory
|
||||
|
||||
default_scope :order => "#{Enumeration.table_name}.position ASC"
|
||||
|
||||
belongs_to :project
|
||||
@@ -29,8 +27,6 @@ class Enumeration < ActiveRecord::Base
|
||||
before_destroy :check_integrity
|
||||
before_save :check_default
|
||||
|
||||
attr_protected :type
|
||||
|
||||
validates_presence_of :name
|
||||
validates_uniqueness_of :name, :scope => [:type, :project_id]
|
||||
validates_length_of :name, :maximum => 30
|
||||
@@ -98,7 +94,7 @@ class Enumeration < ActiveRecord::Base
|
||||
#
|
||||
# Note: subclasses is protected in ActiveRecord
|
||||
def self.get_subclasses
|
||||
subclasses
|
||||
@@subclasses[Enumeration]
|
||||
end
|
||||
|
||||
# Does the +new+ Hash override the previous Enumeration?
|
||||
|
||||
@@ -51,14 +51,6 @@ class Group < Principal
|
||||
end
|
||||
end
|
||||
|
||||
def self.human_attribute_name(attribute_key_name, *args)
|
||||
attr_name = attribute_key_name.to_s
|
||||
if attr_name == 'lastname'
|
||||
attr_name = "name"
|
||||
end
|
||||
super(attr_name, *args)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Removes references that are not handled by associations
|
||||
|
||||
@@ -63,19 +63,27 @@ class Issue < ActiveRecord::Base
|
||||
named_scope :visible, lambda {|*args| { :include => :project,
|
||||
:conditions => Issue.visible_condition(args.shift || User.current, *args) } }
|
||||
|
||||
named_scope :open, lambda {|*args|
|
||||
is_closed = args.size > 0 ? !args.first : false
|
||||
{:conditions => ["#{IssueStatus.table_name}.is_closed = ?", is_closed], :include => :status}
|
||||
}
|
||||
named_scope :open, :conditions => ["#{IssueStatus.table_name}.is_closed = ?", false], :include => :status
|
||||
|
||||
named_scope :recently_updated, :order => "#{Issue.table_name}.updated_on DESC"
|
||||
named_scope :with_limit, lambda { |limit| { :limit => limit} }
|
||||
named_scope :on_active_project, :include => [:status, :project, :tracker],
|
||||
:conditions => ["#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"]
|
||||
|
||||
named_scope :without_version, lambda {
|
||||
{
|
||||
:conditions => { :fixed_version_id => nil}
|
||||
}
|
||||
}
|
||||
|
||||
named_scope :with_query, lambda {|query|
|
||||
{
|
||||
:conditions => Query.merge_conditions(query.statement)
|
||||
}
|
||||
}
|
||||
|
||||
before_create :default_assign
|
||||
before_save :close_duplicates, :update_done_ratio_from_issue_status
|
||||
after_save {|issue| issue.send :after_project_change if !issue.id_changed? && issue.project_id_changed?}
|
||||
after_save :reschedule_following_issues, :update_nested_set_attributes, :update_parent_attributes, :create_journal
|
||||
after_destroy :update_parent_attributes
|
||||
|
||||
@@ -113,96 +121,97 @@ class Issue < ActiveRecord::Base
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(attributes=nil, *args)
|
||||
super
|
||||
def after_initialize
|
||||
if new_record?
|
||||
# set default values for new records only
|
||||
self.status ||= IssueStatus.default
|
||||
self.priority ||= IssuePriority.default
|
||||
self.watcher_user_ids = []
|
||||
end
|
||||
end
|
||||
|
||||
# AR#Base#destroy would raise and StaleObjectError exception
|
||||
# if the issue was already deleted or updated (non matching lock_version).
|
||||
# This is a problem when bulk deleting issues or deleting a project
|
||||
# (because an issue may already be deleted if its parent was deleted
|
||||
# first).
|
||||
# The issue is reloaded by the nested_set before being deleted so
|
||||
# the lock_version condition should not be an issue but we handle it.
|
||||
def destroy
|
||||
super
|
||||
rescue ActiveRecord::StaleObjectError
|
||||
# Stale or already deleted
|
||||
begin
|
||||
reload
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
# The issue was actually already deleted
|
||||
@destroyed = true
|
||||
return freeze
|
||||
end
|
||||
# The issue was stale, retry to destroy
|
||||
super
|
||||
end
|
||||
|
||||
# Overrides Redmine::Acts::Customizable::InstanceMethods#available_custom_fields
|
||||
def available_custom_fields
|
||||
(project && tracker) ? (project.all_issue_custom_fields & tracker.custom_fields.all) : []
|
||||
end
|
||||
|
||||
# Copies attributes from another issue, arg can be an id or an Issue
|
||||
def copy_from(arg, options={})
|
||||
def copy_from(arg)
|
||||
issue = arg.is_a?(Issue) ? arg : Issue.visible.find(arg)
|
||||
self.attributes = issue.attributes.dup.except("id", "root_id", "parent_id", "lft", "rgt", "created_on", "updated_on")
|
||||
self.custom_field_values = issue.custom_field_values.inject({}) {|h,v| h[v.custom_field_id] = v.value; h}
|
||||
self.status = issue.status
|
||||
self.author = User.current
|
||||
unless options[:attachments] == false
|
||||
self.attachments = issue.attachments.map do |attachement|
|
||||
attachement.copy(:container => self)
|
||||
end
|
||||
end
|
||||
@copied_from = issue
|
||||
self
|
||||
end
|
||||
|
||||
# Returns an unsaved copy of the issue
|
||||
def copy(attributes=nil, copy_options={})
|
||||
copy = self.class.new.copy_from(self, copy_options)
|
||||
copy.attributes = attributes if attributes
|
||||
copy
|
||||
end
|
||||
|
||||
# Returns true if the issue is a copy
|
||||
def copy?
|
||||
@copied_from.present?
|
||||
end
|
||||
|
||||
# Moves/copies an issue to a new project and tracker
|
||||
# Returns the moved/copied issue on success, false on failure
|
||||
def move_to_project(new_project, new_tracker=nil, options={})
|
||||
ActiveSupport::Deprecation.warn "Issue#move_to_project is deprecated, use #project= instead."
|
||||
def move_to_project(*args)
|
||||
ret = Issue.transaction do
|
||||
move_to_project_without_transaction(*args) || raise(ActiveRecord::Rollback)
|
||||
end || false
|
||||
end
|
||||
|
||||
if options[:copy]
|
||||
issue = self.copy
|
||||
else
|
||||
issue = self
|
||||
def move_to_project_without_transaction(new_project, new_tracker = nil, options = {})
|
||||
options ||= {}
|
||||
issue = options[:copy] ? self.class.new.copy_from(self) : self
|
||||
|
||||
if new_project && issue.project_id != new_project.id
|
||||
# delete issue relations
|
||||
unless Setting.cross_project_issue_relations?
|
||||
issue.relations_from.clear
|
||||
issue.relations_to.clear
|
||||
end
|
||||
# issue is moved to another project
|
||||
# reassign to the category with same name if any
|
||||
new_category = issue.category.nil? ? nil : new_project.issue_categories.find_by_name(issue.category.name)
|
||||
issue.category = new_category
|
||||
# Keep the fixed_version if it's still valid in the new_project
|
||||
unless new_project.shared_versions.include?(issue.fixed_version)
|
||||
issue.fixed_version = nil
|
||||
end
|
||||
issue.project = new_project
|
||||
if issue.parent && issue.parent.project_id != issue.project_id
|
||||
issue.parent_issue_id = nil
|
||||
end
|
||||
end
|
||||
|
||||
issue.init_journal(User.current, options[:notes])
|
||||
|
||||
# Preserve previous behaviour
|
||||
# #move_to_project doesn't change tracker automatically
|
||||
issue.send :project=, new_project, true
|
||||
if new_tracker
|
||||
issue.tracker = new_tracker
|
||||
issue.reset_custom_values!
|
||||
end
|
||||
if options[:copy]
|
||||
issue.author = User.current
|
||||
issue.custom_field_values = self.custom_field_values.inject({}) {|h,v| h[v.custom_field_id] = v.value; h}
|
||||
issue.status = if options[:attributes] && options[:attributes][:status_id]
|
||||
IssueStatus.find_by_id(options[:attributes][:status_id])
|
||||
else
|
||||
self.status
|
||||
end
|
||||
end
|
||||
# Allow bulk setting of attributes on the issue
|
||||
if options[:attributes]
|
||||
issue.attributes = options[:attributes]
|
||||
end
|
||||
if issue.save
|
||||
if options[:copy]
|
||||
if current_journal && current_journal.notes.present?
|
||||
issue.init_journal(current_journal.user, current_journal.notes)
|
||||
issue.current_journal.notify = false
|
||||
issue.save
|
||||
end
|
||||
else
|
||||
# Manually update project_id on related time entries
|
||||
TimeEntry.update_all("project_id = #{new_project.id}", {:issue_id => id})
|
||||
|
||||
issue.save ? issue : false
|
||||
issue.children.each do |child|
|
||||
unless child.move_to_project_without_transaction(new_project)
|
||||
# Move failed and transaction was rollback'd
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
return false
|
||||
end
|
||||
issue
|
||||
end
|
||||
|
||||
def status_id=(sid)
|
||||
@@ -215,16 +224,6 @@ class Issue < ActiveRecord::Base
|
||||
write_attribute(:priority_id, pid)
|
||||
end
|
||||
|
||||
def category_id=(cid)
|
||||
self.category = nil
|
||||
write_attribute(:category_id, cid)
|
||||
end
|
||||
|
||||
def fixed_version_id=(vid)
|
||||
self.fixed_version = nil
|
||||
write_attribute(:fixed_version_id, vid)
|
||||
end
|
||||
|
||||
def tracker_id=(tid)
|
||||
self.tracker = nil
|
||||
result = write_attribute(:tracker_id, tid)
|
||||
@@ -232,35 +231,6 @@ class Issue < ActiveRecord::Base
|
||||
result
|
||||
end
|
||||
|
||||
def project_id=(project_id)
|
||||
if project_id.to_s != self.project_id.to_s
|
||||
self.project = (project_id.present? ? Project.find_by_id(project_id) : nil)
|
||||
end
|
||||
end
|
||||
|
||||
def project=(project, keep_tracker=false)
|
||||
project_was = self.project
|
||||
write_attribute(:project_id, project ? project.id : nil)
|
||||
association_instance_set('project', project)
|
||||
if project_was && project && project_was != project
|
||||
unless keep_tracker || project.trackers.include?(tracker)
|
||||
self.tracker = project.trackers.first
|
||||
end
|
||||
# Reassign to the category with same name if any
|
||||
if category
|
||||
self.category = project.issue_categories.find_by_name(category.name)
|
||||
end
|
||||
# Keep the fixed_version if it's still valid in the new_project
|
||||
if fixed_version && fixed_version.project != project && !project.shared_versions.include?(fixed_version)
|
||||
self.fixed_version = nil
|
||||
end
|
||||
if parent && parent.project_id != project_id
|
||||
self.parent_issue_id = nil
|
||||
end
|
||||
@custom_field_values = nil
|
||||
end
|
||||
end
|
||||
|
||||
def description=(arg)
|
||||
if arg.is_a?(String)
|
||||
arg = arg.gsub(/(\r\n|\n|\r)/, "\r\n")
|
||||
@@ -268,38 +238,25 @@ class Issue < ActiveRecord::Base
|
||||
write_attribute(:description, arg)
|
||||
end
|
||||
|
||||
# Overrides attributes= so that project and tracker get assigned first
|
||||
def attributes_with_project_and_tracker_first=(new_attributes, *args)
|
||||
# Overrides attributes= so that tracker_id gets assigned first
|
||||
def attributes_with_tracker_first=(new_attributes, *args)
|
||||
return if new_attributes.nil?
|
||||
attrs = new_attributes.dup
|
||||
attrs.stringify_keys!
|
||||
|
||||
%w(project project_id tracker tracker_id).each do |attr|
|
||||
if attrs.has_key?(attr)
|
||||
send "#{attr}=", attrs.delete(attr)
|
||||
end
|
||||
new_tracker_id = new_attributes['tracker_id'] || new_attributes[:tracker_id]
|
||||
if new_tracker_id
|
||||
self.tracker_id = new_tracker_id
|
||||
end
|
||||
send :attributes_without_project_and_tracker_first=, attrs, *args
|
||||
send :attributes_without_tracker_first=, new_attributes, *args
|
||||
end
|
||||
# Do not redefine alias chain on reload (see #4838)
|
||||
alias_method_chain(:attributes=, :project_and_tracker_first) unless method_defined?(:attributes_without_project_and_tracker_first=)
|
||||
alias_method_chain(:attributes=, :tracker_first) unless method_defined?(:attributes_without_tracker_first=)
|
||||
|
||||
def estimated_hours=(h)
|
||||
write_attribute :estimated_hours, (h.is_a?(String) ? h.to_hours : h)
|
||||
end
|
||||
|
||||
safe_attributes 'project_id',
|
||||
:if => lambda {|issue, user|
|
||||
if issue.new_record?
|
||||
issue.copy?
|
||||
elsif user.allowed_to?(:move_issues, issue.project)
|
||||
projects = Issue.allowed_target_projects_on_move(user)
|
||||
projects.include?(issue.project) && projects.size > 1
|
||||
end
|
||||
}
|
||||
|
||||
safe_attributes 'tracker_id',
|
||||
'status_id',
|
||||
'parent_issue_id',
|
||||
'category_id',
|
||||
'assigned_to_id',
|
||||
'priority_id',
|
||||
@@ -319,26 +276,19 @@ class Issue < ActiveRecord::Base
|
||||
'assigned_to_id',
|
||||
'fixed_version_id',
|
||||
'done_ratio',
|
||||
'lock_version',
|
||||
:if => lambda {|issue, user| issue.new_statuses_allowed_to(user).any? }
|
||||
|
||||
safe_attributes 'watcher_user_ids',
|
||||
:if => lambda {|issue, user| issue.new_record? && user.allowed_to?(:add_issue_watchers, issue.project)}
|
||||
|
||||
safe_attributes 'is_private',
|
||||
:if => lambda {|issue, user|
|
||||
user.allowed_to?(:set_issues_private, issue.project) ||
|
||||
(issue.author == user && user.allowed_to?(:set_own_issues_private, issue.project))
|
||||
}
|
||||
|
||||
safe_attributes 'parent_issue_id',
|
||||
:if => lambda {|issue, user| (issue.new_record? || user.allowed_to?(:edit_issues, issue.project)) &&
|
||||
user.allowed_to?(:manage_subtasks, issue.project)}
|
||||
|
||||
# Safely sets attributes
|
||||
# Should be called from controllers instead of #attributes=
|
||||
# attr_accessible is too rough because we still want things like
|
||||
# Issue.new(:project => foo) to work
|
||||
# TODO: move workflow/permission checks from controllers to here
|
||||
def safe_attributes=(attrs, user=User.current)
|
||||
return unless attrs.is_a?(Hash)
|
||||
|
||||
@@ -346,13 +296,7 @@ class Issue < ActiveRecord::Base
|
||||
attrs = delete_unsafe_attributes(attrs, user)
|
||||
return if attrs.empty?
|
||||
|
||||
# Project and Tracker must be set before since new_statuses_allowed_to depends on it.
|
||||
if p = attrs.delete('project_id')
|
||||
if allowed_target_projects(user).collect(&:id).include?(p.to_i)
|
||||
self.project_id = p
|
||||
end
|
||||
end
|
||||
|
||||
# Tracker must be set before since new_statuses_allowed_to depends on it.
|
||||
if t = attrs.delete('tracker_id')
|
||||
self.tracker_id = t
|
||||
end
|
||||
@@ -367,12 +311,15 @@ class Issue < ActiveRecord::Base
|
||||
attrs.reject! {|k,v| %w(priority_id done_ratio start_date due_date estimated_hours).include?(k)}
|
||||
end
|
||||
|
||||
if attrs['parent_issue_id'].present?
|
||||
attrs.delete('parent_issue_id') unless Issue.visible(user).exists?(attrs['parent_issue_id'].to_i)
|
||||
if attrs.has_key?('parent_issue_id')
|
||||
if !user.allowed_to?(:manage_subtasks, project)
|
||||
attrs.delete('parent_issue_id')
|
||||
elsif !attrs['parent_issue_id'].blank?
|
||||
attrs.delete('parent_issue_id') unless Issue.visible(user).exists?(attrs['parent_issue_id'].to_i)
|
||||
end
|
||||
end
|
||||
|
||||
# mass-assignment security bypass
|
||||
self.send :attributes=, attrs, false
|
||||
self.attributes = attrs
|
||||
end
|
||||
|
||||
def done_ratio
|
||||
@@ -446,27 +393,15 @@ class Issue < ActiveRecord::Base
|
||||
|
||||
def init_journal(user, notes = "")
|
||||
@current_journal ||= Journal.new(:journalized => self, :user => user, :notes => notes)
|
||||
if new_record?
|
||||
@current_journal.notify = false
|
||||
else
|
||||
@attributes_before_change = attributes.dup
|
||||
@custom_values_before_change = {}
|
||||
self.custom_field_values.each {|c| @custom_values_before_change.store c.custom_field_id, c.value }
|
||||
end
|
||||
@issue_before_change = self.clone
|
||||
@issue_before_change.status = self.status
|
||||
@custom_values_before_change = {}
|
||||
self.custom_values.each {|c| @custom_values_before_change.store c.custom_field_id, c.value }
|
||||
# Make sure updated_on is updated when adding a note.
|
||||
updated_on_will_change!
|
||||
@current_journal
|
||||
end
|
||||
|
||||
# Returns the id of the last journal or nil
|
||||
def last_journal_id
|
||||
if new_record?
|
||||
nil
|
||||
else
|
||||
journals.first(:order => "#{Journal.table_name}.id DESC").try(:id)
|
||||
end
|
||||
end
|
||||
|
||||
# Return true if the issue is closed, otherwise false
|
||||
def closed?
|
||||
self.status.is_closed?
|
||||
@@ -531,72 +466,46 @@ class Issue < ActiveRecord::Base
|
||||
!relations_to.detect {|ir| ir.relation_type == 'blocks' && !ir.issue_from.closed?}.nil?
|
||||
end
|
||||
|
||||
# Returns an array of statuses that user is able to apply
|
||||
def new_statuses_allowed_to(user=User.current, include_default=false)
|
||||
if new_record? && @copied_from
|
||||
[IssueStatus.default, @copied_from.status].compact.uniq.sort
|
||||
else
|
||||
initial_status = nil
|
||||
if new_record?
|
||||
initial_status = IssueStatus.default
|
||||
elsif status_id_was
|
||||
initial_status = IssueStatus.find_by_id(status_id_was)
|
||||
end
|
||||
initial_status ||= status
|
||||
|
||||
statuses = initial_status.find_new_statuses_allowed_to(
|
||||
user.admin ? Role.all : user.roles_for_project(project),
|
||||
tracker,
|
||||
author == user,
|
||||
assigned_to_id_changed? ? assigned_to_id_was == user.id : assigned_to_id == user.id
|
||||
)
|
||||
statuses << initial_status unless statuses.empty?
|
||||
statuses << IssueStatus.default if include_default
|
||||
statuses = statuses.compact.uniq.sort
|
||||
blocked? ? statuses.reject {|s| s.is_closed?} : statuses
|
||||
end
|
||||
end
|
||||
|
||||
def assigned_to_was
|
||||
if assigned_to_id_changed? && assigned_to_id_was.present?
|
||||
@assigned_to_was ||= User.find_by_id(assigned_to_id_was)
|
||||
end
|
||||
# Returns an array of status that user is able to apply
|
||||
def new_statuses_allowed_to(user, include_default=false)
|
||||
statuses = status.find_new_statuses_allowed_to(
|
||||
user.roles_for_project(project),
|
||||
tracker,
|
||||
author == user,
|
||||
assigned_to_id_changed? ? assigned_to_id_was == user.id : assigned_to_id == user.id
|
||||
)
|
||||
statuses << status unless statuses.empty?
|
||||
statuses << IssueStatus.default if include_default
|
||||
statuses = statuses.uniq.sort
|
||||
blocked? ? statuses.reject {|s| s.is_closed?} : statuses
|
||||
end
|
||||
|
||||
# Returns the mail adresses of users that should be notified
|
||||
def recipients
|
||||
notified = []
|
||||
notified = project.notified_users
|
||||
# Author and assignee are always notified unless they have been
|
||||
# locked or don't want to be notified
|
||||
notified << author if author
|
||||
notified << author if author && author.active? && author.notify_about?(self)
|
||||
if assigned_to
|
||||
notified += (assigned_to.is_a?(Group) ? assigned_to.users : [assigned_to])
|
||||
if assigned_to.is_a?(Group)
|
||||
notified += assigned_to.users.select {|u| u.active? && u.notify_about?(self)}
|
||||
else
|
||||
notified << assigned_to if assigned_to.active? && assigned_to.notify_about?(self)
|
||||
end
|
||||
end
|
||||
if assigned_to_was
|
||||
notified += (assigned_to_was.is_a?(Group) ? assigned_to_was.users : [assigned_to_was])
|
||||
end
|
||||
notified = notified.select {|u| u.active? && u.notify_about?(self)}
|
||||
|
||||
notified += project.notified_users
|
||||
notified.uniq!
|
||||
# Remove users that can not view the issue
|
||||
notified.reject! {|user| !visible?(user)}
|
||||
notified.collect(&:mail)
|
||||
end
|
||||
|
||||
# Returns the number of hours spent on this issue
|
||||
def spent_hours
|
||||
@spent_hours ||= time_entries.sum(:hours) || 0
|
||||
end
|
||||
|
||||
# Returns the total number of hours spent on this issue and its descendants
|
||||
#
|
||||
# Example:
|
||||
# spent_hours => 0.0
|
||||
# spent_hours => 50.2
|
||||
def total_spent_hours
|
||||
@total_spent_hours ||= self_and_descendants.sum("#{TimeEntry.table_name}.hours",
|
||||
:joins => "LEFT JOIN #{TimeEntry.table_name} ON #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id").to_f || 0.0
|
||||
def spent_hours
|
||||
@spent_hours ||= self_and_descendants.sum("#{TimeEntry.table_name}.hours", :include => :time_entries).to_f || 0.0
|
||||
end
|
||||
|
||||
def relations
|
||||
@@ -613,16 +522,6 @@ class Issue < ActiveRecord::Base
|
||||
end
|
||||
end
|
||||
|
||||
# Preloads visible spent time for a collection of issues
|
||||
def self.load_visible_spent_hours(issues, user=User.current)
|
||||
if issues.any?
|
||||
hours_by_issue_id = TimeEntry.visible(user).sum(:hours, :group => :issue_id)
|
||||
issues.each do |issue|
|
||||
issue.instance_variable_set "@spent_hours", (hours_by_issue_id[issue.id] || 0)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Finds an issue relation given its id.
|
||||
def find_relation(relation_id)
|
||||
IssueRelation.find(relation_id, :conditions => ["issue_to_id = ? OR issue_from_id = ?", id, id])
|
||||
@@ -672,13 +571,7 @@ class Issue < ActiveRecord::Base
|
||||
if leaf?
|
||||
if start_date.nil? || start_date < date
|
||||
self.start_date, self.due_date = date, date + duration
|
||||
begin
|
||||
save
|
||||
rescue ActiveRecord::StaleObjectError
|
||||
reload
|
||||
self.start_date, self.due_date = date, date + duration
|
||||
save
|
||||
end
|
||||
save
|
||||
end
|
||||
else
|
||||
leaves.each do |leaf|
|
||||
@@ -714,7 +607,8 @@ class Issue < ActiveRecord::Base
|
||||
s
|
||||
end
|
||||
|
||||
# Saves an issue and a time_entry from the parameters
|
||||
# Saves an issue, time_entry, attachments, and a journal from the parameters
|
||||
# Returns false if save fails
|
||||
def save_issue_with_child_records(params, existing_time_entry=nil)
|
||||
Issue.transaction do
|
||||
if params[:time_entry] && (params[:time_entry][:hours].present? || params[:time_entry][:comments].present?) && User.current.allowed_to?(:log_time, project)
|
||||
@@ -727,13 +621,22 @@ class Issue < ActiveRecord::Base
|
||||
self.time_entries << @time_entry
|
||||
end
|
||||
|
||||
# TODO: Rename hook
|
||||
Redmine::Hook.call_hook(:controller_issues_edit_before_save, { :params => params, :issue => self, :time_entry => @time_entry, :journal => @current_journal})
|
||||
if save
|
||||
if valid?
|
||||
attachments = Attachment.attach_files(self, params[:attachments])
|
||||
# TODO: Rename hook
|
||||
Redmine::Hook.call_hook(:controller_issues_edit_after_save, { :params => params, :issue => self, :time_entry => @time_entry, :journal => @current_journal})
|
||||
else
|
||||
raise ActiveRecord::Rollback
|
||||
Redmine::Hook.call_hook(:controller_issues_edit_before_save, { :params => params, :issue => self, :time_entry => @time_entry, :journal => @current_journal})
|
||||
begin
|
||||
if save
|
||||
# TODO: Rename hook
|
||||
Redmine::Hook.call_hook(:controller_issues_edit_after_save, { :params => params, :issue => self, :time_entry => @time_entry, :journal => @current_journal})
|
||||
else
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
rescue ActiveRecord::StaleObjectError
|
||||
attachments[:files].each(&:destroy)
|
||||
errors.add :base, l(:notice_locking_conflict)
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -823,42 +726,24 @@ class Issue < ActiveRecord::Base
|
||||
end
|
||||
# End ReportsController extraction
|
||||
|
||||
# Returns an array of projects that user can assign the issue to
|
||||
def allowed_target_projects(user=User.current)
|
||||
if new_record?
|
||||
Project.all(:conditions => Project.allowed_to_condition(user, :add_issues))
|
||||
else
|
||||
self.class.allowed_target_projects_on_move(user)
|
||||
# Returns an array of projects that current user can move issues to
|
||||
def self.allowed_target_projects_on_move
|
||||
projects = []
|
||||
if User.current.admin?
|
||||
# admin is allowed to move issues to any active (visible) project
|
||||
projects = Project.visible.all
|
||||
elsif User.current.logged?
|
||||
if Role.non_member.allowed_to?(:move_issues)
|
||||
projects = Project.visible.all
|
||||
else
|
||||
User.current.memberships.each {|m| projects << m.project if m.roles.detect {|r| r.allowed_to?(:move_issues)}}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Returns an array of projects that user can move issues to
|
||||
def self.allowed_target_projects_on_move(user=User.current)
|
||||
Project.all(:conditions => Project.allowed_to_condition(user, :move_issues))
|
||||
projects
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def after_project_change
|
||||
# Update project_id on related time entries
|
||||
TimeEntry.update_all(["project_id = ?", project_id], {:issue_id => id})
|
||||
|
||||
# Delete issue relations
|
||||
unless Setting.cross_project_issue_relations?
|
||||
relations_from.clear
|
||||
relations_to.clear
|
||||
end
|
||||
|
||||
# Move subtasks
|
||||
children.each do |child|
|
||||
# Change project and keep project
|
||||
child.send :project=, project, true
|
||||
unless child.save
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def update_nested_set_attributes
|
||||
if root_id.nil?
|
||||
# issue was just created
|
||||
@@ -913,7 +798,7 @@ class Issue < ActiveRecord::Base
|
||||
def recalculate_attributes_for(issue_id)
|
||||
if issue_id && p = Issue.find_by_id(issue_id)
|
||||
# priority = highest priority of children
|
||||
if priority_position = p.children.maximum("#{IssuePriority.table_name}.position", :joins => :priority)
|
||||
if priority_position = p.children.maximum("#{IssuePriority.table_name}.position", :include => :priority)
|
||||
p.priority = IssuePriority.find_by_position(priority_position)
|
||||
end
|
||||
|
||||
@@ -932,7 +817,7 @@ class Issue < ActiveRecord::Base
|
||||
if average == 0
|
||||
average = 1
|
||||
end
|
||||
done = p.leaves.sum("COALESCE(estimated_hours, #{average}) * (CASE WHEN is_closed = #{connection.quoted_true} THEN 100 ELSE COALESCE(done_ratio, 0) END)", :joins => :status).to_f
|
||||
done = p.leaves.sum("COALESCE(estimated_hours, #{average}) * (CASE WHEN is_closed = #{connection.quoted_true} THEN 100 ELSE COALESCE(done_ratio, 0) END)", :include => :status).to_f
|
||||
progress = done / (average * leaves_count)
|
||||
p.done_ratio = progress.round
|
||||
end
|
||||
@@ -952,12 +837,12 @@ class Issue < ActiveRecord::Base
|
||||
def self.update_versions(conditions=nil)
|
||||
# Only need to update issues with a fixed_version from
|
||||
# a different project and that is not systemwide shared
|
||||
Issue.scoped(:conditions => conditions).all(
|
||||
:conditions => "#{Issue.table_name}.fixed_version_id IS NOT NULL" +
|
||||
" AND #{Issue.table_name}.project_id <> #{Version.table_name}.project_id" +
|
||||
" AND #{Version.table_name}.sharing <> 'system'",
|
||||
:include => [:project, :fixed_version]
|
||||
).each do |issue|
|
||||
Issue.all(:conditions => merge_conditions("#{Issue.table_name}.fixed_version_id IS NOT NULL" +
|
||||
" AND #{Issue.table_name}.project_id <> #{Version.table_name}.project_id" +
|
||||
" AND #{Version.table_name}.sharing <> 'system'",
|
||||
conditions),
|
||||
:include => [:project, :fixed_version]
|
||||
).each do |issue|
|
||||
next if issue.project.nil? || issue.fixed_version.nil?
|
||||
unless issue.project.shared_versions.include?(issue.fixed_version)
|
||||
issue.init_journal(User.current)
|
||||
@@ -976,10 +861,11 @@ class Issue < ActiveRecord::Base
|
||||
|
||||
# Callback on attachment deletion
|
||||
def attachment_removed(obj)
|
||||
if @current_journal && !obj.new_record?
|
||||
@current_journal.details << JournalDetail.new(:property => 'attachment', :prop_key => obj.id, :old_value => obj.filename)
|
||||
@current_journal.save
|
||||
end
|
||||
journal = init_journal(User.current)
|
||||
journal.details << JournalDetail.new(:property => 'attachment',
|
||||
:prop_key => obj.id,
|
||||
:old_value => obj.filename)
|
||||
journal.save
|
||||
end
|
||||
|
||||
# Default assignment based on category
|
||||
@@ -1020,50 +906,24 @@ class Issue < ActiveRecord::Base
|
||||
def create_journal
|
||||
if @current_journal
|
||||
# attributes changes
|
||||
if @attributes_before_change
|
||||
(Issue.column_names - %w(id root_id lft rgt lock_version created_on updated_on)).each {|c|
|
||||
before = @attributes_before_change[c]
|
||||
after = send(c)
|
||||
next if before == after || (before.blank? && after.blank?)
|
||||
@current_journal.details << JournalDetail.new(:property => 'attr',
|
||||
:prop_key => c,
|
||||
:old_value => before,
|
||||
:value => after)
|
||||
}
|
||||
end
|
||||
if @custom_values_before_change
|
||||
# custom fields changes
|
||||
custom_field_values.each {|c|
|
||||
before = @custom_values_before_change[c.custom_field_id]
|
||||
after = c.value
|
||||
next if before == after || (before.blank? && after.blank?)
|
||||
|
||||
if before.is_a?(Array) || after.is_a?(Array)
|
||||
before = [before] unless before.is_a?(Array)
|
||||
after = [after] unless after.is_a?(Array)
|
||||
|
||||
# values removed
|
||||
(before - after).reject(&:blank?).each do |value|
|
||||
@current_journal.details << JournalDetail.new(:property => 'cf',
|
||||
:prop_key => c.custom_field_id,
|
||||
:old_value => value,
|
||||
:value => nil)
|
||||
end
|
||||
# values added
|
||||
(after - before).reject(&:blank?).each do |value|
|
||||
@current_journal.details << JournalDetail.new(:property => 'cf',
|
||||
:prop_key => c.custom_field_id,
|
||||
:old_value => nil,
|
||||
:value => value)
|
||||
end
|
||||
else
|
||||
@current_journal.details << JournalDetail.new(:property => 'cf',
|
||||
:prop_key => c.custom_field_id,
|
||||
:old_value => before,
|
||||
:value => after)
|
||||
end
|
||||
}
|
||||
end
|
||||
(Issue.column_names - %w(id root_id lft rgt lock_version created_on updated_on)).each {|c|
|
||||
before = @issue_before_change.send(c)
|
||||
after = send(c)
|
||||
next if before == after || (before.blank? && after.blank?)
|
||||
@current_journal.details << JournalDetail.new(:property => 'attr',
|
||||
:prop_key => c,
|
||||
:old_value => @issue_before_change.send(c),
|
||||
:value => send(c))
|
||||
}
|
||||
# custom fields changes
|
||||
custom_values.each {|c|
|
||||
next if (@custom_values_before_change[c.custom_field_id]==c.value ||
|
||||
(@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
|
||||
# reset current journal
|
||||
init_journal @current_journal.user, @current_journal.notes
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
class IssueCategory < ActiveRecord::Base
|
||||
include Redmine::SafeAttributes
|
||||
belongs_to :project
|
||||
belongs_to :assigned_to, :class_name => 'Principal', :foreign_key => 'assigned_to_id'
|
||||
has_many :issues, :foreign_key => 'category_id', :dependent => :nullify
|
||||
@@ -25,7 +24,7 @@ class IssueCategory < ActiveRecord::Base
|
||||
validates_uniqueness_of :name, :scope => [:project_id]
|
||||
validates_length_of :name, :maximum => 30
|
||||
|
||||
safe_attributes 'name', 'assigned_to_id'
|
||||
attr_protected :project_id
|
||||
|
||||
named_scope :named, lambda {|arg| { :conditions => ["LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip]}}
|
||||
|
||||
|
||||
@@ -57,8 +57,7 @@ class IssueRelation < ActiveRecord::Base
|
||||
(issue_to.nil? || user.allowed_to?(:manage_issue_relations, issue_to.project)))
|
||||
end
|
||||
|
||||
def initialize(attributes=nil, *args)
|
||||
super
|
||||
def after_initialize
|
||||
if new_record?
|
||||
if relation_type.blank?
|
||||
self.relation_type = IssueRelation::TYPE_RELATES
|
||||
|
||||
@@ -29,9 +29,7 @@ class MailHandler < ActionMailer::Base
|
||||
|
||||
@@handler_options[:issue] ||= {}
|
||||
|
||||
if @@handler_options[:allow_override].is_a?(String)
|
||||
@@handler_options[:allow_override] = @@handler_options[:allow_override].split(',').collect(&:strip)
|
||||
end
|
||||
@@handler_options[:allow_override] = @@handler_options[:allow_override].split(',').collect(&:strip) if @@handler_options[:allow_override].is_a?(String)
|
||||
@@handler_options[:allow_override] ||= []
|
||||
# Project needs to be overridable if not specified
|
||||
@@handler_options[:allow_override] << 'project' unless @@handler_options[:issue].has_key?(:project)
|
||||
@@ -42,12 +40,6 @@ class MailHandler < ActionMailer::Base
|
||||
super email
|
||||
end
|
||||
|
||||
cattr_accessor :ignored_emails_headers
|
||||
@@ignored_emails_headers = {
|
||||
'X-Auto-Response-Suppress' => 'oof',
|
||||
'Auto-Submitted' => /^auto-/
|
||||
}
|
||||
|
||||
# Processes incoming emails
|
||||
# Returns the created object (eg. an issue, a message) or false
|
||||
def receive(email)
|
||||
@@ -55,29 +47,12 @@ class MailHandler < ActionMailer::Base
|
||||
sender_email = email.from.to_a.first.to_s.strip
|
||||
# Ignore emails received from the application emission address to avoid hell cycles
|
||||
if sender_email.downcase == Setting.mail_from.to_s.strip.downcase
|
||||
if logger && logger.info
|
||||
logger.info "MailHandler: ignoring email from Redmine emission address [#{sender_email}]"
|
||||
end
|
||||
logger.info "MailHandler: ignoring email from Redmine emission address [#{sender_email}]" if logger && logger.info
|
||||
return false
|
||||
end
|
||||
# Ignore auto generated emails
|
||||
self.class.ignored_emails_headers.each do |key, ignored_value|
|
||||
value = email.header_string(key)
|
||||
if value
|
||||
value = value.to_s.downcase
|
||||
if (ignored_value.is_a?(Regexp) && value.match(ignored_value)) || value == ignored_value
|
||||
if logger && logger.info
|
||||
logger.info "MailHandler: ignoring email with #{key}:#{value} header"
|
||||
end
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
@user = User.find_by_mail(sender_email) if sender_email.present?
|
||||
if @user && !@user.active?
|
||||
if logger && logger.info
|
||||
logger.info "MailHandler: ignoring email from non-active user [#{@user.login}]"
|
||||
end
|
||||
logger.info "MailHandler: ignoring email from non-active user [#{@user.login}]" if logger && logger.info
|
||||
return false
|
||||
end
|
||||
if @user.nil?
|
||||
@@ -86,23 +61,17 @@ class MailHandler < ActionMailer::Base
|
||||
when 'accept'
|
||||
@user = User.anonymous
|
||||
when 'create'
|
||||
@user = create_user_from_email
|
||||
@user = create_user_from_email(email)
|
||||
if @user
|
||||
if logger && logger.info
|
||||
logger.info "MailHandler: [#{@user.login}] account created"
|
||||
end
|
||||
logger.info "MailHandler: [#{@user.login}] account created" if logger && logger.info
|
||||
Mailer.deliver_account_information(@user, @user.password)
|
||||
else
|
||||
if logger && logger.error
|
||||
logger.error "MailHandler: could not create account for [#{sender_email}]"
|
||||
end
|
||||
logger.error "MailHandler: could not create account for [#{sender_email}]" if logger && logger.error
|
||||
return false
|
||||
end
|
||||
else
|
||||
# Default behaviour, emails from unknown users are ignored
|
||||
if logger && logger.info
|
||||
logger.info "MailHandler: ignoring email from unknown user [#{sender_email}]"
|
||||
end
|
||||
logger.info "MailHandler: ignoring email from unknown user [#{sender_email}]" if logger && logger.info
|
||||
return false
|
||||
end
|
||||
end
|
||||
@@ -180,10 +149,7 @@ class MailHandler < ActionMailer::Base
|
||||
return unless issue
|
||||
# check permission
|
||||
unless @@handler_options[:no_permission_check]
|
||||
unless user.allowed_to?(:add_issue_notes, issue.project) ||
|
||||
user.allowed_to?(:edit_issues, issue.project)
|
||||
raise UnauthorizedAction
|
||||
end
|
||||
raise UnauthorizedAction unless user.allowed_to?(:add_issue_notes, issue.project) || user.allowed_to?(:edit_issues, issue.project)
|
||||
end
|
||||
|
||||
# ignore CLI-supplied defaults for new issues
|
||||
@@ -195,9 +161,7 @@ class MailHandler < ActionMailer::Base
|
||||
journal.notes = cleaned_up_text_body
|
||||
add_attachments(issue)
|
||||
issue.save!
|
||||
if logger && logger.info
|
||||
logger.info "MailHandler: issue ##{issue.id} updated by #{user}"
|
||||
end
|
||||
logger.info "MailHandler: issue ##{issue.id} updated by #{user}" if logger && logger.info
|
||||
journal
|
||||
end
|
||||
|
||||
@@ -228,9 +192,7 @@ class MailHandler < ActionMailer::Base
|
||||
add_attachments(reply)
|
||||
reply
|
||||
else
|
||||
if logger && logger.info
|
||||
logger.info "MailHandler: ignoring reply from [#{sender_email}] to a locked topic"
|
||||
end
|
||||
logger.info "MailHandler: ignoring reply from [#{sender_email}] to a locked topic" if logger && logger.info
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -264,8 +226,7 @@ class MailHandler < ActionMailer::Base
|
||||
@keywords[attr]
|
||||
else
|
||||
@keywords[attr] = begin
|
||||
if (options[:override] || @@handler_options[:allow_override].include?(attr.to_s)) &&
|
||||
(v = extract_keyword!(plain_text_body, attr, options[:format]))
|
||||
if (options[:override] || @@handler_options[:allow_override].include?(attr.to_s)) && (v = extract_keyword!(plain_text_body, attr, options[:format]))
|
||||
v
|
||||
elsif !@@handler_options[:issue][attr].blank?
|
||||
@@handler_options[:issue][attr]
|
||||
@@ -279,12 +240,8 @@ class MailHandler < ActionMailer::Base
|
||||
def extract_keyword!(text, attr, format=nil)
|
||||
keys = [attr.to_s.humanize]
|
||||
if attr.is_a?(Symbol)
|
||||
if user && user.language.present?
|
||||
keys << l("field_#{attr}", :default => '', :locale => user.language)
|
||||
end
|
||||
if Setting.default_language.present?
|
||||
keys << l("field_#{attr}", :default => '', :locale => Setting.default_language)
|
||||
end
|
||||
keys << l("field_#{attr}", :default => '', :locale => user.language) if user && user.language.present?
|
||||
keys << l("field_#{attr}", :default => '', :locale => Setting.default_language) if Setting.default_language.present?
|
||||
end
|
||||
keys.reject! {|k| k.blank?}
|
||||
keys.collect! {|k| Regexp.escape(k)}
|
||||
@@ -312,8 +269,7 @@ class MailHandler < ActionMailer::Base
|
||||
'priority_id' => (k = get_keyword(:priority)) && IssuePriority.named(k).first.try(:id),
|
||||
'category_id' => (k = get_keyword(:category)) && issue.project.issue_categories.named(k).first.try(:id),
|
||||
'assigned_to_id' => assigned_to.try(:id),
|
||||
'fixed_version_id' => (k = get_keyword(:fixed_version, :override => true)) &&
|
||||
issue.project.shared_versions.named(k).first.try(:id),
|
||||
'fixed_version_id' => (k = get_keyword(:fixed_version, :override => true)) && issue.project.shared_versions.named(k).first.try(:id),
|
||||
'start_date' => get_keyword(:start_date, :override => true, :format => '\d{4}-\d{2}-\d{2}'),
|
||||
'due_date' => get_keyword(:due_date, :override => true, :format => '\d{4}-\d{2}-\d{2}'),
|
||||
'estimated_hours' => get_keyword(:estimated_hours, :override => true),
|
||||
@@ -366,8 +322,8 @@ class MailHandler < ActionMailer::Base
|
||||
@full_sanitizer ||= HTML::FullSanitizer.new
|
||||
end
|
||||
|
||||
def self.assign_string_attribute_with_limit(object, attribute, value, limit=nil)
|
||||
limit ||= object.class.columns_hash[attribute.to_s].limit || 255
|
||||
def self.assign_string_attribute_with_limit(object, attribute, value)
|
||||
limit = object.class.columns_hash[attribute.to_s].limit || 255
|
||||
value = value.to_s.slice(0, limit)
|
||||
object.send("#{attribute}=", value)
|
||||
end
|
||||
@@ -378,7 +334,7 @@ class MailHandler < ActionMailer::Base
|
||||
|
||||
# Truncating the email address would result in an invalid format
|
||||
user.mail = email_address
|
||||
assign_string_attribute_with_limit(user, 'login', email_address, User::LOGIN_LENGTH_LIMIT)
|
||||
assign_string_attribute_with_limit(user, 'login', email_address)
|
||||
|
||||
names = fullname.blank? ? email_address.gsub(/@.*$/, '').split('.') : fullname.split
|
||||
assign_string_attribute_with_limit(user, 'firstname', names.shift)
|
||||
@@ -386,13 +342,13 @@ class MailHandler < ActionMailer::Base
|
||||
user.lastname = '-' if user.lastname.blank?
|
||||
|
||||
password_length = [Setting.password_min_length.to_i, 10].max
|
||||
user.password = Redmine::Utils.random_hex(password_length / 2 + 1)
|
||||
user.password = ActiveSupport::SecureRandom.hex(password_length / 2 + 1)
|
||||
user.language = Setting.default_language
|
||||
|
||||
unless user.valid?
|
||||
user.login = "user#{Redmine::Utils.random_hex(6)}" unless user.errors[:login].blank?
|
||||
user.firstname = "-" unless user.errors[:firstname].blank?
|
||||
user.lastname = "-" unless user.errors[:lastname].blank?
|
||||
user.login = "user#{ActiveSupport::SecureRandom.hex(6)}" if user.errors.on(:login)
|
||||
user.firstname = "-" if user.errors.on(:firstname)
|
||||
user.lastname = "-" if user.errors.on(:lastname)
|
||||
end
|
||||
|
||||
user
|
||||
@@ -400,10 +356,10 @@ class MailHandler < ActionMailer::Base
|
||||
|
||||
# Creates a User for the +email+ sender
|
||||
# Returns the user or nil if it could not be created
|
||||
def create_user_from_email
|
||||
def create_user_from_email(email)
|
||||
addr = email.from_addrs.to_a.first
|
||||
if addr && !addr.spec.blank?
|
||||
user = self.class.new_user_from_attributes(addr.spec, TMail::Unquoter.unquote_and_convert_to(addr.name, 'utf-8'))
|
||||
user = self.class.new_user_from_attributes(addr.spec, addr.name)
|
||||
if user.save
|
||||
user
|
||||
else
|
||||
@@ -416,6 +372,8 @@ class MailHandler < ActionMailer::Base
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Removes the email body of text after the truncation configurations.
|
||||
def cleanup_body(body)
|
||||
delimiters = Setting.mail_handler_body_delimiters.to_s.split(/[\r\n]+/).reject(&:blank?).map {|s| Regexp.escape(s)}
|
||||
@@ -430,16 +388,10 @@ class MailHandler < ActionMailer::Base
|
||||
keyword = keyword.to_s.downcase
|
||||
assignable = issue.assignable_users
|
||||
assignee = nil
|
||||
assignee ||= assignable.detect {|a|
|
||||
a.mail.to_s.downcase == keyword ||
|
||||
a.login.to_s.downcase == keyword
|
||||
}
|
||||
assignee ||= assignable.detect {|a| a.mail.to_s.downcase == keyword || a.login.to_s.downcase == keyword}
|
||||
if assignee.nil? && keyword.match(/ /)
|
||||
firstname, lastname = *(keyword.split) # "First Last Throwaway"
|
||||
assignee ||= assignable.detect {|a|
|
||||
a.is_a?(User) && a.firstname.to_s.downcase == firstname &&
|
||||
a.lastname.to_s.downcase == lastname
|
||||
}
|
||||
assignee ||= assignable.detect {|a| a.is_a?(User) && a.firstname.to_s.downcase == firstname && a.lastname.to_s.downcase == lastname}
|
||||
end
|
||||
if assignee.nil?
|
||||
assignee ||= assignable.detect {|a| a.is_a?(Group) && a.name.downcase == keyword}
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
require 'ar_condition'
|
||||
|
||||
class Mailer < ActionMailer::Base
|
||||
layout 'mailer'
|
||||
helper :application
|
||||
@@ -41,7 +43,6 @@ class Mailer < ActionMailer::Base
|
||||
'Issue-Author' => issue.author.login
|
||||
redmine_headers 'Issue-Assignee' => issue.assigned_to.login if issue.assigned_to
|
||||
message_id issue
|
||||
@author = issue.author
|
||||
recipients issue.recipients
|
||||
cc(issue.watcher_recipients - @recipients)
|
||||
subject "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] (#{issue.status.name}) #{issue.subject}"
|
||||
@@ -98,7 +99,6 @@ class Mailer < ActionMailer::Base
|
||||
def document_added(document)
|
||||
redmine_headers 'Project' => document.project.identifier
|
||||
recipients document.recipients
|
||||
@author = User.current
|
||||
subject "[#{document.project.name}] #{l(:label_document_new)}: #{document.title}"
|
||||
body :document => document,
|
||||
:document_url => url_for(:controller => 'documents', :action => 'show', :id => document)
|
||||
@@ -114,7 +114,6 @@ class Mailer < ActionMailer::Base
|
||||
container = attachments.first.container
|
||||
added_to = ''
|
||||
added_to_url = ''
|
||||
@author = attachments.first.author
|
||||
case container.class.name
|
||||
when 'Project'
|
||||
added_to_url = url_for(:controller => 'files', :action => 'index', :project_id => container)
|
||||
@@ -144,7 +143,6 @@ class Mailer < ActionMailer::Base
|
||||
# Mailer.deliver_news_added(news) => sends an email to the news' project recipients
|
||||
def news_added(news)
|
||||
redmine_headers 'Project' => news.project.identifier
|
||||
@author = news.author
|
||||
message_id news
|
||||
recipients news.recipients
|
||||
subject "[#{news.project.name}] #{l(:label_news)}: #{news.title}"
|
||||
@@ -161,7 +159,6 @@ class Mailer < ActionMailer::Base
|
||||
def news_comment_added(comment)
|
||||
news = comment.commented
|
||||
redmine_headers 'Project' => news.project.identifier
|
||||
@author = comment.author
|
||||
message_id comment
|
||||
recipients news.recipients
|
||||
cc news.watcher_recipients
|
||||
@@ -180,7 +177,6 @@ class Mailer < ActionMailer::Base
|
||||
def message_posted(message)
|
||||
redmine_headers 'Project' => message.project.identifier,
|
||||
'Topic-Id' => (message.parent_id || message.id)
|
||||
@author = message.author
|
||||
message_id message
|
||||
references message.parent unless message.parent.nil?
|
||||
recipients(message.recipients)
|
||||
@@ -199,7 +195,6 @@ class Mailer < ActionMailer::Base
|
||||
def wiki_content_added(wiki_content)
|
||||
redmine_headers 'Project' => wiki_content.project.identifier,
|
||||
'Wiki-Page-Id' => wiki_content.page.id
|
||||
@author = wiki_content.author
|
||||
message_id wiki_content
|
||||
recipients wiki_content.recipients
|
||||
cc(wiki_content.page.wiki.watcher_recipients - recipients)
|
||||
@@ -219,7 +214,6 @@ class Mailer < ActionMailer::Base
|
||||
def wiki_content_updated(wiki_content)
|
||||
redmine_headers 'Project' => wiki_content.project.identifier,
|
||||
'Wiki-Page-Id' => wiki_content.page.id
|
||||
@author = wiki_content.author
|
||||
message_id wiki_content
|
||||
recipients wiki_content.recipients
|
||||
cc(wiki_content.page.wiki.watcher_recipients + wiki_content.page.watcher_recipients - recipients)
|
||||
@@ -297,12 +291,12 @@ class Mailer < ActionMailer::Base
|
||||
render_multipart('register', body)
|
||||
end
|
||||
|
||||
def test_email(user)
|
||||
def test(user)
|
||||
set_language_if_valid(user.language)
|
||||
recipients user.mail
|
||||
subject 'Redmine test'
|
||||
body :url => url_for(:controller => 'welcome')
|
||||
render_multipart('test_email', body)
|
||||
render_multipart('test', body)
|
||||
end
|
||||
|
||||
# Overrides default deliver! method to prevent from sending an email
|
||||
@@ -349,17 +343,18 @@ class Mailer < ActionMailer::Base
|
||||
tracker = options[:tracker] ? Tracker.find(options[:tracker]) : nil
|
||||
user_ids = options[:users]
|
||||
|
||||
scope = Issue.open.scoped(:conditions => ["#{Issue.table_name}.assigned_to_id IS NOT NULL" +
|
||||
" AND #{Project.table_name}.status = #{Project::STATUS_ACTIVE}" +
|
||||
" AND #{Issue.table_name}.due_date <= ?", days.day.from_now.to_date]
|
||||
)
|
||||
scope = scope.scoped(:conditions => {:assigned_to_id => user_ids}) if user_ids.present?
|
||||
scope = scope.scoped(:conditions => {:project_id => project.id}) if project
|
||||
scope = scope.scoped(:conditions => {:tracker_id => tracker.id}) if tracker
|
||||
s = ARCondition.new ["#{IssueStatus.table_name}.is_closed = ? AND #{Issue.table_name}.due_date <= ?", false, days.day.from_now.to_date]
|
||||
s << "#{Issue.table_name}.assigned_to_id IS NOT NULL"
|
||||
s << ["#{Issue.table_name}.assigned_to_id IN (?)", user_ids] if user_ids.present?
|
||||
s << "#{Project.table_name}.status = #{Project::STATUS_ACTIVE}"
|
||||
s << "#{Issue.table_name}.project_id = #{project.id}" if project
|
||||
s << "#{Issue.table_name}.tracker_id = #{tracker.id}" if tracker
|
||||
|
||||
issues_by_assignee = scope.all(:include => [:status, :assigned_to, :project, :tracker]).group_by(&:assigned_to)
|
||||
issues_by_assignee = Issue.find(:all, :include => [:status, :assigned_to, :project, :tracker],
|
||||
:conditions => s.conditions
|
||||
).group_by(&:assigned_to)
|
||||
issues_by_assignee.each do |assignee, issues|
|
||||
deliver_reminder(assignee, issues, days) if assignee.is_a?(User) && assignee.active?
|
||||
deliver_reminder(assignee, issues, days) if assignee && assignee.active?
|
||||
end
|
||||
end
|
||||
|
||||
@@ -372,17 +367,6 @@ class Mailer < ActionMailer::Base
|
||||
ActionMailer::Base.perform_deliveries = was_enabled
|
||||
end
|
||||
|
||||
# Sends emails synchronously in the given block
|
||||
def self.with_synched_deliveries(&block)
|
||||
saved_method = ActionMailer::Base.delivery_method
|
||||
if m = saved_method.to_s.match(%r{^async_(.+)$})
|
||||
ActionMailer::Base.delivery_method = m[1].to_sym
|
||||
end
|
||||
yield
|
||||
ensure
|
||||
ActionMailer::Base.delivery_method = saved_method
|
||||
end
|
||||
|
||||
private
|
||||
def initialize_defaults(method_name)
|
||||
super
|
||||
@@ -395,30 +379,22 @@ class Mailer < ActionMailer::Base
|
||||
'X-Redmine-Host' => Setting.host_name,
|
||||
'X-Redmine-Site' => Setting.app_title,
|
||||
'X-Auto-Response-Suppress' => 'OOF',
|
||||
'Auto-Submitted' => 'auto-generated',
|
||||
'List-Id' => "<#{Setting.mail_from.to_s.gsub('@', '.')}>"
|
||||
'Auto-Submitted' => 'auto-generated'
|
||||
end
|
||||
|
||||
# Appends a Redmine header field (name is prepended with 'X-Redmine-')
|
||||
def redmine_headers(h)
|
||||
h.each { |k,v| headers["X-Redmine-#{k}"] = v.to_s }
|
||||
h.each { |k,v| headers["X-Redmine-#{k}"] = v }
|
||||
end
|
||||
|
||||
# Overrides the create_mail method
|
||||
def create_mail
|
||||
# Removes the author from the recipients and cc
|
||||
# Removes the current user from the recipients and cc
|
||||
# if he doesn't want to receive notifications about what he does
|
||||
if @author && @author.logged? && @author.pref[:no_self_notified]
|
||||
if recipients
|
||||
recipients((recipients.is_a?(Array) ? recipients : [recipients]) - [@author.mail])
|
||||
end
|
||||
if cc
|
||||
cc((cc.is_a?(Array) ? cc : [cc]) - [@author.mail])
|
||||
end
|
||||
end
|
||||
|
||||
if @author && @author.logged?
|
||||
redmine_headers 'Sender' => @author.login
|
||||
@author ||= User.current
|
||||
if @author.pref[:no_self_notified]
|
||||
recipients.delete(@author.mail) if recipients
|
||||
cc.delete(@author.mail) if cc
|
||||
end
|
||||
|
||||
notified_users = [recipients, cc].flatten.compact.uniq
|
||||
|
||||
@@ -24,17 +24,9 @@ class Member < ActiveRecord::Base
|
||||
|
||||
validates_presence_of :principal, :project
|
||||
validates_uniqueness_of :user_id, :scope => :project_id
|
||||
validate :validate_role
|
||||
|
||||
before_destroy :set_issue_category_nil
|
||||
after_destroy :unwatch_from_permission_change
|
||||
|
||||
def role
|
||||
end
|
||||
|
||||
def role=
|
||||
end
|
||||
|
||||
def name
|
||||
self.user.name
|
||||
end
|
||||
@@ -58,17 +50,7 @@ class Member < ActiveRecord::Base
|
||||
|
||||
def <=>(member)
|
||||
a, b = roles.sort.first, member.roles.sort.first
|
||||
if a == b
|
||||
if principal
|
||||
principal <=> member.principal
|
||||
else
|
||||
1
|
||||
end
|
||||
elsif a
|
||||
a <=> b
|
||||
else
|
||||
1
|
||||
end
|
||||
a == b ? (principal <=> member.principal) : (a <=> b)
|
||||
end
|
||||
|
||||
def deletable?
|
||||
@@ -83,7 +65,7 @@ class Member < ActiveRecord::Base
|
||||
end
|
||||
end
|
||||
|
||||
def set_issue_category_nil
|
||||
def before_destroy
|
||||
if user
|
||||
# remove category based auto assignments for this member
|
||||
IssueCategory.update_all "assigned_to_id = NULL", ["project_id = ? AND assigned_to_id = ?", project.id, user.id]
|
||||
@@ -99,7 +81,7 @@ class Member < ActiveRecord::Base
|
||||
|
||||
protected
|
||||
|
||||
def validate_role
|
||||
def validate
|
||||
errors.add_on_empty :role if member_roles.empty? && roles.empty?
|
||||
end
|
||||
|
||||
|
||||
@@ -25,9 +25,8 @@ class MemberRole < ActiveRecord::Base
|
||||
after_destroy :remove_role_from_group_users
|
||||
|
||||
validates_presence_of :role
|
||||
validate :validate_role_member
|
||||
|
||||
def validate_role_member
|
||||
def validate
|
||||
errors.add :role_id, :invalid if role && !role.member?
|
||||
end
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user