Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c549c8b721 | ||
|
|
eaaa471d6a | ||
|
|
2b6d8125d1 | ||
|
|
12b75ded08 | ||
|
|
46bf2b9276 | ||
|
|
9d82bff1a8 | ||
|
|
4c75864948 | ||
|
|
9d2474c234 | ||
|
|
7193817dea | ||
|
|
610d7d4ba4 | ||
|
|
4b41788848 | ||
|
|
e1423c7c23 | ||
|
|
9c9f6722f6 | ||
|
|
a8be47295a | ||
|
|
ba98197637 | ||
|
|
a23399f220 | ||
|
|
718cd596e0 | ||
|
|
c5ccfede6d | ||
|
|
7702bdcdab |
No files matched your search
@@ -17,4 +17,3 @@
|
|||||||
/tmp/sockets/*
|
/tmp/sockets/*
|
||||||
/tmp/test/*
|
/tmp/test/*
|
||||||
/vendor/rails
|
/vendor/rails
|
||||||
*.rbc
|
|
||||||
+1
-1
@@ -2,4 +2,4 @@
|
|||||||
|
|
||||||
Redmine is a flexible project management web application written using Ruby on Rails framework.
|
Redmine is a flexible project management web application written using Ruby on Rails framework.
|
||||||
|
|
||||||
More details can be found at in the doc directory or on the official website http://www.redmine.org
|
More details can be found at http://www.redmine.org
|
||||||
@@ -25,15 +25,23 @@ class AccountController < ApplicationController
|
|||||||
# Login request and validation
|
# Login request and validation
|
||||||
def login
|
def login
|
||||||
if request.get?
|
if request.get?
|
||||||
logout_user
|
# Logout user
|
||||||
|
self.logged_user = nil
|
||||||
else
|
else
|
||||||
authenticate_user
|
# Authenticate user
|
||||||
|
if Setting.openid? && using_open_id?
|
||||||
|
open_id_authenticate(params[:openid_url])
|
||||||
|
else
|
||||||
|
password_authentication
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Log out current user and redirect to welcome page
|
# Log out current user and redirect to welcome page
|
||||||
def logout
|
def logout
|
||||||
logout_user
|
cookies.delete :autologin
|
||||||
|
Token.delete_all(["user_id = ? AND action = ?", User.current.id, 'autologin']) if User.current.logged?
|
||||||
|
self.logged_user = nil
|
||||||
redirect_to home_url
|
redirect_to home_url
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -83,9 +91,9 @@ class AccountController < ApplicationController
|
|||||||
else
|
else
|
||||||
@user = User.new(params[:user])
|
@user = User.new(params[:user])
|
||||||
@user.admin = false
|
@user.admin = false
|
||||||
@user.register
|
@user.status = User::STATUS_REGISTERED
|
||||||
if session[:auth_source_registration]
|
if session[:auth_source_registration]
|
||||||
@user.activate
|
@user.status = User::STATUS_ACTIVE
|
||||||
@user.login = session[:auth_source_registration][:login]
|
@user.login = session[:auth_source_registration][:login]
|
||||||
@user.auth_source_id = session[:auth_source_registration][:auth_source_id]
|
@user.auth_source_id = session[:auth_source_registration][:auth_source_id]
|
||||||
if @user.save
|
if @user.save
|
||||||
@@ -116,8 +124,8 @@ class AccountController < ApplicationController
|
|||||||
token = Token.find_by_action_and_value('register', params[:token])
|
token = Token.find_by_action_and_value('register', params[:token])
|
||||||
redirect_to(home_url) && return unless token and !token.expired?
|
redirect_to(home_url) && return unless token and !token.expired?
|
||||||
user = token.user
|
user = token.user
|
||||||
redirect_to(home_url) && return unless user.registered?
|
redirect_to(home_url) && return unless user.status == User::STATUS_REGISTERED
|
||||||
user.activate
|
user.status = User::STATUS_ACTIVE
|
||||||
if user.save
|
if user.save
|
||||||
token.destroy
|
token.destroy
|
||||||
flash[:notice] = l(:notice_account_activated)
|
flash[:notice] = l(:notice_account_activated)
|
||||||
@@ -127,22 +135,6 @@ class AccountController < ApplicationController
|
|||||||
|
|
||||||
private
|
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])
|
|
||||||
else
|
|
||||||
password_authentication
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def password_authentication
|
def password_authentication
|
||||||
user = User.try_to_login(params[:username], params[:password])
|
user = User.try_to_login(params[:username], params[:password])
|
||||||
|
|
||||||
@@ -170,7 +162,7 @@ class AccountController < ApplicationController
|
|||||||
user.mail = registration['email'] unless registration['email'].nil?
|
user.mail = registration['email'] unless registration['email'].nil?
|
||||||
user.firstname, user.lastname = registration['fullname'].split(' ') unless registration['fullname'].nil?
|
user.firstname, user.lastname = registration['fullname'].split(' ') unless registration['fullname'].nil?
|
||||||
user.random_password
|
user.random_password
|
||||||
user.register
|
user.status = User::STATUS_REGISTERED
|
||||||
|
|
||||||
case Setting.self_registration
|
case Setting.self_registration
|
||||||
when '1'
|
when '1'
|
||||||
@@ -218,7 +210,6 @@ class AccountController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def invalid_credentials
|
def invalid_credentials
|
||||||
logger.warn "Failed login for '#{params[:username]}' from #{request.remote_ip} at #{Time.now.utc}"
|
|
||||||
flash.now[:error] = l(:notice_account_invalid_creditentials)
|
flash.now[:error] = l(:notice_account_invalid_creditentials)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -241,7 +232,7 @@ class AccountController < ApplicationController
|
|||||||
# Pass a block for behavior when a user fails to save
|
# Pass a block for behavior when a user fails to save
|
||||||
def register_automatically(user, &block)
|
def register_automatically(user, &block)
|
||||||
# Automatic activation
|
# Automatic activation
|
||||||
user.activate
|
user.status = User::STATUS_ACTIVE
|
||||||
user.last_login_on = Time.now
|
user.last_login_on = Time.now
|
||||||
if user.save
|
if user.save
|
||||||
self.logged_user = user
|
self.logged_user = user
|
||||||
|
|||||||
@@ -1,59 +0,0 @@
|
|||||||
class ActivitiesController < ApplicationController
|
|
||||||
menu_item :activity
|
|
||||||
before_filter :find_optional_project
|
|
||||||
accept_key_auth :index
|
|
||||||
|
|
||||||
def index
|
|
||||||
@days = Setting.activity_days_default.to_i
|
|
||||||
|
|
||||||
if params[:from]
|
|
||||||
begin; @date_to = params[:from].to_date + 1; rescue; end
|
|
||||||
end
|
|
||||||
|
|
||||||
@date_to ||= Date.today + 1
|
|
||||||
@date_from = @date_to - @days
|
|
||||||
@with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1')
|
|
||||||
@author = (params[:user_id].blank? ? nil : User.active.find(params[:user_id]))
|
|
||||||
|
|
||||||
@activity = Redmine::Activity::Fetcher.new(User.current, :project => @project,
|
|
||||||
:with_subprojects => @with_subprojects,
|
|
||||||
:author => @author)
|
|
||||||
@activity.scope_select {|t| !params["show_#{t}"].nil?}
|
|
||||||
@activity.scope = (@author.nil? ? :default : :all) if @activity.scope.empty?
|
|
||||||
|
|
||||||
events = @activity.events(@date_from, @date_to)
|
|
||||||
|
|
||||||
if events.empty? || stale?(:etag => [events.first, User.current])
|
|
||||||
respond_to do |format|
|
|
||||||
format.html {
|
|
||||||
@events_by_day = events.group_by(&:event_date)
|
|
||||||
render :layout => false if request.xhr?
|
|
||||||
}
|
|
||||||
format.atom {
|
|
||||||
title = l(:label_activity)
|
|
||||||
if @author
|
|
||||||
title = @author.name
|
|
||||||
elsif @activity.scope.size == 1
|
|
||||||
title = l("label_#{@activity.scope.first.singularize}_plural")
|
|
||||||
end
|
|
||||||
render_feed(events, :title => "#{@project || Setting.app_title}: #{title}")
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
rescue ActiveRecord::RecordNotFound
|
|
||||||
render_404
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
# TODO: refactor, duplicated in projects_controller
|
|
||||||
def find_optional_project
|
|
||||||
return true unless params[:id]
|
|
||||||
@project = Project.find(params[:id])
|
|
||||||
authorize
|
|
||||||
rescue ActiveRecord::RecordNotFound
|
|
||||||
render_404
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
@@ -22,7 +22,6 @@ class ApplicationController < ActionController::Base
|
|||||||
include Redmine::I18n
|
include Redmine::I18n
|
||||||
|
|
||||||
layout 'base'
|
layout 'base'
|
||||||
exempt_from_layout 'builder'
|
|
||||||
|
|
||||||
# Remove broken cookie after upgrade from 0.8.x (#4292)
|
# Remove broken cookie after upgrade from 0.8.x (#4292)
|
||||||
# See https://rails.lighthouseapp.com/projects/8994/tickets/3360
|
# See https://rails.lighthouseapp.com/projects/8994/tickets/3360
|
||||||
@@ -46,7 +45,7 @@ class ApplicationController < ActionController::Base
|
|||||||
include Redmine::MenuManager::MenuController
|
include Redmine::MenuManager::MenuController
|
||||||
helper Redmine::MenuManager::MenuHelper
|
helper Redmine::MenuManager::MenuHelper
|
||||||
|
|
||||||
Redmine::Scm::Base.all.each do |scm|
|
REDMINE_SUPPORTED_SCM.each do |scm|
|
||||||
require_dependency "repository/#{scm.underscore}"
|
require_dependency "repository/#{scm.underscore}"
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -71,8 +70,8 @@ class ApplicationController < ActionController::Base
|
|||||||
elsif params[:format] == 'atom' && params[:key] && accept_key_auth_actions.include?(params[:action])
|
elsif params[:format] == 'atom' && params[:key] && accept_key_auth_actions.include?(params[:action])
|
||||||
# RSS key authentication does not start a session
|
# RSS key authentication does not start a session
|
||||||
User.find_by_rss_key(params[:key])
|
User.find_by_rss_key(params[:key])
|
||||||
elsif Setting.rest_api_enabled? && ['xml', 'json'].include?(params[:format])
|
elsif Setting.rest_api_enabled? && ['xml', 'json'].include?(params[:format]) && accept_key_auth_actions.include?(params[:action])
|
||||||
if params[:key].present? && accept_key_auth_actions.include?(params[:action])
|
if params[:key].present?
|
||||||
# Use API key
|
# Use API key
|
||||||
User.find_by_api_key(params[:key])
|
User.find_by_api_key(params[:key])
|
||||||
else
|
else
|
||||||
@@ -108,9 +107,8 @@ class ApplicationController < ActionController::Base
|
|||||||
lang = find_language(User.current.language)
|
lang = find_language(User.current.language)
|
||||||
end
|
end
|
||||||
if lang.nil? && request.env['HTTP_ACCEPT_LANGUAGE']
|
if lang.nil? && request.env['HTTP_ACCEPT_LANGUAGE']
|
||||||
accept_lang = parse_qvalues(request.env['HTTP_ACCEPT_LANGUAGE']).first
|
accept_lang = parse_qvalues(request.env['HTTP_ACCEPT_LANGUAGE']).first.downcase
|
||||||
if !accept_lang.blank?
|
if !accept_lang.blank?
|
||||||
accept_lang = accept_lang.downcase
|
|
||||||
lang = find_language(accept_lang) || find_language(accept_lang.split('-').first)
|
lang = find_language(accept_lang) || find_language(accept_lang.split('-').first)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -129,9 +127,8 @@ class ApplicationController < ActionController::Base
|
|||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.html { redirect_to :controller => "account", :action => "login", :back_url => url }
|
format.html { redirect_to :controller => "account", :action => "login", :back_url => url }
|
||||||
format.atom { redirect_to :controller => "account", :action => "login", :back_url => url }
|
format.atom { redirect_to :controller => "account", :action => "login", :back_url => url }
|
||||||
format.xml { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
|
format.xml { head :unauthorized }
|
||||||
format.js { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
|
format.json { head :unauthorized }
|
||||||
format.json { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
|
|
||||||
end
|
end
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
@@ -162,62 +159,6 @@ class ApplicationController < ActionController::Base
|
|||||||
authorize(ctrl, action, global)
|
authorize(ctrl, action, global)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Find project of id params[:id]
|
|
||||||
def find_project
|
|
||||||
@project = Project.find(params[:id])
|
|
||||||
rescue ActiveRecord::RecordNotFound
|
|
||||||
render_404
|
|
||||||
end
|
|
||||||
|
|
||||||
# Find a project based on params[:project_id]
|
|
||||||
# TODO: some subclasses override this, see about merging their logic
|
|
||||||
def find_optional_project
|
|
||||||
@project = Project.find(params[:project_id]) unless params[:project_id].blank?
|
|
||||||
allowed = User.current.allowed_to?({:controller => params[:controller], :action => params[:action]}, @project, :global => true)
|
|
||||||
allowed ? true : deny_access
|
|
||||||
rescue ActiveRecord::RecordNotFound
|
|
||||||
render_404
|
|
||||||
end
|
|
||||||
|
|
||||||
# Finds and sets @project based on @object.project
|
|
||||||
def find_project_from_association
|
|
||||||
render_404 unless @object.present?
|
|
||||||
|
|
||||||
@project = @object.project
|
|
||||||
rescue ActiveRecord::RecordNotFound
|
|
||||||
render_404
|
|
||||||
end
|
|
||||||
|
|
||||||
def find_model_object
|
|
||||||
model = self.class.read_inheritable_attribute('model_object')
|
|
||||||
if model
|
|
||||||
@object = model.find(params[:id])
|
|
||||||
self.instance_variable_set('@' + controller_name.singularize, @object) if @object
|
|
||||||
end
|
|
||||||
rescue ActiveRecord::RecordNotFound
|
|
||||||
render_404
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.model_object(model)
|
|
||||||
write_inheritable_attribute('model_object', model)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Filter for bulk issue operations
|
|
||||||
def find_issues
|
|
||||||
@issues = Issue.find_all_by_id(params[:id] || params[:ids])
|
|
||||||
raise ActiveRecord::RecordNotFound if @issues.empty?
|
|
||||||
projects = @issues.collect(&:project).compact.uniq
|
|
||||||
if projects.size == 1
|
|
||||||
@project = projects.first
|
|
||||||
else
|
|
||||||
# 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
|
|
||||||
rescue ActiveRecord::RecordNotFound
|
|
||||||
render_404
|
|
||||||
end
|
|
||||||
|
|
||||||
# make sure that the user is a member of the project (or admin) if project is private
|
# 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
|
# used as a before_filter for actions that do not require any particular permission on the project
|
||||||
def check_project_privacy
|
def check_project_privacy
|
||||||
@@ -234,10 +175,6 @@ class ApplicationController < ActionController::Base
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def back_url
|
|
||||||
params[:back_url] || request.env['HTTP_REFERER']
|
|
||||||
end
|
|
||||||
|
|
||||||
def redirect_back_or_default(default)
|
def redirect_back_or_default(default)
|
||||||
back_url = CGI.unescape(params[:back_url].to_s)
|
back_url = CGI.unescape(params[:back_url].to_s)
|
||||||
if !back_url.blank?
|
if !back_url.blank?
|
||||||
@@ -257,51 +194,21 @@ class ApplicationController < ActionController::Base
|
|||||||
|
|
||||||
def render_403
|
def render_403
|
||||||
@project = nil
|
@project = nil
|
||||||
respond_to do |format|
|
render :template => "common/403", :layout => (request.xhr? ? false : 'base'), :status => 403
|
||||||
format.html { render :template => "common/403", :layout => use_layout, :status => 403 }
|
|
||||||
format.atom { head 403 }
|
|
||||||
format.xml { head 403 }
|
|
||||||
format.js { head 403 }
|
|
||||||
format.json { head 403 }
|
|
||||||
end
|
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
def render_404
|
def render_404
|
||||||
respond_to do |format|
|
render :template => "common/404", :layout => !request.xhr?, :status => 404
|
||||||
format.html { render :template => "common/404", :layout => use_layout, :status => 404 }
|
|
||||||
format.atom { head 404 }
|
|
||||||
format.xml { head 404 }
|
|
||||||
format.js { head 404 }
|
|
||||||
format.json { head 404 }
|
|
||||||
end
|
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
def render_error(msg)
|
def render_error(msg)
|
||||||
respond_to do |format|
|
flash.now[:error] = msg
|
||||||
format.html {
|
render :text => '', :layout => !request.xhr?, :status => 500
|
||||||
flash.now[:error] = msg
|
|
||||||
render :text => '', :layout => use_layout, :status => 500
|
|
||||||
}
|
|
||||||
format.atom { head 500 }
|
|
||||||
format.xml { head 500 }
|
|
||||||
format.js { head 500 }
|
|
||||||
format.json { head 500 }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Picks which layout to use based on the request
|
|
||||||
#
|
|
||||||
# @return [boolean, string] name of the layout to use or false for no layout
|
|
||||||
def use_layout
|
|
||||||
request.xhr? ? false : 'base'
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def invalid_authenticity_token
|
def invalid_authenticity_token
|
||||||
if api_request?
|
|
||||||
logger.error "Form authenticity token is missing or is invalid. API calls must include a proper Content-type header (text/xml or text/json)."
|
|
||||||
end
|
|
||||||
render_error "Invalid form authenticity token."
|
render_error "Invalid form authenticity token."
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -322,6 +229,27 @@ class ApplicationController < ActionController::Base
|
|||||||
self.class.read_inheritable_attribute('accept_key_auth_actions') || []
|
self.class.read_inheritable_attribute('accept_key_auth_actions') || []
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# TODO: move to model
|
||||||
|
def attach_files(obj, attachments)
|
||||||
|
attached = []
|
||||||
|
unsaved = []
|
||||||
|
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)
|
||||||
|
a.new_record? ? (unsaved << a) : (attached << a)
|
||||||
|
end
|
||||||
|
if unsaved.any?
|
||||||
|
flash[:warning] = l(:warning_attachments_not_saved, unsaved.size)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
attached
|
||||||
|
end
|
||||||
|
|
||||||
# Returns the number of objects that should be displayed
|
# Returns the number of objects that should be displayed
|
||||||
# on the paginated list
|
# on the paginated list
|
||||||
def per_page_option
|
def per_page_option
|
||||||
@@ -362,44 +290,4 @@ class ApplicationController < ActionController::Base
|
|||||||
def filename_for_content_disposition(name)
|
def filename_for_content_disposition(name)
|
||||||
request.env['HTTP_USER_AGENT'] =~ %r{MSIE} ? ERB::Util.url_encode(name) : name
|
request.env['HTTP_USER_AGENT'] =~ %r{MSIE} ? ERB::Util.url_encode(name) : name
|
||||||
end
|
end
|
||||||
|
|
||||||
def api_request?
|
|
||||||
%w(xml json).include? params[:format]
|
|
||||||
end
|
|
||||||
|
|
||||||
# Renders a warning flash if obj has unsaved attachments
|
|
||||||
def render_attachment_warning_if_needed(obj)
|
|
||||||
flash[:warning] = l(:warning_attachments_not_saved, obj.unsaved_attachments.size) if obj.unsaved_attachments.present?
|
|
||||||
end
|
|
||||||
|
|
||||||
# Sets the `flash` notice or error based the number of issues that did not save
|
|
||||||
#
|
|
||||||
# @param [Array, Issue] issues all of the saved and unsaved Issues
|
|
||||||
# @param [Array, Integer] unsaved_issue_ids the issue ids that were not saved
|
|
||||||
def set_flash_from_bulk_issue_save(issues, unsaved_issue_ids)
|
|
||||||
if unsaved_issue_ids.empty?
|
|
||||||
flash[:notice] = l(:notice_successful_update) unless issues.empty?
|
|
||||||
else
|
|
||||||
flash[:error] = l(:notice_failed_to_save_issues,
|
|
||||||
:count => unsaved_issue_ids.size,
|
|
||||||
:total => issues.size,
|
|
||||||
:ids => '#' + unsaved_issue_ids.join(', #'))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Rescues an invalid query statement. Just in case...
|
|
||||||
def query_statement_invalid(exception)
|
|
||||||
logger.error "Query::StatementInvalid: #{exception.message}" if logger
|
|
||||||
session.delete(:query)
|
|
||||||
sort_clear if respond_to?(:sort_clear)
|
|
||||||
render_error "An error occurred while executing the query and has been logged. Please report this error to your Redmine administrator."
|
|
||||||
end
|
|
||||||
|
|
||||||
# Converts the errors on an ActiveRecord object into a common JSON format
|
|
||||||
def object_errors_to_json(object)
|
|
||||||
object.errors.collect do |attribute, error|
|
|
||||||
{ attribute => error }
|
|
||||||
end.to_json
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
end
|
||||||
@@ -20,42 +20,45 @@ class AuthSourcesController < ApplicationController
|
|||||||
|
|
||||||
before_filter :require_admin
|
before_filter :require_admin
|
||||||
|
|
||||||
|
def index
|
||||||
|
list
|
||||||
|
render :action => 'list' unless request.xhr?
|
||||||
|
end
|
||||||
|
|
||||||
# GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html)
|
# GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html)
|
||||||
verify :method => :post, :only => [ :destroy, :create, :update ],
|
verify :method => :post, :only => [ :destroy, :create, :update ],
|
||||||
:redirect_to => { :template => :index }
|
:redirect_to => { :action => :list }
|
||||||
|
|
||||||
def index
|
def list
|
||||||
@auth_source_pages, @auth_sources = paginate auth_source_class.name.tableize, :per_page => 10
|
@auth_source_pages, @auth_sources = paginate :auth_sources, :per_page => 10
|
||||||
render "auth_sources/index"
|
render :action => "list", :layout => false if request.xhr?
|
||||||
end
|
end
|
||||||
|
|
||||||
def new
|
def new
|
||||||
@auth_source = auth_source_class.new
|
@auth_source = AuthSourceLdap.new
|
||||||
render 'auth_sources/new'
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
@auth_source = auth_source_class.new(params[:auth_source])
|
@auth_source = AuthSourceLdap.new(params[:auth_source])
|
||||||
if @auth_source.save
|
if @auth_source.save
|
||||||
flash[:notice] = l(:notice_successful_create)
|
flash[:notice] = l(:notice_successful_create)
|
||||||
redirect_to :action => 'index'
|
redirect_to :action => 'list'
|
||||||
else
|
else
|
||||||
render 'auth_sources/new'
|
render :action => 'new'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def edit
|
def edit
|
||||||
@auth_source = AuthSource.find(params[:id])
|
@auth_source = AuthSource.find(params[:id])
|
||||||
render 'auth_sources/edit'
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
@auth_source = AuthSource.find(params[:id])
|
@auth_source = AuthSource.find(params[:id])
|
||||||
if @auth_source.update_attributes(params[:auth_source])
|
if @auth_source.update_attributes(params[:auth_source])
|
||||||
flash[:notice] = l(:notice_successful_update)
|
flash[:notice] = l(:notice_successful_update)
|
||||||
redirect_to :action => 'index'
|
redirect_to :action => 'list'
|
||||||
else
|
else
|
||||||
render 'auth_sources/edit'
|
render :action => 'edit'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -65,9 +68,9 @@ class AuthSourcesController < ApplicationController
|
|||||||
@auth_method.test_connection
|
@auth_method.test_connection
|
||||||
flash[:notice] = l(:notice_successful_connection)
|
flash[:notice] = l(:notice_successful_connection)
|
||||||
rescue => text
|
rescue => text
|
||||||
flash[:error] = l(:error_unable_to_connect, text.message)
|
flash[:error] = "Unable to connect (#{text})"
|
||||||
end
|
end
|
||||||
redirect_to :action => 'index'
|
redirect_to :action => 'list'
|
||||||
end
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
@@ -76,12 +79,6 @@ class AuthSourcesController < ApplicationController
|
|||||||
@auth_source.destroy
|
@auth_source.destroy
|
||||||
flash[:notice] = l(:notice_successful_delete)
|
flash[:notice] = l(:notice_successful_delete)
|
||||||
end
|
end
|
||||||
redirect_to :action => 'index'
|
redirect_to :action => 'list'
|
||||||
end
|
|
||||||
|
|
||||||
protected
|
|
||||||
|
|
||||||
def auth_source_class
|
|
||||||
AuthSource
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
class AutoCompletesController < ApplicationController
|
|
||||||
before_filter :find_project
|
|
||||||
|
|
||||||
def issues
|
|
||||||
@issues = []
|
|
||||||
q = params[:q].to_s
|
|
||||||
if q.match(/^\d+$/)
|
|
||||||
@issues << @project.issues.visible.find_by_id(q.to_i)
|
|
||||||
end
|
|
||||||
unless q.blank?
|
|
||||||
@issues += @project.issues.visible.find(:all, :conditions => ["LOWER(#{Issue.table_name}.subject) LIKE ?", "%#{q.downcase}%"], :limit => 10)
|
|
||||||
end
|
|
||||||
render :layout => false
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def find_project
|
|
||||||
project_id = (params[:issue] && params[:issue][:project_id]) || params[:project_id]
|
|
||||||
@project = Project.find(project_id)
|
|
||||||
rescue ActiveRecord::RecordNotFound
|
|
||||||
render_404
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
@@ -17,8 +17,7 @@
|
|||||||
|
|
||||||
class BoardsController < ApplicationController
|
class BoardsController < ApplicationController
|
||||||
default_search_scope :messages
|
default_search_scope :messages
|
||||||
before_filter :find_project, :find_board_if_available, :authorize
|
before_filter :find_project, :authorize
|
||||||
accept_key_auth :index, :show
|
|
||||||
|
|
||||||
helper :messages
|
helper :messages
|
||||||
include MessagesHelper
|
include MessagesHelper
|
||||||
@@ -69,33 +68,24 @@ class BoardsController < ApplicationController
|
|||||||
@board.project = @project
|
@board.project = @project
|
||||||
if request.post? && @board.save
|
if request.post? && @board.save
|
||||||
flash[:notice] = l(:notice_successful_create)
|
flash[:notice] = l(:notice_successful_create)
|
||||||
redirect_to_settings_in_projects
|
redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'boards'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def edit
|
def edit
|
||||||
if request.post? && @board.update_attributes(params[:board])
|
if request.post? && @board.update_attributes(params[:board])
|
||||||
redirect_to_settings_in_projects
|
redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'boards'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
@board.destroy
|
@board.destroy
|
||||||
redirect_to_settings_in_projects
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
def redirect_to_settings_in_projects
|
|
||||||
redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'boards'
|
redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'boards'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
def find_project
|
def find_project
|
||||||
@project = Project.find(params[:project_id])
|
@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]
|
@board = @project.boards.find(params[:id]) if params[:id]
|
||||||
rescue ActiveRecord::RecordNotFound
|
rescue ActiveRecord::RecordNotFound
|
||||||
render_404
|
render_404
|
||||||
|
|||||||
@@ -1,41 +0,0 @@
|
|||||||
class CalendarsController < ApplicationController
|
|
||||||
menu_item :calendar
|
|
||||||
before_filter :find_optional_project
|
|
||||||
|
|
||||||
rescue_from Query::StatementInvalid, :with => :query_statement_invalid
|
|
||||||
|
|
||||||
helper :issues
|
|
||||||
helper :projects
|
|
||||||
helper :queries
|
|
||||||
include QueriesHelper
|
|
||||||
helper :sort
|
|
||||||
include SortHelper
|
|
||||||
|
|
||||||
def show
|
|
||||||
if params[:year] and params[:year].to_i > 1900
|
|
||||||
@year = params[:year].to_i
|
|
||||||
if params[:month] and params[:month].to_i > 0 and params[:month].to_i < 13
|
|
||||||
@month = params[:month].to_i
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@year ||= Date.today.year
|
|
||||||
@month ||= Date.today.month
|
|
||||||
|
|
||||||
@calendar = Redmine::Helpers::Calendar.new(Date.civil(@year, @month, 1), current_language, :month)
|
|
||||||
retrieve_query
|
|
||||||
@query.group_by = nil
|
|
||||||
if @query.valid?
|
|
||||||
events = []
|
|
||||||
events += @query.issues(:include => [:tracker, :assigned_to, :priority],
|
|
||||||
:conditions => ["((start_date BETWEEN ? AND ?) OR (due_date BETWEEN ? AND ?))", @calendar.startdt, @calendar.enddt, @calendar.startdt, @calendar.enddt]
|
|
||||||
)
|
|
||||||
events += @query.versions(:conditions => ["effective_date BETWEEN ? AND ?", @calendar.startdt, @calendar.enddt])
|
|
||||||
|
|
||||||
@calendar.events = events
|
|
||||||
end
|
|
||||||
|
|
||||||
render :layout => false if request.xhr?
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
end
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
class ContextMenusController < ApplicationController
|
|
||||||
helper :watchers
|
|
||||||
|
|
||||||
def issues
|
|
||||||
@issues = Issue.find_all_by_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
|
|
||||||
@projects = @issues.collect(&:project).compact.uniq
|
|
||||||
@project = @projects.first if @projects.size == 1
|
|
||||||
|
|
||||||
@can = {:edit => (@project && User.current.allowed_to?(:edit_issues, @project)),
|
|
||||||
:log_time => (@project && User.current.allowed_to?(:log_time, @project)),
|
|
||||||
:update => (@project && (User.current.allowed_to?(:edit_issues, @project) || (User.current.allowed_to?(:change_status, @project) && @allowed_statuses && !@allowed_statuses.empty?))),
|
|
||||||
:move => (@project && User.current.allowed_to?(:move_issues, @project)),
|
|
||||||
:copy => (@issue && @project.trackers.include?(@issue.tracker) && User.current.allowed_to?(:add_issues, @project)),
|
|
||||||
:delete => (@project && User.current.allowed_to?(:delete_issues, @project))
|
|
||||||
}
|
|
||||||
if @project
|
|
||||||
@assignables = @project.assignable_users
|
|
||||||
@assignables << @issue.assigned_to if @issue && @issue.assigned_to && !@assignables.include?(@issue.assigned_to)
|
|
||||||
@trackers = @project.trackers
|
|
||||||
end
|
|
||||||
|
|
||||||
@priorities = IssuePriority.all.reverse
|
|
||||||
@statuses = IssueStatus.find(:all, :order => 'position')
|
|
||||||
@back = back_url
|
|
||||||
|
|
||||||
render :layout => false
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
@@ -56,7 +56,7 @@ class CustomFieldsController < ApplicationController
|
|||||||
@custom_field = CustomField.find(params[:id]).destroy
|
@custom_field = CustomField.find(params[:id]).destroy
|
||||||
redirect_to :action => 'index', :tab => @custom_field.class.name
|
redirect_to :action => 'index', :tab => @custom_field.class.name
|
||||||
rescue
|
rescue
|
||||||
flash[:error] = l(:error_can_not_delete_custom_field)
|
flash[:error] = "Unable to delete custom field"
|
||||||
redirect_to :action => 'index'
|
redirect_to :action => 'index'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -17,10 +17,8 @@
|
|||||||
|
|
||||||
class DocumentsController < ApplicationController
|
class DocumentsController < ApplicationController
|
||||||
default_search_scope :documents
|
default_search_scope :documents
|
||||||
model_object Document
|
|
||||||
before_filter :find_project, :only => [:index, :new]
|
before_filter :find_project, :only => [:index, :new]
|
||||||
before_filter :find_model_object, :except => [:index, :new]
|
before_filter :find_document, :except => [:index, :new]
|
||||||
before_filter :find_project_from_association, :except => [:index, :new]
|
|
||||||
before_filter :authorize
|
before_filter :authorize
|
||||||
|
|
||||||
helper :attachments
|
helper :attachments
|
||||||
@@ -49,8 +47,7 @@ class DocumentsController < ApplicationController
|
|||||||
def new
|
def new
|
||||||
@document = @project.documents.build(params[:document])
|
@document = @project.documents.build(params[:document])
|
||||||
if request.post? and @document.save
|
if request.post? and @document.save
|
||||||
attachments = Attachment.attach_files(@document, params[:attachments])
|
attach_files(@document, params[:attachments])
|
||||||
render_attachment_warning_if_needed(@document)
|
|
||||||
flash[:notice] = l(:notice_successful_create)
|
flash[:notice] = l(:notice_successful_create)
|
||||||
redirect_to :action => 'index', :project_id => @project
|
redirect_to :action => 'index', :project_id => @project
|
||||||
end
|
end
|
||||||
@@ -70,10 +67,8 @@ class DocumentsController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def add_attachment
|
def add_attachment
|
||||||
attachments = Attachment.attach_files(@document, params[:attachments])
|
attachments = attach_files(@document, params[:attachments])
|
||||||
render_attachment_warning_if_needed(@document)
|
Mailer.deliver_attachments_added(attachments) if !attachments.empty? && Setting.notified_events.include?('document_added')
|
||||||
|
|
||||||
Mailer.deliver_attachments_added(attachments[:files]) if attachments.present? && attachments[:files].present? && Setting.notified_events.include?('document_added')
|
|
||||||
redirect_to :action => 'show', :id => @document
|
redirect_to :action => 'show', :id => @document
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -83,4 +78,11 @@ private
|
|||||||
rescue ActiveRecord::RecordNotFound
|
rescue ActiveRecord::RecordNotFound
|
||||||
render_404
|
render_404
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def find_document
|
||||||
|
@document = Document.find(params[:id])
|
||||||
|
@project = @document.project
|
||||||
|
rescue ActiveRecord::RecordNotFound
|
||||||
|
render_404
|
||||||
|
end
|
||||||
end
|
end
|
||||||
@@ -76,12 +76,12 @@ class EnumerationsController < ApplicationController
|
|||||||
@enumeration.destroy
|
@enumeration.destroy
|
||||||
redirect_to :action => 'index'
|
redirect_to :action => 'index'
|
||||||
elsif params[:reassign_to_id]
|
elsif params[:reassign_to_id]
|
||||||
if reassign_to = @enumeration.class.find_by_id(params[:reassign_to_id])
|
if reassign_to = Enumeration.find_by_type_and_id(@enumeration.type, params[:reassign_to_id])
|
||||||
@enumeration.destroy(reassign_to)
|
@enumeration.destroy(reassign_to)
|
||||||
redirect_to :action => 'index'
|
redirect_to :action => 'index'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@enumerations = @enumeration.class.find(:all) - [@enumeration]
|
@enumerations = Enumeration.find(:all, :conditions => ['type = (?)', @enumeration.type]) - [@enumeration]
|
||||||
#rescue
|
#rescue
|
||||||
# flash[:error] = 'Unable to delete enumeration'
|
# flash[:error] = 'Unable to delete enumeration'
|
||||||
# redirect_to :action => 'index'
|
# redirect_to :action => 'index'
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
class FilesController < ApplicationController
|
|
||||||
menu_item :files
|
|
||||||
|
|
||||||
before_filter :find_project
|
|
||||||
before_filter :authorize
|
|
||||||
|
|
||||||
helper :sort
|
|
||||||
include SortHelper
|
|
||||||
|
|
||||||
def index
|
|
||||||
sort_init 'filename', 'asc'
|
|
||||||
sort_update 'filename' => "#{Attachment.table_name}.filename",
|
|
||||||
'created_on' => "#{Attachment.table_name}.created_on",
|
|
||||||
'size' => "#{Attachment.table_name}.filesize",
|
|
||||||
'downloads' => "#{Attachment.table_name}.downloads"
|
|
||||||
|
|
||||||
@containers = [ Project.find(@project.id, :include => :attachments, :order => sort_clause)]
|
|
||||||
@containers += @project.versions.find(:all, :include => :attachments, :order => sort_clause).sort.reverse
|
|
||||||
render :layout => !request.xhr?
|
|
||||||
end
|
|
||||||
|
|
||||||
# TODO: split method into new (GET) and create (POST)
|
|
||||||
def new
|
|
||||||
if request.post?
|
|
||||||
container = (params[:version_id].blank? ? @project : @project.versions.find_by_id(params[:version_id]))
|
|
||||||
attachments = Attachment.attach_files(container, params[:attachments])
|
|
||||||
render_attachment_warning_if_needed(container)
|
|
||||||
|
|
||||||
if !attachments.empty? && !attachments[:files].blank? && Setting.notified_events.include?('file_added')
|
|
||||||
Mailer.deliver_attachments_added(attachments[:files])
|
|
||||||
end
|
|
||||||
redirect_to :controller => 'files', :action => 'index', :id => @project
|
|
||||||
return
|
|
||||||
end
|
|
||||||
@versions = @project.versions.sort
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
class GanttsController < ApplicationController
|
|
||||||
menu_item :gantt
|
|
||||||
before_filter :find_optional_project
|
|
||||||
|
|
||||||
rescue_from Query::StatementInvalid, :with => :query_statement_invalid
|
|
||||||
|
|
||||||
helper :issues
|
|
||||||
helper :projects
|
|
||||||
helper :queries
|
|
||||||
include QueriesHelper
|
|
||||||
helper :sort
|
|
||||||
include SortHelper
|
|
||||||
include Redmine::Export::PDF
|
|
||||||
|
|
||||||
def show
|
|
||||||
@gantt = Redmine::Helpers::Gantt.new(params)
|
|
||||||
retrieve_query
|
|
||||||
@query.group_by = nil
|
|
||||||
if @query.valid?
|
|
||||||
events = []
|
|
||||||
# Issues that have start and due dates
|
|
||||||
events += @query.issues(:include => [:tracker, :assigned_to, :priority],
|
|
||||||
:order => "start_date, due_date",
|
|
||||||
:conditions => ["(((start_date>=? and start_date<=?) or (due_date>=? and due_date<=?) or (start_date<? and due_date>?)) and start_date is not null and due_date is not null)", @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to]
|
|
||||||
)
|
|
||||||
# Issues that don't have a due date but that are assigned to a version with a date
|
|
||||||
events += @query.issues(:include => [:tracker, :assigned_to, :priority, :fixed_version],
|
|
||||||
:order => "start_date, effective_date",
|
|
||||||
:conditions => ["(((start_date>=? and start_date<=?) or (effective_date>=? and effective_date<=?) or (start_date<? and effective_date>?)) and start_date is not null and due_date is null and effective_date is not null)", @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to]
|
|
||||||
)
|
|
||||||
# Versions
|
|
||||||
events += @query.versions(:conditions => ["effective_date BETWEEN ? AND ?", @gantt.date_from, @gantt.date_to])
|
|
||||||
|
|
||||||
@gantt.events = events
|
|
||||||
end
|
|
||||||
|
|
||||||
basename = (@project ? "#{@project.identifier}-" : '') + 'gantt'
|
|
||||||
|
|
||||||
respond_to do |format|
|
|
||||||
format.html { render :action => "show", :layout => !request.xhr? }
|
|
||||||
format.png { send_data(@gantt.to_image(@project), :disposition => 'inline', :type => 'image/png', :filename => "#{basename}.png") } if @gantt.respond_to?('to_image')
|
|
||||||
format.pdf { send_data(gantt_to_pdf(@gantt, @project), :type => 'application/pdf', :filename => "#{basename}.pdf") }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
@@ -57,7 +57,7 @@ class GroupsController < ApplicationController
|
|||||||
|
|
||||||
# GET /groups/1/edit
|
# GET /groups/1/edit
|
||||||
def edit
|
def edit
|
||||||
@group = Group.find(params[:id], :include => :projects)
|
@group = Group.find(params[:id])
|
||||||
end
|
end
|
||||||
|
|
||||||
# POST /groups
|
# POST /groups
|
||||||
@@ -138,25 +138,18 @@ class GroupsController < ApplicationController
|
|||||||
|
|
||||||
def edit_membership
|
def edit_membership
|
||||||
@group = Group.find(params[:id])
|
@group = Group.find(params[:id])
|
||||||
@membership = Member.edit_membership(params[:membership_id], params[:membership], @group)
|
@membership = params[:membership_id] ? Member.find(params[:membership_id]) : Member.new(:principal => @group)
|
||||||
|
@membership.attributes = params[:membership]
|
||||||
@membership.save if request.post?
|
@membership.save if request.post?
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
if @membership.valid?
|
format.html { redirect_to :controller => 'groups', :action => 'edit', :id => @group, :tab => 'memberships' }
|
||||||
format.html { redirect_to :controller => 'groups', :action => 'edit', :id => @group, :tab => 'memberships' }
|
format.js {
|
||||||
format.js {
|
render(:update) {|page|
|
||||||
render(:update) {|page|
|
page.replace_html "tab-content-memberships", :partial => 'groups/memberships'
|
||||||
page.replace_html "tab-content-memberships", :partial => 'groups/memberships'
|
page.visual_effect(:highlight, "member-#{@membership.id}")
|
||||||
page.visual_effect(:highlight, "member-#{@membership.id}")
|
}
|
||||||
}
|
}
|
||||||
}
|
end
|
||||||
else
|
|
||||||
format.js {
|
|
||||||
render(:update) {|page|
|
|
||||||
page.alert(l(:notice_failed_to_save_members, :errors => @membership.errors.full_messages.join(', ')))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def destroy_membership
|
def destroy_membership
|
||||||
|
|||||||
@@ -17,41 +17,10 @@
|
|||||||
|
|
||||||
class IssueCategoriesController < ApplicationController
|
class IssueCategoriesController < ApplicationController
|
||||||
menu_item :settings
|
menu_item :settings
|
||||||
model_object IssueCategory
|
before_filter :find_project, :authorize
|
||||||
before_filter :find_model_object, :except => :new
|
|
||||||
before_filter :find_project_from_association, :except => :new
|
|
||||||
before_filter :find_project, :only => :new
|
|
||||||
before_filter :authorize
|
|
||||||
|
|
||||||
verify :method => :post, :only => :destroy
|
verify :method => :post, :only => :destroy
|
||||||
|
|
||||||
def new
|
|
||||||
@category = @project.issue_categories.build(params[:category])
|
|
||||||
if request.post?
|
|
||||||
if @category.save
|
|
||||||
respond_to do |format|
|
|
||||||
format.html do
|
|
||||||
flash[:notice] = l(:notice_successful_create)
|
|
||||||
redirect_to :controller => 'projects', :action => 'settings', :tab => 'categories', :id => @project
|
|
||||||
end
|
|
||||||
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', '<option></option>' + options_from_collection_for_select(@project.issue_categories, 'id', 'name', @category.id), :id => 'issue_category_id', :name => 'issue[category_id]')
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
respond_to do |format|
|
|
||||||
format.html
|
|
||||||
format.js do
|
|
||||||
render(:update) {|page| page.alert(@category.errors.full_messages.join('\n')) }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def edit
|
def edit
|
||||||
if request.post? and @category.update_attributes(params[:category])
|
if request.post? and @category.update_attributes(params[:category])
|
||||||
flash[:notice] = l(:notice_successful_update)
|
flash[:notice] = l(:notice_successful_update)
|
||||||
@@ -74,15 +43,9 @@ class IssueCategoriesController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
# Wrap ApplicationController's find_model_object method to set
|
|
||||||
# @category instead of just @issue_category
|
|
||||||
def find_model_object
|
|
||||||
super
|
|
||||||
@category = @object
|
|
||||||
end
|
|
||||||
|
|
||||||
def find_project
|
def find_project
|
||||||
@project = Project.find(params[:project_id])
|
@category = IssueCategory.find(params[:id])
|
||||||
|
@project = @category.project
|
||||||
rescue ActiveRecord::RecordNotFound
|
rescue ActiveRecord::RecordNotFound
|
||||||
render_404
|
render_404
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,68 +0,0 @@
|
|||||||
class IssueMovesController < ApplicationController
|
|
||||||
default_search_scope :issues
|
|
||||||
before_filter :find_issues
|
|
||||||
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
|
|
||||||
@@ -16,13 +16,13 @@
|
|||||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
|
||||||
class IssueRelationsController < ApplicationController
|
class IssueRelationsController < ApplicationController
|
||||||
before_filter :find_issue, :find_project_from_association, :authorize
|
before_filter :find_project, :authorize
|
||||||
|
|
||||||
def new
|
def new
|
||||||
@relation = IssueRelation.new(params[:relation])
|
@relation = IssueRelation.new(params[:relation])
|
||||||
@relation.issue_from = @issue
|
@relation.issue_from = @issue
|
||||||
if params[:relation] && m = params[:relation][:issue_to_id].to_s.match(/^#?(\d+)$/)
|
if params[:relation] && !params[:relation][:issue_to_id].blank?
|
||||||
@relation.issue_to = Issue.visible.find_by_id(m[1].to_i)
|
@relation.issue_to = Issue.visible.find_by_id(params[:relation][:issue_to_id])
|
||||||
end
|
end
|
||||||
@relation.save if request.post?
|
@relation.save if request.post?
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
@@ -52,8 +52,9 @@ class IssueRelationsController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
def find_issue
|
def find_project
|
||||||
@issue = @object = Issue.find(params[:issue_id])
|
@issue = Issue.find(params[:issue_id])
|
||||||
|
@project = @issue.project
|
||||||
rescue ActiveRecord::RecordNotFound
|
rescue ActiveRecord::RecordNotFound
|
||||||
render_404
|
render_404
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -21,11 +21,16 @@ class IssueStatusesController < ApplicationController
|
|||||||
before_filter :require_admin
|
before_filter :require_admin
|
||||||
|
|
||||||
verify :method => :post, :only => [ :destroy, :create, :update, :move, :update_issue_done_ratio ],
|
verify :method => :post, :only => [ :destroy, :create, :update, :move, :update_issue_done_ratio ],
|
||||||
:redirect_to => { :action => :index }
|
:redirect_to => { :action => :list }
|
||||||
|
|
||||||
def index
|
def index
|
||||||
|
list
|
||||||
|
render :action => 'list' unless request.xhr?
|
||||||
|
end
|
||||||
|
|
||||||
|
def list
|
||||||
@issue_status_pages, @issue_statuses = paginate :issue_statuses, :per_page => 25, :order => "position"
|
@issue_status_pages, @issue_statuses = paginate :issue_statuses, :per_page => 25, :order => "position"
|
||||||
render :action => "index", :layout => false if request.xhr?
|
render :action => "list", :layout => false if request.xhr?
|
||||||
end
|
end
|
||||||
|
|
||||||
def new
|
def new
|
||||||
@@ -36,7 +41,7 @@ class IssueStatusesController < ApplicationController
|
|||||||
@issue_status = IssueStatus.new(params[:issue_status])
|
@issue_status = IssueStatus.new(params[:issue_status])
|
||||||
if @issue_status.save
|
if @issue_status.save
|
||||||
flash[:notice] = l(:notice_successful_create)
|
flash[:notice] = l(:notice_successful_create)
|
||||||
redirect_to :action => 'index'
|
redirect_to :action => 'list'
|
||||||
else
|
else
|
||||||
render :action => 'new'
|
render :action => 'new'
|
||||||
end
|
end
|
||||||
@@ -50,7 +55,7 @@ class IssueStatusesController < ApplicationController
|
|||||||
@issue_status = IssueStatus.find(params[:id])
|
@issue_status = IssueStatus.find(params[:id])
|
||||||
if @issue_status.update_attributes(params[:issue_status])
|
if @issue_status.update_attributes(params[:issue_status])
|
||||||
flash[:notice] = l(:notice_successful_update)
|
flash[:notice] = l(:notice_successful_update)
|
||||||
redirect_to :action => 'index'
|
redirect_to :action => 'list'
|
||||||
else
|
else
|
||||||
render :action => 'edit'
|
render :action => 'edit'
|
||||||
end
|
end
|
||||||
@@ -58,10 +63,10 @@ class IssueStatusesController < ApplicationController
|
|||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
IssueStatus.find(params[:id]).destroy
|
IssueStatus.find(params[:id]).destroy
|
||||||
redirect_to :action => 'index'
|
redirect_to :action => 'list'
|
||||||
rescue
|
rescue
|
||||||
flash[:error] = l(:error_unable_delete_issue_status)
|
flash[:error] = "Unable to delete issue status"
|
||||||
redirect_to :action => 'index'
|
redirect_to :action => 'list'
|
||||||
end
|
end
|
||||||
|
|
||||||
def update_issue_done_ratio
|
def update_issue_done_ratio
|
||||||
@@ -70,6 +75,6 @@ class IssueStatusesController < ApplicationController
|
|||||||
else
|
else
|
||||||
flash[:error] = l(:error_issue_done_ratios_not_updated)
|
flash[:error] = l(:error_issue_done_ratios_not_updated)
|
||||||
end
|
end
|
||||||
redirect_to :action => 'index'
|
redirect_to :action => 'list'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -16,17 +16,15 @@
|
|||||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
|
||||||
class IssuesController < ApplicationController
|
class IssuesController < ApplicationController
|
||||||
menu_item :new_issue, :only => [:new, :create]
|
menu_item :new_issue, :only => :new
|
||||||
default_search_scope :issues
|
default_search_scope :issues
|
||||||
|
|
||||||
before_filter :find_issue, :only => [:show, :edit, :update]
|
before_filter :find_issue, :only => [:show, :edit, :reply]
|
||||||
before_filter :find_issues, :only => [:bulk_edit, :bulk_update, :move, :perform_move, :destroy]
|
before_filter :find_issues, :only => [:bulk_edit, :move, :destroy]
|
||||||
before_filter :find_project, :only => [:new, :create]
|
before_filter :find_project, :only => [:new, :update_form, :preview]
|
||||||
before_filter :authorize, :except => [:index]
|
before_filter :authorize, :except => [:index, :changes, :gantt, :calendar, :preview, :context_menu]
|
||||||
before_filter :find_optional_project, :only => [:index]
|
before_filter :find_optional_project, :only => [:index, :changes, :gantt, :calendar]
|
||||||
before_filter :check_for_default_issue_status, :only => [:new, :create]
|
accept_key_auth :index, :show, :changes
|
||||||
before_filter :build_new_issue_from_params, :only => [:new, :create]
|
|
||||||
accept_key_auth :index, :show, :create, :update, :destroy
|
|
||||||
|
|
||||||
rescue_from Query::StatementInvalid, :with => :query_statement_invalid
|
rescue_from Query::StatementInvalid, :with => :query_statement_invalid
|
||||||
|
|
||||||
@@ -42,34 +40,28 @@ class IssuesController < ApplicationController
|
|||||||
helper :attachments
|
helper :attachments
|
||||||
include AttachmentsHelper
|
include AttachmentsHelper
|
||||||
helper :queries
|
helper :queries
|
||||||
include QueriesHelper
|
|
||||||
helper :sort
|
helper :sort
|
||||||
include SortHelper
|
include SortHelper
|
||||||
include IssuesHelper
|
include IssuesHelper
|
||||||
helper :timelog
|
helper :timelog
|
||||||
include Redmine::Export::PDF
|
include Redmine::Export::PDF
|
||||||
|
|
||||||
verify :method => [:post, :delete],
|
verify :method => :post,
|
||||||
:only => :destroy,
|
:only => :destroy,
|
||||||
:render => { :nothing => true, :status => :method_not_allowed }
|
: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
|
def index
|
||||||
retrieve_query
|
retrieve_query
|
||||||
sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
|
sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
|
||||||
sort_update(@query.sortable_columns)
|
sort_update({'id' => "#{Issue.table_name}.id"}.merge(@query.available_columns.inject({}) {|h, c| h[c.name.to_s] = c.sortable; h}))
|
||||||
|
|
||||||
if @query.valid?
|
if @query.valid?
|
||||||
limit = case params[:format]
|
limit = per_page_option
|
||||||
when 'csv', 'pdf'
|
respond_to do |format|
|
||||||
Setting.issues_export_limit.to_i
|
format.html { }
|
||||||
when 'atom'
|
format.atom { limit = Setting.feeds_limit.to_i }
|
||||||
Setting.feeds_limit.to_i
|
format.csv { limit = Setting.issues_export_limit.to_i }
|
||||||
else
|
format.pdf { limit = Setting.issues_export_limit.to_i }
|
||||||
per_page_option
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@issue_count = @query.issue_count
|
@issue_count = @query.issue_count
|
||||||
@@ -82,8 +74,6 @@ class IssuesController < ApplicationController
|
|||||||
|
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.html { render :template => 'issues/index.rhtml', :layout => !request.xhr? }
|
format.html { render :template => 'issues/index.rhtml', :layout => !request.xhr? }
|
||||||
format.xml { render :layout => false }
|
|
||||||
format.json { render :text => @issues.to_json, :layout => false }
|
|
||||||
format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
|
format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
|
||||||
format.csv { send_data(issues_to_csv(@issues, @project), :type => 'text/csv; header=present', :filename => 'export.csv') }
|
format.csv { send_data(issues_to_csv(@issues, @project), :type => 'text/csv; header=present', :filename => 'export.csv') }
|
||||||
format.pdf { send_data(issues_to_pdf(@issues, @project, @query), :type => 'application/pdf', :filename => 'export.pdf') }
|
format.pdf { send_data(issues_to_pdf(@issues, @project, @query), :type => 'application/pdf', :filename => 'export.pdf') }
|
||||||
@@ -96,11 +86,26 @@ class IssuesController < ApplicationController
|
|||||||
render_404
|
render_404
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def changes
|
||||||
|
retrieve_query
|
||||||
|
sort_init 'id', 'desc'
|
||||||
|
sort_update({'id' => "#{Issue.table_name}.id"}.merge(@query.available_columns.inject({}) {|h, c| h[c.name.to_s] = c.sortable; h}))
|
||||||
|
|
||||||
|
if @query.valid?
|
||||||
|
@journals = @query.journals(:order => "#{Journal.table_name}.created_on DESC",
|
||||||
|
:limit => 25)
|
||||||
|
end
|
||||||
|
@title = (@project ? @project.name : Setting.app_title) + ": " + (@query.new_record? ? l(:label_changes_details) : @query.name)
|
||||||
|
render :layout => false, :content_type => 'application/atom+xml'
|
||||||
|
rescue ActiveRecord::RecordNotFound
|
||||||
|
render_404
|
||||||
|
end
|
||||||
|
|
||||||
def show
|
def show
|
||||||
@journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
|
@journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
|
||||||
@journals.each_with_index {|j,i| j.indice = i+1}
|
@journals.each_with_index {|j,i| j.indice = i+1}
|
||||||
@journals.reverse! if User.current.wants_comments_in_reverse_order?
|
@journals.reverse! if User.current.wants_comments_in_reverse_order?
|
||||||
@changesets = @issue.changesets.visible.all
|
@changesets = @issue.changesets
|
||||||
@changesets.reverse! if User.current.wants_comments_in_reverse_order?
|
@changesets.reverse! if User.current.wants_comments_in_reverse_order?
|
||||||
@allowed_statuses = @issue.new_statuses_allowed_to(User.current)
|
@allowed_statuses = @issue.new_statuses_allowed_to(User.current)
|
||||||
@edit_allowed = User.current.allowed_to?(:edit_issues, @project)
|
@edit_allowed = User.current.allowed_to?(:edit_issues, @project)
|
||||||
@@ -108,9 +113,7 @@ class IssuesController < ApplicationController
|
|||||||
@time_entry = TimeEntry.new
|
@time_entry = TimeEntry.new
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.html { render :template => 'issues/show.rhtml' }
|
format.html { render :template => 'issues/show.rhtml' }
|
||||||
format.xml { render :layout => false }
|
format.atom { render :action => 'changes', :layout => false, :content_type => 'application/atom+xml' }
|
||||||
format.json { render :text => @issue.to_json, :layout => false }
|
|
||||||
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") }
|
format.pdf { send_data(issue_to_pdf(@issue), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -118,35 +121,47 @@ class IssuesController < ApplicationController
|
|||||||
# Add a new issue
|
# Add a new issue
|
||||||
# The new issue will be created from an existing one if copy_from parameter is given
|
# The new issue will be created from an existing one if copy_from parameter is given
|
||||||
def new
|
def new
|
||||||
respond_to do |format|
|
@issue = Issue.new
|
||||||
format.html { render :action => 'new', :layout => !request.xhr? }
|
@issue.copy_from(params[:copy_from]) if params[:copy_from]
|
||||||
format.js { render :partial => 'attributes' }
|
@issue.project = @project
|
||||||
end
|
# Tracker must be set before custom field values
|
||||||
end
|
@issue.tracker ||= @project.trackers.find((params[:issue] && params[:issue][:tracker_id]) || params[:tracker_id] || :first)
|
||||||
|
if @issue.tracker.nil?
|
||||||
def create
|
render_error l(:error_no_tracker_in_project)
|
||||||
call_hook(:controller_issues_new_before_save, { :params => params, :issue => @issue })
|
|
||||||
if @issue.save
|
|
||||||
attachments = Attachment.attach_files(@issue, params[:attachments])
|
|
||||||
render_attachment_warning_if_needed(@issue)
|
|
||||||
flash[:notice] = l(:notice_successful_create)
|
|
||||||
call_hook(:controller_issues_new_after_save, { :params => params, :issue => @issue})
|
|
||||||
respond_to do |format|
|
|
||||||
format.html {
|
|
||||||
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.xml { render :action => 'show', :status => :created, :location => url_for(:controller => 'issues', :action => 'show', :id => @issue) }
|
|
||||||
format.json { render :text => @issue.to_json, :status => :created, :location => url_for(:controller => 'issues', :action => 'show'), :layout => false }
|
|
||||||
end
|
|
||||||
return
|
return
|
||||||
|
end
|
||||||
|
if params[:issue].is_a?(Hash)
|
||||||
|
@issue.attributes = params[:issue]
|
||||||
|
@issue.watcher_user_ids = params[:issue]['watcher_user_ids'] if User.current.allowed_to?(:add_issue_watchers, @project)
|
||||||
|
end
|
||||||
|
@issue.author = User.current
|
||||||
|
|
||||||
|
default_status = IssueStatus.default
|
||||||
|
unless default_status
|
||||||
|
render_error l(:error_no_default_issue_status)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
@issue.status = default_status
|
||||||
|
@allowed_statuses = ([default_status] + default_status.find_new_statuses_allowed_to(User.current.roles_for_project(@project), @issue.tracker)).uniq
|
||||||
|
|
||||||
|
if request.get? || request.xhr?
|
||||||
|
@issue.start_date ||= Date.today
|
||||||
else
|
else
|
||||||
respond_to do |format|
|
requested_status = IssueStatus.find_by_id(params[:issue][:status_id])
|
||||||
format.html { render :action => 'new' }
|
# Check that the user is allowed to apply the requested status
|
||||||
format.xml { render(:xml => @issue.errors, :status => :unprocessable_entity); return }
|
@issue.status = (@allowed_statuses.include? requested_status) ? requested_status : default_status
|
||||||
format.json { render :text => object_errors_to_json(@issue), :status => :unprocessable_entity, :layout => false }
|
call_hook(:controller_issues_new_before_save, { :params => params, :issue => @issue })
|
||||||
|
if @issue.save
|
||||||
|
attach_files(@issue, params[:attachments])
|
||||||
|
flash[:notice] = l(:notice_successful_create)
|
||||||
|
call_hook(:controller_issues_new_after_save, { :params => params, :issue => @issue})
|
||||||
|
redirect_to(params[:continue] ? { :action => 'new', :tracker_id => @issue.tracker } :
|
||||||
|
{ :action => 'show', :id => @issue })
|
||||||
|
return
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@priorities = IssuePriority.all
|
||||||
|
render :layout => !request.xhr?
|
||||||
end
|
end
|
||||||
|
|
||||||
# Attributes that can be updated on workflow transition (without :edit permission)
|
# Attributes that can be updated on workflow transition (without :edit permission)
|
||||||
@@ -154,65 +169,164 @@ class IssuesController < ApplicationController
|
|||||||
UPDATABLE_ATTRS_ON_TRANSITION = %w(status_id assigned_to_id fixed_version_id done_ratio) unless const_defined?(:UPDATABLE_ATTRS_ON_TRANSITION)
|
UPDATABLE_ATTRS_ON_TRANSITION = %w(status_id assigned_to_id fixed_version_id done_ratio) unless const_defined?(:UPDATABLE_ATTRS_ON_TRANSITION)
|
||||||
|
|
||||||
def edit
|
def edit
|
||||||
update_issue_from_params
|
@allowed_statuses = @issue.new_statuses_allowed_to(User.current)
|
||||||
|
@priorities = IssuePriority.all
|
||||||
|
@edit_allowed = User.current.allowed_to?(:edit_issues, @project)
|
||||||
|
@time_entry = TimeEntry.new
|
||||||
|
|
||||||
@journal = @issue.current_journal
|
@notes = params[:notes]
|
||||||
|
journal = @issue.init_journal(User.current, @notes)
|
||||||
respond_to do |format|
|
# User can change issue attributes only if he has :edit permission or if a workflow transition is allowed
|
||||||
format.html { }
|
if (@edit_allowed || !@allowed_statuses.empty?) && params[:issue]
|
||||||
format.xml { }
|
attrs = params[:issue].dup
|
||||||
|
attrs.delete_if {|k,v| !UPDATABLE_ATTRS_ON_TRANSITION.include?(k) } unless @edit_allowed
|
||||||
|
attrs.delete(:status_id) unless @allowed_statuses.detect {|s| s.id.to_s == attrs[:status_id].to_s}
|
||||||
|
@issue.attributes = attrs
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if request.post?
|
||||||
|
@time_entry = TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => Date.today)
|
||||||
|
@time_entry.attributes = params[:time_entry]
|
||||||
|
attachments = attach_files(@issue, params[:attachments])
|
||||||
|
attachments.each {|a| journal.details << JournalDetail.new(:property => 'attachment', :prop_key => a.id, :value => a.filename)}
|
||||||
|
|
||||||
|
call_hook(:controller_issues_edit_before_save, { :params => params, :issue => @issue, :time_entry => @time_entry, :journal => journal})
|
||||||
|
|
||||||
|
if (@time_entry.hours.nil? || @time_entry.valid?) && @issue.save
|
||||||
|
# Log spend time
|
||||||
|
if User.current.allowed_to?(:log_time, @project)
|
||||||
|
@time_entry.save
|
||||||
|
end
|
||||||
|
if !journal.new_record?
|
||||||
|
# Only send notification if something was actually changed
|
||||||
|
flash[:notice] = l(:notice_successful_update)
|
||||||
|
end
|
||||||
|
call_hook(:controller_issues_edit_after_save, { :params => params, :issue => @issue, :time_entry => @time_entry, :journal => journal})
|
||||||
|
redirect_to(params[:back_to] || {:action => 'show', :id => @issue})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
rescue ActiveRecord::StaleObjectError
|
||||||
|
# Optimistic locking exception
|
||||||
|
flash.now[:error] = l(:notice_locking_conflict)
|
||||||
|
# Remove the previously added attachments if issue was not updated
|
||||||
|
attachments.each(&:destroy)
|
||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def reply
|
||||||
update_issue_from_params
|
journal = Journal.find(params[:journal_id]) if params[:journal_id]
|
||||||
|
if journal
|
||||||
if @issue.save_issue_with_child_records(params, @time_entry)
|
user = journal.user
|
||||||
render_attachment_warning_if_needed(@issue)
|
text = journal.notes
|
||||||
flash[:notice] = l(:notice_successful_update) unless @issue.current_journal.new_record?
|
|
||||||
|
|
||||||
respond_to do |format|
|
|
||||||
format.html { redirect_back_or_default({:action => 'show', :id => @issue}) }
|
|
||||||
format.xml { head :ok }
|
|
||||||
format.json { head :ok }
|
|
||||||
end
|
|
||||||
else
|
else
|
||||||
render_attachment_warning_if_needed(@issue)
|
user = @issue.author
|
||||||
flash[:notice] = l(:notice_successful_update) unless @issue.current_journal.new_record?
|
text = @issue.description
|
||||||
@journal = @issue.current_journal
|
|
||||||
|
|
||||||
respond_to do |format|
|
|
||||||
format.html { render :action => 'edit' }
|
|
||||||
format.xml { render :xml => @issue.errors, :status => :unprocessable_entity }
|
|
||||||
format.json { render :text => object_errors_to_json(@issue), :status => :unprocessable_entity, :layout => false }
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
content = "#{ll(Setting.default_language, :text_user_wrote, user)}\\n> "
|
||||||
|
content << text.to_s.strip.gsub(%r{<pre>((.|\s)*?)</pre>}m, '[...]').gsub('"', '\"').gsub(/(\r?\n|\r\n?)/, "\\n> ") + "\\n\\n"
|
||||||
|
render(:update) { |page|
|
||||||
|
page.<< "$('notes').value = \"#{content}\";"
|
||||||
|
page.show 'update'
|
||||||
|
page << "Form.Element.focus('notes');"
|
||||||
|
page << "Element.scrollTo('update');"
|
||||||
|
page << "$('notes').scrollTop = $('notes').scrollHeight - $('notes').clientHeight;"
|
||||||
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
# Bulk edit a set of issues
|
# Bulk edit a set of issues
|
||||||
def bulk_edit
|
def bulk_edit
|
||||||
@issues.sort!
|
if request.post?
|
||||||
|
tracker = params[:tracker_id].blank? ? nil : @project.trackers.find_by_id(params[:tracker_id])
|
||||||
|
status = params[:status_id].blank? ? nil : IssueStatus.find_by_id(params[:status_id])
|
||||||
|
priority = params[:priority_id].blank? ? nil : IssuePriority.find_by_id(params[:priority_id])
|
||||||
|
assigned_to = (params[:assigned_to_id].blank? || params[:assigned_to_id] == 'none') ? nil : User.find_by_id(params[:assigned_to_id])
|
||||||
|
category = (params[:category_id].blank? || params[:category_id] == 'none') ? nil : @project.issue_categories.find_by_id(params[:category_id])
|
||||||
|
fixed_version = (params[:fixed_version_id].blank? || params[:fixed_version_id] == 'none') ? nil : @project.shared_versions.find_by_id(params[:fixed_version_id])
|
||||||
|
custom_field_values = params[:custom_field_values] ? params[:custom_field_values].reject {|k,v| v.blank?} : nil
|
||||||
|
|
||||||
|
unsaved_issue_ids = []
|
||||||
|
@issues.each do |issue|
|
||||||
|
journal = issue.init_journal(User.current, params[:notes])
|
||||||
|
issue.tracker = tracker if tracker
|
||||||
|
issue.priority = priority if priority
|
||||||
|
issue.assigned_to = assigned_to if assigned_to || params[:assigned_to_id] == 'none'
|
||||||
|
issue.category = category if category || params[:category_id] == 'none'
|
||||||
|
issue.fixed_version = fixed_version if fixed_version || params[:fixed_version_id] == 'none'
|
||||||
|
issue.start_date = params[:start_date] unless params[:start_date].blank?
|
||||||
|
issue.due_date = params[:due_date] unless params[:due_date].blank?
|
||||||
|
issue.done_ratio = params[:done_ratio] unless params[:done_ratio].blank?
|
||||||
|
issue.custom_field_values = custom_field_values if custom_field_values && !custom_field_values.empty?
|
||||||
|
call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue })
|
||||||
|
# Don't save any change to the issue if the user is not authorized to apply the requested status
|
||||||
|
unless (status.nil? || (issue.new_statuses_allowed_to(User.current).include?(status) && issue.status = status)) && issue.save
|
||||||
|
# Keep unsaved issue ids to display them in flash error
|
||||||
|
unsaved_issue_ids << issue.id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if unsaved_issue_ids.empty?
|
||||||
|
flash[:notice] = l(:notice_successful_update) unless @issues.empty?
|
||||||
|
else
|
||||||
|
flash[:error] = l(:notice_failed_to_save_issues, :count => unsaved_issue_ids.size,
|
||||||
|
:total => @issues.size,
|
||||||
|
:ids => '#' + unsaved_issue_ids.join(', #'))
|
||||||
|
end
|
||||||
|
redirect_to(params[:back_to] || {:controller => 'issues', :action => 'index', :project_id => @project})
|
||||||
|
return
|
||||||
|
end
|
||||||
@available_statuses = Workflow.available_statuses(@project)
|
@available_statuses = Workflow.available_statuses(@project)
|
||||||
@custom_fields = @project.all_issue_custom_fields
|
@custom_fields = @project.all_issue_custom_fields
|
||||||
end
|
end
|
||||||
|
|
||||||
def bulk_update
|
def move
|
||||||
@issues.sort!
|
@copy = params[:copy_options] && params[:copy_options][:copy]
|
||||||
attributes = parse_params_for_bulk_issue_attributes(params)
|
@allowed_projects = []
|
||||||
|
# find projects to which the user is allowed to move the issue
|
||||||
unsaved_issue_ids = []
|
if User.current.admin?
|
||||||
@issues.each do |issue|
|
# admin is allowed to move issues to any active (visible) project
|
||||||
issue.reload
|
@allowed_projects = Project.find(:all, :conditions => Project.visible_by(User.current))
|
||||||
journal = issue.init_journal(User.current, params[:notes])
|
else
|
||||||
issue.safe_attributes = attributes
|
User.current.memberships.each {|m| @allowed_projects << m.project if m.roles.detect {|r| r.allowed_to?(:move_issues)}}
|
||||||
call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue })
|
|
||||||
unless issue.save
|
|
||||||
# Keep unsaved issue ids to display them in flash error
|
|
||||||
unsaved_issue_ids << issue.id
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
set_flash_from_bulk_issue_save(@issues, unsaved_issue_ids)
|
@target_project = @allowed_projects.detect {|p| p.id.to_s == params[:new_project_id]} if params[:new_project_id]
|
||||||
redirect_back_or_default({:controller => 'issues', :action => 'index', :project_id => @project})
|
@target_project ||= @project
|
||||||
|
@trackers = @target_project.trackers
|
||||||
|
@available_statuses = Workflow.available_statuses(@project)
|
||||||
|
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|
|
||||||
|
changed_attributes = {}
|
||||||
|
[:assigned_to_id, :status_id, :start_date, :due_date].each do |valid_attribute|
|
||||||
|
unless params[valid_attribute].blank?
|
||||||
|
changed_attributes[valid_attribute] = (params[valid_attribute] == 'none' ? nil : params[valid_attribute])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
issue.init_journal(User.current)
|
||||||
|
if r = issue.move_to(@target_project, new_tracker, {:copy => @copy, :attributes => changed_attributes})
|
||||||
|
moved_issues << r
|
||||||
|
else
|
||||||
|
unsaved_issue_ids << issue.id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if unsaved_issue_ids.empty?
|
||||||
|
flash[:notice] = l(:notice_successful_update) unless @issues.empty?
|
||||||
|
else
|
||||||
|
flash[:error] = l(:notice_failed_to_save_issues, :count => unsaved_issue_ids.size,
|
||||||
|
:total => @issues.size,
|
||||||
|
:ids => '#' + unsaved_issue_ids.join(', #'))
|
||||||
|
end
|
||||||
|
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
|
||||||
|
render :layout => false if request.xhr?
|
||||||
end
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
@@ -232,18 +346,117 @@ class IssuesController < ApplicationController
|
|||||||
TimeEntry.update_all("issue_id = #{reassign_to.id}", ['issue_id IN (?)', @issues])
|
TimeEntry.update_all("issue_id = #{reassign_to.id}", ['issue_id IN (?)', @issues])
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
unless params[:format] == 'xml' || params[:format] == 'json'
|
# display the destroy form
|
||||||
# display the destroy form if it's a user request
|
return
|
||||||
return
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@issues.each(&:destroy)
|
@issues.each(&:destroy)
|
||||||
respond_to do |format|
|
redirect_to :action => 'index', :project_id => @project
|
||||||
format.html { redirect_to :action => 'index', :project_id => @project }
|
end
|
||||||
format.xml { head :ok }
|
|
||||||
format.json { head :ok }
|
def gantt
|
||||||
|
@gantt = Redmine::Helpers::Gantt.new(params)
|
||||||
|
retrieve_query
|
||||||
|
if @query.valid?
|
||||||
|
events = []
|
||||||
|
# Issues that have start and due dates
|
||||||
|
events += @query.issues(:include => [:tracker, :assigned_to, :priority],
|
||||||
|
:order => "start_date, due_date",
|
||||||
|
:conditions => ["(((start_date>=? and start_date<=?) or (due_date>=? and due_date<=?) or (start_date<? and due_date>?)) and start_date is not null and due_date is not null)", @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to]
|
||||||
|
)
|
||||||
|
# Issues that don't have a due date but that are assigned to a version with a date
|
||||||
|
events += @query.issues(:include => [:tracker, :assigned_to, :priority, :fixed_version],
|
||||||
|
:order => "start_date, effective_date",
|
||||||
|
:conditions => ["(((start_date>=? and start_date<=?) or (effective_date>=? and effective_date<=?) or (start_date<? and effective_date>?)) and start_date is not null and due_date is null and effective_date is not null)", @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to]
|
||||||
|
)
|
||||||
|
# Versions
|
||||||
|
events += @query.versions(:conditions => ["effective_date BETWEEN ? AND ?", @gantt.date_from, @gantt.date_to])
|
||||||
|
|
||||||
|
@gantt.events = events
|
||||||
end
|
end
|
||||||
|
|
||||||
|
basename = (@project ? "#{@project.identifier}-" : '') + 'gantt'
|
||||||
|
|
||||||
|
respond_to do |format|
|
||||||
|
format.html { render :template => "issues/gantt.rhtml", :layout => !request.xhr? }
|
||||||
|
format.png { send_data(@gantt.to_image, :disposition => 'inline', :type => 'image/png', :filename => "#{basename}.png") } if @gantt.respond_to?('to_image')
|
||||||
|
format.pdf { send_data(gantt_to_pdf(@gantt, @project), :type => 'application/pdf', :filename => "#{basename}.pdf") }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def calendar
|
||||||
|
if params[:year] and params[:year].to_i > 1900
|
||||||
|
@year = params[:year].to_i
|
||||||
|
if params[:month] and params[:month].to_i > 0 and params[:month].to_i < 13
|
||||||
|
@month = params[:month].to_i
|
||||||
|
end
|
||||||
|
end
|
||||||
|
@year ||= Date.today.year
|
||||||
|
@month ||= Date.today.month
|
||||||
|
|
||||||
|
@calendar = Redmine::Helpers::Calendar.new(Date.civil(@year, @month, 1), current_language, :month)
|
||||||
|
retrieve_query
|
||||||
|
if @query.valid?
|
||||||
|
events = []
|
||||||
|
events += @query.issues(:include => [:tracker, :assigned_to, :priority],
|
||||||
|
:conditions => ["((start_date BETWEEN ? AND ?) OR (due_date BETWEEN ? AND ?))", @calendar.startdt, @calendar.enddt, @calendar.startdt, @calendar.enddt]
|
||||||
|
)
|
||||||
|
events += @query.versions(:conditions => ["effective_date BETWEEN ? AND ?", @calendar.startdt, @calendar.enddt])
|
||||||
|
|
||||||
|
@calendar.events = events
|
||||||
|
end
|
||||||
|
|
||||||
|
render :layout => false if request.xhr?
|
||||||
|
end
|
||||||
|
|
||||||
|
def context_menu
|
||||||
|
@issues = Issue.find_all_by_id(params[:ids], :include => :project)
|
||||||
|
if (@issues.size == 1)
|
||||||
|
@issue = @issues.first
|
||||||
|
@allowed_statuses = @issue.new_statuses_allowed_to(User.current)
|
||||||
|
end
|
||||||
|
projects = @issues.collect(&:project).compact.uniq
|
||||||
|
@project = projects.first if projects.size == 1
|
||||||
|
|
||||||
|
@can = {:edit => (@project && User.current.allowed_to?(:edit_issues, @project)),
|
||||||
|
:log_time => (@project && User.current.allowed_to?(:log_time, @project)),
|
||||||
|
:update => (@project && (User.current.allowed_to?(:edit_issues, @project) || (User.current.allowed_to?(:change_status, @project) && @allowed_statuses && !@allowed_statuses.empty?))),
|
||||||
|
:move => (@project && User.current.allowed_to?(:move_issues, @project)),
|
||||||
|
:copy => (@issue && @project.trackers.include?(@issue.tracker) && User.current.allowed_to?(:add_issues, @project)),
|
||||||
|
:delete => (@project && User.current.allowed_to?(:delete_issues, @project))
|
||||||
|
}
|
||||||
|
if @project
|
||||||
|
@assignables = @project.assignable_users
|
||||||
|
@assignables << @issue.assigned_to if @issue && @issue.assigned_to && !@assignables.include?(@issue.assigned_to)
|
||||||
|
@trackers = @project.trackers
|
||||||
|
end
|
||||||
|
|
||||||
|
@priorities = IssuePriority.all.reverse
|
||||||
|
@statuses = IssueStatus.find(:all, :order => 'position')
|
||||||
|
@back = params[:back_url] || request.env['HTTP_REFERER']
|
||||||
|
|
||||||
|
render :layout => false
|
||||||
|
end
|
||||||
|
|
||||||
|
def update_form
|
||||||
|
if params[:id].blank?
|
||||||
|
@issue = Issue.new
|
||||||
|
@issue.project = @project
|
||||||
|
else
|
||||||
|
@issue = @project.issues.visible.find(params[:id])
|
||||||
|
end
|
||||||
|
@issue.attributes = params[:issue]
|
||||||
|
@allowed_statuses = ([@issue.status] + @issue.status.find_new_statuses_allowed_to(User.current.roles_for_project(@project), @issue.tracker)).uniq
|
||||||
|
@priorities = IssuePriority.all
|
||||||
|
|
||||||
|
render :partial => 'attributes'
|
||||||
|
end
|
||||||
|
|
||||||
|
def preview
|
||||||
|
@issue = @project.issues.find_by_id(params[:id]) unless params[:id].blank?
|
||||||
|
@attachements = @issue.attachments if @issue
|
||||||
|
@text = params[:notes] || (params[:issue] ? params[:issue][:description] : nil)
|
||||||
|
render :partial => 'common/preview'
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
@@ -254,75 +467,75 @@ private
|
|||||||
render_404
|
render_404
|
||||||
end
|
end
|
||||||
|
|
||||||
def find_project
|
# Filter for bulk operations
|
||||||
project_id = (params[:issue] && params[:issue][:project_id]) || params[:project_id]
|
def find_issues
|
||||||
@project = Project.find(project_id)
|
@issues = Issue.find_all_by_id(params[:id] || params[:ids])
|
||||||
|
raise ActiveRecord::RecordNotFound if @issues.empty?
|
||||||
|
projects = @issues.collect(&:project).compact.uniq
|
||||||
|
if projects.size == 1
|
||||||
|
@project = projects.first
|
||||||
|
else
|
||||||
|
# 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
|
||||||
rescue ActiveRecord::RecordNotFound
|
rescue ActiveRecord::RecordNotFound
|
||||||
render_404
|
render_404
|
||||||
end
|
end
|
||||||
|
|
||||||
# Used by #edit and #update to set some common instance variables
|
def find_project
|
||||||
# from the params
|
@project = Project.find(params[:project_id])
|
||||||
# TODO: Refactor, not everything in here is needed by #edit
|
rescue ActiveRecord::RecordNotFound
|
||||||
def update_issue_from_params
|
render_404
|
||||||
@allowed_statuses = @issue.new_statuses_allowed_to(User.current)
|
|
||||||
@priorities = IssuePriority.all
|
|
||||||
@edit_allowed = User.current.allowed_to?(:edit_issues, @project)
|
|
||||||
@time_entry = TimeEntry.new
|
|
||||||
|
|
||||||
@notes = params[:notes] || (params[:issue].present? ? params[:issue][:notes] : nil)
|
|
||||||
@issue.init_journal(User.current, @notes)
|
|
||||||
# User can change issue attributes only if he has :edit permission or if a workflow transition is allowed
|
|
||||||
if (@edit_allowed || !@allowed_statuses.empty?) && params[:issue]
|
|
||||||
attrs = params[:issue].dup
|
|
||||||
attrs.delete_if {|k,v| !UPDATABLE_ATTRS_ON_TRANSITION.include?(k) } unless @edit_allowed
|
|
||||||
attrs.delete(:status_id) unless @allowed_statuses.detect {|s| s.id.to_s == attrs[:status_id].to_s}
|
|
||||||
@issue.safe_attributes = attrs
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# TODO: Refactor, lots of extra code in here
|
def find_optional_project
|
||||||
# TODO: Changing tracker on an existing issue should not trigger this
|
@project = Project.find(params[:project_id]) unless params[:project_id].blank?
|
||||||
def build_new_issue_from_params
|
allowed = User.current.allowed_to?({:controller => params[:controller], :action => params[:action]}, @project, :global => true)
|
||||||
if params[:id].blank?
|
allowed ? true : deny_access
|
||||||
@issue = Issue.new
|
rescue ActiveRecord::RecordNotFound
|
||||||
@issue.copy_from(params[:copy_from]) if params[:copy_from]
|
render_404
|
||||||
@issue.project = @project
|
end
|
||||||
else
|
|
||||||
@issue = @project.issues.visible.find(params[:id])
|
|
||||||
end
|
|
||||||
|
|
||||||
@issue.project = @project
|
# Retrieve query from session or build a new query
|
||||||
# Tracker must be set before custom field values
|
def retrieve_query
|
||||||
@issue.tracker ||= @project.trackers.find((params[:issue] && params[:issue][:tracker_id]) || params[:tracker_id] || :first)
|
if !params[:query_id].blank?
|
||||||
if @issue.tracker.nil?
|
cond = "project_id IS NULL"
|
||||||
render_error l(:error_no_tracker_in_project)
|
cond << " OR project_id = #{@project.id}" if @project
|
||||||
return false
|
@query = Query.find(params[:query_id], :conditions => cond)
|
||||||
end
|
@query.project = @project
|
||||||
@issue.start_date ||= Date.today
|
session[:query] = {:id => @query.id, :project_id => @query.project_id}
|
||||||
if params[:issue].is_a?(Hash)
|
sort_clear
|
||||||
@issue.safe_attributes = params[:issue]
|
else
|
||||||
if User.current.allowed_to?(:add_issue_watchers, @project) && @issue.new_record?
|
if params[:set_filter] || session[:query].nil? || session[:query][:project_id] != (@project ? @project.id : nil)
|
||||||
@issue.watcher_user_ids = params[:issue]['watcher_user_ids']
|
# Give it a name, required to be valid
|
||||||
|
@query = Query.new(:name => "_")
|
||||||
|
@query.project = @project
|
||||||
|
if params[:fields] and params[:fields].is_a? Array
|
||||||
|
params[:fields].each do |field|
|
||||||
|
@query.add_filter(field, params[:operators][field], params[:values][field])
|
||||||
|
end
|
||||||
|
else
|
||||||
|
@query.available_filters.keys.each do |field|
|
||||||
|
@query.add_short_filter(field, params[field]) if params[field]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
@query.group_by = params[:group_by]
|
||||||
|
@query.column_names = params[:query] && params[:query][:column_names]
|
||||||
|
session[:query] = {:project_id => @query.project_id, :filters => @query.filters, :group_by => @query.group_by, :column_names => @query.column_names}
|
||||||
|
else
|
||||||
|
@query = Query.find_by_id(session[:query][:id]) if session[:query][:id]
|
||||||
|
@query ||= Query.new(:name => "_", :project => @project, :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names])
|
||||||
|
@query.project = @project
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@issue.author = User.current
|
|
||||||
@priorities = IssuePriority.all
|
|
||||||
@allowed_statuses = @issue.new_statuses_allowed_to(User.current, true)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def check_for_default_issue_status
|
# Rescues an invalid query statement. Just in case...
|
||||||
if IssueStatus.default.nil?
|
def query_statement_invalid(exception)
|
||||||
render_error l(:error_no_default_issue_status)
|
logger.error "Query::StatementInvalid: #{exception.message}" if logger
|
||||||
return false
|
session.delete(:query)
|
||||||
end
|
sort_clear
|
||||||
end
|
render_error "An error occurred while executing the query and has been logged. Please report this error to your Redmine administrator."
|
||||||
|
|
||||||
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'}
|
|
||||||
attributes[:custom_field_values].reject! {|k,v| v.blank?} if attributes[:custom_field_values]
|
|
||||||
attributes
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -16,54 +16,7 @@
|
|||||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
|
||||||
class JournalsController < ApplicationController
|
class JournalsController < ApplicationController
|
||||||
before_filter :find_journal, :only => [:edit]
|
before_filter :find_journal
|
||||||
before_filter :find_issue, :only => [:new]
|
|
||||||
before_filter :find_optional_project, :only => [:index]
|
|
||||||
accept_key_auth :index
|
|
||||||
|
|
||||||
helper :issues
|
|
||||||
helper :queries
|
|
||||||
include QueriesHelper
|
|
||||||
helper :sort
|
|
||||||
include SortHelper
|
|
||||||
|
|
||||||
def index
|
|
||||||
retrieve_query
|
|
||||||
sort_init 'id', 'desc'
|
|
||||||
sort_update(@query.sortable_columns)
|
|
||||||
|
|
||||||
if @query.valid?
|
|
||||||
@journals = @query.journals(:order => "#{Journal.table_name}.created_on DESC",
|
|
||||||
:limit => 25)
|
|
||||||
end
|
|
||||||
@title = (@project ? @project.name : Setting.app_title) + ": " + (@query.new_record? ? l(:label_changes_details) : @query.name)
|
|
||||||
render :layout => false, :content_type => 'application/atom+xml'
|
|
||||||
rescue ActiveRecord::RecordNotFound
|
|
||||||
render_404
|
|
||||||
end
|
|
||||||
|
|
||||||
def new
|
|
||||||
journal = Journal.find(params[:journal_id]) if params[:journal_id]
|
|
||||||
if journal
|
|
||||||
user = journal.user
|
|
||||||
text = journal.notes
|
|
||||||
else
|
|
||||||
user = @issue.author
|
|
||||||
text = @issue.description
|
|
||||||
end
|
|
||||||
# Replaces pre blocks with [...]
|
|
||||||
text = text.to_s.strip.gsub(%r{<pre>((.|\s)*?)</pre>}m, '[...]')
|
|
||||||
content = "#{ll(Setting.default_language, :text_user_wrote, user)}\n> "
|
|
||||||
content << text.gsub(/(\r?\n|\r\n?)/, "\n> ") + "\n\n"
|
|
||||||
|
|
||||||
render(:update) { |page|
|
|
||||||
page.<< "$('notes').value = \"#{escape_javascript content}\";"
|
|
||||||
page.show 'update'
|
|
||||||
page << "Form.Element.focus('notes');"
|
|
||||||
page << "Element.scrollTo('update');"
|
|
||||||
page << "$('notes').scrollTop = $('notes').scrollHeight - $('notes').clientHeight;"
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
def edit
|
def edit
|
||||||
if request.post?
|
if request.post?
|
||||||
@@ -85,12 +38,4 @@ private
|
|||||||
rescue ActiveRecord::RecordNotFound
|
rescue ActiveRecord::RecordNotFound
|
||||||
render_404
|
render_404
|
||||||
end
|
end
|
||||||
|
|
||||||
# TODO: duplicated in IssuesController
|
|
||||||
def find_issue
|
|
||||||
@issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
|
|
||||||
@project = @issue.project
|
|
||||||
rescue ActiveRecord::RecordNotFound
|
|
||||||
render_404
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
# redMine - project management software
|
|
||||||
# Copyright (C) 2006 Jean-Philippe Lang
|
|
||||||
#
|
|
||||||
# This program is free software; you can redistribute it and/or
|
|
||||||
# modify it under the terms of the GNU General Public License
|
|
||||||
# as published by the Free Software Foundation; either version 2
|
|
||||||
# of the License, or (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program; if not, write to the Free Software
|
|
||||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
||||||
|
|
||||||
class LdapAuthSourcesController < AuthSourcesController
|
|
||||||
|
|
||||||
protected
|
|
||||||
|
|
||||||
def auth_source_class
|
|
||||||
AuthSourceLdap
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -16,9 +16,7 @@
|
|||||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
|
||||||
class MembersController < ApplicationController
|
class MembersController < ApplicationController
|
||||||
model_object Member
|
before_filter :find_member, :except => [:new, :autocomplete_for_member]
|
||||||
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 :find_project, :only => [:new, :autocomplete_for_member]
|
||||||
before_filter :authorize
|
before_filter :authorize
|
||||||
|
|
||||||
@@ -36,30 +34,13 @@ class MembersController < ApplicationController
|
|||||||
@project.members << members
|
@project.members << members
|
||||||
end
|
end
|
||||||
respond_to do |format|
|
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 {
|
||||||
format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'members', :id => @project }
|
render(:update) {|page|
|
||||||
|
page.replace_html "tab-content-members", :partial => 'projects/settings/members'
|
||||||
format.js {
|
members.each {|member| page.visual_effect(:highlight, "member-#{member.id}") }
|
||||||
render(:update) {|page|
|
|
||||||
page.replace_html "tab-content-members", :partial => 'projects/settings/members'
|
|
||||||
page << 'hideOnLoad()'
|
|
||||||
members.each {|member| page.visual_effect(:highlight, "member-#{member.id}") }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
}
|
||||||
|
|
||||||
format.js {
|
|
||||||
render(:update) {|page|
|
|
||||||
errors = members.collect {|m|
|
|
||||||
m.errors.full_messages
|
|
||||||
}.flatten.uniq
|
|
||||||
|
|
||||||
page.alert(l(:notice_failed_to_save_members, :errors => errors.join(', ')))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -70,7 +51,6 @@ class MembersController < ApplicationController
|
|||||||
format.js {
|
format.js {
|
||||||
render(:update) {|page|
|
render(:update) {|page|
|
||||||
page.replace_html "tab-content-members", :partial => 'projects/settings/members'
|
page.replace_html "tab-content-members", :partial => 'projects/settings/members'
|
||||||
page << 'hideOnLoad()'
|
|
||||||
page.visual_effect(:highlight, "member-#{@member.id}")
|
page.visual_effect(:highlight, "member-#{@member.id}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -84,11 +64,7 @@ class MembersController < ApplicationController
|
|||||||
end
|
end
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'members', :id => @project }
|
format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'members', :id => @project }
|
||||||
format.js { render(:update) {|page|
|
format.js { render(:update) {|page| page.replace_html "tab-content-members", :partial => 'projects/settings/members'} }
|
||||||
page.replace_html "tab-content-members", :partial => 'projects/settings/members'
|
|
||||||
page << 'hideOnLoad()'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -97,4 +73,17 @@ class MembersController < ApplicationController
|
|||||||
render :layout => false
|
render :layout => false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
def find_project
|
||||||
|
@project = Project.find(params[:id])
|
||||||
|
rescue ActiveRecord::RecordNotFound
|
||||||
|
render_404
|
||||||
|
end
|
||||||
|
|
||||||
|
def find_member
|
||||||
|
@member = Member.find(params[:id])
|
||||||
|
@project = @member.project
|
||||||
|
rescue ActiveRecord::RecordNotFound
|
||||||
|
render_404
|
||||||
|
end
|
||||||
end
|
end
|
||||||
@@ -29,24 +29,10 @@ class MessagesController < ApplicationController
|
|||||||
helper :attachments
|
helper :attachments
|
||||||
include AttachmentsHelper
|
include AttachmentsHelper
|
||||||
|
|
||||||
REPLIES_PER_PAGE = 25 unless const_defined?(:REPLIES_PER_PAGE)
|
|
||||||
|
|
||||||
# Show a topic and its replies
|
# Show a topic and its replies
|
||||||
def show
|
def show
|
||||||
page = params[:page]
|
@replies = @topic.children.find(:all, :include => [:author, :attachments, {:board => :project}])
|
||||||
# Find the page of the requested reply
|
@replies.reverse! if User.current.wants_comments_in_reverse_order?
|
||||||
if params[:r] && page.nil?
|
|
||||||
offset = @topic.children.count(:conditions => ["#{Message.table_name}.id < ?", params[:r].to_i])
|
|
||||||
page = 1 + offset / REPLIES_PER_PAGE
|
|
||||||
end
|
|
||||||
|
|
||||||
@reply_count = @topic.children.count
|
|
||||||
@reply_pages = Paginator.new self, @reply_count, REPLIES_PER_PAGE, page
|
|
||||||
@replies = @topic.children.find(:all, :include => [:author, :attachments, {:board => :project}],
|
|
||||||
:order => "#{Message.table_name}.created_on ASC",
|
|
||||||
:limit => @reply_pages.items_per_page,
|
|
||||||
:offset => @reply_pages.current.offset)
|
|
||||||
|
|
||||||
@reply = Message.new(:subject => "RE: #{@message.subject}")
|
@reply = Message.new(:subject => "RE: #{@message.subject}")
|
||||||
render :action => "show", :layout => false if request.xhr?
|
render :action => "show", :layout => false if request.xhr?
|
||||||
end
|
end
|
||||||
@@ -62,8 +48,7 @@ class MessagesController < ApplicationController
|
|||||||
end
|
end
|
||||||
if request.post? && @message.save
|
if request.post? && @message.save
|
||||||
call_hook(:controller_messages_new_after_save, { :params => params, :message => @message})
|
call_hook(:controller_messages_new_after_save, { :params => params, :message => @message})
|
||||||
attachments = Attachment.attach_files(@message, params[:attachments])
|
attach_files(@message, params[:attachments])
|
||||||
render_attachment_warning_if_needed(@message)
|
|
||||||
redirect_to :action => 'show', :id => @message
|
redirect_to :action => 'show', :id => @message
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -76,10 +61,9 @@ class MessagesController < ApplicationController
|
|||||||
@topic.children << @reply
|
@topic.children << @reply
|
||||||
if !@reply.new_record?
|
if !@reply.new_record?
|
||||||
call_hook(:controller_messages_reply_after_save, { :params => params, :message => @reply})
|
call_hook(:controller_messages_reply_after_save, { :params => params, :message => @reply})
|
||||||
attachments = Attachment.attach_files(@reply, params[:attachments])
|
attach_files(@reply, params[:attachments])
|
||||||
render_attachment_warning_if_needed(@reply)
|
|
||||||
end
|
end
|
||||||
redirect_to :action => 'show', :id => @topic, :r => @reply
|
redirect_to :action => 'show', :id => @topic
|
||||||
end
|
end
|
||||||
|
|
||||||
# Edit a message
|
# Edit a message
|
||||||
@@ -90,11 +74,10 @@ class MessagesController < ApplicationController
|
|||||||
@message.sticky = params[:message]['sticky']
|
@message.sticky = params[:message]['sticky']
|
||||||
end
|
end
|
||||||
if request.post? && @message.update_attributes(params[:message])
|
if request.post? && @message.update_attributes(params[:message])
|
||||||
attachments = Attachment.attach_files(@message, params[:attachments])
|
attach_files(@message, params[:attachments])
|
||||||
render_attachment_warning_if_needed(@message)
|
|
||||||
flash[:notice] = l(:notice_successful_update)
|
flash[:notice] = l(:notice_successful_update)
|
||||||
@message.reload
|
@message.reload
|
||||||
redirect_to :action => 'show', :board_id => @message.board, :id => @message.root, :r => (@message.parent_id && @message.id)
|
redirect_to :action => 'show', :board_id => @message.board, :id => @message.root
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -104,7 +87,7 @@ class MessagesController < ApplicationController
|
|||||||
@message.destroy
|
@message.destroy
|
||||||
redirect_to @message.parent.nil? ?
|
redirect_to @message.parent.nil? ?
|
||||||
{ :controller => 'boards', :action => 'show', :project_id => @project, :id => @board } :
|
{ :controller => 'boards', :action => 'show', :project_id => @project, :id => @board } :
|
||||||
{ :action => 'show', :id => @message.parent, :r => @message }
|
{ :action => 'show', :id => @message.parent }
|
||||||
end
|
end
|
||||||
|
|
||||||
def quote
|
def quote
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ class MyController < ApplicationController
|
|||||||
# Manage user's password
|
# Manage user's password
|
||||||
def password
|
def password
|
||||||
@user = User.current
|
@user = User.current
|
||||||
unless @user.change_password_allowed?
|
if @user.auth_source_id
|
||||||
flash[:error] = l(:notice_can_t_change_password)
|
flash[:error] = l(:notice_can_t_change_password)
|
||||||
redirect_to :action => 'account'
|
redirect_to :action => 'account'
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -17,9 +17,7 @@
|
|||||||
|
|
||||||
class NewsController < ApplicationController
|
class NewsController < ApplicationController
|
||||||
default_search_scope :news
|
default_search_scope :news
|
||||||
model_object News
|
before_filter :find_news, :except => [:new, :index, :preview]
|
||||||
before_filter :find_model_object, :except => [:new, :index, :preview]
|
|
||||||
before_filter :find_project_from_association, :except => [:new, :index, :preview]
|
|
||||||
before_filter :find_project, :only => [:new, :preview]
|
before_filter :find_project, :only => [:new, :preview]
|
||||||
before_filter :authorize, :except => [:index, :preview]
|
before_filter :authorize, :except => [:index, :preview]
|
||||||
before_filter :find_optional_project, :only => :index
|
before_filter :find_optional_project, :only => :index
|
||||||
@@ -90,6 +88,13 @@ class NewsController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
def find_news
|
||||||
|
@news = News.find(params[:id])
|
||||||
|
@project = @news.project
|
||||||
|
rescue ActiveRecord::RecordNotFound
|
||||||
|
render_404
|
||||||
|
end
|
||||||
|
|
||||||
def find_project
|
def find_project
|
||||||
@project = Project.find(params[:project_id])
|
@project = Project.find(params[:project_id])
|
||||||
rescue ActiveRecord::RecordNotFound
|
rescue ActiveRecord::RecordNotFound
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
class PreviewsController < ApplicationController
|
|
||||||
before_filter :find_project
|
|
||||||
|
|
||||||
def issue
|
|
||||||
@issue = @project.issues.find_by_id(params[:id]) unless params[:id].blank?
|
|
||||||
if @issue
|
|
||||||
@attachements = @issue.attachments
|
|
||||||
@description = params[:issue] && params[:issue][:description]
|
|
||||||
if @description && @description.gsub(/(\r?\n|\n\r?)/, "\n") == @issue.description.to_s.gsub(/(\r?\n|\n\r?)/, "\n")
|
|
||||||
@description = nil
|
|
||||||
end
|
|
||||||
@notes = params[:notes]
|
|
||||||
else
|
|
||||||
@description = (params[:issue] ? params[:issue][:description] : nil)
|
|
||||||
end
|
|
||||||
render :layout => false
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def find_project
|
|
||||||
project_id = (params[:issue] && params[:issue][:project_id]) || params[:project_id]
|
|
||||||
@project = Project.find(project_id)
|
|
||||||
rescue ActiveRecord::RecordNotFound
|
|
||||||
render_404
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
class ProjectEnumerationsController < ApplicationController
|
|
||||||
before_filter :find_project
|
|
||||||
before_filter :authorize
|
|
||||||
|
|
||||||
def save
|
|
||||||
if request.post? && params[:enumerations]
|
|
||||||
Project.transaction do
|
|
||||||
params[:enumerations].each do |id, activity|
|
|
||||||
@project.update_or_create_time_entry_activity(id, activity)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
flash[:notice] = l(:notice_successful_update)
|
|
||||||
end
|
|
||||||
|
|
||||||
redirect_to :controller => 'projects', :action => 'settings', :tab => 'activities', :id => @project
|
|
||||||
end
|
|
||||||
|
|
||||||
def destroy
|
|
||||||
@project.time_entry_activities.each do |time_entry_activity|
|
|
||||||
time_entry_activity.destroy(time_entry_activity.parent)
|
|
||||||
end
|
|
||||||
flash[:notice] = l(:notice_successful_update)
|
|
||||||
redirect_to :controller => 'projects', :action => 'settings', :tab => 'activities', :id => @project
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
@@ -17,29 +17,30 @@
|
|||||||
|
|
||||||
class ProjectsController < ApplicationController
|
class ProjectsController < ApplicationController
|
||||||
menu_item :overview
|
menu_item :overview
|
||||||
|
menu_item :activity, :only => :activity
|
||||||
menu_item :roadmap, :only => :roadmap
|
menu_item :roadmap, :only => :roadmap
|
||||||
|
menu_item :files, :only => [:list_files, :add_file]
|
||||||
menu_item :settings, :only => :settings
|
menu_item :settings, :only => :settings
|
||||||
|
|
||||||
before_filter :find_project, :except => [ :index, :list, :new, :create, :copy ]
|
before_filter :find_project, :except => [ :index, :list, :add, :copy, :activity ]
|
||||||
before_filter :authorize, :except => [ :index, :list, :new, :create, :copy, :archive, :unarchive, :destroy]
|
before_filter :find_optional_project, :only => :activity
|
||||||
before_filter :authorize_global, :only => [:new, :create]
|
before_filter :authorize, :except => [ :index, :list, :add, :copy, :archive, :unarchive, :destroy, :activity ]
|
||||||
|
before_filter :authorize_global, :only => :add
|
||||||
before_filter :require_admin, :only => [ :copy, :archive, :unarchive, :destroy ]
|
before_filter :require_admin, :only => [ :copy, :archive, :unarchive, :destroy ]
|
||||||
accept_key_auth :index, :show, :create, :update, :destroy
|
accept_key_auth :activity
|
||||||
|
|
||||||
after_filter :only => [:create, :edit, :update, :archive, :unarchive, :destroy] do |controller|
|
after_filter :only => [:add, :edit, :archive, :unarchive, :destroy] do |controller|
|
||||||
if controller.request.post?
|
if controller.request.post?
|
||||||
controller.send :expire_action, :controller => 'welcome', :action => 'robots.txt'
|
controller.send :expire_action, :controller => 'welcome', :action => 'robots.txt'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# TODO: convert to PUT only
|
|
||||||
verify :method => [:post, :put], :only => :update, :render => {:nothing => true, :status => :method_not_allowed }
|
|
||||||
|
|
||||||
helper :sort
|
helper :sort
|
||||||
include SortHelper
|
include SortHelper
|
||||||
helper :custom_fields
|
helper :custom_fields
|
||||||
include CustomFieldsHelper
|
include CustomFieldsHelper
|
||||||
helper :issues
|
helper :issues
|
||||||
|
helper IssuesHelper
|
||||||
helper :queries
|
helper :queries
|
||||||
include QueriesHelper
|
include QueriesHelper
|
||||||
helper :repositories
|
helper :repositories
|
||||||
@@ -52,9 +53,6 @@ class ProjectsController < ApplicationController
|
|||||||
format.html {
|
format.html {
|
||||||
@projects = Project.visible.find(:all, :order => 'lft')
|
@projects = Project.visible.find(:all, :order => 'lft')
|
||||||
}
|
}
|
||||||
format.xml {
|
|
||||||
@projects = Project.visible.find(:all, :order => 'lft')
|
|
||||||
}
|
|
||||||
format.atom {
|
format.atom {
|
||||||
projects = Project.visible.find(:all, :order => 'created_on DESC',
|
projects = Project.visible.find(:all, :order => 'created_on DESC',
|
||||||
:limit => Setting.feeds_limit.to_i)
|
:limit => Setting.feeds_limit.to_i)
|
||||||
@@ -63,45 +61,30 @@ class ProjectsController < ApplicationController
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def new
|
# Add a new project
|
||||||
|
def add
|
||||||
@issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
|
@issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
|
||||||
@trackers = Tracker.all
|
@trackers = Tracker.all
|
||||||
@project = Project.new(params[:project])
|
@project = Project.new(params[:project])
|
||||||
|
if request.get?
|
||||||
@project.identifier = Project.next_identifier if Setting.sequential_project_identifiers?
|
@project.identifier = Project.next_identifier if Setting.sequential_project_identifiers?
|
||||||
@project.trackers = Tracker.all
|
@project.trackers = Tracker.all
|
||||||
@project.is_public = Setting.default_projects_public?
|
@project.is_public = Setting.default_projects_public?
|
||||||
@project.enabled_module_names = Setting.default_projects_modules
|
@project.enabled_module_names = Setting.default_projects_modules
|
||||||
end
|
|
||||||
|
|
||||||
def create
|
|
||||||
@issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
|
|
||||||
@trackers = Tracker.all
|
|
||||||
@project = Project.new(params[:project])
|
|
||||||
|
|
||||||
@project.enabled_module_names = params[:enabled_modules]
|
|
||||||
if validate_parent_id && @project.save
|
|
||||||
@project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
|
|
||||||
# Add current user as a project member if he is not admin
|
|
||||||
unless User.current.admin?
|
|
||||||
r = Role.givable.find_by_id(Setting.new_project_user_role_id.to_i) || Role.givable.first
|
|
||||||
m = Member.new(:user => User.current, :roles => [r])
|
|
||||||
@project.members << m
|
|
||||||
end
|
|
||||||
respond_to do |format|
|
|
||||||
format.html {
|
|
||||||
flash[:notice] = l(:notice_successful_create)
|
|
||||||
redirect_to :controller => 'projects', :action => 'settings', :id => @project
|
|
||||||
}
|
|
||||||
format.xml { render :action => 'show', :status => :created, :location => url_for(:controller => 'projects', :action => 'show', :id => @project.id) }
|
|
||||||
end
|
|
||||||
else
|
else
|
||||||
respond_to do |format|
|
@project.enabled_module_names = params[:enabled_modules]
|
||||||
format.html { render :action => 'new' }
|
if validate_parent_id && @project.save
|
||||||
format.xml { render :xml => @project.errors, :status => :unprocessable_entity }
|
@project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
|
||||||
|
# Add current user as a project member if he is not admin
|
||||||
|
unless User.current.admin?
|
||||||
|
r = Role.givable.find_by_id(Setting.new_project_user_role_id.to_i) || Role.givable.first
|
||||||
|
m = Member.new(:user => User.current, :roles => [r])
|
||||||
|
@project.members << m
|
||||||
|
end
|
||||||
|
flash[:notice] = l(:notice_successful_create)
|
||||||
|
redirect_to :controller => 'projects', :action => 'settings', :id => @project
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def copy
|
def copy
|
||||||
@@ -119,20 +102,18 @@ class ProjectsController < ApplicationController
|
|||||||
redirect_to :controller => 'admin', :action => 'projects'
|
redirect_to :controller => 'admin', :action => 'projects'
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
Mailer.with_deliveries(params[:notifications] == '1') do
|
@project = Project.new(params[:project])
|
||||||
@project = Project.new(params[:project])
|
@project.enabled_module_names = params[:enabled_modules]
|
||||||
@project.enabled_module_names = params[:enabled_modules]
|
if validate_parent_id && @project.copy(@source_project, :only => params[:only])
|
||||||
if validate_parent_id && @project.copy(@source_project, :only => params[:only])
|
@project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
|
||||||
@project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
|
flash[:notice] = l(:notice_successful_create)
|
||||||
flash[:notice] = l(:notice_successful_create)
|
redirect_to :controller => 'admin', :action => 'projects'
|
||||||
redirect_to :controller => 'projects', :action => 'settings'
|
elsif !@project.new_record?
|
||||||
elsif !@project.new_record?
|
# Project was created
|
||||||
# Project was created
|
# But some objects were not copied due to validation failures
|
||||||
# But some objects were not copied due to validation failures
|
# (eg. issues from disabled trackers)
|
||||||
# (eg. issues from disabled trackers)
|
# TODO: inform about that
|
||||||
# TODO: inform about that
|
redirect_to :controller => 'admin', :action => 'projects'
|
||||||
redirect_to :controller => 'projects', :action => 'settings'
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
rescue ActiveRecord::RecordNotFound
|
rescue ActiveRecord::RecordNotFound
|
||||||
@@ -166,11 +147,6 @@ class ProjectsController < ApplicationController
|
|||||||
:conditions => cond).to_f
|
:conditions => cond).to_f
|
||||||
end
|
end
|
||||||
@key = User.current.rss_key
|
@key = User.current.rss_key
|
||||||
|
|
||||||
respond_to do |format|
|
|
||||||
format.html
|
|
||||||
format.xml
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def settings
|
def settings
|
||||||
@@ -182,34 +158,23 @@ class ProjectsController < ApplicationController
|
|||||||
@wiki ||= @project.wiki
|
@wiki ||= @project.wiki
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Edit @project
|
||||||
def edit
|
def edit
|
||||||
end
|
if request.post?
|
||||||
|
@project.attributes = params[:project]
|
||||||
def update
|
if validate_parent_id && @project.save
|
||||||
@project.attributes = params[:project]
|
@project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
|
||||||
if validate_parent_id && @project.save
|
flash[:notice] = l(:notice_successful_update)
|
||||||
@project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
|
redirect_to :action => 'settings', :id => @project
|
||||||
respond_to do |format|
|
else
|
||||||
format.html {
|
settings
|
||||||
flash[:notice] = l(:notice_successful_update)
|
render :action => 'settings'
|
||||||
redirect_to :action => 'settings', :id => @project
|
|
||||||
}
|
|
||||||
format.xml { head :ok }
|
|
||||||
end
|
|
||||||
else
|
|
||||||
respond_to do |format|
|
|
||||||
format.html {
|
|
||||||
settings
|
|
||||||
render :action => 'settings'
|
|
||||||
}
|
|
||||||
format.xml { render :xml => @project.errors, :status => :unprocessable_entity }
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def modules
|
def modules
|
||||||
@project.enabled_module_names = params[:enabled_modules]
|
@project.enabled_module_names = params[:enabled_modules]
|
||||||
flash[:notice] = l(:notice_successful_update)
|
|
||||||
redirect_to :action => 'settings', :id => @project, :tab => 'modules'
|
redirect_to :action => 'settings', :id => @project, :tab => 'modules'
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -230,22 +195,197 @@ class ProjectsController < ApplicationController
|
|||||||
# Delete @project
|
# Delete @project
|
||||||
def destroy
|
def destroy
|
||||||
@project_to_destroy = @project
|
@project_to_destroy = @project
|
||||||
if request.get?
|
if request.post? and params[:confirm]
|
||||||
# display confirmation view
|
@project_to_destroy.destroy
|
||||||
else
|
redirect_to :controller => 'admin', :action => 'projects'
|
||||||
if params[:format] == 'xml' || params[:confirm]
|
|
||||||
@project_to_destroy.destroy
|
|
||||||
respond_to do |format|
|
|
||||||
format.html { redirect_to :controller => 'admin', :action => 'projects' }
|
|
||||||
format.xml { head :ok }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
# hide project in layout
|
# hide project in layout
|
||||||
@project = nil
|
@project = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Add a new issue category to @project
|
||||||
|
def add_issue_category
|
||||||
|
@category = @project.issue_categories.build(params[:category])
|
||||||
|
if request.post?
|
||||||
|
if @category.save
|
||||||
|
respond_to do |format|
|
||||||
|
format.html do
|
||||||
|
flash[:notice] = l(:notice_successful_create)
|
||||||
|
redirect_to :action => 'settings', :tab => 'categories', :id => @project
|
||||||
|
end
|
||||||
|
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', '<option></option>' + options_from_collection_for_select(@project.issue_categories, 'id', 'name', @category.id), :id => 'issue_category_id', :name => 'issue[category_id]')
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
respond_to do |format|
|
||||||
|
format.html
|
||||||
|
format.js do
|
||||||
|
render(:update) {|page| page.alert(@category.errors.full_messages.join('\n')) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Add a new version to @project
|
||||||
|
def add_version
|
||||||
|
@version = @project.versions.build
|
||||||
|
if params[:version]
|
||||||
|
attributes = params[:version].dup
|
||||||
|
attributes.delete('sharing') unless attributes.nil? || @version.allowed_sharings.include?(attributes['sharing'])
|
||||||
|
@version.attributes = attributes
|
||||||
|
end
|
||||||
|
if request.post?
|
||||||
|
if @version.save
|
||||||
|
respond_to do |format|
|
||||||
|
format.html do
|
||||||
|
flash[:notice] = l(:notice_successful_create)
|
||||||
|
redirect_to :action => 'settings', :tab => 'versions', :id => @project
|
||||||
|
end
|
||||||
|
format.js do
|
||||||
|
# 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
|
||||||
|
end
|
||||||
|
else
|
||||||
|
respond_to do |format|
|
||||||
|
format.html
|
||||||
|
format.js do
|
||||||
|
render(:update) {|page| page.alert(@version.errors.full_messages.join('\n')) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def add_file
|
||||||
|
if request.post?
|
||||||
|
container = (params[:version_id].blank? ? @project : @project.versions.find_by_id(params[:version_id]))
|
||||||
|
attachments = attach_files(container, params[:attachments])
|
||||||
|
if !attachments.empty? && Setting.notified_events.include?('file_added')
|
||||||
|
Mailer.deliver_attachments_added(attachments)
|
||||||
|
end
|
||||||
|
redirect_to :controller => 'projects', :action => 'list_files', :id => @project
|
||||||
|
return
|
||||||
|
end
|
||||||
|
@versions = @project.versions.sort
|
||||||
|
end
|
||||||
|
|
||||||
|
def save_activities
|
||||||
|
if request.post? && params[:enumerations]
|
||||||
|
Project.transaction do
|
||||||
|
params[:enumerations].each do |id, activity|
|
||||||
|
@project.update_or_create_time_entry_activity(id, activity)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
redirect_to :controller => 'projects', :action => 'settings', :tab => 'activities', :id => @project
|
||||||
|
end
|
||||||
|
|
||||||
|
def reset_activities
|
||||||
|
@project.time_entry_activities.each do |time_entry_activity|
|
||||||
|
time_entry_activity.destroy(time_entry_activity.parent)
|
||||||
|
end
|
||||||
|
redirect_to :controller => 'projects', :action => 'settings', :tab => 'activities', :id => @project
|
||||||
|
end
|
||||||
|
|
||||||
|
def list_files
|
||||||
|
sort_init 'filename', 'asc'
|
||||||
|
sort_update 'filename' => "#{Attachment.table_name}.filename",
|
||||||
|
'created_on' => "#{Attachment.table_name}.created_on",
|
||||||
|
'size' => "#{Attachment.table_name}.filesize",
|
||||||
|
'downloads' => "#{Attachment.table_name}.downloads"
|
||||||
|
|
||||||
|
@containers = [ Project.find(@project.id, :include => :attachments, :order => sort_clause)]
|
||||||
|
@containers += @project.versions.find(:all, :include => :attachments, :order => sort_clause).sort.reverse
|
||||||
|
render :layout => !request.xhr?
|
||||||
|
end
|
||||||
|
|
||||||
|
def roadmap
|
||||||
|
@trackers = @project.trackers.find(:all, :order => 'position')
|
||||||
|
retrieve_selected_tracker_ids(@trackers, @trackers.select {|t| t.is_in_roadmap?})
|
||||||
|
@with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1')
|
||||||
|
project_ids = @with_subprojects ? @project.self_and_descendants.collect(&:id) : [@project.id]
|
||||||
|
|
||||||
|
@versions = @project.shared_versions.sort
|
||||||
|
@versions.reject! {|version| version.closed? || version.completed? } unless params[:completed]
|
||||||
|
|
||||||
|
@issues_by_version = {}
|
||||||
|
unless @selected_tracker_ids.empty?
|
||||||
|
@versions.each do |version|
|
||||||
|
conditions = {:tracker_id => @selected_tracker_ids}
|
||||||
|
if !@project.versions.include?(version)
|
||||||
|
conditions.merge!(:project_id => project_ids)
|
||||||
|
end
|
||||||
|
issues = version.fixed_issues.visible.find(:all,
|
||||||
|
:include => [:project, :status, :tracker, :priority],
|
||||||
|
:conditions => conditions,
|
||||||
|
: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].empty?}
|
||||||
|
end
|
||||||
|
|
||||||
|
def activity
|
||||||
|
@days = Setting.activity_days_default.to_i
|
||||||
|
|
||||||
|
if params[:from]
|
||||||
|
begin; @date_to = params[:from].to_date + 1; rescue; end
|
||||||
|
end
|
||||||
|
|
||||||
|
@date_to ||= Date.today + 1
|
||||||
|
@date_from = @date_to - @days
|
||||||
|
@with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1')
|
||||||
|
@author = (params[:user_id].blank? ? nil : User.active.find(params[:user_id]))
|
||||||
|
|
||||||
|
@activity = Redmine::Activity::Fetcher.new(User.current, :project => @project,
|
||||||
|
:with_subprojects => @with_subprojects,
|
||||||
|
:author => @author)
|
||||||
|
@activity.scope_select {|t| !params["show_#{t}"].nil?}
|
||||||
|
@activity.scope = (@author.nil? ? :default : :all) if @activity.scope.empty?
|
||||||
|
|
||||||
|
events = @activity.events(@date_from, @date_to)
|
||||||
|
|
||||||
|
if events.empty? || stale?(:etag => [events.first, User.current])
|
||||||
|
respond_to do |format|
|
||||||
|
format.html {
|
||||||
|
@events_by_day = events.group_by(&:event_date)
|
||||||
|
render :layout => false if request.xhr?
|
||||||
|
}
|
||||||
|
format.atom {
|
||||||
|
title = l(:label_activity)
|
||||||
|
if @author
|
||||||
|
title = @author.name
|
||||||
|
elsif @activity.scope.size == 1
|
||||||
|
title = l("label_#{@activity.scope.first.singularize}_plural")
|
||||||
|
end
|
||||||
|
render_feed(events, :title => "#{@project || Setting.app_title}: #{title}")
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
rescue ActiveRecord::RecordNotFound
|
||||||
|
render_404
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
# Find project of id params[:id]
|
||||||
|
# if not found, redirect to project list
|
||||||
|
# Used as a before_filter
|
||||||
|
def find_project
|
||||||
|
@project = Project.find(params[:id])
|
||||||
|
rescue ActiveRecord::RecordNotFound
|
||||||
|
render_404
|
||||||
|
end
|
||||||
|
|
||||||
def find_optional_project
|
def find_optional_project
|
||||||
return true unless params[:id]
|
return true unless params[:id]
|
||||||
@project = Project.find(params[:id])
|
@project = Project.find(params[:id])
|
||||||
@@ -254,6 +394,14 @@ private
|
|||||||
render_404
|
render_404
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def retrieve_selected_tracker_ids(selectable_trackers, default_trackers=nil)
|
||||||
|
if ids = params[:tracker_ids]
|
||||||
|
@selected_tracker_ids = (ids.is_a? Array) ? ids.collect { |id| id.to_i.to_s } : ids.split('/').collect { |id| id.to_i.to_s }
|
||||||
|
else
|
||||||
|
@selected_tracker_ids = (default_trackers || selectable_trackers).collect {|t| t.id.to_s }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# Validates parent_id param according to user's permissions
|
# Validates parent_id param according to user's permissions
|
||||||
# TODO: move it to Project model in a validation that depends on User.current
|
# TODO: move it to Project model in a validation that depends on User.current
|
||||||
def validate_parent_id
|
def validate_parent_id
|
||||||
|
|||||||
@@ -27,7 +27,9 @@ class QueriesController < ApplicationController
|
|||||||
@query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
|
@query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
|
||||||
@query.column_names = nil if params[:default_columns]
|
@query.column_names = nil if params[:default_columns]
|
||||||
|
|
||||||
@query.add_filters(params[:fields], params[:operators], params[:values]) if params[:fields]
|
params[:fields].each do |field|
|
||||||
|
@query.add_filter(field, params[:operators][field], params[:values][field])
|
||||||
|
end if params[:fields]
|
||||||
@query.group_by ||= params[:group_by]
|
@query.group_by ||= params[:group_by]
|
||||||
|
|
||||||
if request.post? && params[:confirm] && @query.save
|
if request.post? && params[:confirm] && @query.save
|
||||||
@@ -41,7 +43,9 @@ class QueriesController < ApplicationController
|
|||||||
def edit
|
def edit
|
||||||
if request.post?
|
if request.post?
|
||||||
@query.filters = {}
|
@query.filters = {}
|
||||||
@query.add_filters(params[:fields], params[:operators], params[:values]) if params[:fields]
|
params[:fields].each do |field|
|
||||||
|
@query.add_filter(field, params[:operators][field], params[:values][field])
|
||||||
|
end if params[:fields]
|
||||||
@query.attributes = params[:query]
|
@query.attributes = params[:query]
|
||||||
@query.project = nil if params[:query_is_for_all]
|
@query.project = nil if params[:query_is_for_all]
|
||||||
@query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
|
@query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
|
||||||
@@ -70,7 +74,7 @@ private
|
|||||||
|
|
||||||
def find_optional_project
|
def find_optional_project
|
||||||
@project = Project.find(params[:project_id]) if params[:project_id]
|
@project = Project.find(params[:project_id]) if params[:project_id]
|
||||||
render_403 unless User.current.allowed_to?(:save_queries, @project, :global => true)
|
User.current.allowed_to?(:save_queries, @project, :global => true)
|
||||||
rescue ActiveRecord::RecordNotFound
|
rescue ActiveRecord::RecordNotFound
|
||||||
render_404
|
render_404
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -17,79 +17,184 @@
|
|||||||
|
|
||||||
class ReportsController < ApplicationController
|
class ReportsController < ApplicationController
|
||||||
menu_item :issues
|
menu_item :issues
|
||||||
before_filter :find_project, :authorize, :find_issue_statuses
|
before_filter :find_project, :authorize
|
||||||
|
|
||||||
def issue_report
|
def issue_report
|
||||||
@trackers = @project.trackers
|
@statuses = IssueStatus.find(:all, :order => 'position')
|
||||||
@versions = @project.shared_versions.sort
|
|
||||||
@priorities = IssuePriority.all
|
|
||||||
@categories = @project.issue_categories
|
|
||||||
@assignees = @project.members.collect { |m| m.user }.sort
|
|
||||||
@authors = @project.members.collect { |m| m.user }.sort
|
|
||||||
@subprojects = @project.descendants.visible
|
|
||||||
|
|
||||||
@issues_by_tracker = Issue.by_tracker(@project)
|
|
||||||
@issues_by_version = Issue.by_version(@project)
|
|
||||||
@issues_by_priority = Issue.by_priority(@project)
|
|
||||||
@issues_by_category = Issue.by_category(@project)
|
|
||||||
@issues_by_assigned_to = Issue.by_assigned_to(@project)
|
|
||||||
@issues_by_author = Issue.by_author(@project)
|
|
||||||
@issues_by_subproject = Issue.by_subproject(@project) || []
|
|
||||||
|
|
||||||
render :template => "reports/issue_report"
|
|
||||||
end
|
|
||||||
|
|
||||||
def issue_report_details
|
|
||||||
case params[:detail]
|
case params[:detail]
|
||||||
when "tracker"
|
when "tracker"
|
||||||
@field = "tracker_id"
|
@field = "tracker_id"
|
||||||
@rows = @project.trackers
|
@rows = @project.trackers
|
||||||
@data = Issue.by_tracker(@project)
|
@data = issues_by_tracker
|
||||||
@report_title = l(:field_tracker)
|
@report_title = l(:field_tracker)
|
||||||
|
render :template => "reports/issue_report_details"
|
||||||
when "version"
|
when "version"
|
||||||
@field = "fixed_version_id"
|
@field = "fixed_version_id"
|
||||||
@rows = @project.shared_versions.sort
|
@rows = @project.shared_versions.sort
|
||||||
@data = Issue.by_version(@project)
|
@data = issues_by_version
|
||||||
@report_title = l(:field_version)
|
@report_title = l(:field_version)
|
||||||
|
render :template => "reports/issue_report_details"
|
||||||
when "priority"
|
when "priority"
|
||||||
@field = "priority_id"
|
@field = "priority_id"
|
||||||
@rows = IssuePriority.all
|
@rows = IssuePriority.all
|
||||||
@data = Issue.by_priority(@project)
|
@data = issues_by_priority
|
||||||
@report_title = l(:field_priority)
|
@report_title = l(:field_priority)
|
||||||
|
render :template => "reports/issue_report_details"
|
||||||
when "category"
|
when "category"
|
||||||
@field = "category_id"
|
@field = "category_id"
|
||||||
@rows = @project.issue_categories
|
@rows = @project.issue_categories
|
||||||
@data = Issue.by_category(@project)
|
@data = issues_by_category
|
||||||
@report_title = l(:field_category)
|
@report_title = l(:field_category)
|
||||||
|
render :template => "reports/issue_report_details"
|
||||||
when "assigned_to"
|
when "assigned_to"
|
||||||
@field = "assigned_to_id"
|
@field = "assigned_to_id"
|
||||||
@rows = @project.members.collect { |m| m.user }.sort
|
@rows = @project.members.collect { |m| m.user }
|
||||||
@data = Issue.by_assigned_to(@project)
|
@data = issues_by_assigned_to
|
||||||
@report_title = l(:field_assigned_to)
|
@report_title = l(:field_assigned_to)
|
||||||
|
render :template => "reports/issue_report_details"
|
||||||
when "author"
|
when "author"
|
||||||
@field = "author_id"
|
@field = "author_id"
|
||||||
@rows = @project.members.collect { |m| m.user }.sort
|
@rows = @project.members.collect { |m| m.user }
|
||||||
@data = Issue.by_author(@project)
|
@data = issues_by_author
|
||||||
@report_title = l(:field_author)
|
@report_title = l(:field_author)
|
||||||
|
render :template => "reports/issue_report_details"
|
||||||
when "subproject"
|
when "subproject"
|
||||||
@field = "project_id"
|
@field = "project_id"
|
||||||
@rows = @project.descendants.visible
|
@rows = @project.descendants.active
|
||||||
@data = Issue.by_subproject(@project) || []
|
@data = issues_by_subproject
|
||||||
@report_title = l(:field_subproject)
|
@report_title = l(:field_subproject)
|
||||||
end
|
render :template => "reports/issue_report_details"
|
||||||
|
else
|
||||||
|
@trackers = @project.trackers
|
||||||
|
@versions = @project.shared_versions.sort
|
||||||
|
@priorities = IssuePriority.all
|
||||||
|
@categories = @project.issue_categories
|
||||||
|
@assignees = @project.members.collect { |m| m.user }
|
||||||
|
@authors = @project.members.collect { |m| m.user }
|
||||||
|
@subprojects = @project.descendants.active
|
||||||
|
issues_by_tracker
|
||||||
|
issues_by_version
|
||||||
|
issues_by_priority
|
||||||
|
issues_by_category
|
||||||
|
issues_by_assigned_to
|
||||||
|
issues_by_author
|
||||||
|
issues_by_subproject
|
||||||
|
|
||||||
respond_to do |format|
|
render :template => "reports/issue_report"
|
||||||
if @field
|
|
||||||
format.html {}
|
|
||||||
else
|
|
||||||
format.html { redirect_to :action => 'issue_report', :id => @project }
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
# Find project of id params[:id]
|
||||||
|
def find_project
|
||||||
|
@project = Project.find(params[:id])
|
||||||
|
rescue ActiveRecord::RecordNotFound
|
||||||
|
render_404
|
||||||
|
end
|
||||||
|
|
||||||
def find_issue_statuses
|
def issues_by_tracker
|
||||||
@statuses = IssueStatus.find(:all, :order => 'position')
|
@issues_by_tracker ||=
|
||||||
|
ActiveRecord::Base.connection.select_all("select s.id as status_id,
|
||||||
|
s.is_closed as closed,
|
||||||
|
t.id as tracker_id,
|
||||||
|
count(i.id) as total
|
||||||
|
from
|
||||||
|
#{Issue.table_name} i, #{IssueStatus.table_name} s, #{Tracker.table_name} t
|
||||||
|
where
|
||||||
|
i.status_id=s.id
|
||||||
|
and i.tracker_id=t.id
|
||||||
|
and i.project_id=#{@project.id}
|
||||||
|
group by s.id, s.is_closed, t.id")
|
||||||
|
end
|
||||||
|
|
||||||
|
def issues_by_version
|
||||||
|
@issues_by_version ||=
|
||||||
|
ActiveRecord::Base.connection.select_all("select s.id as status_id,
|
||||||
|
s.is_closed as closed,
|
||||||
|
v.id as fixed_version_id,
|
||||||
|
count(i.id) as total
|
||||||
|
from
|
||||||
|
#{Issue.table_name} i, #{IssueStatus.table_name} s, #{Version.table_name} v
|
||||||
|
where
|
||||||
|
i.status_id=s.id
|
||||||
|
and i.fixed_version_id=v.id
|
||||||
|
and i.project_id=#{@project.id}
|
||||||
|
group by s.id, s.is_closed, v.id")
|
||||||
|
end
|
||||||
|
|
||||||
|
def issues_by_priority
|
||||||
|
@issues_by_priority ||=
|
||||||
|
ActiveRecord::Base.connection.select_all("select s.id as status_id,
|
||||||
|
s.is_closed as closed,
|
||||||
|
p.id as priority_id,
|
||||||
|
count(i.id) as total
|
||||||
|
from
|
||||||
|
#{Issue.table_name} i, #{IssueStatus.table_name} s, #{IssuePriority.table_name} p
|
||||||
|
where
|
||||||
|
i.status_id=s.id
|
||||||
|
and i.priority_id=p.id
|
||||||
|
and i.project_id=#{@project.id}
|
||||||
|
group by s.id, s.is_closed, p.id")
|
||||||
|
end
|
||||||
|
|
||||||
|
def issues_by_category
|
||||||
|
@issues_by_category ||=
|
||||||
|
ActiveRecord::Base.connection.select_all("select s.id as status_id,
|
||||||
|
s.is_closed as closed,
|
||||||
|
c.id as category_id,
|
||||||
|
count(i.id) as total
|
||||||
|
from
|
||||||
|
#{Issue.table_name} i, #{IssueStatus.table_name} s, #{IssueCategory.table_name} c
|
||||||
|
where
|
||||||
|
i.status_id=s.id
|
||||||
|
and i.category_id=c.id
|
||||||
|
and i.project_id=#{@project.id}
|
||||||
|
group by s.id, s.is_closed, c.id")
|
||||||
|
end
|
||||||
|
|
||||||
|
def issues_by_assigned_to
|
||||||
|
@issues_by_assigned_to ||=
|
||||||
|
ActiveRecord::Base.connection.select_all("select s.id as status_id,
|
||||||
|
s.is_closed as closed,
|
||||||
|
a.id as assigned_to_id,
|
||||||
|
count(i.id) as total
|
||||||
|
from
|
||||||
|
#{Issue.table_name} i, #{IssueStatus.table_name} s, #{User.table_name} a
|
||||||
|
where
|
||||||
|
i.status_id=s.id
|
||||||
|
and i.assigned_to_id=a.id
|
||||||
|
and i.project_id=#{@project.id}
|
||||||
|
group by s.id, s.is_closed, a.id")
|
||||||
|
end
|
||||||
|
|
||||||
|
def issues_by_author
|
||||||
|
@issues_by_author ||=
|
||||||
|
ActiveRecord::Base.connection.select_all("select s.id as status_id,
|
||||||
|
s.is_closed as closed,
|
||||||
|
a.id as author_id,
|
||||||
|
count(i.id) as total
|
||||||
|
from
|
||||||
|
#{Issue.table_name} i, #{IssueStatus.table_name} s, #{User.table_name} a
|
||||||
|
where
|
||||||
|
i.status_id=s.id
|
||||||
|
and i.author_id=a.id
|
||||||
|
and i.project_id=#{@project.id}
|
||||||
|
group by s.id, s.is_closed, a.id")
|
||||||
|
end
|
||||||
|
|
||||||
|
def issues_by_subproject
|
||||||
|
@issues_by_subproject ||=
|
||||||
|
ActiveRecord::Base.connection.select_all("select s.id as status_id,
|
||||||
|
s.is_closed as closed,
|
||||||
|
i.project_id as project_id,
|
||||||
|
count(i.id) as total
|
||||||
|
from
|
||||||
|
#{Issue.table_name} i, #{IssueStatus.table_name} s
|
||||||
|
where
|
||||||
|
i.status_id=s.id
|
||||||
|
and i.project_id IN (#{@project.descendants.active.collect{|p| p.id}.join(',')})
|
||||||
|
group by s.id, s.is_closed, i.project_id") if @project.descendants.active.any?
|
||||||
|
@issues_by_subproject ||= []
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -24,7 +24,6 @@ class InvalidRevisionParam < Exception; end
|
|||||||
|
|
||||||
class RepositoriesController < ApplicationController
|
class RepositoriesController < ApplicationController
|
||||||
menu_item :repository
|
menu_item :repository
|
||||||
menu_item :settings, :only => :edit
|
|
||||||
default_search_scope :changesets
|
default_search_scope :changesets
|
||||||
|
|
||||||
before_filter :find_repository, :except => :edit
|
before_filter :find_repository, :except => :edit
|
||||||
@@ -44,13 +43,7 @@ class RepositoriesController < ApplicationController
|
|||||||
@repository.attributes = params[:repository]
|
@repository.attributes = params[:repository]
|
||||||
@repository.save
|
@repository.save
|
||||||
end
|
end
|
||||||
render(:update) do |page|
|
render(:update) {|page| page.replace_html "tab-content-repository", :partial => 'projects/settings/repository'}
|
||||||
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
|
end
|
||||||
|
|
||||||
def committers
|
def committers
|
||||||
@@ -197,6 +190,12 @@ class RepositoriesController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
def find_project
|
||||||
|
@project = Project.find(params[:id])
|
||||||
|
rescue ActiveRecord::RecordNotFound
|
||||||
|
render_404
|
||||||
|
end
|
||||||
|
|
||||||
def find_repository
|
def find_repository
|
||||||
@project = Project.find(params[:id])
|
@project = Project.find(params[:id])
|
||||||
@repository = @project.repository
|
@repository = @project.repository
|
||||||
|
|||||||
@@ -21,11 +21,16 @@ class RolesController < ApplicationController
|
|||||||
before_filter :require_admin
|
before_filter :require_admin
|
||||||
|
|
||||||
verify :method => :post, :only => [ :destroy, :move ],
|
verify :method => :post, :only => [ :destroy, :move ],
|
||||||
:redirect_to => { :action => :index }
|
:redirect_to => { :action => :list }
|
||||||
|
|
||||||
def index
|
def index
|
||||||
|
list
|
||||||
|
render :action => 'list' unless request.xhr?
|
||||||
|
end
|
||||||
|
|
||||||
|
def list
|
||||||
@role_pages, @roles = paginate :roles, :per_page => 25, :order => 'builtin, position'
|
@role_pages, @roles = paginate :roles, :per_page => 25, :order => 'builtin, position'
|
||||||
render :action => "index", :layout => false if request.xhr?
|
render :action => "list", :layout => false if request.xhr?
|
||||||
end
|
end
|
||||||
|
|
||||||
def new
|
def new
|
||||||
@@ -57,7 +62,7 @@ class RolesController < ApplicationController
|
|||||||
@role.destroy
|
@role.destroy
|
||||||
redirect_to :action => 'index'
|
redirect_to :action => 'index'
|
||||||
rescue
|
rescue
|
||||||
flash[:error] = l(:error_can_not_remove_role)
|
flash[:error] = 'This role is in use and can not be deleted.'
|
||||||
redirect_to :action => 'index'
|
redirect_to :action => 'index'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -43,12 +43,12 @@ class SearchController < ApplicationController
|
|||||||
begin; offset = params[:offset].to_time if params[:offset]; rescue; end
|
begin; offset = params[:offset].to_time if params[:offset]; rescue; end
|
||||||
|
|
||||||
# quick jump to an issue
|
# quick jump to an issue
|
||||||
if @question.match(/^#?(\d+)$/) && Issue.visible.find_by_id($1.to_i)
|
if @question.match(/^#?(\d+)$/) && Issue.visible.find_by_id($1)
|
||||||
redirect_to :controller => "issues", :action => "show", :id => $1
|
redirect_to :controller => "issues", :action => "show", :id => $1
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@object_types = Redmine::Search.available_search_types.dup
|
@object_types = %w(issues news documents changesets wiki_pages messages projects)
|
||||||
if projects_to_search.is_a? Project
|
if projects_to_search.is_a? Project
|
||||||
# don't search projects
|
# don't search projects
|
||||||
@object_types.delete('projects')
|
@object_types.delete('projects')
|
||||||
@@ -62,19 +62,21 @@ class SearchController < ApplicationController
|
|||||||
# extract tokens from the question
|
# extract tokens from the question
|
||||||
# eg. hello "bye bye" => ["hello", "bye bye"]
|
# eg. hello "bye bye" => ["hello", "bye bye"]
|
||||||
@tokens = @question.scan(%r{((\s|^)"[\s\w]+"(\s|$)|\S+)}).collect {|m| m.first.gsub(%r{(^\s*"\s*|\s*"\s*$)}, '')}
|
@tokens = @question.scan(%r{((\s|^)"[\s\w]+"(\s|$)|\S+)}).collect {|m| m.first.gsub(%r{(^\s*"\s*|\s*"\s*$)}, '')}
|
||||||
# tokens must be at least 2 characters long
|
# tokens must be at least 3 character long
|
||||||
@tokens = @tokens.uniq.select {|w| w.length > 1 }
|
@tokens = @tokens.uniq.select {|w| w.length > 2 }
|
||||||
|
|
||||||
if !@tokens.empty?
|
if !@tokens.empty?
|
||||||
# no more than 5 tokens to search for
|
# no more than 5 tokens to search for
|
||||||
@tokens.slice! 5..-1 if @tokens.size > 5
|
@tokens.slice! 5..-1 if @tokens.size > 5
|
||||||
|
# strings used in sql like statement
|
||||||
|
like_tokens = @tokens.collect {|w| "%#{w.downcase}%"}
|
||||||
|
|
||||||
@results = []
|
@results = []
|
||||||
@results_by_type = Hash.new {|h,k| h[k] = 0}
|
@results_by_type = Hash.new {|h,k| h[k] = 0}
|
||||||
|
|
||||||
limit = 10
|
limit = 10
|
||||||
@scope.each do |s|
|
@scope.each do |s|
|
||||||
r, c = s.singularize.camelcase.constantize.search(@tokens, projects_to_search,
|
r, c = s.singularize.camelcase.constantize.search(like_tokens, projects_to_search,
|
||||||
:all_words => @all_words,
|
:all_words => @all_words,
|
||||||
:titles_only => @titles_only,
|
:titles_only => @titles_only,
|
||||||
:limit => (limit+1),
|
:limit => (limit+1),
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ class TimelogController < ApplicationController
|
|||||||
menu_item :issues
|
menu_item :issues
|
||||||
before_filter :find_project, :authorize, :only => [:edit, :destroy]
|
before_filter :find_project, :authorize, :only => [:edit, :destroy]
|
||||||
before_filter :find_optional_project, :only => [:report, :details]
|
before_filter :find_optional_project, :only => [:report, :details]
|
||||||
before_filter :load_available_criterias, :only => [:report]
|
|
||||||
|
|
||||||
verify :method => :post, :only => :destroy, :redirect_to => { :action => :details }
|
verify :method => :post, :only => :destroy, :redirect_to => { :action => :details }
|
||||||
|
|
||||||
@@ -31,6 +30,51 @@ class TimelogController < ApplicationController
|
|||||||
include CustomFieldsHelper
|
include CustomFieldsHelper
|
||||||
|
|
||||||
def report
|
def report
|
||||||
|
@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
|
||||||
|
|
||||||
@criterias = params[:criterias] || []
|
@criterias = params[:criterias] || []
|
||||||
@criterias = @criterias.select{|criteria| @available_criterias.has_key? criteria}
|
@criterias = @criterias.select{|criteria| @available_criterias.has_key? criteria}
|
||||||
@criterias.uniq!
|
@criterias.uniq!
|
||||||
@@ -50,12 +94,13 @@ class TimelogController < ApplicationController
|
|||||||
elsif @issue.nil?
|
elsif @issue.nil?
|
||||||
sql_condition = @project.project_condition(Setting.display_subprojects_issues?)
|
sql_condition = @project.project_condition(Setting.display_subprojects_issues?)
|
||||||
else
|
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}"
|
sql_condition = "#{TimeEntry.table_name}.issue_id = #{@issue.id}"
|
||||||
end
|
end
|
||||||
|
|
||||||
sql = "SELECT #{sql_select}, tyear, tmonth, tweek, spent_on, SUM(hours) AS hours"
|
sql = "SELECT #{sql_select}, tyear, tmonth, tweek, spent_on, SUM(hours) AS hours"
|
||||||
sql << " FROM #{TimeEntry.table_name}"
|
sql << " FROM #{TimeEntry.table_name}"
|
||||||
sql << time_report_joins
|
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"
|
||||||
sql << " WHERE"
|
sql << " WHERE"
|
||||||
sql << " (%s) AND" % sql_condition
|
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 << " (spent_on BETWEEN '%s' AND '%s')" % [ActiveRecord::Base.connection.quoted_date(@from), ActiveRecord::Base.connection.quoted_date(@to)]
|
||||||
@@ -121,7 +166,7 @@ class TimelogController < ApplicationController
|
|||||||
elsif @issue.nil?
|
elsif @issue.nil?
|
||||||
cond << @project.project_condition(Setting.display_subprojects_issues?)
|
cond << @project.project_condition(Setting.display_subprojects_issues?)
|
||||||
else
|
else
|
||||||
cond << "#{Issue.table_name}.root_id = #{@issue.root_id} AND #{Issue.table_name}.lft >= #{@issue.lft} AND #{Issue.table_name}.rgt <= #{@issue.rgt}"
|
cond << ["#{TimeEntry.table_name}.issue_id = ?", @issue.id]
|
||||||
end
|
end
|
||||||
|
|
||||||
retrieve_date_range
|
retrieve_date_range
|
||||||
@@ -131,7 +176,7 @@ class TimelogController < ApplicationController
|
|||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.html {
|
format.html {
|
||||||
# Paginate results
|
# Paginate results
|
||||||
@entry_count = TimeEntry.count(:include => [:project, :issue], :conditions => cond.conditions)
|
@entry_count = TimeEntry.count(:include => :project, :conditions => cond.conditions)
|
||||||
@entry_pages = Paginator.new self, @entry_count, per_page_option, params['page']
|
@entry_pages = Paginator.new self, @entry_count, per_page_option, params['page']
|
||||||
@entries = TimeEntry.find(:all,
|
@entries = TimeEntry.find(:all,
|
||||||
:include => [:project, :activity, :user, {:issue => :tracker}],
|
:include => [:project, :activity, :user, {:issue => :tracker}],
|
||||||
@@ -139,7 +184,7 @@ class TimelogController < ApplicationController
|
|||||||
:order => sort_clause,
|
:order => sort_clause,
|
||||||
:limit => @entry_pages.items_per_page,
|
:limit => @entry_pages.items_per_page,
|
||||||
:offset => @entry_pages.current.offset)
|
:offset => @entry_pages.current.offset)
|
||||||
@total_hours = TimeEntry.sum(:hours, :include => [:project, :issue], :conditions => cond.conditions).to_f
|
@total_hours = TimeEntry.sum(:hours, :include => :project, :conditions => cond.conditions).to_f
|
||||||
|
|
||||||
render :layout => !request.xhr?
|
render :layout => !request.xhr?
|
||||||
}
|
}
|
||||||
@@ -180,11 +225,8 @@ class TimelogController < ApplicationController
|
|||||||
def destroy
|
def destroy
|
||||||
(render_404; return) unless @time_entry
|
(render_404; return) unless @time_entry
|
||||||
(render_403; return) unless @time_entry.editable_by?(User.current)
|
(render_403; return) unless @time_entry.editable_by?(User.current)
|
||||||
if @time_entry.destroy && @time_entry.destroyed?
|
@time_entry.destroy
|
||||||
flash[:notice] = l(:notice_successful_delete)
|
flash[:notice] = l(:notice_successful_delete)
|
||||||
else
|
|
||||||
flash[:error] = l(:notice_unable_delete_time_entry)
|
|
||||||
end
|
|
||||||
redirect_to :back
|
redirect_to :back
|
||||||
rescue ::ActionController::RedirectBackError
|
rescue ::ActionController::RedirectBackError
|
||||||
redirect_to :action => 'details', :project_id => @time_entry.project
|
redirect_to :action => 'details', :project_id => @time_entry.project
|
||||||
@@ -260,65 +302,7 @@ private
|
|||||||
end
|
end
|
||||||
|
|
||||||
@from, @to = @to, @from if @from && @to && @from > @to
|
@from, @to = @to, @from if @from && @to && @from > @to
|
||||||
@from ||= (TimeEntry.earilest_date_for_project(@project) || Date.today)
|
@from ||= (TimeEntry.minimum(:spent_on, :include => :project, :conditions => Project.allowed_to_condition(User.current, :view_time_entries)) || Date.today) - 1
|
||||||
@to ||= (TimeEntry.latest_date_for_project(@project) || Date.today)
|
@to ||= (TimeEntry.maximum(:spent_on, :include => :project, :conditions => Project.allowed_to_condition(User.current, :view_time_entries)) || 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"
|
|
||||||
call_hook(:controller_timelog_time_report_joins, {:sql => sql} )
|
|
||||||
sql
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -20,11 +20,16 @@ class TrackersController < ApplicationController
|
|||||||
|
|
||||||
before_filter :require_admin
|
before_filter :require_admin
|
||||||
|
|
||||||
verify :method => :post, :only => :destroy, :redirect_to => { :action => :index }
|
|
||||||
|
|
||||||
def index
|
def index
|
||||||
|
list
|
||||||
|
render :action => 'list' unless request.xhr?
|
||||||
|
end
|
||||||
|
|
||||||
|
verify :method => :post, :only => :destroy, :redirect_to => { :action => :list }
|
||||||
|
|
||||||
|
def list
|
||||||
@tracker_pages, @trackers = paginate :trackers, :per_page => 10, :order => 'position'
|
@tracker_pages, @trackers = paginate :trackers, :per_page => 10, :order => 'position'
|
||||||
render :action => "index", :layout => false if request.xhr?
|
render :action => "list", :layout => false if request.xhr?
|
||||||
end
|
end
|
||||||
|
|
||||||
def new
|
def new
|
||||||
@@ -35,7 +40,7 @@ class TrackersController < ApplicationController
|
|||||||
@tracker.workflows.copy(copy_from)
|
@tracker.workflows.copy(copy_from)
|
||||||
end
|
end
|
||||||
flash[:notice] = l(:notice_successful_create)
|
flash[:notice] = l(:notice_successful_create)
|
||||||
redirect_to :action => 'index'
|
redirect_to :action => 'list'
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
@trackers = Tracker.find :all, :order => 'position'
|
@trackers = Tracker.find :all, :order => 'position'
|
||||||
@@ -46,7 +51,7 @@ class TrackersController < ApplicationController
|
|||||||
@tracker = Tracker.find(params[:id])
|
@tracker = Tracker.find(params[:id])
|
||||||
if request.post? and @tracker.update_attributes(params[:tracker])
|
if request.post? and @tracker.update_attributes(params[:tracker])
|
||||||
flash[:notice] = l(:notice_successful_update)
|
flash[:notice] = l(:notice_successful_update)
|
||||||
redirect_to :action => 'index'
|
redirect_to :action => 'list'
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
@projects = Project.find(:all)
|
@projects = Project.find(:all)
|
||||||
@@ -55,10 +60,10 @@ class TrackersController < ApplicationController
|
|||||||
def destroy
|
def destroy
|
||||||
@tracker = Tracker.find(params[:id])
|
@tracker = Tracker.find(params[:id])
|
||||||
unless @tracker.issues.empty?
|
unless @tracker.issues.empty?
|
||||||
flash[:error] = l(:error_can_not_delete_tracker)
|
flash[:error] = "This tracker contains issues and can\'t be deleted."
|
||||||
else
|
else
|
||||||
@tracker.destroy
|
@tracker.destroy
|
||||||
end
|
end
|
||||||
redirect_to :action => 'index'
|
redirect_to :action => 'list'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -50,20 +50,20 @@ class UsersController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def show
|
def show
|
||||||
@user = User.find(params[:id])
|
@user = User.active.find(params[:id])
|
||||||
@custom_values = @user.custom_values
|
@custom_values = @user.custom_values
|
||||||
|
|
||||||
# show projects based on current user visibility
|
# show only public projects and private projects that the logged in user is also a member of
|
||||||
@memberships = @user.memberships.all(:conditions => Project.visible_by(User.current))
|
@memberships = @user.memberships.select do |membership|
|
||||||
|
membership.project.is_public? || (User.current.member_of?(membership.project))
|
||||||
|
end
|
||||||
|
|
||||||
events = Redmine::Activity::Fetcher.new(User.current, :author => @user).events(nil, nil, :limit => 10)
|
events = Redmine::Activity::Fetcher.new(User.current, :author => @user).events(nil, nil, :limit => 10)
|
||||||
@events_by_day = events.group_by(&:event_date)
|
@events_by_day = events.group_by(&:event_date)
|
||||||
|
|
||||||
unless User.current.admin?
|
if @user != User.current && !User.current.admin? && @memberships.empty? && events.empty?
|
||||||
if !@user.active? || (@user != User.current && @memberships.empty? && events.empty?)
|
render_404
|
||||||
render_404
|
return
|
||||||
return
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
render :layout => 'base'
|
render :layout => 'base'
|
||||||
|
|
||||||
@@ -95,9 +95,7 @@ class UsersController < ApplicationController
|
|||||||
if request.post?
|
if request.post?
|
||||||
@user.admin = params[:user][:admin] if params[:user][:admin]
|
@user.admin = params[:user][:admin] if params[:user][:admin]
|
||||||
@user.login = params[:user][:login] if params[:user][:login]
|
@user.login = params[:user][:login] if params[:user][:login]
|
||||||
if params[:password].present? && (@user.auth_source_id.nil? || params[:user][:auth_source_id].blank?)
|
@user.password, @user.password_confirmation = params[:password], params[:password_confirmation] unless params[:password].nil? or params[:password].empty? or @user.auth_source_id
|
||||||
@user.password, @user.password_confirmation = params[:password], params[:password_confirmation]
|
|
||||||
end
|
|
||||||
@user.group_ids = params[:user][:group_ids] if params[:user][:group_ids]
|
@user.group_ids = params[:user][:group_ids] if params[:user][:group_ids]
|
||||||
@user.attributes = params[:user]
|
@user.attributes = params[:user]
|
||||||
# Was the account actived ? (do it before User#save clears the change)
|
# Was the account actived ? (do it before User#save clears the change)
|
||||||
@@ -120,25 +118,18 @@ class UsersController < ApplicationController
|
|||||||
|
|
||||||
def edit_membership
|
def edit_membership
|
||||||
@user = User.find(params[:id])
|
@user = User.find(params[:id])
|
||||||
@membership = Member.edit_membership(params[:membership_id], params[:membership], @user)
|
@membership = params[:membership_id] ? Member.find(params[:membership_id]) : Member.new(:principal => @user)
|
||||||
|
@membership.attributes = params[:membership]
|
||||||
@membership.save if request.post?
|
@membership.save if request.post?
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
if @membership.valid?
|
format.html { redirect_to :controller => 'users', :action => 'edit', :id => @user, :tab => 'memberships' }
|
||||||
format.html { redirect_to :controller => 'users', :action => 'edit', :id => @user, :tab => 'memberships' }
|
format.js {
|
||||||
format.js {
|
render(:update) {|page|
|
||||||
render(:update) {|page|
|
page.replace_html "tab-content-memberships", :partial => 'users/memberships'
|
||||||
page.replace_html "tab-content-memberships", :partial => 'users/memberships'
|
page.visual_effect(:highlight, "member-#{@membership.id}")
|
||||||
page.visual_effect(:highlight, "member-#{@membership.id}")
|
}
|
||||||
}
|
}
|
||||||
}
|
end
|
||||||
else
|
|
||||||
format.js {
|
|
||||||
render(:update) {|page|
|
|
||||||
page.alert(l(:notice_failed_to_save_members, :errors => @membership.errors.full_messages.join(', ')))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def destroy_membership
|
def destroy_membership
|
||||||
|
|||||||
@@ -17,102 +17,23 @@
|
|||||||
|
|
||||||
class VersionsController < ApplicationController
|
class VersionsController < ApplicationController
|
||||||
menu_item :roadmap
|
menu_item :roadmap
|
||||||
model_object Version
|
before_filter :find_version, :except => :close_completed
|
||||||
before_filter :find_model_object, :except => [:index, :new, :create, :close_completed]
|
before_filter :find_project, :only => :close_completed
|
||||||
before_filter :find_project_from_association, :except => [:index, :new, :create, :close_completed]
|
|
||||||
before_filter :find_project, :only => [:index, :new, :create, :close_completed]
|
|
||||||
before_filter :authorize
|
before_filter :authorize
|
||||||
|
|
||||||
helper :custom_fields
|
helper :custom_fields
|
||||||
helper :projects
|
helper :projects
|
||||||
|
|
||||||
def index
|
|
||||||
@trackers = @project.trackers.find(:all, :order => 'position')
|
|
||||||
retrieve_selected_tracker_ids(@trackers, @trackers.select {|t| t.is_in_roadmap?})
|
|
||||||
@with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1')
|
|
||||||
project_ids = @with_subprojects ? @project.self_and_descendants.collect(&:id) : [@project.id]
|
|
||||||
|
|
||||||
@versions = @project.shared_versions || []
|
|
||||||
@versions += @project.rolled_up_versions.visible if @with_subprojects
|
|
||||||
@versions = @versions.uniq.sort
|
|
||||||
@versions.reject! {|version| version.closed? || version.completed? } unless params[:completed]
|
|
||||||
|
|
||||||
@issues_by_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?}
|
|
||||||
end
|
|
||||||
|
|
||||||
def show
|
def show
|
||||||
@issues = @version.fixed_issues.visible.find(:all,
|
|
||||||
:include => [:status, :tracker, :priority],
|
|
||||||
:order => "#{Tracker.table_name}.position, #{Issue.table_name}.id")
|
|
||||||
end
|
|
||||||
|
|
||||||
def 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.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.attributes = attributes
|
|
||||||
end
|
|
||||||
|
|
||||||
if request.post?
|
|
||||||
if @version.save
|
|
||||||
respond_to do |format|
|
|
||||||
format.html do
|
|
||||||
flash[:notice] = l(:notice_successful_create)
|
|
||||||
redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
|
|
||||||
end
|
|
||||||
format.js do
|
|
||||||
# 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
|
|
||||||
end
|
|
||||||
else
|
|
||||||
respond_to do |format|
|
|
||||||
format.html { render :action => 'new' }
|
|
||||||
format.js do
|
|
||||||
render(:update) {|page| page.alert(@version.errors.full_messages.join('\n')) }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def edit
|
def edit
|
||||||
end
|
|
||||||
|
|
||||||
def update
|
|
||||||
if request.post? && params[:version]
|
if request.post? && params[:version]
|
||||||
attributes = params[:version].dup
|
attributes = params[:version].dup
|
||||||
attributes.delete('sharing') unless @version.allowed_sharings.include?(attributes['sharing'])
|
attributes.delete('sharing') unless @version.allowed_sharings.include?(attributes['sharing'])
|
||||||
if @version.update_attributes(attributes)
|
if @version.update_attributes(attributes)
|
||||||
flash[:notice] = l(:notice_successful_update)
|
flash[:notice] = l(:notice_successful_update)
|
||||||
redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
|
redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
|
||||||
else
|
|
||||||
respond_to do |format|
|
|
||||||
format.html { render :action => 'edit' }
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -125,13 +46,11 @@ class VersionsController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
if @version.fixed_issues.empty?
|
@version.destroy
|
||||||
@version.destroy
|
redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
|
||||||
redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
|
rescue
|
||||||
else
|
flash[:error] = l(:notice_unable_delete_version)
|
||||||
flash[:error] = l(:notice_unable_delete_version)
|
redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
|
||||||
redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def status_by
|
def status_by
|
||||||
@@ -142,18 +61,16 @@ class VersionsController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
def find_version
|
||||||
|
@version = Version.find(params[:id])
|
||||||
|
@project = @version.project
|
||||||
|
rescue ActiveRecord::RecordNotFound
|
||||||
|
render_404
|
||||||
|
end
|
||||||
|
|
||||||
def find_project
|
def find_project
|
||||||
@project = Project.find(params[:project_id])
|
@project = Project.find(params[:project_id])
|
||||||
rescue ActiveRecord::RecordNotFound
|
rescue ActiveRecord::RecordNotFound
|
||||||
render_404
|
render_404
|
||||||
end
|
end
|
||||||
|
|
||||||
def retrieve_selected_tracker_ids(selectable_trackers, default_trackers=nil)
|
|
||||||
if ids = params[:tracker_ids]
|
|
||||||
@selected_tracker_ids = (ids.is_a? Array) ? ids.collect { |id| id.to_i.to_s } : ids.split('/').collect { |id| id.to_i.to_s }
|
|
||||||
else
|
|
||||||
@selected_tracker_ids = (default_trackers || selectable_trackers).collect {|t| t.id.to_s }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
end
|
||||||
@@ -83,7 +83,7 @@ private
|
|||||||
replace_ids = [params[:replace]]
|
replace_ids = [params[:replace]]
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
replace_ids = ['watcher']
|
replace_ids = 'watcher'
|
||||||
end
|
end
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.html { redirect_to :back }
|
format.html { redirect_to :back }
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ class WikiController < ApplicationController
|
|||||||
page_title = params[:page]
|
page_title = params[:page]
|
||||||
@page = @wiki.find_or_new_page(page_title)
|
@page = @wiki.find_or_new_page(page_title)
|
||||||
if @page.new_record?
|
if @page.new_record?
|
||||||
if User.current.allowed_to?(:edit_wiki_pages, @project) && editable?
|
if User.current.allowed_to?(:edit_wiki_pages, @project)
|
||||||
edit
|
edit
|
||||||
render :action => 'edit'
|
render :action => 'edit'
|
||||||
else
|
else
|
||||||
@@ -47,17 +47,15 @@ class WikiController < ApplicationController
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
@content = @page.content_for_version(params[:version])
|
@content = @page.content_for_version(params[:version])
|
||||||
if User.current.allowed_to?(:export_wiki_pages, @project)
|
if params[:format] == 'html'
|
||||||
if params[:format] == 'html'
|
export = render_to_string :action => 'export', :layout => false
|
||||||
export = render_to_string :action => 'export', :layout => false
|
send_data(export, :type => 'text/html', :filename => "#{@page.title}.html")
|
||||||
send_data(export, :type => 'text/html', :filename => "#{@page.title}.html")
|
return
|
||||||
return
|
elsif params[:format] == 'txt'
|
||||||
elsif params[:format] == 'txt'
|
send_data(@content.text, :type => 'text/plain', :filename => "#{@page.title}.txt")
|
||||||
send_data(@content.text, :type => 'text/plain', :filename => "#{@page.title}.txt")
|
return
|
||||||
return
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
@editable = editable?
|
@editable = editable?
|
||||||
render :action => 'show'
|
render :action => 'show'
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -76,8 +74,6 @@ class WikiController < ApplicationController
|
|||||||
@content.version = @page.content.version
|
@content.version = @page.content.version
|
||||||
else
|
else
|
||||||
if !@page.new_record? && @content.text == params[:content][:text]
|
if !@page.new_record? && @content.text == params[:content][:text]
|
||||||
attachments = Attachment.attach_files(@page, params[:attachments])
|
|
||||||
render_attachment_warning_if_needed(@page)
|
|
||||||
# don't save if text wasn't changed
|
# don't save if text wasn't changed
|
||||||
redirect_to :action => 'index', :id => @project, :page => @page.title
|
redirect_to :action => 'index', :id => @project, :page => @page.title
|
||||||
return
|
return
|
||||||
@@ -88,8 +84,6 @@ class WikiController < ApplicationController
|
|||||||
@content.author = User.current
|
@content.author = User.current
|
||||||
# if page is new @page.save will also save content, but not if page isn't a new record
|
# 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)
|
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})
|
call_hook(:controller_wiki_edit_after_save, { :params => params, :page => @page})
|
||||||
redirect_to :action => 'index', :id => @project, :page => @page.title
|
redirect_to :action => 'index', :id => @project, :page => @page.title
|
||||||
end
|
end
|
||||||
@@ -183,13 +177,9 @@ class WikiController < ApplicationController
|
|||||||
@pages_by_parent_id = @pages.group_by(&:parent_id)
|
@pages_by_parent_id = @pages.group_by(&:parent_id)
|
||||||
# export wiki to a single html file
|
# export wiki to a single html file
|
||||||
when 'export'
|
when 'export'
|
||||||
if User.current.allowed_to?(:export_wiki_pages, @project)
|
@pages = @wiki.pages.find :all, :order => 'title'
|
||||||
@pages = @wiki.pages.find :all, :order => 'title'
|
export = render_to_string :action => 'export_multiple', :layout => false
|
||||||
export = render_to_string :action => 'export_multiple', :layout => false
|
send_data(export, :type => 'text/html', :filename => "wiki.html")
|
||||||
send_data(export, :type => 'text/html', :filename => "wiki.html")
|
|
||||||
else
|
|
||||||
redirect_to :action => 'index', :id => @project, :page => nil
|
|
||||||
end
|
|
||||||
return
|
return
|
||||||
else
|
else
|
||||||
# requested special page doesn't exist, redirect to default page
|
# requested special page doesn't exist, redirect to default page
|
||||||
@@ -213,8 +203,7 @@ class WikiController < ApplicationController
|
|||||||
|
|
||||||
def add_attachment
|
def add_attachment
|
||||||
return render_403 unless editable?
|
return render_403 unless editable?
|
||||||
attachments = Attachment.attach_files(@page, params[:attachments])
|
attach_files(@page, params[:attachments])
|
||||||
render_attachment_warning_if_needed(@page)
|
|
||||||
redirect_to :action => 'index', :page => @page.title
|
redirect_to :action => 'index', :page => @page.title
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -34,4 +34,11 @@ class WikisController < ApplicationController
|
|||||||
redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'wiki'
|
redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'wiki'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
def find_project
|
||||||
|
@project = Project.find(params[:id])
|
||||||
|
rescue ActiveRecord::RecordNotFound
|
||||||
|
render_404
|
||||||
|
end
|
||||||
end
|
end
|
||||||
@@ -19,8 +19,6 @@ class WorkflowsController < ApplicationController
|
|||||||
layout 'admin'
|
layout 'admin'
|
||||||
|
|
||||||
before_filter :require_admin
|
before_filter :require_admin
|
||||||
before_filter :find_roles
|
|
||||||
before_filter :find_trackers
|
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@workflow_counts = Workflow.count_by_tracker_and_role
|
@workflow_counts = Workflow.count_by_tracker_and_role
|
||||||
@@ -42,6 +40,8 @@ class WorkflowsController < ApplicationController
|
|||||||
redirect_to :action => 'edit', :role_id => @role, :tracker_id => @tracker
|
redirect_to :action => 'edit', :role_id => @role, :tracker_id => @tracker
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@roles = Role.find(:all, :order => 'builtin, position')
|
||||||
|
@trackers = Tracker.find(:all, :order => 'position')
|
||||||
|
|
||||||
@used_statuses_only = (params[:used_statuses_only] == '0' ? false : true)
|
@used_statuses_only = (params[:used_statuses_only] == '0' ? false : true)
|
||||||
if @tracker && @used_statuses_only && @tracker.issue_statuses.any?
|
if @tracker && @used_statuses_only && @tracker.issue_statuses.any?
|
||||||
@@ -51,6 +51,8 @@ class WorkflowsController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def copy
|
def copy
|
||||||
|
@trackers = Tracker.find(:all, :order => 'position')
|
||||||
|
@roles = Role.find(:all, :order => 'builtin, position')
|
||||||
|
|
||||||
if params[:source_tracker_id].blank? || params[:source_tracker_id] == 'any'
|
if params[:source_tracker_id].blank? || params[:source_tracker_id] == 'any'
|
||||||
@source_tracker = nil
|
@source_tracker = nil
|
||||||
@@ -78,14 +80,4 @@ class WorkflowsController < ApplicationController
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def find_roles
|
|
||||||
@roles = Role.find(:all, :order => 'builtin, position')
|
|
||||||
end
|
|
||||||
|
|
||||||
def find_trackers
|
|
||||||
@trackers = Tracker.find(:all, :order => 'position')
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
@@ -20,4 +20,12 @@ module AdminHelper
|
|||||||
options_for_select([[l(:label_all), ''],
|
options_for_select([[l(:label_all), ''],
|
||||||
[l(:status_active), 1]], selected)
|
[l(:status_active), 1]], selected)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def css_project_classes(project)
|
||||||
|
s = 'project'
|
||||||
|
s << ' root' if project.root?
|
||||||
|
s << ' child' if project.child?
|
||||||
|
s << (project.leaf? ? ' leaf' : ' parent')
|
||||||
|
s
|
||||||
|
end
|
||||||
end
|
end
|
||||||
@@ -15,6 +15,8 @@
|
|||||||
# along with this program; if not, write to the Free Software
|
# along with this program; if not, write to the Free Software
|
||||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
|
||||||
|
require 'coderay'
|
||||||
|
require 'coderay/helpers/file_type'
|
||||||
require 'forwardable'
|
require 'forwardable'
|
||||||
require 'cgi'
|
require 'cgi'
|
||||||
|
|
||||||
@@ -32,11 +34,6 @@ module ApplicationHelper
|
|||||||
end
|
end
|
||||||
|
|
||||||
# Display a link if user is authorized
|
# Display a link if user is authorized
|
||||||
#
|
|
||||||
# @param [String] name Anchor text (passed to link_to)
|
|
||||||
# @param [Hash] options Hash params. This will checked by authorize_for to see if the user is authorized
|
|
||||||
# @param [optional, Hash] html_options Options passed to link_to
|
|
||||||
# @param [optional, Hash] parameters_for_method_reference Extra parameters for link_to
|
|
||||||
def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
|
def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
|
||||||
link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
|
link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
|
||||||
end
|
end
|
||||||
@@ -108,23 +105,6 @@ module ApplicationHelper
|
|||||||
link_to(text, {:controller => 'repositories', :action => 'revision', :id => project, :rev => revision}, :title => l(:label_revision_id, revision))
|
link_to(text, {:controller => 'repositories', :action => 'revision', :id => project, :rev => revision}, :title => l(:label_revision_id, revision))
|
||||||
end
|
end
|
||||||
|
|
||||||
# Generates a link to a project if active
|
|
||||||
# Examples:
|
|
||||||
#
|
|
||||||
# link_to_project(project) # => link to the specified project overview
|
|
||||||
# link_to_project(project, :action=>'settings') # => link to project settings
|
|
||||||
# link_to_project(project, {:only_path => false}, :class => "project") # => 3rd arg adds html options
|
|
||||||
# link_to_project(project, {}, :class => "project") # => html options with default url (project overview)
|
|
||||||
#
|
|
||||||
def link_to_project(project, options={}, html_options = nil)
|
|
||||||
if project.active?
|
|
||||||
url = {:controller => 'projects', :action => 'show', :id => project}.merge(options)
|
|
||||||
link_to(h(project), url, html_options)
|
|
||||||
else
|
|
||||||
h(project)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def toggle_link(name, id, options={})
|
def toggle_link(name, id, options={})
|
||||||
onclick = "Element.toggle('#{id}'); "
|
onclick = "Element.toggle('#{id}'); "
|
||||||
onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
|
onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
|
||||||
@@ -225,12 +205,7 @@ module ApplicationHelper
|
|||||||
s = ''
|
s = ''
|
||||||
project_tree(projects) do |project, level|
|
project_tree(projects) do |project, level|
|
||||||
name_prefix = (level > 0 ? (' ' * 2 * level + '» ') : '')
|
name_prefix = (level > 0 ? (' ' * 2 * level + '» ') : '')
|
||||||
tag_options = {:value => project.id}
|
tag_options = {:value => project.id, :selected => ((project == options[:selected]) ? 'selected' : nil)}
|
||||||
if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project))
|
|
||||||
tag_options[:selected] = 'selected'
|
|
||||||
else
|
|
||||||
tag_options[:selected] = nil
|
|
||||||
end
|
|
||||||
tag_options.merge!(yield(project)) if block_given?
|
tag_options.merge!(yield(project)) if block_given?
|
||||||
s << content_tag('option', name_prefix + h(project), tag_options)
|
s << content_tag('option', name_prefix + h(project), tag_options)
|
||||||
end
|
end
|
||||||
@@ -286,16 +261,6 @@ module ApplicationHelper
|
|||||||
truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
|
truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
|
||||||
end
|
end
|
||||||
|
|
||||||
# Truncates at line break after 250 characters or options[:length]
|
|
||||||
def truncate_lines(string, options={})
|
|
||||||
length = options[:length] || 250
|
|
||||||
if string.to_s =~ /\A(.{#{length}}.*?)$/m
|
|
||||||
"#{$1}..."
|
|
||||||
else
|
|
||||||
string
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def html_hours(text)
|
def html_hours(text)
|
||||||
text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>')
|
text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>')
|
||||||
end
|
end
|
||||||
@@ -307,14 +272,15 @@ module ApplicationHelper
|
|||||||
def time_tag(time)
|
def time_tag(time)
|
||||||
text = distance_of_time_in_words(Time.now, time)
|
text = distance_of_time_in_words(Time.now, time)
|
||||||
if @project
|
if @project
|
||||||
link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => time.to_date}, :title => format_time(time))
|
link_to(text, {:controller => 'projects', :action => 'activity', :id => @project, :from => time.to_date}, :title => format_time(time))
|
||||||
else
|
else
|
||||||
content_tag('acronym', text, :title => format_time(time))
|
content_tag('acronym', text, :title => format_time(time))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def syntax_highlight(name, content)
|
def syntax_highlight(name, content)
|
||||||
Redmine::SyntaxHighlighting.highlight_by_filename(content, name)
|
type = CodeRay::FileType[name]
|
||||||
|
type ? CodeRay.scan(content, type).html : h(content)
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_path_param(path)
|
def to_path_param(path)
|
||||||
@@ -323,7 +289,6 @@ module ApplicationHelper
|
|||||||
|
|
||||||
def pagination_links_full(paginator, count=nil, options={})
|
def pagination_links_full(paginator, count=nil, options={})
|
||||||
page_param = options.delete(:page_param) || :page
|
page_param = options.delete(:page_param) || :page
|
||||||
per_page_links = options.delete(:per_page_links)
|
|
||||||
url_param = params.dup
|
url_param = params.dup
|
||||||
# don't reuse query params if filters are present
|
# don't reuse query params if filters are present
|
||||||
url_param.merge!(:fields => nil, :values => nil, :operators => nil) if url_param.delete(:set_filter)
|
url_param.merge!(:fields => nil, :values => nil, :operators => nil) if url_param.delete(:set_filter)
|
||||||
@@ -342,10 +307,10 @@ module ApplicationHelper
|
|||||||
end
|
end
|
||||||
|
|
||||||
unless count.nil?
|
unless count.nil?
|
||||||
html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})"
|
html << [
|
||||||
if per_page_links != false && links = per_page_links(paginator.items_per_page)
|
" (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})",
|
||||||
html << " | #{links}"
|
per_page_links(paginator.items_per_page)
|
||||||
end
|
].compact.join(' | ')
|
||||||
end
|
end
|
||||||
|
|
||||||
html
|
html
|
||||||
@@ -390,12 +355,12 @@ module ApplicationHelper
|
|||||||
ancestors = (@project.root? ? [] : @project.ancestors.visible)
|
ancestors = (@project.root? ? [] : @project.ancestors.visible)
|
||||||
if ancestors.any?
|
if ancestors.any?
|
||||||
root = ancestors.shift
|
root = ancestors.shift
|
||||||
b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
|
b << link_to(h(root), {:controller => 'projects', :action => 'show', :id => root, :jump => current_menu_item}, :class => 'root')
|
||||||
if ancestors.size > 2
|
if ancestors.size > 2
|
||||||
b << '…'
|
b << '…'
|
||||||
ancestors = ancestors[-2, 2]
|
ancestors = ancestors[-2, 2]
|
||||||
end
|
end
|
||||||
b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
|
b += ancestors.collect {|p| link_to(h(p), {:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item}, :class => 'ancestor') }
|
||||||
end
|
end
|
||||||
b << h(@project)
|
b << h(@project)
|
||||||
b.join(' » ')
|
b.join(' » ')
|
||||||
@@ -415,19 +380,6 @@ module ApplicationHelper
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns the theme, controller name, and action as css classes for the
|
|
||||||
# HTML body.
|
|
||||||
def body_css_classes
|
|
||||||
css = []
|
|
||||||
if theme = Redmine::Themes.theme(Setting.ui_theme)
|
|
||||||
css << 'theme-' + theme.name
|
|
||||||
end
|
|
||||||
|
|
||||||
css << 'controller-' + params[:controller]
|
|
||||||
css << 'action-' + params[:action]
|
|
||||||
css.join(' ')
|
|
||||||
end
|
|
||||||
|
|
||||||
def accesskey(s)
|
def accesskey(s)
|
||||||
Redmine::AccessKeys.key_for s
|
Redmine::AccessKeys.key_for s
|
||||||
end
|
end
|
||||||
@@ -444,87 +396,61 @@ module ApplicationHelper
|
|||||||
text = args.shift
|
text = args.shift
|
||||||
when 2
|
when 2
|
||||||
obj = args.shift
|
obj = args.shift
|
||||||
attr = args.shift
|
text = obj.send(args.shift).to_s
|
||||||
text = obj.send(attr).to_s
|
|
||||||
else
|
else
|
||||||
raise ArgumentError, 'invalid arguments to textilizable'
|
raise ArgumentError, 'invalid arguments to textilizable'
|
||||||
end
|
end
|
||||||
return '' if text.blank?
|
return '' if text.blank?
|
||||||
project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
|
|
||||||
only_path = options.delete(:only_path) == false ? false : true
|
only_path = options.delete(:only_path) == false ? false : true
|
||||||
|
|
||||||
text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr) { |macro, args| exec_macro(macro, obj, args) }
|
|
||||||
|
|
||||||
parse_non_pre_blocks(text) do |text|
|
|
||||||
[:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links].each do |method_name|
|
|
||||||
send method_name, text, project, obj, attr, only_path, options
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def parse_non_pre_blocks(text)
|
|
||||||
s = StringScanner.new(text)
|
|
||||||
tags = []
|
|
||||||
parsed = ''
|
|
||||||
while !s.eos?
|
|
||||||
s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
|
|
||||||
text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
|
|
||||||
if tags.empty?
|
|
||||||
yield text
|
|
||||||
end
|
|
||||||
parsed << text
|
|
||||||
if tag
|
|
||||||
if closing
|
|
||||||
if tags.last == tag.downcase
|
|
||||||
tags.pop
|
|
||||||
end
|
|
||||||
else
|
|
||||||
tags << tag.downcase
|
|
||||||
end
|
|
||||||
parsed << full_tag
|
|
||||||
end
|
|
||||||
end
|
|
||||||
# Close any non closing tags
|
|
||||||
while tag = tags.pop
|
|
||||||
parsed << "</#{tag}>"
|
|
||||||
end
|
|
||||||
parsed
|
|
||||||
end
|
|
||||||
|
|
||||||
def parse_inline_attachments(text, project, obj, attr, only_path, options)
|
|
||||||
# when using an image link, try to use an attachment, if possible
|
# when using an image link, try to use an attachment, if possible
|
||||||
if options[:attachments] || (obj && obj.respond_to?(:attachments))
|
attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
|
||||||
attachments = nil
|
|
||||||
text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
|
if attachments
|
||||||
filename, ext, alt, alttext = $1.downcase, $2, $3, $4
|
attachments = attachments.sort_by(&:created_on).reverse
|
||||||
attachments ||= (options[:attachments] || obj.attachments).sort_by(&:created_on).reverse
|
text = text.gsub(/!((\<|\=|\>)?(\([^\)]+\))?(\[[^\]]+\])?(\{[^\}]+\})?)(\S+\.(bmp|gif|jpg|jpeg|png))!/i) do |m|
|
||||||
|
style = $1
|
||||||
|
filename = $6.downcase
|
||||||
# search for the picture in attachments
|
# search for the picture in attachments
|
||||||
if found = attachments.detect { |att| att.filename.downcase == filename }
|
if found = attachments.detect { |att| att.filename.downcase == filename }
|
||||||
image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found
|
image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found
|
||||||
desc = found.description.to_s.gsub('"', '')
|
desc = found.description.to_s.gsub(/^([^\(\)]*).*$/, "\\1")
|
||||||
if !desc.blank? && alttext.blank?
|
alt = desc.blank? ? nil : "(#{desc})"
|
||||||
alt = " title=\"#{desc}\" alt=\"#{desc}\""
|
"!#{style}#{image_url}#{alt}!"
|
||||||
end
|
|
||||||
"src=\"#{image_url}\"#{alt}"
|
|
||||||
else
|
else
|
||||||
m
|
m
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
# Wiki links
|
text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text) { |macro, args| exec_macro(macro, obj, args) }
|
||||||
#
|
|
||||||
# Examples:
|
# different methods for formatting wiki links
|
||||||
# [[mypage]]
|
case options[:wiki_links]
|
||||||
# [[mypage|mytext]]
|
when :local
|
||||||
# wiki links can refer other project wikis, using project name or identifier:
|
# used for local links to html files
|
||||||
# [[project:]] -> wiki starting page
|
format_wiki_link = Proc.new {|project, title, anchor| "#{title}.html" }
|
||||||
# [[project:|mytext]]
|
when :anchor
|
||||||
# [[project:mypage]]
|
# used for single-file wiki export
|
||||||
# [[project:mypage|mytext]]
|
format_wiki_link = Proc.new {|project, title, anchor| "##{title}" }
|
||||||
def parse_wiki_links(text, project, obj, attr, only_path, options)
|
else
|
||||||
text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
|
format_wiki_link = Proc.new {|project, title, anchor| url_for(:only_path => only_path, :controller => 'wiki', :action => 'index', :id => project, :page => title, :anchor => anchor) }
|
||||||
|
end
|
||||||
|
|
||||||
|
project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
|
||||||
|
|
||||||
|
# Wiki links
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
# [[mypage]]
|
||||||
|
# [[mypage|mytext]]
|
||||||
|
# wiki links can refer other project wikis, using project name or identifier:
|
||||||
|
# [[project:]] -> wiki starting page
|
||||||
|
# [[project:|mytext]]
|
||||||
|
# [[project:mypage]]
|
||||||
|
# [[project:mypage|mytext]]
|
||||||
|
text = text.gsub(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
|
||||||
link_project = project
|
link_project = project
|
||||||
esc, all, page, title = $1, $2, $3, $5
|
esc, all, page, title = $1, $2, $3, $5
|
||||||
if esc.nil?
|
if esc.nil?
|
||||||
@@ -542,13 +468,8 @@ module ApplicationHelper
|
|||||||
end
|
end
|
||||||
# check if page exists
|
# check if page exists
|
||||||
wiki_page = link_project.wiki.find_page(page)
|
wiki_page = link_project.wiki.find_page(page)
|
||||||
url = case options[:wiki_links]
|
link_to((title || page), format_wiki_link.call(link_project, Wiki.titleize(page), anchor),
|
||||||
when :local; "#{title}.html"
|
:class => ('wiki-page' + (wiki_page ? '' : ' new')))
|
||||||
when :anchor; "##{title}" # used for single-file wiki export
|
|
||||||
else
|
|
||||||
url_for(:only_path => only_path, :controller => 'wiki', :action => 'index', :id => link_project, :page => Wiki.titleize(page), :anchor => anchor)
|
|
||||||
end
|
|
||||||
link_to((title || page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
|
|
||||||
else
|
else
|
||||||
# project or wiki doesn't exist
|
# project or wiki doesn't exist
|
||||||
all
|
all
|
||||||
@@ -557,47 +478,45 @@ module ApplicationHelper
|
|||||||
all
|
all
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
# Redmine links
|
# Redmine links
|
||||||
#
|
#
|
||||||
# Examples:
|
# Examples:
|
||||||
# Issues:
|
# Issues:
|
||||||
# #52 -> Link to issue #52
|
# #52 -> Link to issue #52
|
||||||
# Changesets:
|
# Changesets:
|
||||||
# r52 -> Link to revision 52
|
# r52 -> Link to revision 52
|
||||||
# commit:a85130f -> Link to scmid starting with a85130f
|
# commit:a85130f -> Link to scmid starting with a85130f
|
||||||
# Documents:
|
# Documents:
|
||||||
# document#17 -> Link to document with id 17
|
# document#17 -> Link to document with id 17
|
||||||
# document:Greetings -> Link to the document with title "Greetings"
|
# document:Greetings -> Link to the document with title "Greetings"
|
||||||
# document:"Some document" -> Link to the document with title "Some document"
|
# document:"Some document" -> Link to the document with title "Some document"
|
||||||
# Versions:
|
# Versions:
|
||||||
# version#3 -> Link to version with id 3
|
# version#3 -> Link to version with id 3
|
||||||
# version:1.0.0 -> Link to version named "1.0.0"
|
# version:1.0.0 -> Link to version named "1.0.0"
|
||||||
# version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
|
# version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
|
||||||
# Attachments:
|
# Attachments:
|
||||||
# attachment:file.zip -> Link to the attachment of the current object named file.zip
|
# attachment:file.zip -> Link to the attachment of the current object named file.zip
|
||||||
# Source files:
|
# Source files:
|
||||||
# source:some/file -> Link to the file located at /some/file in the project's repository
|
# source:some/file -> Link to the file located at /some/file in the project's repository
|
||||||
# source:some/file@52 -> Link to the file's revision 52
|
# source:some/file@52 -> Link to the file's revision 52
|
||||||
# source:some/file#L120 -> Link to line 120 of the file
|
# source:some/file#L120 -> Link to line 120 of the file
|
||||||
# source:some/file@52#L120 -> Link to line 120 of the file's revision 52
|
# source:some/file@52#L120 -> Link to line 120 of the file's revision 52
|
||||||
# export:some/file -> Force the download of the file
|
# export:some/file -> Force the download of the file
|
||||||
# Forum messages:
|
# Forum messages:
|
||||||
# message#1218 -> Link to message with id 1218
|
# message#1218 -> Link to message with id 1218
|
||||||
def parse_redmine_links(text, project, obj, attr, only_path, options)
|
text = text.gsub(%r{([\s\(,\-\>]|^)(!)?(attachment|document|version|commit|source|export|message)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|,|\s|<|$)}) do |m|
|
||||||
text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(attachment|document|version|commit|source|export|message|project)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|,|\s|\]|<|$)}) do |m|
|
leading, esc, prefix, sep, oid = $1, $2, $3, $5 || $7, $6 || $8
|
||||||
leading, esc, prefix, sep, identifier = $1, $2, $3, $5 || $7, $6 || $8
|
|
||||||
link = nil
|
link = nil
|
||||||
if esc.nil?
|
if esc.nil?
|
||||||
if prefix.nil? && sep == 'r'
|
if prefix.nil? && sep == 'r'
|
||||||
if project && (changeset = project.changesets.find_by_revision(identifier))
|
if project && (changeset = project.changesets.find_by_revision(oid))
|
||||||
link = link_to("r#{identifier}", {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
|
link = link_to("r#{oid}", {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => oid},
|
||||||
:class => 'changeset',
|
:class => 'changeset',
|
||||||
:title => truncate_single_line(changeset.comments, :length => 100))
|
:title => truncate_single_line(changeset.comments, :length => 100))
|
||||||
end
|
end
|
||||||
elsif sep == '#'
|
elsif sep == '#'
|
||||||
oid = identifier.to_i
|
oid = oid.to_i
|
||||||
case prefix
|
case prefix
|
||||||
when nil
|
when nil
|
||||||
if issue = Issue.visible.find_by_id(oid, :include => :status)
|
if issue = Issue.visible.find_by_id(oid, :include => :status)
|
||||||
@@ -625,14 +544,10 @@ module ApplicationHelper
|
|||||||
:anchor => (message.parent ? "message-#{message.id}" : nil)},
|
:anchor => (message.parent ? "message-#{message.id}" : nil)},
|
||||||
:class => 'message'
|
:class => 'message'
|
||||||
end
|
end
|
||||||
when 'project'
|
|
||||||
if p = Project.visible.find_by_id(oid)
|
|
||||||
link = link_to_project(p, {:only_path => only_path}, :class => 'project')
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
elsif sep == ':'
|
elsif sep == ':'
|
||||||
# removes the double quotes if any
|
# removes the double quotes if any
|
||||||
name = identifier.gsub(%r{^"(.*)"$}, "\\1")
|
name = oid.gsub(%r{^"(.*)"$}, "\\1")
|
||||||
case prefix
|
case prefix
|
||||||
when 'document'
|
when 'document'
|
||||||
if project && document = project.documents.find_by_title(name)
|
if project && document = project.documents.find_by_title(name)
|
||||||
@@ -662,20 +577,17 @@ module ApplicationHelper
|
|||||||
:class => (prefix == 'export' ? 'source download' : 'source')
|
:class => (prefix == 'export' ? 'source download' : 'source')
|
||||||
end
|
end
|
||||||
when 'attachment'
|
when 'attachment'
|
||||||
attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
|
|
||||||
if attachments && attachment = attachments.detect {|a| a.filename == name }
|
if attachments && attachment = attachments.detect {|a| a.filename == name }
|
||||||
link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
|
link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
|
||||||
:class => 'attachment'
|
:class => 'attachment'
|
||||||
end
|
end
|
||||||
when 'project'
|
|
||||||
if p = Project.visible.find(:first, :conditions => ["identifier = :s OR LOWER(name) = :s", {:s => name.downcase}])
|
|
||||||
link = link_to_project(p, {:only_path => only_path}, :class => 'project')
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
leading + (link || "#{prefix}#{sep}#{identifier}")
|
leading + (link || "#{prefix}#{sep}#{oid}")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
text
|
||||||
end
|
end
|
||||||
|
|
||||||
# Same as Rails' simple_format helper without using paragraphs
|
# Same as Rails' simple_format helper without using paragraphs
|
||||||
@@ -730,28 +642,6 @@ module ApplicationHelper
|
|||||||
content_tag('p', legend, :class => 'pourcent')
|
content_tag('p', legend, :class => 'pourcent')
|
||||||
end
|
end
|
||||||
|
|
||||||
def checked_image(checked=true)
|
|
||||||
if checked
|
|
||||||
image_tag 'toggle_check.png'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def context_menu(url)
|
|
||||||
unless @context_menu_included
|
|
||||||
content_for :header_tags do
|
|
||||||
javascript_include_tag('context_menu') +
|
|
||||||
stylesheet_link_tag('context_menu')
|
|
||||||
end
|
|
||||||
if l(:direction) == 'rtl'
|
|
||||||
content_for :header_tags do
|
|
||||||
stylesheet_link_tag('context_menu_rtl')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@context_menu_included = true
|
|
||||||
end
|
|
||||||
javascript_tag "new ContextMenu('#{ url_for(url) }')"
|
|
||||||
end
|
|
||||||
|
|
||||||
def context_menu_link(name, url, options={})
|
def context_menu_link(name, url, options={})
|
||||||
options[:class] ||= ''
|
options[:class] ||= ''
|
||||||
if options.delete(:selected)
|
if options.delete(:selected)
|
||||||
@@ -810,7 +700,7 @@ module ApplicationHelper
|
|||||||
# +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
|
# +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
|
||||||
def avatar(user, options = { })
|
def avatar(user, options = { })
|
||||||
if Setting.gravatar_enabled?
|
if Setting.gravatar_enabled?
|
||||||
options.merge!({:ssl => (defined?(request) && request.ssl?), :default => Setting.gravatar_default})
|
options.merge!({:ssl => Setting.protocol == 'https', :default => Setting.gravatar_default})
|
||||||
email = nil
|
email = nil
|
||||||
if user.respond_to?(:mail)
|
if user.respond_to?(:mail)
|
||||||
email = user.mail
|
email = user.mail
|
||||||
@@ -821,10 +711,6 @@ module ApplicationHelper
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def favicon
|
|
||||||
"<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />"
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def wiki_helper
|
def wiki_helper
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
module CalendarsHelper
|
|
||||||
def link_to_previous_month(year, month)
|
|
||||||
target_year, target_month = if month == 1
|
|
||||||
[year - 1, 12]
|
|
||||||
else
|
|
||||||
[year, month - 1]
|
|
||||||
end
|
|
||||||
|
|
||||||
name = if target_month == 12
|
|
||||||
"#{month_name(target_month)} #{target_year}"
|
|
||||||
else
|
|
||||||
"#{month_name(target_month)}"
|
|
||||||
end
|
|
||||||
|
|
||||||
link_to_remote ('« ' + name),
|
|
||||||
{:update => "content", :url => { :year => target_year, :month => target_month }},
|
|
||||||
{:href => url_for(:action => 'show', :year => target_year, :month => target_month)}
|
|
||||||
end
|
|
||||||
|
|
||||||
def link_to_next_month(year, month)
|
|
||||||
target_year, target_month = if month == 12
|
|
||||||
[year + 1, 1]
|
|
||||||
else
|
|
||||||
[year, month + 1]
|
|
||||||
end
|
|
||||||
|
|
||||||
name = if target_month == 1
|
|
||||||
"#{month_name(target_month)} #{target_year}"
|
|
||||||
else
|
|
||||||
"#{month_name(target_month)}"
|
|
||||||
end
|
|
||||||
|
|
||||||
link_to_remote (name + ' »'),
|
|
||||||
{:update => "content", :url => { :year => target_year, :month => target_month }},
|
|
||||||
{:href => url_for(:action => 'show', :year => target_year, :month =>target_month)}
|
|
||||||
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -36,8 +36,7 @@ module CustomFieldsHelper
|
|||||||
field_name = "#{name}[custom_field_values][#{custom_field.id}]"
|
field_name = "#{name}[custom_field_values][#{custom_field.id}]"
|
||||||
field_id = "#{name}_custom_field_values_#{custom_field.id}"
|
field_id = "#{name}_custom_field_values_#{custom_field.id}"
|
||||||
|
|
||||||
field_format = Redmine::CustomFieldFormat.find_by_name(custom_field.field_format)
|
case custom_field.field_format
|
||||||
case field_format.edit_as
|
|
||||||
when "date"
|
when "date"
|
||||||
text_field_tag(field_name, custom_value.value, :id => field_id, :size => 10) +
|
text_field_tag(field_name, custom_value.value, :id => field_id, :size => 10) +
|
||||||
calendar_for(field_id)
|
calendar_for(field_id)
|
||||||
@@ -68,11 +67,10 @@ module CustomFieldsHelper
|
|||||||
custom_field_label_tag(name, custom_value) + custom_field_tag(name, custom_value)
|
custom_field_label_tag(name, custom_value) + custom_field_tag(name, custom_value)
|
||||||
end
|
end
|
||||||
|
|
||||||
def custom_field_tag_for_bulk_edit(name, custom_field)
|
def custom_field_tag_for_bulk_edit(custom_field)
|
||||||
field_name = "#{name}[custom_field_values][#{custom_field.id}]"
|
field_name = "custom_field_values[#{custom_field.id}]"
|
||||||
field_id = "#{name}_custom_field_values_#{custom_field.id}"
|
field_id = "custom_field_values_#{custom_field.id}"
|
||||||
field_format = Redmine::CustomFieldFormat.find_by_name(custom_field.field_format)
|
case custom_field.field_format
|
||||||
case field_format.edit_as
|
|
||||||
when "date"
|
when "date"
|
||||||
text_field_tag(field_name, '', :id => field_id, :size => 10) +
|
text_field_tag(field_name, '', :id => field_id, :size => 10) +
|
||||||
calendar_for(field_id)
|
calendar_for(field_id)
|
||||||
@@ -97,11 +95,19 @@ module CustomFieldsHelper
|
|||||||
|
|
||||||
# Return a string used to display a custom value
|
# Return a string used to display a custom value
|
||||||
def format_value(value, field_format)
|
def format_value(value, field_format)
|
||||||
Redmine::CustomFieldFormat.format_value(value, field_format) # Proxy
|
return "" unless value && !value.empty?
|
||||||
|
case field_format
|
||||||
|
when "date"
|
||||||
|
begin; format_date(value.to_date); rescue; value end
|
||||||
|
when "bool"
|
||||||
|
l(value == "1" ? :general_text_Yes : :general_text_No)
|
||||||
|
else
|
||||||
|
value
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Return an array of custom field formats which can be used in select_tag
|
# Return an array of custom field formats which can be used in select_tag
|
||||||
def custom_field_formats_for_select
|
def custom_field_formats_for_select
|
||||||
Redmine::CustomFieldFormat.as_select
|
CustomField::FIELD_FORMATS.sort {|a,b| a[1][:order]<=>b[1][:order]}.collect { |k| [ l(k[1][:name]), k[0] ] }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
module IssueMovesHelper
|
|
||||||
end
|
|
||||||
@@ -18,66 +18,19 @@
|
|||||||
module IssuesHelper
|
module IssuesHelper
|
||||||
include ApplicationHelper
|
include ApplicationHelper
|
||||||
|
|
||||||
def issue_list(issues, &block)
|
|
||||||
ancestors = []
|
|
||||||
issues.each do |issue|
|
|
||||||
while (ancestors.any? && !issue.is_descendant_of?(ancestors.last))
|
|
||||||
ancestors.pop
|
|
||||||
end
|
|
||||||
yield issue, ancestors.size
|
|
||||||
ancestors << issue unless issue.leaf?
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Renders a HTML/CSS tooltip
|
|
||||||
#
|
|
||||||
# To use, a trigger div is needed. This is a div with the class of "tooltip"
|
|
||||||
# that contains this method wrapped in a span with the class of "tip"
|
|
||||||
#
|
|
||||||
# <div class="tooltip"><%= link_to_issue(issue) %>
|
|
||||||
# <span class="tip"><%= render_issue_tooltip(issue) %></span>
|
|
||||||
# </div>
|
|
||||||
#
|
|
||||||
def render_issue_tooltip(issue)
|
def render_issue_tooltip(issue)
|
||||||
@cached_label_status ||= l(:field_status)
|
|
||||||
@cached_label_start_date ||= l(:field_start_date)
|
@cached_label_start_date ||= l(:field_start_date)
|
||||||
@cached_label_due_date ||= l(:field_due_date)
|
@cached_label_due_date ||= l(:field_due_date)
|
||||||
@cached_label_assigned_to ||= l(:field_assigned_to)
|
@cached_label_assigned_to ||= l(:field_assigned_to)
|
||||||
@cached_label_priority ||= l(:field_priority)
|
@cached_label_priority ||= l(:field_priority)
|
||||||
|
|
||||||
link_to_issue(issue) + "<br /><br />" +
|
link_to_issue(issue) + "<br /><br />" +
|
||||||
"<strong>#{@cached_label_status}</strong>: #{issue.status.name}<br />" +
|
|
||||||
"<strong>#{@cached_label_start_date}</strong>: #{format_date(issue.start_date)}<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_due_date}</strong>: #{format_date(issue.due_date)}<br />" +
|
||||||
"<strong>#{@cached_label_assigned_to}</strong>: #{issue.assigned_to}<br />" +
|
"<strong>#{@cached_label_assigned_to}</strong>: #{issue.assigned_to}<br />" +
|
||||||
"<strong>#{@cached_label_priority}</strong>: #{issue.priority.name}"
|
"<strong>#{@cached_label_priority}</strong>: #{issue.priority.name}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def render_issue_subject_with_tree(issue)
|
|
||||||
s = ''
|
|
||||||
issue.ancestors.each do |ancestor|
|
|
||||||
s << '<div>' + content_tag('p', link_to_issue(ancestor))
|
|
||||||
end
|
|
||||||
s << '<div>' + content_tag('h3', h(issue.subject))
|
|
||||||
s << '</div>' * (issue.ancestors.size + 1)
|
|
||||||
s
|
|
||||||
end
|
|
||||||
|
|
||||||
def render_descendants_tree(issue)
|
|
||||||
s = '<form><table class="list issues">'
|
|
||||||
issue_list(issue.descendants.sort_by(&:lft)) do |child, level|
|
|
||||||
s << content_tag('tr',
|
|
||||||
content_tag('td', check_box_tag("ids[]", child.id, false, :id => nil), :class => 'checkbox') +
|
|
||||||
content_tag('td', link_to_issue(child, :truncate => 60), :class => 'subject') +
|
|
||||||
content_tag('td', h(child.status)) +
|
|
||||||
content_tag('td', link_to_user(child.assigned_to)) +
|
|
||||||
content_tag('td', progress_bar(child.done_ratio, :width => '80px')),
|
|
||||||
:class => "issue issue-#{child.id} hascontextmenu #{level > 0 ? "idnt idnt-#{level}" : nil}")
|
|
||||||
end
|
|
||||||
s << '</form></table>'
|
|
||||||
s
|
|
||||||
end
|
|
||||||
|
|
||||||
def render_custom_fields_rows(issue)
|
def render_custom_fields_rows(issue)
|
||||||
return if issue.custom_field_values.empty?
|
return if issue.custom_field_values.empty?
|
||||||
ordered_values = []
|
ordered_values = []
|
||||||
@@ -114,25 +67,35 @@ module IssuesHelper
|
|||||||
def show_detail(detail, no_html=false)
|
def show_detail(detail, no_html=false)
|
||||||
case detail.property
|
case detail.property
|
||||||
when 'attr'
|
when 'attr'
|
||||||
field = detail.prop_key.to_s.gsub(/\_id$/, "")
|
label = l(("field_" + detail.prop_key.to_s.gsub(/\_id$/, "")).to_sym)
|
||||||
label = l(("field_" + field).to_sym)
|
case detail.prop_key
|
||||||
case
|
when 'due_date', 'start_date'
|
||||||
when ['due_date', 'start_date'].include?(detail.prop_key)
|
|
||||||
value = format_date(detail.value.to_date) if detail.value
|
value = format_date(detail.value.to_date) if detail.value
|
||||||
old_value = format_date(detail.old_value.to_date) if detail.old_value
|
old_value = format_date(detail.old_value.to_date) if detail.old_value
|
||||||
|
when 'project_id'
|
||||||
when ['project_id', 'status_id', 'tracker_id', 'assigned_to_id', 'priority_id', 'category_id', 'fixed_version_id'].include?(detail.prop_key)
|
p = Project.find_by_id(detail.value) and value = p.name if detail.value
|
||||||
value = find_name_by_reflection(field, detail.value)
|
p = Project.find_by_id(detail.old_value) and old_value = p.name if detail.old_value
|
||||||
old_value = find_name_by_reflection(field, detail.old_value)
|
when 'status_id'
|
||||||
|
s = IssueStatus.find_by_id(detail.value) and value = s.name if detail.value
|
||||||
when detail.prop_key == 'estimated_hours'
|
s = IssueStatus.find_by_id(detail.old_value) and old_value = s.name if detail.old_value
|
||||||
|
when 'tracker_id'
|
||||||
|
t = Tracker.find_by_id(detail.value) and value = t.name if detail.value
|
||||||
|
t = Tracker.find_by_id(detail.old_value) and old_value = t.name if detail.old_value
|
||||||
|
when 'assigned_to_id'
|
||||||
|
u = User.find_by_id(detail.value) and value = u.name if detail.value
|
||||||
|
u = User.find_by_id(detail.old_value) and old_value = u.name if detail.old_value
|
||||||
|
when 'priority_id'
|
||||||
|
e = IssuePriority.find_by_id(detail.value) and value = e.name if detail.value
|
||||||
|
e = IssuePriority.find_by_id(detail.old_value) and old_value = e.name if detail.old_value
|
||||||
|
when 'category_id'
|
||||||
|
c = IssueCategory.find_by_id(detail.value) and value = c.name if detail.value
|
||||||
|
c = IssueCategory.find_by_id(detail.old_value) and old_value = c.name if detail.old_value
|
||||||
|
when 'fixed_version_id'
|
||||||
|
v = Version.find_by_id(detail.value) and value = format_version_name(v) if detail.value
|
||||||
|
v = Version.find_by_id(detail.old_value) and old_value = format_version_name(v) if detail.old_value
|
||||||
|
when 'estimated_hours'
|
||||||
value = "%0.02f" % detail.value.to_f unless detail.value.blank?
|
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?
|
old_value = "%0.02f" % detail.old_value.to_f unless detail.old_value.blank?
|
||||||
|
|
||||||
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?
|
|
||||||
end
|
end
|
||||||
when 'cf'
|
when 'cf'
|
||||||
custom_field = CustomField.find_by_id(detail.prop_key)
|
custom_field = CustomField.find_by_id(detail.prop_key)
|
||||||
@@ -178,15 +141,6 @@ module IssuesHelper
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Find the name of an associated record stored in the field attribute
|
|
||||||
def find_name_by_reflection(field, id)
|
|
||||||
association = Issue.reflect_on_association(field.to_sym)
|
|
||||||
if association
|
|
||||||
record = association.class_name.constantize.find_by_id(id)
|
|
||||||
return record.name if record
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def issues_to_csv(issues, project = nil)
|
def issues_to_csv(issues, project = nil)
|
||||||
ic = Iconv.new(l(:general_csv_encoding), 'UTF-8')
|
ic = Iconv.new(l(:general_csv_encoding), 'UTF-8')
|
||||||
decimal_separator = l(:general_csv_decimal_separator)
|
decimal_separator = l(:general_csv_decimal_separator)
|
||||||
@@ -206,7 +160,6 @@ module IssuesHelper
|
|||||||
l(:field_due_date),
|
l(:field_due_date),
|
||||||
l(:field_done_ratio),
|
l(:field_done_ratio),
|
||||||
l(:field_estimated_hours),
|
l(:field_estimated_hours),
|
||||||
l(:field_parent_issue),
|
|
||||||
l(:field_created_on),
|
l(:field_created_on),
|
||||||
l(:field_updated_on)
|
l(:field_updated_on)
|
||||||
]
|
]
|
||||||
@@ -233,7 +186,6 @@ module IssuesHelper
|
|||||||
format_date(issue.due_date),
|
format_date(issue.due_date),
|
||||||
issue.done_ratio,
|
issue.done_ratio,
|
||||||
issue.estimated_hours.to_s.gsub('.', decimal_separator),
|
issue.estimated_hours.to_s.gsub('.', decimal_separator),
|
||||||
issue.parent_id,
|
|
||||||
format_time(issue.created_on),
|
format_time(issue.created_on),
|
||||||
format_time(issue.updated_on)
|
format_time(issue.updated_on)
|
||||||
]
|
]
|
||||||
@@ -244,30 +196,4 @@ module IssuesHelper
|
|||||||
end
|
end
|
||||||
export
|
export
|
||||||
end
|
end
|
||||||
|
|
||||||
def gantt_zoom_link(gantt, in_or_out)
|
|
||||||
img_attributes = {:style => 'height:1.4em; width:1.4em; margin-left: 3px;'} # em for accessibility
|
|
||||||
|
|
||||||
case in_or_out
|
|
||||||
when :in
|
|
||||||
if gantt.zoom < 4
|
|
||||||
link_to_remote(l(:text_zoom_in) + image_tag('zoom_in.png', img_attributes.merge(:alt => l(:text_zoom_in))),
|
|
||||||
{:url => gantt.params.merge(:zoom => (gantt.zoom+1)), :update => 'content'},
|
|
||||||
{:href => url_for(gantt.params.merge(:zoom => (gantt.zoom+1)))})
|
|
||||||
else
|
|
||||||
l(:text_zoom_in) +
|
|
||||||
image_tag('zoom_in_g.png', img_attributes.merge(:alt => l(:text_zoom_in)))
|
|
||||||
end
|
|
||||||
|
|
||||||
when :out
|
|
||||||
if gantt.zoom > 1
|
|
||||||
link_to_remote(l(:text_zoom_out) + image_tag('zoom_out.png', img_attributes.merge(:alt => l(:text_zoom_out))),
|
|
||||||
{:url => gantt.params.merge(:zoom => (gantt.zoom-1)), :update => 'content'},
|
|
||||||
{:href => url_for(gantt.params.merge(:zoom => (gantt.zoom-1)))})
|
|
||||||
else
|
|
||||||
l(:text_zoom_out) +
|
|
||||||
image_tag('zoom_out_g.png', img_attributes.merge(:alt => l(:text_zoom_out)))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
@@ -16,13 +16,13 @@
|
|||||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
|
||||||
module JournalsHelper
|
module JournalsHelper
|
||||||
def render_notes(issue, journal, options={})
|
def render_notes(journal, options={})
|
||||||
content = ''
|
content = ''
|
||||||
editable = User.current.logged? && (User.current.allowed_to?(:edit_issue_notes, issue.project) || (journal.user == User.current && User.current.allowed_to?(:edit_own_issue_notes, issue.project)))
|
editable = journal.editable_by?(User.current)
|
||||||
links = []
|
links = []
|
||||||
if !journal.notes.blank?
|
if !journal.notes.blank?
|
||||||
links << link_to_remote(image_tag('comment.png'),
|
links << link_to_remote(image_tag('comment.png'),
|
||||||
{ :url => {:controller => 'journals', :action => 'new', :id => issue, :journal_id => journal} },
|
{ :url => {:controller => 'issues', :action => 'reply', :id => journal.journalized, :journal_id => journal} },
|
||||||
:title => l(:button_quote)) if options[:reply_links]
|
:title => l(:button_quote)) if options[:reply_links]
|
||||||
links << link_to_in_place_notes_editor(image_tag('edit.png'), "journal-#{journal.id}-notes",
|
links << link_to_in_place_notes_editor(image_tag('edit.png'), "journal-#{journal.id}-notes",
|
||||||
{ :controller => 'journals', :action => 'edit', :id => journal },
|
{ :controller => 'journals', :action => 'edit', :id => journal },
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ module MessagesHelper
|
|||||||
:action => 'show',
|
:action => 'show',
|
||||||
:board_id => message.board_id,
|
:board_id => message.board_id,
|
||||||
:id => message.root,
|
:id => message.root,
|
||||||
:r => (message.parent_id && message.id),
|
|
||||||
:anchor => (message.parent_id ? "message-#{message.id}" : nil)
|
:anchor => (message.parent_id ? "message-#{message.id}" : nil)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -46,7 +46,7 @@ module ProjectsHelper
|
|||||||
options = ''
|
options = ''
|
||||||
options << "<option value=''></option>" if project.allowed_parents.include?(nil)
|
options << "<option value=''></option>" if project.allowed_parents.include?(nil)
|
||||||
options << project_tree_options_for_select(project.allowed_parents.compact, :selected => selected)
|
options << project_tree_options_for_select(project.allowed_parents.compact, :selected => selected)
|
||||||
content_tag('select', options, :name => 'project[parent_id]', :id => 'project_parent_id')
|
content_tag('select', options, :name => 'project[parent_id]')
|
||||||
end
|
end
|
||||||
|
|
||||||
# Renders a tree of projects as a nested set of unordered lists
|
# Renders a tree of projects as a nested set of unordered lists
|
||||||
@@ -56,10 +56,7 @@ module ProjectsHelper
|
|||||||
s = ''
|
s = ''
|
||||||
if projects.any?
|
if projects.any?
|
||||||
ancestors = []
|
ancestors = []
|
||||||
original_project = @project
|
|
||||||
projects.each do |project|
|
projects.each do |project|
|
||||||
# set the project environment to please macros.
|
|
||||||
@project = project
|
|
||||||
if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
|
if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
|
||||||
s << "<ul class='projects #{ ancestors.empty? ? 'root' : nil}'>\n"
|
s << "<ul class='projects #{ ancestors.empty? ? 'root' : nil}'>\n"
|
||||||
else
|
else
|
||||||
@@ -72,13 +69,12 @@ module ProjectsHelper
|
|||||||
end
|
end
|
||||||
classes = (ancestors.empty? ? 'root' : 'child')
|
classes = (ancestors.empty? ? 'root' : 'child')
|
||||||
s << "<li class='#{classes}'><div class='#{classes}'>" +
|
s << "<li class='#{classes}'><div class='#{classes}'>" +
|
||||||
link_to_project(project, {}, :class => "project #{User.current.member_of?(project) ? 'my-project' : nil}")
|
link_to(h(project), {:controller => 'projects', :action => 'show', :id => project}, :class => "project #{User.current.member_of?(project) ? 'my-project' : nil}")
|
||||||
s << "<div class='wiki description'>#{textilizable(project.short_description, :project => project)}</div>" unless project.description.blank?
|
s << "<div class='wiki description'>#{textilizable(project.short_description, :project => project)}</div>" unless project.description.blank?
|
||||||
s << "</div>\n"
|
s << "</div>\n"
|
||||||
ancestors << project
|
ancestors << project
|
||||||
end
|
end
|
||||||
s << ("</li></ul>\n" * ancestors.size)
|
s << ("</li></ul>\n" * ancestors.size)
|
||||||
@project = original_project
|
|
||||||
end
|
end
|
||||||
s
|
s
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -50,51 +50,15 @@ module QueriesHelper
|
|||||||
when 'User'
|
when 'User'
|
||||||
link_to_user value
|
link_to_user value
|
||||||
when 'Project'
|
when 'Project'
|
||||||
link_to_project value
|
link_to(h(value), :controller => 'projects', :action => 'show', :id => value)
|
||||||
when 'Version'
|
when 'Version'
|
||||||
link_to(h(value), :controller => 'versions', :action => 'show', :id => value)
|
link_to(h(value), :controller => 'versions', :action => 'show', :id => value)
|
||||||
when 'TrueClass'
|
when 'TrueClass'
|
||||||
l(:general_text_Yes)
|
l(:general_text_Yes)
|
||||||
when 'FalseClass'
|
when 'FalseClass'
|
||||||
l(:general_text_No)
|
l(:general_text_No)
|
||||||
when 'Issue'
|
|
||||||
link_to_issue(value, :subject => false)
|
|
||||||
else
|
else
|
||||||
h(value)
|
h(value)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Retrieve query from session or build a new query
|
|
||||||
def retrieve_query
|
|
||||||
if !params[:query_id].blank?
|
|
||||||
cond = "project_id IS NULL"
|
|
||||||
cond << " OR project_id = #{@project.id}" if @project
|
|
||||||
@query = Query.find(params[:query_id], :conditions => cond)
|
|
||||||
@query.project = @project
|
|
||||||
session[:query] = {:id => @query.id, :project_id => @query.project_id}
|
|
||||||
sort_clear
|
|
||||||
else
|
|
||||||
if api_request? || params[:set_filter] || session[:query].nil? || session[:query][:project_id] != (@project ? @project.id : nil)
|
|
||||||
# Give it a name, required to be valid
|
|
||||||
@query = Query.new(:name => "_")
|
|
||||||
@query.project = @project
|
|
||||||
if params[:fields] and params[:fields].is_a? Array
|
|
||||||
params[:fields].each do |field|
|
|
||||||
@query.add_filter(field, params[:operators][field], params[:values][field])
|
|
||||||
end
|
|
||||||
else
|
|
||||||
@query.available_filters.keys.each do |field|
|
|
||||||
@query.add_short_filter(field, params[field]) if params[field]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@query.group_by = params[:group_by]
|
|
||||||
@query.column_names = params[:query] && params[:query][:column_names]
|
|
||||||
session[:query] = {:project_id => @query.project_id, :filters => @query.filters, :group_by => @query.group_by, :column_names => @query.column_names}
|
|
||||||
else
|
|
||||||
@query = Query.find_by_id(session[:query][:id]) if session[:query][:id]
|
|
||||||
@query ||= Query.new(:name => "_", :project => @project, :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names])
|
|
||||||
@query.project = @project
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
@@ -52,19 +52,17 @@ module RepositoriesHelper
|
|||||||
else
|
else
|
||||||
change
|
change
|
||||||
end
|
end
|
||||||
end.compact
|
end.compact
|
||||||
|
|
||||||
tree = { }
|
tree = { }
|
||||||
changes.each do |change|
|
changes.each do |change|
|
||||||
p = tree
|
p = tree
|
||||||
dirs = change.path.to_s.split('/').select {|d| !d.blank?}
|
dirs = change.path.to_s.split('/').select {|d| !d.blank?}
|
||||||
path = ''
|
|
||||||
dirs.each do |dir|
|
dirs.each do |dir|
|
||||||
path += '/' + dir
|
|
||||||
p[:s] ||= {}
|
p[:s] ||= {}
|
||||||
p = p[:s]
|
p = p[:s]
|
||||||
p[path] ||= {}
|
p[dir] ||= {}
|
||||||
p = p[path]
|
p = p[dir]
|
||||||
end
|
end
|
||||||
p[:c] = change
|
p[:c] = change
|
||||||
end
|
end
|
||||||
@@ -78,26 +76,21 @@ module RepositoriesHelper
|
|||||||
output = ''
|
output = ''
|
||||||
output << '<ul>'
|
output << '<ul>'
|
||||||
tree.keys.sort.each do |file|
|
tree.keys.sort.each do |file|
|
||||||
|
s = !tree[file][:s].nil?
|
||||||
|
c = tree[file][:c]
|
||||||
|
|
||||||
style = 'change'
|
style = 'change'
|
||||||
text = File.basename(h(file))
|
style << ' folder' if s
|
||||||
if s = tree[file][:s]
|
style << " change-#{c.action}" if c
|
||||||
style << ' folder'
|
|
||||||
path_param = to_path_param(@repository.relative_path(file))
|
text = h(file)
|
||||||
text = link_to(text, :controller => 'repositories',
|
unless c.nil?
|
||||||
:action => 'show',
|
|
||||||
:id => @project,
|
|
||||||
:path => path_param,
|
|
||||||
:rev => @changeset.revision)
|
|
||||||
output << "<li class='#{style}'>#{text}</li>"
|
|
||||||
output << render_changes_tree(s)
|
|
||||||
elsif c = tree[file][:c]
|
|
||||||
style << " change-#{c.action}"
|
|
||||||
path_param = to_path_param(@repository.relative_path(c.path))
|
path_param = to_path_param(@repository.relative_path(c.path))
|
||||||
text = link_to(text, :controller => 'repositories',
|
text = link_to(text, :controller => 'repositories',
|
||||||
:action => 'entry',
|
:action => 'entry',
|
||||||
:id => @project,
|
:id => @project,
|
||||||
:path => path_param,
|
:path => path_param,
|
||||||
:rev => @changeset.revision) unless c.action == 'D'
|
:rev => @changeset.revision) unless s || c.action == 'D'
|
||||||
text << " - #{c.revision}" unless c.revision.blank?
|
text << " - #{c.revision}" unless c.revision.blank?
|
||||||
text << ' (' + link_to('diff', :controller => 'repositories',
|
text << ' (' + link_to('diff', :controller => 'repositories',
|
||||||
:action => 'diff',
|
:action => 'diff',
|
||||||
@@ -105,8 +98,9 @@ module RepositoriesHelper
|
|||||||
:path => path_param,
|
:path => path_param,
|
||||||
:rev => @changeset.revision) + ') ' if c.action == 'M'
|
:rev => @changeset.revision) + ') ' if c.action == 'M'
|
||||||
text << ' ' + content_tag('span', c.from_path, :class => 'copied-from') unless c.from_path.blank?
|
text << ' ' + content_tag('span', c.from_path, :class => 'copied-from') unless c.from_path.blank?
|
||||||
output << "<li class='#{style}'>#{text}</li>"
|
|
||||||
end
|
end
|
||||||
|
output << "<li class='#{style}'>#{text}</li>"
|
||||||
|
output << render_changes_tree(tree[file][:s]) if s
|
||||||
end
|
end
|
||||||
output << '</ul>'
|
output << '</ul>'
|
||||||
output
|
output
|
||||||
@@ -132,7 +126,7 @@ module RepositoriesHelper
|
|||||||
|
|
||||||
def scm_select_tag(repository)
|
def scm_select_tag(repository)
|
||||||
scm_options = [["--- #{l(:actionview_instancetag_blank_option)} ---", '']]
|
scm_options = [["--- #{l(:actionview_instancetag_blank_option)} ---", '']]
|
||||||
Redmine::Scm::Base.all.each do |scm|
|
REDMINE_SUPPORTED_SCM.each do |scm|
|
||||||
scm_options << ["Repository::#{scm}".constantize.scm_name, scm] if Setting.enabled_scm.include?(scm) || (repository && repository.class.name.demodulize == scm)
|
scm_options << ["Repository::#{scm}".constantize.scm_name, scm] if Setting.enabled_scm.include?(scm) || (repository && repository.class.name.demodulize == scm)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ module SortHelper
|
|||||||
def to_sql
|
def to_sql
|
||||||
sql = @criteria.collect do |k,o|
|
sql = @criteria.collect do |k,o|
|
||||||
if s = @available_criteria[k]
|
if s = @available_criteria[k]
|
||||||
(o ? s.to_a : s.to_a.collect {|c| append_desc(c)}).join(', ')
|
(o ? s.to_a : s.to_a.collect {|c| "#{c} DESC"}).join(', ')
|
||||||
end
|
end
|
||||||
end.compact.join(', ')
|
end.compact.join(', ')
|
||||||
sql.blank? ? nil : sql
|
sql.blank? ? nil : sql
|
||||||
@@ -120,15 +120,6 @@ module SortHelper
|
|||||||
@criteria.slice!(3)
|
@criteria.slice!(3)
|
||||||
self
|
self
|
||||||
end
|
end
|
||||||
|
|
||||||
# Appends DESC to the sort criterion unless it has a fixed order
|
|
||||||
def append_desc(criterion)
|
|
||||||
if criterion =~ / (asc|desc)$/i
|
|
||||||
criterion
|
|
||||||
else
|
|
||||||
"#{criterion} DESC"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def sort_name
|
def sort_name
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ module TimelogHelper
|
|||||||
if value.to_s.empty?
|
if value.to_s.empty?
|
||||||
data.select {|row| row[criteria].blank? }
|
data.select {|row| row[criteria].blank? }
|
||||||
else
|
else
|
||||||
data.select {|row| row[criteria].to_s == value.to_s}
|
data.select {|row| row[criteria] == value}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -48,8 +48,8 @@ module WatchersHelper
|
|||||||
# Returns a comma separated list of users watching the given object
|
# Returns a comma separated list of users watching the given object
|
||||||
def watchers_list(object)
|
def watchers_list(object)
|
||||||
remove_allowed = User.current.allowed_to?("delete_#{object.class.name.underscore}_watchers".to_sym, object.project)
|
remove_allowed = User.current.allowed_to?("delete_#{object.class.name.underscore}_watchers".to_sym, object.project)
|
||||||
lis = object.watcher_users.collect do |user|
|
object.watcher_users.collect do |user|
|
||||||
s = avatar(user, :size => "16").to_s + link_to_user(user, :class => 'user').to_s
|
s = content_tag('span', link_to_user(user), :class => 'user')
|
||||||
if remove_allowed
|
if remove_allowed
|
||||||
url = {:controller => 'watchers',
|
url = {:controller => 'watchers',
|
||||||
:action => 'destroy',
|
:action => 'destroy',
|
||||||
@@ -59,11 +59,9 @@ module WatchersHelper
|
|||||||
s += ' ' + link_to_remote(image_tag('delete.png'),
|
s += ' ' + link_to_remote(image_tag('delete.png'),
|
||||||
{:url => url},
|
{:url => url},
|
||||||
:href => url_for(url),
|
:href => url_for(url),
|
||||||
:style => "vertical-align: middle",
|
:style => "vertical-align: middle")
|
||||||
:class => "delete")
|
|
||||||
end
|
end
|
||||||
"<li>#{ s }</li>"
|
s
|
||||||
end
|
end.join(",\n")
|
||||||
lis.empty? ? "" : "<ul>#{ lis.join("\n") }</ul>"
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -134,33 +134,6 @@ class Attachment < ActiveRecord::Base
|
|||||||
File.readable?(diskfile)
|
File.readable?(diskfile)
|
||||||
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)
|
|
||||||
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)
|
|
||||||
|
|
||||||
if a.new_record?
|
|
||||||
obj.unsaved_attachments ||= []
|
|
||||||
obj.unsaved_attachments << a
|
|
||||||
else
|
|
||||||
attached << a
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
{:files => attached, :unsaved => obj.unsaved_attachments}
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
private
|
||||||
def sanitize_filename(value)
|
def sanitize_filename(value)
|
||||||
# get only the filename, not the whole path
|
# get only the filename, not the whole path
|
||||||
@@ -174,18 +147,14 @@ private
|
|||||||
|
|
||||||
# Returns an ASCII or hashed filename
|
# Returns an ASCII or hashed filename
|
||||||
def self.disk_filename(filename)
|
def self.disk_filename(filename)
|
||||||
timestamp = DateTime.now.strftime("%y%m%d%H%M%S")
|
df = DateTime.now.strftime("%y%m%d%H%M%S") + "_"
|
||||||
ascii = ''
|
|
||||||
if filename =~ %r{^[a-zA-Z0-9_\.\-]*$}
|
if filename =~ %r{^[a-zA-Z0-9_\.\-]*$}
|
||||||
ascii = filename
|
df << filename
|
||||||
else
|
else
|
||||||
ascii = Digest::MD5.hexdigest(filename)
|
df << Digest::MD5.hexdigest(filename)
|
||||||
# keep the extension if any
|
# keep the extension if any
|
||||||
ascii << $1 if filename =~ %r{(\.[a-zA-Z0-9]+)$}
|
df << $1 if filename =~ %r{(\.[a-zA-Z0-9]+)$}
|
||||||
end
|
end
|
||||||
while File.exist?(File.join(@@storage_path, "#{timestamp}_#{ascii}"))
|
df
|
||||||
timestamp.succ!
|
|
||||||
end
|
|
||||||
"#{timestamp}_#{ascii}"
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -32,15 +32,6 @@ class AuthSource < ActiveRecord::Base
|
|||||||
"Abstract"
|
"Abstract"
|
||||||
end
|
end
|
||||||
|
|
||||||
def allow_password_changes?
|
|
||||||
self.class.allow_password_changes?
|
|
||||||
end
|
|
||||||
|
|
||||||
# Does this auth source backend allow password changes?
|
|
||||||
def self.allow_password_changes?
|
|
||||||
false
|
|
||||||
end
|
|
||||||
|
|
||||||
# Try to authenticate a user not yet registered against available sources
|
# Try to authenticate a user not yet registered against available sources
|
||||||
def self.authenticate(login, password)
|
def self.authenticate(login, password)
|
||||||
AuthSource.find(:all, :conditions => ["onthefly_register=?", true]).each do |source|
|
AuthSource.find(:all, :conditions => ["onthefly_register=?", true]).each do |source|
|
||||||
|
|||||||
@@ -33,12 +33,30 @@ class AuthSourceLdap < AuthSource
|
|||||||
|
|
||||||
def authenticate(login, password)
|
def authenticate(login, password)
|
||||||
return nil if login.blank? || password.blank?
|
return nil if login.blank? || password.blank?
|
||||||
attrs = get_user_dn(login)
|
attrs = []
|
||||||
|
# get user's DN
|
||||||
if attrs && attrs[:dn] && authenticate_dn(attrs[:dn], password)
|
ldap_con = initialize_ldap_con(self.account, self.account_password)
|
||||||
logger.debug "Authentication successful for '#{login}'" if logger && logger.debug?
|
login_filter = Net::LDAP::Filter.eq( self.attr_login, login )
|
||||||
return attrs.except(:dn)
|
object_filter = Net::LDAP::Filter.eq( "objectClass", "*" )
|
||||||
|
dn = String.new
|
||||||
|
ldap_con.search( :base => self.base_dn,
|
||||||
|
:filter => object_filter & login_filter,
|
||||||
|
# only ask for the DN if on-the-fly registration is disabled
|
||||||
|
:attributes=> (onthefly_register? ? ['dn', self.attr_firstname, self.attr_lastname, self.attr_mail] : ['dn'])) do |entry|
|
||||||
|
dn = entry.dn
|
||||||
|
attrs = [:firstname => AuthSourceLdap.get_attr(entry, self.attr_firstname),
|
||||||
|
:lastname => AuthSourceLdap.get_attr(entry, self.attr_lastname),
|
||||||
|
:mail => AuthSourceLdap.get_attr(entry, self.attr_mail),
|
||||||
|
:auth_source_id => self.id ] if onthefly_register?
|
||||||
end
|
end
|
||||||
|
return nil if dn.empty?
|
||||||
|
logger.debug "DN found for #{login}: #{dn}" if logger && logger.debug?
|
||||||
|
# authenticate user
|
||||||
|
ldap_con = initialize_ldap_con(dn, password)
|
||||||
|
return nil unless ldap_con.bind
|
||||||
|
# return user's attributes
|
||||||
|
logger.debug "Authentication successful for '#{login}'" if logger && logger.debug?
|
||||||
|
attrs
|
||||||
rescue Net::LDAP::LdapError => text
|
rescue Net::LDAP::LdapError => text
|
||||||
raise "LdapError: " + text
|
raise "LdapError: " + text
|
||||||
end
|
end
|
||||||
@@ -72,56 +90,6 @@ class AuthSourceLdap < AuthSource
|
|||||||
Net::LDAP.new options
|
Net::LDAP.new options
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_user_attributes_from_ldap_entry(entry)
|
|
||||||
{
|
|
||||||
:dn => entry.dn,
|
|
||||||
:firstname => AuthSourceLdap.get_attr(entry, self.attr_firstname),
|
|
||||||
:lastname => AuthSourceLdap.get_attr(entry, self.attr_lastname),
|
|
||||||
:mail => AuthSourceLdap.get_attr(entry, self.attr_mail),
|
|
||||||
:auth_source_id => self.id
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
# Return the attributes needed for the LDAP search. It will only
|
|
||||||
# include the user attributes if on-the-fly registration is enabled
|
|
||||||
def search_attributes
|
|
||||||
if onthefly_register?
|
|
||||||
['dn', self.attr_firstname, self.attr_lastname, self.attr_mail]
|
|
||||||
else
|
|
||||||
['dn']
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Check if a DN (user record) authenticates with the password
|
|
||||||
def authenticate_dn(dn, password)
|
|
||||||
if dn.present? && password.present?
|
|
||||||
initialize_ldap_con(dn, password).bind
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Get the user's dn and any attributes for them, given their login
|
|
||||||
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 = {}
|
|
||||||
|
|
||||||
ldap_con.search( :base => self.base_dn,
|
|
||||||
:filter => object_filter & login_filter,
|
|
||||||
:attributes=> search_attributes) do |entry|
|
|
||||||
|
|
||||||
if onthefly_register?
|
|
||||||
attrs = get_user_attributes_from_ldap_entry(entry)
|
|
||||||
else
|
|
||||||
attrs = {:dn => entry.dn}
|
|
||||||
end
|
|
||||||
|
|
||||||
logger.debug "DN found for #{login}: #{attrs[:dn]}" if logger && logger.debug?
|
|
||||||
end
|
|
||||||
|
|
||||||
attrs
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.get_attr(entry, attr_name)
|
def self.get_attr(entry, attr_name)
|
||||||
if !attr_name.blank?
|
if !attr_name.blank?
|
||||||
entry[attr_name].is_a?(Array) ? entry[attr_name].first : entry[attr_name]
|
entry[attr_name].is_a?(Array) ? entry[attr_name].first : entry[attr_name]
|
||||||
|
|||||||
+1
-1
@@ -18,7 +18,7 @@
|
|||||||
class Board < ActiveRecord::Base
|
class Board < ActiveRecord::Base
|
||||||
belongs_to :project
|
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 :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"
|
has_many :messages, :dependent => :delete_all, :order => "#{Message.table_name}.created_on DESC"
|
||||||
belongs_to :last_message, :class_name => 'Message', :foreign_key => :last_message_id
|
belongs_to :last_message, :class_name => 'Message', :foreign_key => :last_message_id
|
||||||
acts_as_list :scope => :project_id
|
acts_as_list :scope => :project_id
|
||||||
acts_as_watchable
|
acts_as_watchable
|
||||||
|
|||||||
@@ -19,13 +19,8 @@ class Change < ActiveRecord::Base
|
|||||||
belongs_to :changeset
|
belongs_to :changeset
|
||||||
|
|
||||||
validates_presence_of :changeset_id, :action, :path
|
validates_presence_of :changeset_id, :action, :path
|
||||||
before_save :init_path
|
|
||||||
|
|
||||||
def relative_path
|
def relative_path
|
||||||
changeset.repository.relative_path(path)
|
changeset.repository.relative_path(path)
|
||||||
end
|
end
|
||||||
|
|
||||||
def init_path
|
|
||||||
self.path ||= ""
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
+12
-42
@@ -1,5 +1,5 @@
|
|||||||
# Redmine - project management software
|
# Redmine - project management software
|
||||||
# Copyright (C) 2006-2010 Jean-Philippe Lang
|
# Copyright (C) 2006-2008 Jean-Philippe Lang
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or
|
# This program is free software; you can redistribute it and/or
|
||||||
# modify it under the terms of the GNU General Public License
|
# modify it under the terms of the GNU General Public License
|
||||||
@@ -41,9 +41,6 @@ class Changeset < ActiveRecord::Base
|
|||||||
validates_uniqueness_of :revision, :scope => :repository_id
|
validates_uniqueness_of :revision, :scope => :repository_id
|
||||||
validates_uniqueness_of :scmid, :scope => :repository_id, :allow_nil => true
|
validates_uniqueness_of :scmid, :scope => :repository_id, :allow_nil => true
|
||||||
|
|
||||||
named_scope :visible, lambda {|*args| { :include => {:repository => :project},
|
|
||||||
:conditions => Project.allowed_to_condition(args.first || User.current, :view_changesets) } }
|
|
||||||
|
|
||||||
def revision=(r)
|
def revision=(r)
|
||||||
write_attribute :revision, (r.nil? ? nil : r.to_s)
|
write_attribute :revision, (r.nil? ? nil : r.to_s)
|
||||||
end
|
end
|
||||||
@@ -57,10 +54,6 @@ class Changeset < ActiveRecord::Base
|
|||||||
super
|
super
|
||||||
end
|
end
|
||||||
|
|
||||||
def committer=(arg)
|
|
||||||
write_attribute(:committer, self.class.to_utf8(arg.to_s))
|
|
||||||
end
|
|
||||||
|
|
||||||
def project
|
def project
|
||||||
repository.project
|
repository.project
|
||||||
end
|
end
|
||||||
@@ -76,6 +69,7 @@ class Changeset < ActiveRecord::Base
|
|||||||
def after_create
|
def after_create
|
||||||
scan_comment_for_issue_ids
|
scan_comment_for_issue_ids
|
||||||
end
|
end
|
||||||
|
require 'pp'
|
||||||
|
|
||||||
def scan_comment_for_issue_ids
|
def scan_comment_for_issue_ids
|
||||||
return if comments.blank?
|
return if comments.blank?
|
||||||
@@ -83,6 +77,9 @@ class Changeset < ActiveRecord::Base
|
|||||||
ref_keywords = Setting.commit_ref_keywords.downcase.split(",").collect(&:strip)
|
ref_keywords = Setting.commit_ref_keywords.downcase.split(",").collect(&:strip)
|
||||||
# keywords used to fix issues
|
# keywords used to fix issues
|
||||||
fix_keywords = Setting.commit_fix_keywords.downcase.split(",").collect(&:strip)
|
fix_keywords = Setting.commit_fix_keywords.downcase.split(",").collect(&:strip)
|
||||||
|
# status and optional done ratio applied
|
||||||
|
fix_status = IssueStatus.find_by_id(Setting.commit_fix_status_id)
|
||||||
|
done_ratio = Setting.commit_fix_done_ratio.blank? ? nil : Setting.commit_fix_done_ratio.to_i
|
||||||
|
|
||||||
kw_regexp = (ref_keywords + fix_keywords).collect{|kw| Regexp.escape(kw)}.join("|")
|
kw_regexp = (ref_keywords + fix_keywords).collect{|kw| Regexp.escape(kw)}.join("|")
|
||||||
return if kw_regexp.blank?
|
return if kw_regexp.blank?
|
||||||
@@ -93,14 +90,14 @@ class Changeset < ActiveRecord::Base
|
|||||||
# find any issue ID in the comments
|
# find any issue ID in the comments
|
||||||
target_issue_ids = []
|
target_issue_ids = []
|
||||||
comments.scan(%r{([\s\(\[,-]|^)#(\d+)(?=[[:punct:]]|\s|<|$)}).each { |m| target_issue_ids << m[1] }
|
comments.scan(%r{([\s\(\[,-]|^)#(\d+)(?=[[:punct:]]|\s|<|$)}).each { |m| target_issue_ids << m[1] }
|
||||||
referenced_issues += find_referenced_issues_by_id(target_issue_ids)
|
referenced_issues += repository.project.issues.find_all_by_id(target_issue_ids)
|
||||||
end
|
end
|
||||||
|
|
||||||
comments.scan(Regexp.new("(#{kw_regexp})[\s:]+(([\s,;&]*#?\\d+)+)", Regexp::IGNORECASE)).each do |match|
|
comments.scan(Regexp.new("(#{kw_regexp})[\s:]+(([\s,;&]*#?\\d+)+)", Regexp::IGNORECASE)).each do |match|
|
||||||
action = match[0]
|
action = match[0]
|
||||||
target_issue_ids = match[1].scan(/\d+/)
|
target_issue_ids = match[1].scan(/\d+/)
|
||||||
target_issues = find_referenced_issues_by_id(target_issue_ids)
|
target_issues = repository.project.issues.find_all_by_id(target_issue_ids)
|
||||||
if fix_keywords.include?(action.downcase) && fix_status = IssueStatus.find_by_id(Setting.commit_fix_status_id)
|
if fix_status && fix_keywords.include?(action.downcase)
|
||||||
# update status of issues
|
# update status of issues
|
||||||
logger.debug "Issues fixed by changeset #{self.revision}: #{issue_ids.join(', ')}." if logger && logger.debug?
|
logger.debug "Issues fixed by changeset #{self.revision}: #{issue_ids.join(', ')}." if logger && logger.debug?
|
||||||
target_issues.each do |issue|
|
target_issues.each do |issue|
|
||||||
@@ -114,9 +111,7 @@ class Changeset < ActiveRecord::Base
|
|||||||
end
|
end
|
||||||
journal = issue.init_journal(user || User.anonymous, ll(Setting.default_language, :text_status_changed_by_changeset, csettext))
|
journal = issue.init_journal(user || User.anonymous, ll(Setting.default_language, :text_status_changed_by_changeset, csettext))
|
||||||
issue.status = fix_status
|
issue.status = fix_status
|
||||||
unless Setting.commit_fix_done_ratio.blank?
|
issue.done_ratio = done_ratio if done_ratio
|
||||||
issue.done_ratio = Setting.commit_fix_done_ratio.to_i
|
|
||||||
end
|
|
||||||
Redmine::Hook.call_hook(:model_changeset_scan_commit_for_issue_ids_pre_issue_update,
|
Redmine::Hook.call_hook(:model_changeset_scan_commit_for_issue_ids_pre_issue_update,
|
||||||
{ :changeset => self, :issue => issue })
|
{ :changeset => self, :issue => issue })
|
||||||
issue.save
|
issue.save
|
||||||
@@ -125,8 +120,7 @@ class Changeset < ActiveRecord::Base
|
|||||||
referenced_issues += target_issues
|
referenced_issues += target_issues
|
||||||
end
|
end
|
||||||
|
|
||||||
referenced_issues.uniq!
|
self.issues = referenced_issues.uniq
|
||||||
self.issues = referenced_issues unless referenced_issues.empty?
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def short_comments
|
def short_comments
|
||||||
@@ -152,26 +146,8 @@ class Changeset < ActiveRecord::Base
|
|||||||
to_utf8(str.to_s.strip)
|
to_utf8(str.to_s.strip)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Creates a new Change from it's common parameters
|
|
||||||
def create_change(change)
|
|
||||||
Change.create(:changeset => self,
|
|
||||||
:action => change[:action],
|
|
||||||
:path => change[:path],
|
|
||||||
:from_path => change[:from_path],
|
|
||||||
:from_revision => change[:from_revision])
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
# Finds issues that can be referenced by the commit message
|
|
||||||
# i.e. issues that belong to the repository project, a subproject or a parent project
|
|
||||||
def find_referenced_issues_by_id(ids)
|
|
||||||
return [] if ids.compact.empty?
|
|
||||||
Issue.find_all_by_id(ids, :include => :project).select {|issue|
|
|
||||||
project == issue.project || project.is_ancestor_of?(issue.project) || project.is_descendant_of?(issue.project)
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
def split_comments
|
def split_comments
|
||||||
comments =~ /\A(.+?)\r?\n(.*)$/m
|
comments =~ /\A(.+?)\r?\n(.*)$/m
|
||||||
@short_comments = $1 || comments
|
@short_comments = $1 || comments
|
||||||
@@ -184,17 +160,11 @@ class Changeset < ActiveRecord::Base
|
|||||||
encoding = Setting.commit_logs_encoding.to_s.strip
|
encoding = Setting.commit_logs_encoding.to_s.strip
|
||||||
unless encoding.blank? || encoding == 'UTF-8'
|
unless encoding.blank? || encoding == 'UTF-8'
|
||||||
begin
|
begin
|
||||||
str = Iconv.conv('UTF-8', encoding, str)
|
return Iconv.conv('UTF-8', encoding, str)
|
||||||
rescue Iconv::Failure
|
rescue Iconv::Failure
|
||||||
# do nothing here
|
# do nothing here
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
# removes invalid UTF8 sequences
|
str
|
||||||
begin
|
|
||||||
Iconv.conv('UTF-8//IGNORE', 'UTF-8', str + ' ')[0..-3]
|
|
||||||
rescue Iconv::InvalidEncoding
|
|
||||||
# "UTF-8//IGNORE" is not supported on some OS
|
|
||||||
str
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -20,11 +20,20 @@ class CustomField < ActiveRecord::Base
|
|||||||
acts_as_list :scope => 'type = \'#{self.class}\''
|
acts_as_list :scope => 'type = \'#{self.class}\''
|
||||||
serialize :possible_values
|
serialize :possible_values
|
||||||
|
|
||||||
|
FIELD_FORMATS = { "string" => { :name => :label_string, :order => 1 },
|
||||||
|
"text" => { :name => :label_text, :order => 2 },
|
||||||
|
"int" => { :name => :label_integer, :order => 3 },
|
||||||
|
"float" => { :name => :label_float, :order => 4 },
|
||||||
|
"list" => { :name => :label_list, :order => 5 },
|
||||||
|
"date" => { :name => :label_date, :order => 6 },
|
||||||
|
"bool" => { :name => :label_boolean, :order => 7 }
|
||||||
|
}.freeze
|
||||||
|
|
||||||
validates_presence_of :name, :field_format
|
validates_presence_of :name, :field_format
|
||||||
validates_uniqueness_of :name, :scope => :type
|
validates_uniqueness_of :name, :scope => :type
|
||||||
validates_length_of :name, :maximum => 30
|
validates_length_of :name, :maximum => 30
|
||||||
validates_format_of :name, :with => /^[\w\s\.\'\-]*$/i
|
validates_format_of :name, :with => /^[\w\s\.\'\-]*$/i
|
||||||
validates_inclusion_of :field_format, :in => Redmine::CustomFieldFormat.available_formats
|
validates_inclusion_of :field_format, :in => FIELD_FORMATS.keys
|
||||||
|
|
||||||
def initialize(attributes = nil)
|
def initialize(attributes = nil)
|
||||||
super
|
super
|
||||||
|
|||||||
@@ -46,4 +46,11 @@ class Document < ActiveRecord::Base
|
|||||||
end
|
end
|
||||||
@updated_on
|
@updated_on
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Returns the mail adresses of users that should be notified
|
||||||
|
def recipients
|
||||||
|
notified = project.notified_users
|
||||||
|
notified.reject! {|user| !visible?(user)}
|
||||||
|
notified.collect(&:mail)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
@@ -31,7 +31,6 @@ class Group < Principal
|
|||||||
|
|
||||||
def user_added(user)
|
def user_added(user)
|
||||||
members.each do |member|
|
members.each do |member|
|
||||||
next if member.project.nil?
|
|
||||||
user_member = Member.find_by_project_id_and_user_id(member.project_id, user.id) || Member.new(:project_id => member.project_id, :user_id => user.id)
|
user_member = Member.find_by_project_id_and_user_id(member.project_id, user.id) || Member.new(:project_id => member.project_id, :user_id => user.id)
|
||||||
member.member_roles.each do |member_role|
|
member.member_roles.each do |member_role|
|
||||||
user_member.member_roles << MemberRole.new(:role => member_role.role, :inherited_from => member_role.id)
|
user_member.member_roles << MemberRole.new(:role => member_role.role, :inherited_from => member_role.id)
|
||||||
|
|||||||
+85
-480
@@ -32,7 +32,6 @@ class Issue < ActiveRecord::Base
|
|||||||
has_many :relations_from, :class_name => 'IssueRelation', :foreign_key => 'issue_from_id', :dependent => :delete_all
|
has_many :relations_from, :class_name => 'IssueRelation', :foreign_key => 'issue_from_id', :dependent => :delete_all
|
||||||
has_many :relations_to, :class_name => 'IssueRelation', :foreign_key => 'issue_to_id', :dependent => :delete_all
|
has_many :relations_to, :class_name => 'IssueRelation', :foreign_key => 'issue_to_id', :dependent => :delete_all
|
||||||
|
|
||||||
acts_as_nested_set :scope => 'root_id'
|
|
||||||
acts_as_attachable :after_remove => :attachment_removed
|
acts_as_attachable :after_remove => :attachment_removed
|
||||||
acts_as_customizable
|
acts_as_customizable
|
||||||
acts_as_watchable
|
acts_as_watchable
|
||||||
@@ -49,10 +48,7 @@ class Issue < ActiveRecord::Base
|
|||||||
|
|
||||||
DONE_RATIO_OPTIONS = %w(issue_field issue_status)
|
DONE_RATIO_OPTIONS = %w(issue_field issue_status)
|
||||||
|
|
||||||
attr_reader :current_journal
|
|
||||||
|
|
||||||
validates_presence_of :subject, :priority, :project, :tracker, :author, :status
|
validates_presence_of :subject, :priority, :project, :tracker, :author, :status
|
||||||
|
|
||||||
validates_length_of :subject, :maximum => 255
|
validates_length_of :subject, :maximum => 255
|
||||||
validates_inclusion_of :done_ratio, :in => 0..100
|
validates_inclusion_of :done_ratio, :in => 0..100
|
||||||
validates_numericality_of :estimated_hours, :allow_nil => true
|
validates_numericality_of :estimated_hours, :allow_nil => true
|
||||||
@@ -62,16 +58,8 @@ class Issue < ActiveRecord::Base
|
|||||||
|
|
||||||
named_scope :open, :conditions => ["#{IssueStatus.table_name}.is_closed = ?", false], :include => :status
|
named_scope :open, :conditions => ["#{IssueStatus.table_name}.is_closed = ?", false], :include => :status
|
||||||
|
|
||||||
named_scope :recently_updated, :order => "#{self.table_name}.updated_on DESC"
|
before_save :update_done_ratio_from_issue_status
|
||||||
named_scope :with_limit, lambda { |limit| { :limit => limit} }
|
after_save :create_journal
|
||||||
named_scope :on_active_project, :include => [:status, :project, :tracker],
|
|
||||||
:conditions => ["#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"]
|
|
||||||
|
|
||||||
before_create :default_assign
|
|
||||||
before_save :close_duplicates, :update_done_ratio_from_issue_status
|
|
||||||
after_save :reschedule_following_issues, :update_nested_set_attributes, :update_parent_attributes, :create_journal
|
|
||||||
after_destroy :destroy_children
|
|
||||||
after_destroy :update_parent_attributes
|
|
||||||
|
|
||||||
# Returns true if usr or current user is allowed to view the issue
|
# Returns true if usr or current user is allowed to view the issue
|
||||||
def visible?(usr=nil)
|
def visible?(usr=nil)
|
||||||
@@ -92,81 +80,61 @@ class Issue < ActiveRecord::Base
|
|||||||
end
|
end
|
||||||
|
|
||||||
def copy_from(arg)
|
def copy_from(arg)
|
||||||
issue = arg.is_a?(Issue) ? arg : Issue.visible.find(arg)
|
issue = arg.is_a?(Issue) ? arg : Issue.find(arg)
|
||||||
self.attributes = issue.attributes.dup.except("id", "root_id", "parent_id", "lft", "rgt", "created_on", "updated_on")
|
self.attributes = issue.attributes.dup.except("id", "created_on", "updated_on")
|
||||||
self.custom_field_values = issue.custom_field_values.inject({}) {|h,v| h[v.custom_field_id] = v.value; h}
|
self.custom_values = issue.custom_values.collect {|v| v.clone}
|
||||||
self.status = issue.status
|
self.status = issue.status
|
||||||
self
|
self
|
||||||
end
|
end
|
||||||
|
|
||||||
# Moves/copies an issue to a new project and tracker
|
# Moves/copies an issue to a new project and tracker
|
||||||
# Returns the moved/copied issue on success, false on failure
|
# Returns the moved/copied issue on success, false on failure
|
||||||
def move_to_project(*args)
|
def move_to(new_project, new_tracker = nil, options = {})
|
||||||
ret = Issue.transaction do
|
|
||||||
move_to_project_without_transaction(*args) || raise(ActiveRecord::Rollback)
|
|
||||||
end || false
|
|
||||||
end
|
|
||||||
|
|
||||||
def move_to_project_without_transaction(new_project, new_tracker = nil, options = {})
|
|
||||||
options ||= {}
|
options ||= {}
|
||||||
issue = options[:copy] ? self.class.new.copy_from(self) : self
|
issue = options[:copy] ? self.clone : self
|
||||||
|
transaction do
|
||||||
if new_project && issue.project_id != new_project.id
|
if new_project && issue.project_id != new_project.id
|
||||||
# delete issue relations
|
# delete issue relations
|
||||||
unless Setting.cross_project_issue_relations?
|
unless Setting.cross_project_issue_relations?
|
||||||
issue.relations_from.clear
|
issue.relations_from.clear
|
||||||
issue.relations_to.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
|
|
||||||
if new_tracker
|
|
||||||
issue.tracker = new_tracker
|
|
||||||
issue.reset_custom_values!
|
|
||||||
end
|
|
||||||
if options[:copy]
|
|
||||||
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
|
|
||||||
unless options[:copy]
|
|
||||||
# Manually update project_id on related time entries
|
|
||||||
TimeEntry.update_all("project_id = #{new_project.id}", {:issue_id => id})
|
|
||||||
|
|
||||||
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
|
||||||
|
# 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
|
||||||
|
end
|
||||||
|
if new_tracker
|
||||||
|
issue.tracker = new_tracker
|
||||||
|
end
|
||||||
|
if options[:copy]
|
||||||
|
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
|
||||||
|
unless options[:copy]
|
||||||
|
# Manually update project_id on related time entries
|
||||||
|
TimeEntry.update_all("project_id = #{new_project.id}", {:issue_id => id})
|
||||||
|
end
|
||||||
|
else
|
||||||
|
Issue.connection.rollback_db_transaction
|
||||||
|
return false
|
||||||
end
|
end
|
||||||
else
|
|
||||||
return false
|
|
||||||
end
|
end
|
||||||
issue
|
return issue
|
||||||
end
|
|
||||||
|
|
||||||
def status_id=(sid)
|
|
||||||
self.status = nil
|
|
||||||
write_attribute(:status_id, sid)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def priority_id=(pid)
|
def priority_id=(pid)
|
||||||
@@ -176,6 +144,7 @@ class Issue < ActiveRecord::Base
|
|||||||
|
|
||||||
def tracker_id=(tid)
|
def tracker_id=(tid)
|
||||||
self.tracker = nil
|
self.tracker = nil
|
||||||
|
write_attribute(:tracker_id, tid)
|
||||||
result = write_attribute(:tracker_id, tid)
|
result = write_attribute(:tracker_id, tid)
|
||||||
@custom_field_values = nil
|
@custom_field_values = nil
|
||||||
result
|
result
|
||||||
@@ -190,62 +159,14 @@ class Issue < ActiveRecord::Base
|
|||||||
end
|
end
|
||||||
send :attributes_without_tracker_first=, new_attributes, *args
|
send :attributes_without_tracker_first=, new_attributes, *args
|
||||||
end
|
end
|
||||||
# Do not redefine alias chain on reload (see #4838)
|
alias_method_chain :attributes=, :tracker_first
|
||||||
alias_method_chain(:attributes=, :tracker_first) unless method_defined?(:attributes_without_tracker_first=)
|
|
||||||
|
|
||||||
def estimated_hours=(h)
|
def estimated_hours=(h)
|
||||||
write_attribute :estimated_hours, (h.is_a?(String) ? h.to_hours : h)
|
write_attribute :estimated_hours, (h.is_a?(String) ? h.to_hours : h)
|
||||||
end
|
end
|
||||||
|
|
||||||
SAFE_ATTRIBUTES = %w(
|
|
||||||
tracker_id
|
|
||||||
status_id
|
|
||||||
parent_issue_id
|
|
||||||
category_id
|
|
||||||
assigned_to_id
|
|
||||||
priority_id
|
|
||||||
fixed_version_id
|
|
||||||
subject
|
|
||||||
description
|
|
||||||
start_date
|
|
||||||
due_date
|
|
||||||
done_ratio
|
|
||||||
estimated_hours
|
|
||||||
custom_field_values
|
|
||||||
lock_version
|
|
||||||
) unless const_defined?(:SAFE_ATTRIBUTES)
|
|
||||||
|
|
||||||
# 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 if attrs.nil?
|
|
||||||
attrs = attrs.reject {|k,v| !SAFE_ATTRIBUTES.include?(k)}
|
|
||||||
if attrs['status_id']
|
|
||||||
unless new_statuses_allowed_to(user).collect(&:id).include?(attrs['status_id'].to_i)
|
|
||||||
attrs.delete('status_id')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
unless leaf?
|
|
||||||
attrs.reject! {|k,v| %w(priority_id done_ratio start_date due_date estimated_hours).include?(k)}
|
|
||||||
end
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
self.attributes = attrs
|
|
||||||
end
|
|
||||||
|
|
||||||
def done_ratio
|
def done_ratio
|
||||||
if Issue.use_status_for_done_ratio? && status && status.default_done_ratio
|
if Issue.use_status_for_done_ratio? && status && status.default_done_ratio?
|
||||||
status.default_done_ratio
|
status.default_done_ratio
|
||||||
else
|
else
|
||||||
read_attribute(:done_ratio)
|
read_attribute(:done_ratio)
|
||||||
@@ -287,32 +208,44 @@ class Issue < ActiveRecord::Base
|
|||||||
errors.add :tracker_id, :inclusion
|
errors.add :tracker_id, :inclusion
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# Checks parent issue assignment
|
def before_create
|
||||||
if @parent_issue
|
# default assignment based on category
|
||||||
if @parent_issue.project_id != project_id
|
if assigned_to.nil? && category && category.assigned_to
|
||||||
errors.add :parent_issue_id, :not_same_project
|
self.assigned_to = category.assigned_to
|
||||||
elsif !new_record?
|
|
||||||
# moving an existing issue
|
|
||||||
if @parent_issue.root_id != root_id
|
|
||||||
# we can always move to another tree
|
|
||||||
elsif move_possible?(@parent_issue)
|
|
||||||
# move accepted inside tree
|
|
||||||
else
|
|
||||||
errors.add :parent_issue_id, :not_a_valid_parent
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Set the done_ratio using the status if that setting is set. This will keep the done_ratios
|
# Set the done_ratio using the status if that setting is set. This will keep the done_ratios
|
||||||
# even if the user turns off the setting later
|
# even if the user turns off the setting later
|
||||||
def update_done_ratio_from_issue_status
|
def update_done_ratio_from_issue_status
|
||||||
if Issue.use_status_for_done_ratio? && status && status.default_done_ratio
|
if Issue.use_status_for_done_ratio? && status && status.default_done_ratio?
|
||||||
self.done_ratio = status.default_done_ratio
|
self.done_ratio = status.default_done_ratio
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def after_save
|
||||||
|
# Reload is needed in order to get the right status
|
||||||
|
reload
|
||||||
|
|
||||||
|
# Update start/due dates of following issues
|
||||||
|
relations_from.each(&:set_issue_to_dates)
|
||||||
|
|
||||||
|
# Close duplicates if the issue was closed
|
||||||
|
if @issue_before_change && !@issue_before_change.closed? && self.closed?
|
||||||
|
duplicates.each do |duplicate|
|
||||||
|
# Reload is need in case the duplicate was updated by a previous duplicate
|
||||||
|
duplicate.reload
|
||||||
|
# Don't re-close it if it's already closed
|
||||||
|
next if duplicate.closed?
|
||||||
|
# Same user and notes
|
||||||
|
duplicate.init_journal(@current_journal.user, @current_journal.notes)
|
||||||
|
duplicate.update_attribute :status, self.status
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def init_journal(user, notes = "")
|
def init_journal(user, notes = "")
|
||||||
@current_journal ||= Journal.new(:journalized => self, :user => user, :notes => notes)
|
@current_journal ||= Journal.new(:journalized => self, :user => user, :notes => notes)
|
||||||
@issue_before_change = self.clone
|
@issue_before_change = self.clone
|
||||||
@@ -341,33 +274,14 @@ class Issue < ActiveRecord::Base
|
|||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
# Return true if the issue is being closed
|
|
||||||
def closing?
|
|
||||||
if !new_record? && status_id_changed?
|
|
||||||
status_was = IssueStatus.find_by_id(status_id_was)
|
|
||||||
status_new = IssueStatus.find_by_id(status_id)
|
|
||||||
if status_was && status_new && !status_was.is_closed? && status_new.is_closed?
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
false
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns true if the issue is overdue
|
# Returns true if the issue is overdue
|
||||||
def overdue?
|
def overdue?
|
||||||
!due_date.nil? && (due_date < Date.today) && !status.is_closed?
|
!due_date.nil? && (due_date < Date.today) && !status.is_closed?
|
||||||
end
|
end
|
||||||
|
|
||||||
# Does this issue have children?
|
|
||||||
def children?
|
|
||||||
!leaf?
|
|
||||||
end
|
|
||||||
|
|
||||||
# Users the issue can be assigned to
|
# Users the issue can be assigned to
|
||||||
def assignable_users
|
def assignable_users
|
||||||
users = project.assignable_users
|
project.assignable_users
|
||||||
users << author if author
|
|
||||||
users.uniq.sort
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Versions that the issue can be assigned to
|
# Versions that the issue can be assigned to
|
||||||
@@ -381,10 +295,9 @@ class Issue < ActiveRecord::Base
|
|||||||
end
|
end
|
||||||
|
|
||||||
# Returns an array of status that user is able to apply
|
# Returns an array of status that user is able to apply
|
||||||
def new_statuses_allowed_to(user, include_default=false)
|
def new_statuses_allowed_to(user)
|
||||||
statuses = status.find_new_statuses_allowed_to(user.roles_for_project(project), tracker)
|
statuses = status.find_new_statuses_allowed_to(user.roles_for_project(project), tracker)
|
||||||
statuses << status unless statuses.empty?
|
statuses << status unless statuses.empty?
|
||||||
statuses << IssueStatus.default if include_default
|
|
||||||
statuses = statuses.uniq.sort
|
statuses = statuses.uniq.sort
|
||||||
blocked? ? statuses.reject {|s| s.is_closed?} : statuses
|
blocked? ? statuses.reject {|s| s.is_closed?} : statuses
|
||||||
end
|
end
|
||||||
@@ -401,13 +314,13 @@ class Issue < ActiveRecord::Base
|
|||||||
notified.collect(&:mail)
|
notified.collect(&:mail)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns the total number of hours spent on this issue and its descendants
|
# Returns the total number of hours spent on this issue.
|
||||||
#
|
#
|
||||||
# Example:
|
# Example:
|
||||||
# spent_hours => 0.0
|
# spent_hours => 0
|
||||||
# spent_hours => 50.2
|
# spent_hours => 50
|
||||||
def spent_hours
|
def spent_hours
|
||||||
@spent_hours ||= self_and_descendants.sum("#{TimeEntry.table_name}.hours", :include => :time_entries).to_f || 0.0
|
@spent_hours ||= time_entries.sum(:hours) || 0
|
||||||
end
|
end
|
||||||
|
|
||||||
def relations
|
def relations
|
||||||
@@ -444,34 +357,7 @@ class Issue < ActiveRecord::Base
|
|||||||
end
|
end
|
||||||
|
|
||||||
def soonest_start
|
def soonest_start
|
||||||
@soonest_start ||= (
|
@soonest_start ||= relations_to.collect{|relation| relation.successor_soonest_start}.compact.min
|
||||||
relations_to.collect{|relation| relation.successor_soonest_start} +
|
|
||||||
ancestors.collect(&:soonest_start)
|
|
||||||
).compact.max
|
|
||||||
end
|
|
||||||
|
|
||||||
def reschedule_after(date)
|
|
||||||
return if date.nil?
|
|
||||||
if leaf?
|
|
||||||
if start_date.nil? || start_date < date
|
|
||||||
self.start_date, self.due_date = date, date + duration
|
|
||||||
save
|
|
||||||
end
|
|
||||||
else
|
|
||||||
leaves.each do |leaf|
|
|
||||||
leaf.reschedule_after(date)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def <=>(issue)
|
|
||||||
if issue.nil?
|
|
||||||
-1
|
|
||||||
elsif root_id != issue.root_id
|
|
||||||
(root_id || 0) <=> (issue.root_id || 0)
|
|
||||||
else
|
|
||||||
(lft || 0) <=> (issue.lft || 0)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_s
|
def to_s
|
||||||
@@ -488,42 +374,6 @@ class Issue < ActiveRecord::Base
|
|||||||
s
|
s
|
||||||
end
|
end
|
||||||
|
|
||||||
# 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? && User.current.allowed_to?(:log_time, project)
|
|
||||||
@time_entry = existing_time_entry || TimeEntry.new
|
|
||||||
@time_entry.project = project
|
|
||||||
@time_entry.issue = self
|
|
||||||
@time_entry.user = User.current
|
|
||||||
@time_entry.spent_on = Date.today
|
|
||||||
@time_entry.attributes = params[:time_entry]
|
|
||||||
self.time_entries << @time_entry
|
|
||||||
end
|
|
||||||
|
|
||||||
if valid?
|
|
||||||
attachments = Attachment.attach_files(self, params[:attachments])
|
|
||||||
|
|
||||||
attachments[:files].each {|a| @current_journal.details << JournalDetail.new(:property => 'attachment', :prop_key => a.id, :value => a.filename)}
|
|
||||||
# TODO: Rename hook
|
|
||||||
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_to_base l(:notice_locking_conflict)
|
|
||||||
raise ActiveRecord::Rollback
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Unassigns issues from +version+ if it's no longer shared with issue's project
|
# Unassigns issues from +version+ if it's no longer shared with issue's project
|
||||||
def self.update_versions_from_sharing_change(version)
|
def self.update_versions_from_sharing_change(version)
|
||||||
# Update issues assigned to the version
|
# Update issues assigned to the version
|
||||||
@@ -538,189 +388,8 @@ class Issue < ActiveRecord::Base
|
|||||||
Issue.update_versions(["#{Version.table_name}.project_id IN (?) OR #{Issue.table_name}.project_id IN (?)", moved_project_ids, moved_project_ids])
|
Issue.update_versions(["#{Version.table_name}.project_id IN (?) OR #{Issue.table_name}.project_id IN (?)", moved_project_ids, moved_project_ids])
|
||||||
end
|
end
|
||||||
|
|
||||||
def parent_issue_id=(arg)
|
|
||||||
parent_issue_id = arg.blank? ? nil : arg.to_i
|
|
||||||
if parent_issue_id && @parent_issue = Issue.find_by_id(parent_issue_id)
|
|
||||||
@parent_issue.id
|
|
||||||
else
|
|
||||||
@parent_issue = nil
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def parent_issue_id
|
|
||||||
if instance_variable_defined? :@parent_issue
|
|
||||||
@parent_issue.nil? ? nil : @parent_issue.id
|
|
||||||
else
|
|
||||||
parent_id
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Extracted from the ReportsController.
|
|
||||||
def self.by_tracker(project)
|
|
||||||
count_and_group_by(:project => project,
|
|
||||||
:field => 'tracker_id',
|
|
||||||
:joins => Tracker.table_name)
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.by_version(project)
|
|
||||||
count_and_group_by(:project => project,
|
|
||||||
:field => 'fixed_version_id',
|
|
||||||
:joins => Version.table_name)
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.by_priority(project)
|
|
||||||
count_and_group_by(:project => project,
|
|
||||||
:field => 'priority_id',
|
|
||||||
:joins => IssuePriority.table_name)
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.by_category(project)
|
|
||||||
count_and_group_by(:project => project,
|
|
||||||
:field => 'category_id',
|
|
||||||
:joins => IssueCategory.table_name)
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.by_assigned_to(project)
|
|
||||||
count_and_group_by(:project => project,
|
|
||||||
:field => 'assigned_to_id',
|
|
||||||
:joins => User.table_name)
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.by_author(project)
|
|
||||||
count_and_group_by(:project => project,
|
|
||||||
:field => 'author_id',
|
|
||||||
:joins => User.table_name)
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.by_subproject(project)
|
|
||||||
ActiveRecord::Base.connection.select_all("select s.id as status_id,
|
|
||||||
s.is_closed as closed,
|
|
||||||
i.project_id as project_id,
|
|
||||||
count(i.id) as total
|
|
||||||
from
|
|
||||||
#{Issue.table_name} i, #{IssueStatus.table_name} s
|
|
||||||
where
|
|
||||||
i.status_id=s.id
|
|
||||||
and i.project_id IN (#{project.descendants.active.collect{|p| p.id}.join(',')})
|
|
||||||
group by s.id, s.is_closed, i.project_id") if project.descendants.active.any?
|
|
||||||
end
|
|
||||||
# End ReportsController extraction
|
|
||||||
|
|
||||||
# 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
|
|
||||||
projects
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def update_nested_set_attributes
|
|
||||||
if root_id.nil?
|
|
||||||
# issue was just created
|
|
||||||
self.root_id = (@parent_issue.nil? ? id : @parent_issue.root_id)
|
|
||||||
set_default_left_and_right
|
|
||||||
Issue.update_all("root_id = #{root_id}, lft = #{lft}, rgt = #{rgt}", ["id = ?", id])
|
|
||||||
if @parent_issue
|
|
||||||
move_to_child_of(@parent_issue)
|
|
||||||
end
|
|
||||||
reload
|
|
||||||
elsif parent_issue_id != parent_id
|
|
||||||
former_parent_id = parent_id
|
|
||||||
# moving an existing issue
|
|
||||||
if @parent_issue && @parent_issue.root_id == root_id
|
|
||||||
# inside the same tree
|
|
||||||
move_to_child_of(@parent_issue)
|
|
||||||
else
|
|
||||||
# to another tree
|
|
||||||
unless root?
|
|
||||||
move_to_right_of(root)
|
|
||||||
reload
|
|
||||||
end
|
|
||||||
old_root_id = root_id
|
|
||||||
self.root_id = (@parent_issue.nil? ? id : @parent_issue.root_id )
|
|
||||||
target_maxright = nested_set_scope.maximum(right_column_name) || 0
|
|
||||||
offset = target_maxright + 1 - lft
|
|
||||||
Issue.update_all("root_id = #{root_id}, lft = lft + #{offset}, rgt = rgt + #{offset}",
|
|
||||||
["root_id = ? AND lft >= ? AND rgt <= ? ", old_root_id, lft, rgt])
|
|
||||||
self[left_column_name] = lft + offset
|
|
||||||
self[right_column_name] = rgt + offset
|
|
||||||
if @parent_issue
|
|
||||||
move_to_child_of(@parent_issue)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
reload
|
|
||||||
# delete invalid relations of all descendants
|
|
||||||
self_and_descendants.each do |issue|
|
|
||||||
issue.relations.each do |relation|
|
|
||||||
relation.destroy unless relation.valid?
|
|
||||||
end
|
|
||||||
end
|
|
||||||
# update former parent
|
|
||||||
recalculate_attributes_for(former_parent_id) if former_parent_id
|
|
||||||
end
|
|
||||||
remove_instance_variable(:@parent_issue) if instance_variable_defined?(:@parent_issue)
|
|
||||||
end
|
|
||||||
|
|
||||||
def update_parent_attributes
|
|
||||||
recalculate_attributes_for(parent_id) if parent_id
|
|
||||||
end
|
|
||||||
|
|
||||||
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", :include => :priority)
|
|
||||||
p.priority = IssuePriority.find_by_position(priority_position)
|
|
||||||
end
|
|
||||||
|
|
||||||
# start/due dates = lowest/highest dates of children
|
|
||||||
p.start_date = p.children.minimum(:start_date)
|
|
||||||
p.due_date = p.children.maximum(:due_date)
|
|
||||||
if p.start_date && p.due_date && p.due_date < p.start_date
|
|
||||||
p.start_date, p.due_date = p.due_date, p.start_date
|
|
||||||
end
|
|
||||||
|
|
||||||
# done ratio = weighted average ratio of leaves
|
|
||||||
unless Issue.use_status_for_done_ratio? && p.status && p.status.default_done_ratio
|
|
||||||
leaves_count = p.leaves.count
|
|
||||||
if leaves_count > 0
|
|
||||||
average = p.leaves.average(:estimated_hours).to_f
|
|
||||||
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)", :include => :status).to_f
|
|
||||||
progress = done / (average * leaves_count)
|
|
||||||
p.done_ratio = progress.round
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# estimate = sum of leaves estimates
|
|
||||||
p.estimated_hours = p.leaves.sum(:estimated_hours).to_f
|
|
||||||
p.estimated_hours = nil if p.estimated_hours == 0.0
|
|
||||||
|
|
||||||
# ancestors will be recursively updated
|
|
||||||
p.save(false)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def destroy_children
|
|
||||||
unless leaf?
|
|
||||||
children.each do |child|
|
|
||||||
child.destroy
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Update issues so their versions are not pointing to a
|
# Update issues so their versions are not pointing to a
|
||||||
# fixed_version that is not shared with the issue's project
|
# fixed_version that is not shared with the issue's project
|
||||||
def self.update_versions(conditions=nil)
|
def self.update_versions(conditions=nil)
|
||||||
@@ -750,45 +419,12 @@ class Issue < ActiveRecord::Base
|
|||||||
journal.save
|
journal.save
|
||||||
end
|
end
|
||||||
|
|
||||||
# Default assignment based on category
|
|
||||||
def default_assign
|
|
||||||
if assigned_to.nil? && category && category.assigned_to
|
|
||||||
self.assigned_to = category.assigned_to
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Updates start/due dates of following issues
|
|
||||||
def reschedule_following_issues
|
|
||||||
if start_date_changed? || due_date_changed?
|
|
||||||
relations_from.each do |relation|
|
|
||||||
relation.set_issue_to_dates
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Closes duplicates if the issue is being closed
|
|
||||||
def close_duplicates
|
|
||||||
if closing?
|
|
||||||
duplicates.each do |duplicate|
|
|
||||||
# Reload is need in case the duplicate was updated by a previous duplicate
|
|
||||||
duplicate.reload
|
|
||||||
# Don't re-close it if it's already closed
|
|
||||||
next if duplicate.closed?
|
|
||||||
# Same user and notes
|
|
||||||
if @current_journal
|
|
||||||
duplicate.init_journal(@current_journal.user, @current_journal.notes)
|
|
||||||
end
|
|
||||||
duplicate.update_attribute :status, self.status
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Saves the changes in a Journal
|
# Saves the changes in a Journal
|
||||||
# Called after_save
|
# Called after_save
|
||||||
def create_journal
|
def create_journal
|
||||||
if @current_journal
|
if @current_journal
|
||||||
# attributes changes
|
# attributes changes
|
||||||
(Issue.column_names - %w(id description root_id lft rgt lock_version created_on updated_on)).each {|c|
|
(Issue.column_names - %w(id description lock_version created_on updated_on)).each {|c|
|
||||||
@current_journal.details << JournalDetail.new(:property => 'attr',
|
@current_journal.details << JournalDetail.new(:property => 'attr',
|
||||||
:prop_key => c,
|
:prop_key => c,
|
||||||
:old_value => @issue_before_change.send(c),
|
:old_value => @issue_before_change.send(c),
|
||||||
@@ -804,37 +440,6 @@ class Issue < ActiveRecord::Base
|
|||||||
:value => c.value)
|
:value => c.value)
|
||||||
}
|
}
|
||||||
@current_journal.save
|
@current_journal.save
|
||||||
# reset current journal
|
|
||||||
init_journal @current_journal.user, @current_journal.notes
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Query generator for selecting groups of issue counts for a project
|
|
||||||
# based on specific criteria
|
|
||||||
#
|
|
||||||
# Options
|
|
||||||
# * project - Project to search in.
|
|
||||||
# * field - String. Issue field to key off of in the grouping.
|
|
||||||
# * joins - String. The table name to join against.
|
|
||||||
def self.count_and_group_by(options)
|
|
||||||
project = options.delete(:project)
|
|
||||||
select_field = options.delete(:field)
|
|
||||||
joins = options.delete(:joins)
|
|
||||||
|
|
||||||
where = "i.#{select_field}=j.id"
|
|
||||||
|
|
||||||
ActiveRecord::Base.connection.select_all("select s.id as status_id,
|
|
||||||
s.is_closed as closed,
|
|
||||||
j.id as #{select_field},
|
|
||||||
count(i.id) as total
|
|
||||||
from
|
|
||||||
#{Issue.table_name} i, #{IssueStatus.table_name} s, #{joins} j
|
|
||||||
where
|
|
||||||
i.status_id=s.id
|
|
||||||
and #{where}
|
|
||||||
and i.project_id=#{project.id}
|
|
||||||
group by s.id, s.is_closed, j.id")
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
end
|
end
|
||||||
@@ -21,19 +21,15 @@ class IssueRelation < ActiveRecord::Base
|
|||||||
|
|
||||||
TYPE_RELATES = "relates"
|
TYPE_RELATES = "relates"
|
||||||
TYPE_DUPLICATES = "duplicates"
|
TYPE_DUPLICATES = "duplicates"
|
||||||
TYPE_DUPLICATED = "duplicated"
|
|
||||||
TYPE_BLOCKS = "blocks"
|
TYPE_BLOCKS = "blocks"
|
||||||
TYPE_BLOCKED = "blocked"
|
|
||||||
TYPE_PRECEDES = "precedes"
|
TYPE_PRECEDES = "precedes"
|
||||||
TYPE_FOLLOWS = "follows"
|
TYPE_FOLLOWS = "follows"
|
||||||
|
|
||||||
TYPES = { TYPE_RELATES => { :name => :label_relates_to, :sym_name => :label_relates_to, :order => 1, :sym => TYPE_RELATES },
|
TYPES = { TYPE_RELATES => { :name => :label_relates_to, :sym_name => :label_relates_to, :order => 1 },
|
||||||
TYPE_DUPLICATES => { :name => :label_duplicates, :sym_name => :label_duplicated_by, :order => 2, :sym => TYPE_DUPLICATED },
|
TYPE_DUPLICATES => { :name => :label_duplicates, :sym_name => :label_duplicated_by, :order => 2 },
|
||||||
TYPE_DUPLICATED => { :name => :label_duplicated_by, :sym_name => :label_duplicates, :order => 3, :sym => TYPE_DUPLICATES, :reverse => TYPE_DUPLICATES },
|
TYPE_BLOCKS => { :name => :label_blocks, :sym_name => :label_blocked_by, :order => 3 },
|
||||||
TYPE_BLOCKS => { :name => :label_blocks, :sym_name => :label_blocked_by, :order => 4, :sym => TYPE_BLOCKED },
|
TYPE_PRECEDES => { :name => :label_precedes, :sym_name => :label_follows, :order => 4 },
|
||||||
TYPE_BLOCKED => { :name => :label_blocked_by, :sym_name => :label_blocks, :order => 5, :sym => TYPE_BLOCKS, :reverse => TYPE_BLOCKS },
|
TYPE_FOLLOWS => { :name => :label_follows, :sym_name => :label_precedes, :order => 5 }
|
||||||
TYPE_PRECEDES => { :name => :label_precedes, :sym_name => :label_follows, :order => 6, :sym => TYPE_FOLLOWS },
|
|
||||||
TYPE_FOLLOWS => { :name => :label_follows, :sym_name => :label_precedes, :order => 7, :sym => TYPE_PRECEDES, :reverse => TYPE_PRECEDES }
|
|
||||||
}.freeze
|
}.freeze
|
||||||
|
|
||||||
validates_presence_of :issue_from, :issue_to, :relation_type
|
validates_presence_of :issue_from, :issue_to, :relation_type
|
||||||
@@ -48,7 +44,6 @@ class IssueRelation < ActiveRecord::Base
|
|||||||
errors.add :issue_to_id, :invalid if issue_from_id == issue_to_id
|
errors.add :issue_to_id, :invalid if issue_from_id == issue_to_id
|
||||||
errors.add :issue_to_id, :not_same_project unless issue_from.project_id == issue_to.project_id || Setting.cross_project_issue_relations?
|
errors.add :issue_to_id, :not_same_project unless issue_from.project_id == issue_to.project_id || Setting.cross_project_issue_relations?
|
||||||
errors.add_to_base :circular_dependency if issue_to.all_dependent_issues.include? issue_from
|
errors.add_to_base :circular_dependency if issue_to.all_dependent_issues.include? issue_from
|
||||||
errors.add_to_base :cant_link_an_issue_with_a_descendant if issue_from.is_descendant_of?(issue_to) || issue_from.is_ancestor_of?(issue_to)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -56,17 +51,6 @@ class IssueRelation < ActiveRecord::Base
|
|||||||
(self.issue_from_id == issue.id) ? issue_to : issue_from
|
(self.issue_from_id == issue.id) ? issue_to : issue_from
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns the relation type for +issue+
|
|
||||||
def relation_type_for(issue)
|
|
||||||
if TYPES[relation_type]
|
|
||||||
if self.issue_from_id == issue.id
|
|
||||||
relation_type
|
|
||||||
else
|
|
||||||
TYPES[relation_type][:sym]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def label_for(issue)
|
def label_for(issue)
|
||||||
TYPES[relation_type] ? TYPES[relation_type][(self.issue_from_id == issue.id) ? :name : :sym_name] : :unknow
|
TYPES[relation_type] ? TYPES[relation_type][(self.issue_from_id == issue.id) ? :name : :sym_name] : :unknow
|
||||||
end
|
end
|
||||||
@@ -84,8 +68,9 @@ class IssueRelation < ActiveRecord::Base
|
|||||||
|
|
||||||
def set_issue_to_dates
|
def set_issue_to_dates
|
||||||
soonest_start = self.successor_soonest_start
|
soonest_start = self.successor_soonest_start
|
||||||
if soonest_start
|
if soonest_start && (!issue_to.start_date || issue_to.start_date < soonest_start)
|
||||||
issue_to.reschedule_after(soonest_start)
|
issue_to.start_date, issue_to.due_date = successor_soonest_start, successor_soonest_start + issue_to.duration
|
||||||
|
issue_to.save
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -100,13 +85,12 @@ class IssueRelation < ActiveRecord::Base
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
# Reverses the relation if needed so that it gets stored in the proper way
|
|
||||||
def reverse_if_needed
|
def reverse_if_needed
|
||||||
if TYPES.has_key?(relation_type) && TYPES[relation_type][:reverse]
|
if (TYPE_FOLLOWS == relation_type)
|
||||||
issue_tmp = issue_to
|
issue_tmp = issue_to
|
||||||
self.issue_to = issue_from
|
self.issue_to = issue_from
|
||||||
self.issue_from = issue_tmp
|
self.issue_from = issue_tmp
|
||||||
self.relation_type = TYPES[relation_type][:reverse]
|
self.relation_type = TYPE_PRECEDES
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -17,11 +17,9 @@
|
|||||||
|
|
||||||
class IssueStatus < ActiveRecord::Base
|
class IssueStatus < ActiveRecord::Base
|
||||||
before_destroy :check_integrity
|
before_destroy :check_integrity
|
||||||
has_many :workflows, :foreign_key => "old_status_id"
|
has_many :workflows, :foreign_key => "old_status_id", :dependent => :delete_all
|
||||||
acts_as_list
|
acts_as_list
|
||||||
|
|
||||||
before_destroy :delete_workflows
|
|
||||||
|
|
||||||
validates_presence_of :name
|
validates_presence_of :name
|
||||||
validates_uniqueness_of :name
|
validates_uniqueness_of :name
|
||||||
validates_length_of :name, :maximum => 30
|
validates_length_of :name, :maximum => 30
|
||||||
@@ -91,9 +89,4 @@ private
|
|||||||
def check_integrity
|
def check_integrity
|
||||||
raise "Can't delete status" if Issue.find(:first, :conditions => ["status_id=?", self.id])
|
raise "Can't delete status" if Issue.find(:first, :conditions => ["status_id=?", self.id])
|
||||||
end
|
end
|
||||||
|
|
||||||
# Deletes associated workflows
|
|
||||||
def delete_workflows
|
|
||||||
Workflow.delete_all(["old_status_id = :id OR new_status_id = :id", {:id => id}])
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
@@ -65,12 +65,4 @@ class Journal < ActiveRecord::Base
|
|||||||
def attachments
|
def attachments
|
||||||
journalized.respond_to?(:attachments) ? journalized.attachments : nil
|
journalized.respond_to?(:attachments) ? journalized.attachments : nil
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns a string of css classes
|
|
||||||
def css_classes
|
|
||||||
s = 'journal'
|
|
||||||
s << ' has-notes' unless notes.blank?
|
|
||||||
s << ' has-details' unless details.blank?
|
|
||||||
s
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
@@ -49,7 +49,7 @@ class MailHandler < ActionMailer::Base
|
|||||||
logger.info "MailHandler: ignoring email from Redmine emission address [#{sender_email}]" if logger && logger.info
|
logger.info "MailHandler: ignoring email from Redmine emission address [#{sender_email}]" if logger && logger.info
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
@user = User.find_by_mail(sender_email) if sender_email.present?
|
@user = User.find_by_mail(sender_email)
|
||||||
if @user && !@user.active?
|
if @user && !@user.active?
|
||||||
logger.info "MailHandler: ignoring email from non-active user [#{@user.login}]" if logger && logger.info
|
logger.info "MailHandler: ignoring email from non-active user [#{@user.login}]" if logger && logger.info
|
||||||
return false
|
return false
|
||||||
@@ -120,21 +120,18 @@ class MailHandler < ActionMailer::Base
|
|||||||
category = (get_keyword(:category) && project.issue_categories.find_by_name(get_keyword(:category)))
|
category = (get_keyword(:category) && project.issue_categories.find_by_name(get_keyword(:category)))
|
||||||
priority = (get_keyword(:priority) && IssuePriority.find_by_name(get_keyword(:priority)))
|
priority = (get_keyword(:priority) && IssuePriority.find_by_name(get_keyword(:priority)))
|
||||||
status = (get_keyword(:status) && IssueStatus.find_by_name(get_keyword(:status)))
|
status = (get_keyword(:status) && IssueStatus.find_by_name(get_keyword(:status)))
|
||||||
assigned_to = (get_keyword(:assigned_to, :override => true) && find_user_from_keyword(get_keyword(:assigned_to, :override => true)))
|
|
||||||
due_date = get_keyword(:due_date, :override => true)
|
|
||||||
start_date = get_keyword(:start_date, :override => true)
|
|
||||||
|
|
||||||
# check permission
|
# check permission
|
||||||
unless @@handler_options[:no_permission_check]
|
unless @@handler_options[:no_permission_check]
|
||||||
raise UnauthorizedAction unless user.allowed_to?(:add_issues, project)
|
raise UnauthorizedAction unless user.allowed_to?(:add_issues, project)
|
||||||
end
|
end
|
||||||
|
|
||||||
issue = Issue.new(:author => user, :project => project, :tracker => tracker, :category => category, :priority => priority, :due_date => due_date, :start_date => start_date, :assigned_to => assigned_to)
|
issue = Issue.new(:author => user, :project => project, :tracker => tracker, :category => category, :priority => priority)
|
||||||
# check workflow
|
# check workflow
|
||||||
if status && issue.new_statuses_allowed_to(user).include?(status)
|
if status && issue.new_statuses_allowed_to(user).include?(status)
|
||||||
issue.status = status
|
issue.status = status
|
||||||
end
|
end
|
||||||
issue.subject = email.subject.chomp[0,255]
|
issue.subject = email.subject.chomp
|
||||||
if issue.subject.blank?
|
if issue.subject.blank?
|
||||||
issue.subject = '(no subject)'
|
issue.subject = '(no subject)'
|
||||||
end
|
end
|
||||||
@@ -166,9 +163,6 @@ class MailHandler < ActionMailer::Base
|
|||||||
# Adds a note to an existing issue
|
# Adds a note to an existing issue
|
||||||
def receive_issue_reply(issue_id)
|
def receive_issue_reply(issue_id)
|
||||||
status = (get_keyword(:status) && IssueStatus.find_by_name(get_keyword(:status)))
|
status = (get_keyword(:status) && IssueStatus.find_by_name(get_keyword(:status)))
|
||||||
due_date = get_keyword(:due_date, :override => true)
|
|
||||||
start_date = get_keyword(:start_date, :override => true)
|
|
||||||
assigned_to = (get_keyword(:assigned_to, :override => true) && find_user_from_keyword(get_keyword(:assigned_to, :override => true)))
|
|
||||||
|
|
||||||
issue = Issue.find_by_id(issue_id)
|
issue = Issue.find_by_id(issue_id)
|
||||||
return unless issue
|
return unless issue
|
||||||
@@ -185,10 +179,6 @@ class MailHandler < ActionMailer::Base
|
|||||||
if status && issue.new_statuses_allowed_to(user).include?(status)
|
if status && issue.new_statuses_allowed_to(user).include?(status)
|
||||||
issue.status = status
|
issue.status = status
|
||||||
end
|
end
|
||||||
issue.start_date = start_date if start_date
|
|
||||||
issue.due_date = due_date if due_date
|
|
||||||
issue.assigned_to = assigned_to if assigned_to
|
|
||||||
|
|
||||||
issue.save!
|
issue.save!
|
||||||
logger.info "MailHandler: issue ##{issue.id} updated by #{user}" if logger && logger.info
|
logger.info "MailHandler: issue ##{issue.id} updated by #{user}" if logger && logger.info
|
||||||
journal
|
journal
|
||||||
@@ -255,7 +245,7 @@ class MailHandler < ActionMailer::Base
|
|||||||
@keywords[attr]
|
@keywords[attr]
|
||||||
else
|
else
|
||||||
@keywords[attr] = begin
|
@keywords[attr] = begin
|
||||||
if (options[:override] || @@handler_options[:allow_override].include?(attr.to_s)) && plain_text_body.gsub!(/^#{attr.to_s.humanize}[ \t]*:[ \t]*(.+)\s*$/i, '')
|
if (options[:override] || @@handler_options[:allow_override].include?(attr.to_s)) && plain_text_body.gsub!(/^#{attr}[ \t]*:[ \t]*(.+)\s*$/i, '')
|
||||||
$1.strip
|
$1.strip
|
||||||
elsif !@@handler_options[:issue][attr].blank?
|
elsif !@@handler_options[:issue][attr].blank?
|
||||||
@@handler_options[:issue][attr]
|
@@handler_options[:issue][attr]
|
||||||
@@ -323,14 +313,4 @@ class MailHandler < ActionMailer::Base
|
|||||||
end
|
end
|
||||||
body.strip
|
body.strip
|
||||||
end
|
end
|
||||||
|
|
||||||
def find_user_from_keyword(keyword)
|
|
||||||
user ||= User.find_by_mail(keyword)
|
|
||||||
user ||= User.find_by_login(keyword)
|
|
||||||
if user.nil? && keyword.match(/ /)
|
|
||||||
firstname, lastname = *(keyword.split) # "First Last Throwaway"
|
|
||||||
user ||= User.find_by_firstname_and_lastname(firstname, lastname)
|
|
||||||
end
|
|
||||||
user
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
+6
-45
@@ -15,8 +15,6 @@
|
|||||||
# along with this program; if not, write to the Free Software
|
# along with this program; if not, write to the Free Software
|
||||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
|
||||||
require 'ar_condition'
|
|
||||||
|
|
||||||
class Mailer < ActionMailer::Base
|
class Mailer < ActionMailer::Base
|
||||||
layout 'mailer'
|
layout 'mailer'
|
||||||
helper :application
|
helper :application
|
||||||
@@ -82,7 +80,7 @@ class Mailer < ActionMailer::Base
|
|||||||
def reminder(user, issues, days)
|
def reminder(user, issues, days)
|
||||||
set_language_if_valid user.language
|
set_language_if_valid user.language
|
||||||
recipients user.mail
|
recipients user.mail
|
||||||
subject l(:mail_subject_reminder, :count => issues.size, :days => days)
|
subject l(:mail_subject_reminder, issues.size)
|
||||||
body :issues => issues,
|
body :issues => issues,
|
||||||
:days => days,
|
:days => days,
|
||||||
:issues_url => url_for(:controller => 'issues', :action => 'index', :set_filter => 1, :assigned_to_id => user.id, :sort_key => 'due_date', :sort_order => 'asc')
|
:issues_url => url_for(:controller => 'issues', :action => 'index', :set_filter => 1, :assigned_to_id => user.id, :sort_key => 'due_date', :sort_order => 'asc')
|
||||||
@@ -116,11 +114,11 @@ class Mailer < ActionMailer::Base
|
|||||||
when 'Project'
|
when 'Project'
|
||||||
added_to_url = url_for(:controller => 'projects', :action => 'list_files', :id => container)
|
added_to_url = url_for(:controller => 'projects', :action => 'list_files', :id => container)
|
||||||
added_to = "#{l(:label_project)}: #{container}"
|
added_to = "#{l(:label_project)}: #{container}"
|
||||||
recipients container.project.notified_users.select {|user| user.allowed_to?(:view_files, container.project)}.collect {|u| u.mail}
|
recipients container.project.notified_users.select {|user| user.allowed_to?(:view_files, container.project)}
|
||||||
when 'Version'
|
when 'Version'
|
||||||
added_to_url = url_for(:controller => 'projects', :action => 'list_files', :id => container.project_id)
|
added_to_url = url_for(:controller => 'projects', :action => 'list_files', :id => container.project_id)
|
||||||
added_to = "#{l(:label_version)}: #{container.name}"
|
added_to = "#{l(:label_version)}: #{container.name}"
|
||||||
recipients container.project.notified_users.select {|user| user.allowed_to?(:view_files, container.project)}.collect {|u| u.mail}
|
recipients container.project.notified_users.select {|user| user.allowed_to?(:view_files, container.project)}
|
||||||
when 'Document'
|
when 'Document'
|
||||||
added_to_url = url_for(:controller => 'documents', :action => 'show', :id => container.id)
|
added_to_url = url_for(:controller => 'documents', :action => 'show', :id => container.id)
|
||||||
added_to = "#{l(:label_document)}: #{container.title}"
|
added_to = "#{l(:label_document)}: #{container.title}"
|
||||||
@@ -163,7 +161,7 @@ class Mailer < ActionMailer::Base
|
|||||||
cc((message.root.watcher_recipients + message.board.watcher_recipients).uniq - @recipients)
|
cc((message.root.watcher_recipients + message.board.watcher_recipients).uniq - @recipients)
|
||||||
subject "[#{message.board.project.name} - #{message.board.name} - msg#{message.root.id}] #{message.subject}"
|
subject "[#{message.board.project.name} - #{message.board.name} - msg#{message.root.id}] #{message.subject}"
|
||||||
body :message => message,
|
body :message => message,
|
||||||
:message_url => url_for(message.event_url)
|
:message_url => url_for(:controller => 'messages', :action => 'show', :board_id => message.board_id, :id => message.root)
|
||||||
render_multipart('message_posted', body)
|
render_multipart('message_posted', body)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -274,7 +272,6 @@ class Mailer < ActionMailer::Base
|
|||||||
# Overrides default deliver! method to prevent from sending an email
|
# Overrides default deliver! method to prevent from sending an email
|
||||||
# with no recipient, cc or bcc
|
# with no recipient, cc or bcc
|
||||||
def deliver!(mail = @mail)
|
def deliver!(mail = @mail)
|
||||||
set_language_if_valid @initial_language
|
|
||||||
return false if (recipients.nil? || recipients.empty?) &&
|
return false if (recipients.nil? || recipients.empty?) &&
|
||||||
(cc.nil? || cc.empty?) &&
|
(cc.nil? || cc.empty?) &&
|
||||||
(bcc.nil? || bcc.empty?)
|
(bcc.nil? || bcc.empty?)
|
||||||
@@ -286,21 +283,7 @@ class Mailer < ActionMailer::Base
|
|||||||
if @references_objects
|
if @references_objects
|
||||||
mail.references = @references_objects.collect {|o| self.class.message_id_for(o)}
|
mail.references = @references_objects.collect {|o| self.class.message_id_for(o)}
|
||||||
end
|
end
|
||||||
|
super(mail)
|
||||||
# Log errors when raise_delivery_errors is set to false, Rails does not
|
|
||||||
raise_errors = self.class.raise_delivery_errors
|
|
||||||
self.class.raise_delivery_errors = true
|
|
||||||
begin
|
|
||||||
return super(mail)
|
|
||||||
rescue Exception => e
|
|
||||||
if raise_errors
|
|
||||||
raise e
|
|
||||||
elsif mylogger
|
|
||||||
mylogger.error "The following error occured while sending email notification: \"#{e.message}\". Check your configuration in config/email.yml."
|
|
||||||
end
|
|
||||||
ensure
|
|
||||||
self.class.raise_delivery_errors = raise_errors
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Sends reminders to issue assignees
|
# Sends reminders to issue assignees
|
||||||
@@ -308,16 +291,13 @@ class Mailer < ActionMailer::Base
|
|||||||
# * :days => how many days in the future to remind about (defaults to 7)
|
# * :days => how many days in the future to remind about (defaults to 7)
|
||||||
# * :tracker => id of tracker for filtering issues (defaults to all trackers)
|
# * :tracker => id of tracker for filtering issues (defaults to all trackers)
|
||||||
# * :project => id or identifier of project to process (defaults to all projects)
|
# * :project => id or identifier of project to process (defaults to all projects)
|
||||||
# * :users => array of user ids who should be reminded
|
|
||||||
def self.reminders(options={})
|
def self.reminders(options={})
|
||||||
days = options[:days] || 7
|
days = options[:days] || 7
|
||||||
project = options[:project] ? Project.find(options[:project]) : nil
|
project = options[:project] ? Project.find(options[:project]) : nil
|
||||||
tracker = options[:tracker] ? Tracker.find(options[:tracker]) : nil
|
tracker = options[:tracker] ? Tracker.find(options[:tracker]) : nil
|
||||||
user_ids = options[:users]
|
|
||||||
|
|
||||||
s = ARCondition.new ["#{IssueStatus.table_name}.is_closed = ? AND #{Issue.table_name}.due_date <= ?", false, days.day.from_now.to_date]
|
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 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 << "#{Project.table_name}.status = #{Project::STATUS_ACTIVE}"
|
||||||
s << "#{Issue.table_name}.project_id = #{project.id}" if project
|
s << "#{Issue.table_name}.project_id = #{project.id}" if project
|
||||||
s << "#{Issue.table_name}.tracker_id = #{tracker.id}" if tracker
|
s << "#{Issue.table_name}.tracker_id = #{tracker.id}" if tracker
|
||||||
@@ -330,19 +310,9 @@ class Mailer < ActionMailer::Base
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Activates/desactivates email deliveries during +block+
|
|
||||||
def self.with_deliveries(enabled = true, &block)
|
|
||||||
was_enabled = ActionMailer::Base.perform_deliveries
|
|
||||||
ActionMailer::Base.perform_deliveries = !!enabled
|
|
||||||
yield
|
|
||||||
ensure
|
|
||||||
ActionMailer::Base.perform_deliveries = was_enabled
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
private
|
||||||
def initialize_defaults(method_name)
|
def initialize_defaults(method_name)
|
||||||
super
|
super
|
||||||
@initial_language = current_language
|
|
||||||
set_language_if_valid Setting.default_language
|
set_language_if_valid Setting.default_language
|
||||||
from Setting.mail_from
|
from Setting.mail_from
|
||||||
|
|
||||||
@@ -368,14 +338,9 @@ class Mailer < ActionMailer::Base
|
|||||||
recipients.delete(@author.mail) if recipients
|
recipients.delete(@author.mail) if recipients
|
||||||
cc.delete(@author.mail) if cc
|
cc.delete(@author.mail) if cc
|
||||||
end
|
end
|
||||||
|
|
||||||
notified_users = [recipients, cc].flatten.compact.uniq
|
|
||||||
# Rails would log recipients only, not cc and bcc
|
|
||||||
mylogger.info "Sending email notification to: #{notified_users.join(', ')}" if mylogger
|
|
||||||
|
|
||||||
# Blind carbon copy recipients
|
# Blind carbon copy recipients
|
||||||
if Setting.bcc_recipients?
|
if Setting.bcc_recipients?
|
||||||
bcc(notified_users)
|
bcc([recipients, cc].flatten.compact.uniq)
|
||||||
recipients []
|
recipients []
|
||||||
cc []
|
cc []
|
||||||
end
|
end
|
||||||
@@ -426,10 +391,6 @@ class Mailer < ActionMailer::Base
|
|||||||
@references_objects ||= []
|
@references_objects ||= []
|
||||||
@references_objects << object
|
@references_objects << object
|
||||||
end
|
end
|
||||||
|
|
||||||
def mylogger
|
|
||||||
RAILS_DEFAULT_LOGGER
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Patch TMail so that message_id is not overwritten
|
# Patch TMail so that message_id is not overwritten
|
||||||
|
|||||||
@@ -72,17 +72,10 @@ class Member < ActiveRecord::Base
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Find or initilize a Member with an id, attributes, and for a Principal
|
|
||||||
def self.edit_membership(id, new_attributes, principal=nil)
|
|
||||||
@membership = id.present? ? Member.find(id) : Member.new(:principal => principal)
|
|
||||||
@membership.attributes = new_attributes
|
|
||||||
@membership
|
|
||||||
end
|
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
def validate
|
def validate
|
||||||
errors.add_on_empty :role if member_roles.empty? && roles.empty?
|
errors.add_to_base "Role can't be blank" if member_roles.empty? && roles.empty?
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ class Message < ActiveRecord::Base
|
|||||||
:description => :content,
|
:description => :content,
|
||||||
:type => Proc.new {|o| o.parent_id.nil? ? 'message' : 'reply'},
|
:type => Proc.new {|o| o.parent_id.nil? ? 'message' : 'reply'},
|
||||||
:url => Proc.new {|o| {:controller => 'messages', :action => 'show', :board_id => o.board_id}.merge(o.parent_id.nil? ? {:id => o.id} :
|
:url => Proc.new {|o| {:controller => 'messages', :action => 'show', :board_id => o.board_id}.merge(o.parent_id.nil? ? {:id => o.id} :
|
||||||
{:id => o.parent_id, :r => o.id, :anchor => "message-#{o.id}"})}
|
{:id => o.parent_id, :anchor => "message-#{o.id}"})}
|
||||||
|
|
||||||
acts_as_activity_provider :find_options => {:include => [{:board => :project}, :author]},
|
acts_as_activity_provider :find_options => {:include => [{:board => :project}, :author]},
|
||||||
:author_key => :author_id
|
:author_key => :author_id
|
||||||
@@ -90,6 +90,13 @@ class Message < ActiveRecord::Base
|
|||||||
usr && usr.logged? && (usr.allowed_to?(:delete_messages, project) || (self.author == usr && usr.allowed_to?(:delete_own_messages, project)))
|
usr && usr.logged? && (usr.allowed_to?(:delete_messages, project) || (self.author == usr && usr.allowed_to?(:delete_own_messages, project)))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Returns the mail adresses of users that should be notified
|
||||||
|
def recipients
|
||||||
|
notified = project.notified_users
|
||||||
|
notified.reject! {|user| !visible?(user)}
|
||||||
|
notified.collect(&:mail)
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def add_author_as_watcher
|
def add_author_as_watcher
|
||||||
|
|||||||
@@ -33,6 +33,13 @@ class News < ActiveRecord::Base
|
|||||||
!user.nil? && user.allowed_to?(:view_news, project)
|
!user.nil? && user.allowed_to?(:view_news, project)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Returns the mail adresses of users that should be notified
|
||||||
|
def recipients
|
||||||
|
notified = project.notified_users
|
||||||
|
notified.reject! {|user| !visible?(user)}
|
||||||
|
notified.collect(&:mail)
|
||||||
|
end
|
||||||
|
|
||||||
# returns latest news for projects visible by user
|
# returns latest news for projects visible by user
|
||||||
def self.latest(user = User.current, count = 5)
|
def self.latest(user = User.current, count = 5)
|
||||||
find(:all, :limit => count, :conditions => Project.allowed_to_condition(user, :view_news), :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC")
|
find(:all, :limit => count, :conditions => Project.allowed_to_condition(user, :view_news), :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC")
|
||||||
|
|||||||
+1
-19
@@ -16,7 +16,7 @@
|
|||||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
|
||||||
class Principal < ActiveRecord::Base
|
class Principal < ActiveRecord::Base
|
||||||
set_table_name "#{table_name_prefix}users#{table_name_suffix}"
|
set_table_name 'users'
|
||||||
|
|
||||||
has_many :members, :foreign_key => 'user_id', :dependent => :destroy
|
has_many :members, :foreign_key => 'user_id', :dependent => :destroy
|
||||||
has_many :memberships, :class_name => 'Member', :foreign_key => 'user_id', :include => [ :project, :roles ], :conditions => "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}", :order => "#{Project.table_name}.name"
|
has_many :memberships, :class_name => 'Member', :foreign_key => 'user_id', :include => [ :project, :roles ], :conditions => "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}", :order => "#{Project.table_name}.name"
|
||||||
@@ -32,12 +32,6 @@ class Principal < ActiveRecord::Base
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
before_create :set_default_empty_values
|
|
||||||
|
|
||||||
def name(formatter = nil)
|
|
||||||
to_s
|
|
||||||
end
|
|
||||||
|
|
||||||
def <=>(principal)
|
def <=>(principal)
|
||||||
if self.class.name == principal.class.name
|
if self.class.name == principal.class.name
|
||||||
self.to_s.downcase <=> principal.to_s.downcase
|
self.to_s.downcase <=> principal.to_s.downcase
|
||||||
@@ -46,16 +40,4 @@ class Principal < ActiveRecord::Base
|
|||||||
principal.class.name <=> self.class.name
|
principal.class.name <=> self.class.name
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
protected
|
|
||||||
|
|
||||||
# Make sure we don't try to insert NULL values (see #4632)
|
|
||||||
def set_default_empty_values
|
|
||||||
self.login ||= ''
|
|
||||||
self.hashed_password ||= ''
|
|
||||||
self.firstname ||= ''
|
|
||||||
self.lastname ||= ''
|
|
||||||
self.mail ||= ''
|
|
||||||
true
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
+7
-60
@@ -51,14 +51,14 @@ class Project < ActiveRecord::Base
|
|||||||
:join_table => "#{table_name_prefix}custom_fields_projects#{table_name_suffix}",
|
:join_table => "#{table_name_prefix}custom_fields_projects#{table_name_suffix}",
|
||||||
:association_foreign_key => 'custom_field_id'
|
:association_foreign_key => 'custom_field_id'
|
||||||
|
|
||||||
acts_as_nested_set :order => 'name'
|
acts_as_nested_set :order => 'name', :dependent => :destroy
|
||||||
acts_as_attachable :view_permission => :view_files,
|
acts_as_attachable :view_permission => :view_files,
|
||||||
:delete_permission => :manage_files
|
:delete_permission => :manage_files
|
||||||
|
|
||||||
acts_as_customizable
|
acts_as_customizable
|
||||||
acts_as_searchable :columns => ['name', 'identifier', 'description'], :project_key => 'id', :permission => nil
|
acts_as_searchable :columns => ['name', 'description'], :project_key => 'id', :permission => nil
|
||||||
acts_as_event :title => Proc.new {|o| "#{l(:label_project)}: #{o.name}"},
|
acts_as_event :title => Proc.new {|o| "#{l(:label_project)}: #{o.name}"},
|
||||||
:url => Proc.new {|o| {:controller => 'projects', :action => 'show', :id => o}},
|
:url => Proc.new {|o| {:controller => 'projects', :action => 'show', :id => o.id}},
|
||||||
:author => nil
|
:author => nil
|
||||||
|
|
||||||
attr_protected :status, :enabled_module_names
|
attr_protected :status, :enabled_module_names
|
||||||
@@ -74,7 +74,7 @@ class Project < ActiveRecord::Base
|
|||||||
# reserved words
|
# reserved words
|
||||||
validates_exclusion_of :identifier, :in => %w( new )
|
validates_exclusion_of :identifier, :in => %w( new )
|
||||||
|
|
||||||
before_destroy :delete_all_members, :destroy_children
|
before_destroy :delete_all_members
|
||||||
|
|
||||||
named_scope :has_module, lambda { |mod| { :conditions => ["#{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name=?)", mod.to_s] } }
|
named_scope :has_module, lambda { |mod| { :conditions => ["#{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name=?)", mod.to_s] } }
|
||||||
named_scope :active, { :conditions => "#{Project.table_name}.status = #{STATUS_ACTIVE}"}
|
named_scope :active, { :conditions => "#{Project.table_name}.status = #{STATUS_ACTIVE}"}
|
||||||
@@ -249,7 +249,7 @@ class Project < ActiveRecord::Base
|
|||||||
return @allowed_parents if @allowed_parents
|
return @allowed_parents if @allowed_parents
|
||||||
@allowed_parents = Project.find(:all, :conditions => Project.allowed_to_condition(User.current, :add_subprojects))
|
@allowed_parents = Project.find(:all, :conditions => Project.allowed_to_condition(User.current, :add_subprojects))
|
||||||
@allowed_parents = @allowed_parents - self_and_descendants
|
@allowed_parents = @allowed_parents - self_and_descendants
|
||||||
if User.current.allowed_to?(:add_project, nil, :global => true) || (!new_record? && parent.nil?)
|
if User.current.allowed_to?(:add_project, nil, :global => true)
|
||||||
@allowed_parents << nil
|
@allowed_parents << nil
|
||||||
end
|
end
|
||||||
unless parent.nil? || @allowed_parents.empty? || @allowed_parents.include?(parent)
|
unless parent.nil? || @allowed_parents.empty? || @allowed_parents.include?(parent)
|
||||||
@@ -337,13 +337,6 @@ class Project < ActiveRecord::Base
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns a scope of the Versions on subprojects
|
|
||||||
def rolled_up_versions
|
|
||||||
@rolled_up_versions ||=
|
|
||||||
Version.scoped(:include => :project,
|
|
||||||
:conditions => ["#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status = #{STATUS_ACTIVE}", lft, rgt])
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns a scope of the Versions used by the project
|
# Returns a scope of the Versions used by the project
|
||||||
def shared_versions
|
def shared_versions
|
||||||
@shared_versions ||=
|
@shared_versions ||=
|
||||||
@@ -413,14 +406,6 @@ class Project < ActiveRecord::Base
|
|||||||
description.gsub(/^(.{#{length}}[^\n\r]*).*$/m, '\1...').strip if description
|
description.gsub(/^(.{#{length}}[^\n\r]*).*$/m, '\1...').strip if description
|
||||||
end
|
end
|
||||||
|
|
||||||
def css_classes
|
|
||||||
s = 'project'
|
|
||||||
s << ' root' if root?
|
|
||||||
s << ' child' if child?
|
|
||||||
s << (leaf? ? ' leaf' : ' parent')
|
|
||||||
s
|
|
||||||
end
|
|
||||||
|
|
||||||
# Return true if this project is allowed to do the specified action.
|
# Return true if this project is allowed to do the specified action.
|
||||||
# action can be:
|
# action can be:
|
||||||
# * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
|
# * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
|
||||||
@@ -450,15 +435,6 @@ class Project < ActiveRecord::Base
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns an array of projects that are in this project's hierarchy
|
|
||||||
#
|
|
||||||
# Example: parents, children, siblings
|
|
||||||
def hierarchy
|
|
||||||
parents = project.self_and_ancestors || []
|
|
||||||
descendants = project.descendants || []
|
|
||||||
project_hierarchy = parents | descendants # Set union
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns an auto-generated project identifier based on the last identifier used
|
# Returns an auto-generated project identifier based on the last identifier used
|
||||||
def self.next_identifier
|
def self.next_identifier
|
||||||
p = Project.find(:first, :order => 'created_on DESC')
|
p = Project.find(:first, :order => 'created_on DESC')
|
||||||
@@ -523,36 +499,17 @@ class Project < ActiveRecord::Base
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
# Destroys children before destroying self
|
|
||||||
def destroy_children
|
|
||||||
children.each do |child|
|
|
||||||
child.destroy
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Copies wiki from +project+
|
# Copies wiki from +project+
|
||||||
def copy_wiki(project)
|
def copy_wiki(project)
|
||||||
# Check that the source project has a wiki first
|
# Check that the source project has a wiki first
|
||||||
unless project.wiki.nil?
|
unless project.wiki.nil?
|
||||||
self.wiki ||= Wiki.new
|
self.wiki ||= Wiki.new
|
||||||
wiki.attributes = project.wiki.attributes.dup.except("id", "project_id")
|
wiki.attributes = project.wiki.attributes.dup.except("id", "project_id")
|
||||||
wiki_pages_map = {}
|
|
||||||
project.wiki.pages.each do |page|
|
project.wiki.pages.each do |page|
|
||||||
# Skip pages without content
|
|
||||||
next if page.content.nil?
|
|
||||||
new_wiki_content = WikiContent.new(page.content.attributes.dup.except("id", "page_id", "updated_on"))
|
new_wiki_content = WikiContent.new(page.content.attributes.dup.except("id", "page_id", "updated_on"))
|
||||||
new_wiki_page = WikiPage.new(page.attributes.dup.except("id", "wiki_id", "created_on", "parent_id"))
|
new_wiki_page = WikiPage.new(page.attributes.dup.except("id", "wiki_id", "created_on", "parent_id"))
|
||||||
new_wiki_page.content = new_wiki_content
|
new_wiki_page.content = new_wiki_content
|
||||||
wiki.pages << new_wiki_page
|
wiki.pages << new_wiki_page
|
||||||
wiki_pages_map[page.id] = new_wiki_page
|
|
||||||
end
|
|
||||||
wiki.save
|
|
||||||
# Reproduce page hierarchy
|
|
||||||
project.wiki.pages.each do |page|
|
|
||||||
if page.parent_id && wiki_pages_map[page.id]
|
|
||||||
wiki_pages_map[page.id].parent = wiki_pages_map[page.parent_id]
|
|
||||||
wiki_pages_map[page.id].save
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -581,12 +538,9 @@ class Project < ActiveRecord::Base
|
|||||||
# value. Used to map the two togeather for issue relations.
|
# value. Used to map the two togeather for issue relations.
|
||||||
issues_map = {}
|
issues_map = {}
|
||||||
|
|
||||||
# Get issues sorted by root_id, lft so that parent issues
|
project.issues.each do |issue|
|
||||||
# get copied before their children
|
|
||||||
project.issues.find(:all, :order => 'root_id, lft').each do |issue|
|
|
||||||
new_issue = Issue.new
|
new_issue = Issue.new
|
||||||
new_issue.copy_from(issue)
|
new_issue.copy_from(issue)
|
||||||
new_issue.project = self
|
|
||||||
# Reassign fixed_versions by name, since names are unique per
|
# Reassign fixed_versions by name, since names are unique per
|
||||||
# project and the versions for self are not yet saved
|
# project and the versions for self are not yet saved
|
||||||
if issue.fixed_version
|
if issue.fixed_version
|
||||||
@@ -597,13 +551,6 @@ class Project < ActiveRecord::Base
|
|||||||
if issue.category
|
if issue.category
|
||||||
new_issue.category = self.issue_categories.select {|c| c.name == issue.category.name}.first
|
new_issue.category = self.issue_categories.select {|c| c.name == issue.category.name}.first
|
||||||
end
|
end
|
||||||
# Parent issue
|
|
||||||
if issue.parent_id
|
|
||||||
if copied_parent = issues_map[issue.parent_id]
|
|
||||||
new_issue.parent_issue_id = copied_parent.id
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
self.issues << new_issue
|
self.issues << new_issue
|
||||||
issues_map[issue.id] = new_issue
|
issues_map[issue.id] = new_issue
|
||||||
end
|
end
|
||||||
@@ -673,7 +620,7 @@ class Project < ActiveRecord::Base
|
|||||||
|
|
||||||
def allowed_permissions
|
def allowed_permissions
|
||||||
@allowed_permissions ||= begin
|
@allowed_permissions ||= begin
|
||||||
module_names = enabled_modules.all(:select => :name).collect {|m| m.name}
|
module_names = enabled_modules.collect {|m| m.name}
|
||||||
Redmine::AccessControl.modules_permissions(module_names).collect {|p| p.name}
|
Redmine::AccessControl.modules_permissions(module_names).collect {|p| p.name}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
+4
-41
@@ -27,11 +27,10 @@ class QueryColumn
|
|||||||
self.groupable = name.to_s
|
self.groupable = name.to_s
|
||||||
end
|
end
|
||||||
self.default_order = options[:default_order]
|
self.default_order = options[:default_order]
|
||||||
@caption_key = options[:caption] || "field_#{name}"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def caption
|
def caption
|
||||||
l(@caption_key)
|
l("field_#{name}")
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns true if the column is sortable, otherwise false
|
# Returns true if the column is sortable, otherwise false
|
||||||
@@ -121,7 +120,6 @@ class Query < ActiveRecord::Base
|
|||||||
@@available_columns = [
|
@@available_columns = [
|
||||||
QueryColumn.new(:project, :sortable => "#{Project.table_name}.name", :groupable => true),
|
QueryColumn.new(:project, :sortable => "#{Project.table_name}.name", :groupable => true),
|
||||||
QueryColumn.new(:tracker, :sortable => "#{Tracker.table_name}.position", :groupable => true),
|
QueryColumn.new(:tracker, :sortable => "#{Tracker.table_name}.position", :groupable => true),
|
||||||
QueryColumn.new(:parent, :sortable => ["#{Issue.table_name}.root_id", "#{Issue.table_name}.lft ASC"], :default_order => 'desc', :caption => :field_parent_issue),
|
|
||||||
QueryColumn.new(:status, :sortable => "#{IssueStatus.table_name}.position", :groupable => true),
|
QueryColumn.new(:status, :sortable => "#{IssueStatus.table_name}.position", :groupable => true),
|
||||||
QueryColumn.new(:priority, :sortable => "#{IssuePriority.table_name}.position", :default_order => 'desc', :groupable => true),
|
QueryColumn.new(:priority, :sortable => "#{IssuePriority.table_name}.position", :default_order => 'desc', :groupable => true),
|
||||||
QueryColumn.new(:subject, :sortable => "#{Issue.table_name}.subject"),
|
QueryColumn.new(:subject, :sortable => "#{Issue.table_name}.subject"),
|
||||||
@@ -187,11 +185,9 @@ class Query < ActiveRecord::Base
|
|||||||
if project
|
if project
|
||||||
user_values += project.users.sort.collect{|s| [s.name, s.id.to_s] }
|
user_values += project.users.sort.collect{|s| [s.name, s.id.to_s] }
|
||||||
else
|
else
|
||||||
project_ids = Project.all(:conditions => Project.visible_by(User.current)).collect(&:id)
|
# members of the user's projects
|
||||||
if project_ids.any?
|
# OPTIMIZE: Is selecting from users per project (N+1)
|
||||||
# members of the user's projects
|
user_values += User.current.projects.collect(&:users).flatten.uniq.sort.collect{|s| [s.name, s.id.to_s] }
|
||||||
user_values += User.active.find(:all, :conditions => ["#{User.table_name}.id IN (SELECT DISTINCT user_id FROM members WHERE project_id IN (?))", project_ids]).sort.collect{|s| [s.name, s.id.to_s] }
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
@available_filters["assigned_to_id"] = { :type => :list_optional, :order => 4, :values => user_values } unless user_values.empty?
|
@available_filters["assigned_to_id"] = { :type => :list_optional, :order => 4, :values => user_values } unless user_values.empty?
|
||||||
@available_filters["author_id"] = { :type => :list, :order => 5, :values => user_values } unless user_values.empty?
|
@available_filters["author_id"] = { :type => :list, :order => 5, :values => user_values } unless user_values.empty?
|
||||||
@@ -214,17 +210,7 @@ class Query < ActiveRecord::Base
|
|||||||
add_custom_fields_filters(@project.all_issue_custom_fields)
|
add_custom_fields_filters(@project.all_issue_custom_fields)
|
||||||
else
|
else
|
||||||
# global filters for cross project issue list
|
# global filters for cross project issue list
|
||||||
system_shared_versions = Version.visible.find_all_by_sharing('system')
|
|
||||||
unless system_shared_versions.empty?
|
|
||||||
@available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => system_shared_versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] } }
|
|
||||||
end
|
|
||||||
add_custom_fields_filters(IssueCustomField.find(:all, :conditions => {:is_filter => true, :is_for_all => true}))
|
add_custom_fields_filters(IssueCustomField.find(:all, :conditions => {:is_filter => true, :is_for_all => true}))
|
||||||
# project filter
|
|
||||||
project_values = Project.all(:conditions => Project.visible_by(User.current), :order => 'lft').map do |p|
|
|
||||||
pre = (p.level > 0 ? ('--' * p.level + ' ') : '')
|
|
||||||
["#{pre}#{p.name}",p.id.to_s]
|
|
||||||
end
|
|
||||||
@available_filters["project_id"] = { :type => :list, :order => 1, :values => project_values}
|
|
||||||
end
|
end
|
||||||
@available_filters
|
@available_filters
|
||||||
end
|
end
|
||||||
@@ -250,13 +236,6 @@ class Query < ActiveRecord::Base
|
|||||||
add_filter field, (parms[0] || "="), [parms[1] || ""]
|
add_filter field, (parms[0] || "="), [parms[1] || ""]
|
||||||
end
|
end
|
||||||
|
|
||||||
# Add multiple filters using +add_filter+
|
|
||||||
def add_filters(fields, operators, values)
|
|
||||||
fields.each do |field|
|
|
||||||
add_filter(field, operators[field], values[field])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def has_filter?(field)
|
def has_filter?(field)
|
||||||
filters and filters[field]
|
filters and filters[field]
|
||||||
end
|
end
|
||||||
@@ -283,27 +262,11 @@ class Query < ActiveRecord::Base
|
|||||||
).collect {|cf| QueryCustomFieldColumn.new(cf) }
|
).collect {|cf| QueryCustomFieldColumn.new(cf) }
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.available_columns=(v)
|
|
||||||
self.available_columns = (v)
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.add_available_column(column)
|
|
||||||
self.available_columns << (column) if column.is_a?(QueryColumn)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns an array of columns that can be used to group the results
|
# Returns an array of columns that can be used to group the results
|
||||||
def groupable_columns
|
def groupable_columns
|
||||||
available_columns.select {|c| c.groupable}
|
available_columns.select {|c| c.groupable}
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns a Hash of columns and the key for sorting
|
|
||||||
def sortable_columns
|
|
||||||
{'id' => "#{Issue.table_name}.id"}.merge(available_columns.inject({}) {|h, column|
|
|
||||||
h[column.name.to_s] = column.sortable
|
|
||||||
h
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
def columns
|
def columns
|
||||||
if has_default_columns?
|
if has_default_columns?
|
||||||
available_columns.select do |c|
|
available_columns.select do |c|
|
||||||
|
|||||||
@@ -136,7 +136,6 @@ class Repository < ActiveRecord::Base
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
@committers = nil
|
@committers = nil
|
||||||
@found_committer_users = nil
|
|
||||||
true
|
true
|
||||||
else
|
else
|
||||||
false
|
false
|
||||||
@@ -147,34 +146,24 @@ class Repository < ActiveRecord::Base
|
|||||||
# It will return nil if the committer is not yet mapped and if no User
|
# It will return nil if the committer is not yet mapped and if no User
|
||||||
# with the same username or email was found
|
# with the same username or email was found
|
||||||
def find_committer_user(committer)
|
def find_committer_user(committer)
|
||||||
unless committer.blank?
|
if committer
|
||||||
@found_committer_users ||= {}
|
|
||||||
return @found_committer_users[committer] if @found_committer_users.has_key?(committer)
|
|
||||||
|
|
||||||
user = nil
|
|
||||||
c = changesets.find(:first, :conditions => {:committer => committer}, :include => :user)
|
c = changesets.find(:first, :conditions => {:committer => committer}, :include => :user)
|
||||||
if c && c.user
|
if c && c.user
|
||||||
user = c.user
|
c.user
|
||||||
elsif committer.strip =~ /^([^<]+)(<(.*)>)?$/
|
elsif committer.strip =~ /^([^<]+)(<(.*)>)?$/
|
||||||
username, email = $1.strip, $3
|
username, email = $1.strip, $3
|
||||||
u = User.find_by_login(username)
|
u = User.find_by_login(username)
|
||||||
u ||= User.find_by_mail(email) unless email.blank?
|
u ||= User.find_by_mail(email) unless email.blank?
|
||||||
user = u
|
u
|
||||||
end
|
end
|
||||||
@found_committer_users[committer] = user
|
|
||||||
user
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Fetches new changesets for all repositories of active projects
|
# fetch new changesets for all repositories
|
||||||
# Can be called periodically by an external script
|
# can be called periodically by an external script
|
||||||
# eg. ruby script/runner "Repository.fetch_changesets"
|
# eg. ruby script/runner "Repository.fetch_changesets"
|
||||||
def self.fetch_changesets
|
def self.fetch_changesets
|
||||||
Project.active.has_module(:repository).find(:all, :include => :repository).each do |project|
|
find(:all).each(&:fetch_changesets)
|
||||||
if project.repository
|
|
||||||
project.repository.fetch_changesets
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# scan changeset comments to find related and fixed issues for all repositories
|
# scan changeset comments to find related and fixed issues for all repositories
|
||||||
|
|||||||
@@ -85,7 +85,11 @@ class Repository::Darcs < Repository
|
|||||||
:comments => revision.message)
|
:comments => revision.message)
|
||||||
|
|
||||||
revision.paths.each do |change|
|
revision.paths.each do |change|
|
||||||
changeset.create_change(change)
|
Change.create(:changeset => changeset,
|
||||||
|
:action => change[:action],
|
||||||
|
:path => change[:path],
|
||||||
|
:from_path => change[:from_path],
|
||||||
|
:from_revision => change[:from_revision])
|
||||||
end
|
end
|
||||||
next_rev += 1
|
next_rev += 1
|
||||||
end if revisions
|
end if revisions
|
||||||
|
|||||||
@@ -40,26 +40,23 @@ class Repository::Git < Repository
|
|||||||
# With SCM's that have a sequential commit numbering, redmine is able to be
|
# With SCM's that have a sequential commit numbering, redmine is able to be
|
||||||
# clever and only fetch changesets going forward from the most recent one
|
# clever and only fetch changesets going forward from the most recent one
|
||||||
# it knows about. However, with git, you never know if people have merged
|
# it knows about. However, with git, you never know if people have merged
|
||||||
# commits into the middle of the repository history, so we should parse
|
# commits into the middle of the repository history, so we always have to
|
||||||
# the entire log. Since it's way too slow for large repositories, we only
|
# parse the entire log.
|
||||||
# parse 1 week before the last known commit.
|
|
||||||
# The repository can still be fully reloaded by calling #clear_changesets
|
|
||||||
# before fetching changesets (eg. for offline resync)
|
|
||||||
def fetch_changesets
|
def fetch_changesets
|
||||||
c = changesets.find(:first, :order => 'committed_on DESC')
|
# Save ourselves an expensive operation if we're already up to date
|
||||||
since = (c ? c.committed_on - 7.days : nil)
|
return if scm.num_revisions == changesets.count
|
||||||
|
|
||||||
revisions = scm.revisions('', nil, nil, :all => true, :since => since)
|
revisions = scm.revisions('', nil, nil, :all => true)
|
||||||
return if revisions.nil? || revisions.empty?
|
return if revisions.nil? || revisions.empty?
|
||||||
|
|
||||||
recent_changesets = changesets.find(:all, :conditions => ['committed_on >= ?', since])
|
# Find revisions that redmine knows about already
|
||||||
|
existing_revisions = changesets.find(:all).map!{|c| c.scmid}
|
||||||
|
|
||||||
# Clean out revisions that are no longer in git
|
# Clean out revisions that are no longer in git
|
||||||
recent_changesets.each {|c| c.destroy unless revisions.detect {|r| r.scmid.to_s == c.scmid.to_s }}
|
Changeset.delete_all(["scmid NOT IN (?) AND repository_id = (?)", revisions.map{|r| r.scmid}, self.id])
|
||||||
|
|
||||||
# Subtract revisions that redmine already knows about
|
# Subtract revisions that redmine already knows about
|
||||||
recent_revisions = recent_changesets.map{|c| c.scmid}
|
revisions.reject!{|r| existing_revisions.include?(r.scmid)}
|
||||||
revisions.reject!{|r| recent_revisions.include?(r.scmid)}
|
|
||||||
|
|
||||||
# Save the remaining ones to the database
|
# Save the remaining ones to the database
|
||||||
revisions.each{|r| r.save(self)} unless revisions.nil?
|
revisions.each{|r| r.save(self)} unless revisions.nil?
|
||||||
|
|||||||
@@ -78,7 +78,11 @@ class Repository::Mercurial < Repository
|
|||||||
:comments => revision.message)
|
:comments => revision.message)
|
||||||
|
|
||||||
revision.paths.each do |change|
|
revision.paths.each do |change|
|
||||||
changeset.create_change(change)
|
Change.create(:changeset => changeset,
|
||||||
|
:action => change[:action],
|
||||||
|
:path => change[:path],
|
||||||
|
:from_path => change[:from_path],
|
||||||
|
:from_revision => change[:from_revision])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end unless revisions.nil?
|
end unless revisions.nil?
|
||||||
|
|||||||
@@ -63,7 +63,11 @@ class Repository::Subversion < Repository
|
|||||||
:comments => revision.message)
|
:comments => revision.message)
|
||||||
|
|
||||||
revision.paths.each do |change|
|
revision.paths.each do |change|
|
||||||
changeset.create_change(change)
|
Change.create(:changeset => changeset,
|
||||||
|
:action => change[:action],
|
||||||
|
:path => change[:path],
|
||||||
|
:from_path => change[:from_path],
|
||||||
|
:from_revision => change[:from_revision])
|
||||||
end unless changeset.new_record?
|
end unless changeset.new_record?
|
||||||
end
|
end
|
||||||
end unless revisions.nil?
|
end unless revisions.nil?
|
||||||
|
|||||||
+4
-20
@@ -120,30 +120,14 @@ class Role < ActiveRecord::Base
|
|||||||
find(:all, :conditions => {:builtin => 0}, :order => 'position')
|
find(:all, :conditions => {:builtin => 0}, :order => 'position')
|
||||||
end
|
end
|
||||||
|
|
||||||
# Return the builtin 'non member' role. If the role doesn't exist,
|
# Return the builtin 'non member' role
|
||||||
# it will be created on the fly.
|
|
||||||
def self.non_member
|
def self.non_member
|
||||||
non_member_role = find(:first, :conditions => {:builtin => BUILTIN_NON_MEMBER})
|
find(:first, :conditions => {:builtin => BUILTIN_NON_MEMBER}) || raise('Missing non-member builtin role.')
|
||||||
if non_member_role.nil?
|
|
||||||
non_member_role = create(:name => 'Non member', :position => 0) do |role|
|
|
||||||
role.builtin = BUILTIN_NON_MEMBER
|
|
||||||
end
|
|
||||||
raise 'Unable to create the non-member role.' if non_member_role.new_record?
|
|
||||||
end
|
|
||||||
non_member_role
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Return the builtin 'anonymous' role. If the role doesn't exist,
|
# Return the builtin 'anonymous' role
|
||||||
# it will be created on the fly.
|
|
||||||
def self.anonymous
|
def self.anonymous
|
||||||
anonymous_role = find(:first, :conditions => {:builtin => BUILTIN_ANONYMOUS})
|
find(:first, :conditions => {:builtin => BUILTIN_ANONYMOUS}) || raise('Missing anonymous builtin role.')
|
||||||
if anonymous_role.nil?
|
|
||||||
anonymous_role = create(:name => 'Anonymous', :position => 0) do |role|
|
|
||||||
role.builtin = BUILTIN_ANONYMOUS
|
|
||||||
end
|
|
||||||
raise 'Unable to create the anonymous role.' if anonymous_role.new_record?
|
|
||||||
end
|
|
||||||
anonymous_role
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ class Setting < ActiveRecord::Base
|
|||||||
end
|
end
|
||||||
|
|
||||||
def value=(v)
|
def value=(v)
|
||||||
v = v.to_yaml if v && @@available_settings[name] && @@available_settings[name]['serialized']
|
v = v.to_yaml if v && @@available_settings[name]['serialized']
|
||||||
write_attribute(:value, v.to_s)
|
write_attribute(:value, v.to_s)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -81,20 +81,4 @@ class TimeEntry < ActiveRecord::Base
|
|||||||
yield
|
yield
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.earilest_date_for_project(project=nil)
|
|
||||||
finder_conditions = ARCondition.new(Project.allowed_to_condition(User.current, :view_time_entries))
|
|
||||||
if project
|
|
||||||
finder_conditions << ["project_id IN (?)", project.hierarchy.collect(&:id)]
|
|
||||||
end
|
|
||||||
TimeEntry.minimum(:spent_on, :include => :project, :conditions => finder_conditions.conditions)
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.latest_date_for_project(project=nil)
|
|
||||||
finder_conditions = ARCondition.new(Project.allowed_to_condition(User.current, :view_time_entries))
|
|
||||||
if project
|
|
||||||
finder_conditions << ["project_id IN (?)", project.hierarchy.collect(&:id)]
|
|
||||||
end
|
|
||||||
TimeEntry.maximum(:spent_on, :include => :project, :conditions => finder_conditions.conditions)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
+6
-62
@@ -53,7 +53,7 @@ class User < Principal
|
|||||||
attr_protected :login, :admin, :password, :password_confirmation, :hashed_password, :group_ids
|
attr_protected :login, :admin, :password, :password_confirmation, :hashed_password, :group_ids
|
||||||
|
|
||||||
validates_presence_of :login, :firstname, :lastname, :mail, :if => Proc.new { |user| !user.is_a?(AnonymousUser) }
|
validates_presence_of :login, :firstname, :lastname, :mail, :if => Proc.new { |user| !user.is_a?(AnonymousUser) }
|
||||||
validates_uniqueness_of :login, :if => Proc.new { |user| !user.login.blank? }, :case_sensitive => false
|
validates_uniqueness_of :login, :if => Proc.new { |user| !user.login.blank? }
|
||||||
validates_uniqueness_of :mail, :if => Proc.new { |user| !user.mail.blank? }, :case_sensitive => false
|
validates_uniqueness_of :mail, :if => Proc.new { |user| !user.mail.blank? }, :case_sensitive => false
|
||||||
# Login must contain lettres, numbers, underscores only
|
# Login must contain lettres, numbers, underscores only
|
||||||
validates_format_of :login, :with => /^[a-z0-9_\-@\.]*$/i
|
validates_format_of :login, :with => /^[a-z0-9_\-@\.]*$/i
|
||||||
@@ -71,7 +71,7 @@ class User < Principal
|
|||||||
|
|
||||||
def before_save
|
def before_save
|
||||||
# update hashed_password if password was set
|
# update hashed_password if password was set
|
||||||
self.hashed_password = User.hash_password(self.password) if self.password && self.auth_source_id.blank?
|
self.hashed_password = User.hash_password(self.password) if self.password
|
||||||
end
|
end
|
||||||
|
|
||||||
def reload(*args)
|
def reload(*args)
|
||||||
@@ -79,10 +79,6 @@ class User < Principal
|
|||||||
super
|
super
|
||||||
end
|
end
|
||||||
|
|
||||||
def mail=(arg)
|
|
||||||
write_attribute(:mail, arg.to_s.strip)
|
|
||||||
end
|
|
||||||
|
|
||||||
def identity_url=(url)
|
def identity_url=(url)
|
||||||
if url.blank?
|
if url.blank?
|
||||||
write_attribute(:identity_url, '')
|
write_attribute(:identity_url, '')
|
||||||
@@ -100,7 +96,7 @@ class User < Principal
|
|||||||
def self.try_to_login(login, password)
|
def self.try_to_login(login, password)
|
||||||
# Make sure no one can sign in with an empty password
|
# Make sure no one can sign in with an empty password
|
||||||
return nil if password.to_s.empty?
|
return nil if password.to_s.empty?
|
||||||
user = find_by_login(login)
|
user = find(:first, :conditions => ["login=?", login])
|
||||||
if user
|
if user
|
||||||
# user is already in local database
|
# user is already in local database
|
||||||
return nil if !user.active?
|
return nil if !user.active?
|
||||||
@@ -115,12 +111,12 @@ class User < Principal
|
|||||||
# user is not yet registered, try to authenticate with available sources
|
# user is not yet registered, try to authenticate with available sources
|
||||||
attrs = AuthSource.authenticate(login, password)
|
attrs = AuthSource.authenticate(login, password)
|
||||||
if attrs
|
if attrs
|
||||||
user = new(attrs)
|
user = new(*attrs)
|
||||||
user.login = login
|
user.login = login
|
||||||
user.language = Setting.default_language
|
user.language = Setting.default_language
|
||||||
if user.save
|
if user.save
|
||||||
user.reload
|
user.reload
|
||||||
logger.info("User '#{user.login}' created from external auth source: #{user.auth_source.type} - #{user.auth_source.name}") if logger && user.auth_source
|
logger.info("User '#{user.login}' created from the LDAP") if logger
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -164,42 +160,8 @@ class User < Principal
|
|||||||
self.status == STATUS_LOCKED
|
self.status == STATUS_LOCKED
|
||||||
end
|
end
|
||||||
|
|
||||||
def activate
|
|
||||||
self.status = STATUS_ACTIVE
|
|
||||||
end
|
|
||||||
|
|
||||||
def register
|
|
||||||
self.status = STATUS_REGISTERED
|
|
||||||
end
|
|
||||||
|
|
||||||
def lock
|
|
||||||
self.status = STATUS_LOCKED
|
|
||||||
end
|
|
||||||
|
|
||||||
def activate!
|
|
||||||
update_attribute(:status, STATUS_ACTIVE)
|
|
||||||
end
|
|
||||||
|
|
||||||
def register!
|
|
||||||
update_attribute(:status, STATUS_REGISTERED)
|
|
||||||
end
|
|
||||||
|
|
||||||
def lock!
|
|
||||||
update_attribute(:status, STATUS_LOCKED)
|
|
||||||
end
|
|
||||||
|
|
||||||
def check_password?(clear_password)
|
def check_password?(clear_password)
|
||||||
if auth_source_id.present?
|
User.hash_password(clear_password) == self.hashed_password
|
||||||
auth_source.authenticate(self.login, clear_password)
|
|
||||||
else
|
|
||||||
User.hash_password(clear_password) == self.hashed_password
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Does the backend storage allow this user to change their password?
|
|
||||||
def change_password_allowed?
|
|
||||||
return true if auth_source_id.blank?
|
|
||||||
return auth_source.allow_password_changes?
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Generate and set a random password. Useful for automated user creation
|
# Generate and set a random password. Useful for automated user creation
|
||||||
@@ -250,18 +212,6 @@ class User < Principal
|
|||||||
notified_projects_ids
|
notified_projects_ids
|
||||||
end
|
end
|
||||||
|
|
||||||
# Find a user account by matching the exact login and then a case-insensitive
|
|
||||||
# version. Exact matches will be given priority.
|
|
||||||
def self.find_by_login(login)
|
|
||||||
# force string comparison to be case sensitive on MySQL
|
|
||||||
type_cast = (ActiveRecord::Base.connection.adapter_name == 'MySQL') ? 'BINARY' : ''
|
|
||||||
|
|
||||||
# First look for an exact match
|
|
||||||
user = first(:conditions => ["#{type_cast} login = ?", login])
|
|
||||||
# Fail over to case-insensitive if none was found
|
|
||||||
user ||= first(:conditions => ["#{type_cast} LOWER(login) = ?", login.to_s.downcase])
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.find_by_rss_key(key)
|
def self.find_by_rss_key(key)
|
||||||
token = Token.find_by_value(key)
|
token = Token.find_by_value(key)
|
||||||
token && token.user.active? ? token.user : nil
|
token && token.user.active? ? token.user : nil
|
||||||
@@ -353,12 +303,6 @@ class User < Principal
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Is the user allowed to do the specified action on any project?
|
|
||||||
# See allowed_to? for the actions and valid options.
|
|
||||||
def allowed_to_globally?(action, options)
|
|
||||||
allowed_to?(action, nil, options.reverse_merge(:global => true))
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.current=(user)
|
def self.current=(user)
|
||||||
@current_user = user
|
@current_user = user
|
||||||
end
|
end
|
||||||
|
|||||||
+13
-22
@@ -1,5 +1,5 @@
|
|||||||
# Redmine - project management software
|
# redMine - project management software
|
||||||
# Copyright (C) 2006-2010 Jean-Philippe Lang
|
# Copyright (C) 2006 Jean-Philippe Lang
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or
|
# This program is free software; you can redistribute it and/or
|
||||||
# modify it under the terms of the GNU General Public License
|
# modify it under the terms of the GNU General Public License
|
||||||
@@ -16,9 +16,10 @@
|
|||||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
|
||||||
class Version < ActiveRecord::Base
|
class Version < ActiveRecord::Base
|
||||||
|
before_destroy :check_integrity
|
||||||
after_update :update_issues_from_sharing_change
|
after_update :update_issues_from_sharing_change
|
||||||
belongs_to :project
|
belongs_to :project
|
||||||
has_many :fixed_issues, :class_name => 'Issue', :foreign_key => 'fixed_version_id', :dependent => :nullify
|
has_many :fixed_issues, :class_name => 'Issue', :foreign_key => 'fixed_version_id'
|
||||||
acts_as_customizable
|
acts_as_customizable
|
||||||
acts_as_attachable :view_permission => :view_files,
|
acts_as_attachable :view_permission => :view_files,
|
||||||
:delete_permission => :manage_files
|
:delete_permission => :manage_files
|
||||||
@@ -51,9 +52,8 @@ class Version < ActiveRecord::Base
|
|||||||
end
|
end
|
||||||
|
|
||||||
# Returns the total estimated time for this version
|
# Returns the total estimated time for this version
|
||||||
# (sum of leaves estimated_hours)
|
|
||||||
def estimated_hours
|
def estimated_hours
|
||||||
@estimated_hours ||= fixed_issues.leaves.sum(:estimated_hours).to_f
|
@estimated_hours ||= fixed_issues.sum(:estimated_hours).to_f
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns the total reported time for this version
|
# Returns the total reported time for this version
|
||||||
@@ -124,25 +124,13 @@ class Version < ActiveRecord::Base
|
|||||||
|
|
||||||
def to_s; name end
|
def to_s; name end
|
||||||
|
|
||||||
# Versions are sorted by effective_date and "Project Name - Version name"
|
# Versions are sorted by effective_date and name
|
||||||
# Those with no effective_date are at the end, sorted by "Project Name - Version name"
|
# Those with no effective_date are at the end, sorted by name
|
||||||
def <=>(version)
|
def <=>(version)
|
||||||
if self.effective_date
|
if self.effective_date
|
||||||
if version.effective_date
|
version.effective_date ? (self.effective_date == version.effective_date ? self.name <=> version.name : self.effective_date <=> version.effective_date) : -1
|
||||||
if self.effective_date == version.effective_date
|
|
||||||
"#{self.project.name} - #{self.name}" <=> "#{version.project.name} - #{version.name}"
|
|
||||||
else
|
|
||||||
self.effective_date <=> version.effective_date
|
|
||||||
end
|
|
||||||
else
|
|
||||||
-1
|
|
||||||
end
|
|
||||||
else
|
else
|
||||||
if version.effective_date
|
version.effective_date ? 1 : (self.name <=> version.name)
|
||||||
1
|
|
||||||
else
|
|
||||||
"#{self.project.name} - #{self.name}" <=> "#{version.project.name} - #{version.name}"
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -167,7 +155,10 @@ class Version < ActiveRecord::Base
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
def check_integrity
|
||||||
|
raise "Can't delete version" if self.fixed_issues.find(:first)
|
||||||
|
end
|
||||||
|
|
||||||
# Update the issue's fixed versions. Used if a version's sharing changes.
|
# Update the issue's fixed versions. Used if a version's sharing changes.
|
||||||
def update_issues_from_sharing_change
|
def update_issues_from_sharing_change
|
||||||
|
|||||||
@@ -29,12 +29,6 @@ class Wiki < ActiveRecord::Base
|
|||||||
!user.nil? && user.allowed_to?(:view_wiki_pages, project)
|
!user.nil? && user.allowed_to?(:view_wiki_pages, project)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns the wiki page that acts as the sidebar content
|
|
||||||
# or nil if no such page exists
|
|
||||||
def sidebar
|
|
||||||
@sidebar ||= find_page('Sidebar', :with_redirect => false)
|
|
||||||
end
|
|
||||||
|
|
||||||
# find the page with the given title
|
# find the page with the given title
|
||||||
# if page doesn't exist, return a new page
|
# if page doesn't exist, return a new page
|
||||||
def find_or_new_page(title)
|
def find_or_new_page(title)
|
||||||
|
|||||||
@@ -34,10 +34,6 @@ class WikiContent < ActiveRecord::Base
|
|||||||
page.project
|
page.project
|
||||||
end
|
end
|
||||||
|
|
||||||
def attachments
|
|
||||||
page.nil? ? [] : page.attachments
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns the mail adresses of users that should be notified
|
# Returns the mail adresses of users that should be notified
|
||||||
def recipients
|
def recipients
|
||||||
notified = project.notified_users
|
notified = project.notified_users
|
||||||
|
|||||||
@@ -41,15 +41,6 @@ class WikiPage < ActiveRecord::Base
|
|||||||
validates_uniqueness_of :title, :scope => :wiki_id, :case_sensitive => false
|
validates_uniqueness_of :title, :scope => :wiki_id, :case_sensitive => false
|
||||||
validates_associated :content
|
validates_associated :content
|
||||||
|
|
||||||
# Wiki pages that are protected by default
|
|
||||||
DEFAULT_PROTECTED_PAGES = %w(sidebar)
|
|
||||||
|
|
||||||
def after_initialize
|
|
||||||
if new_record? && DEFAULT_PROTECTED_PAGES.include?(title.to_s.downcase)
|
|
||||||
self.protected = true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def visible?(user=User.current)
|
def visible?(user=User.current)
|
||||||
!user.nil? && user.allowed_to?(:view_wiki_pages, project)
|
!user.nil? && user.allowed_to?(:view_wiki_pages, project)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class Workflow < ActiveRecord::Base
|
|||||||
trackers.each do |tracker|
|
trackers.each do |tracker|
|
||||||
t = []
|
t = []
|
||||||
roles.each do |role|
|
roles.each do |role|
|
||||||
row = counts.detect {|c| c['role_id'].to_s == role.id.to_s && c['tracker_id'].to_s == tracker.id.to_s}
|
row = counts.detect {|c| c['role_id'] == role.id.to_s && c['tracker_id'] == tracker.id.to_s}
|
||||||
t << [role, (row.nil? ? 0 : row['c'].to_i)]
|
t << [role, (row.nil? ? 0 : row['c'].to_i)]
|
||||||
end
|
end
|
||||||
result << [tracker, t]
|
result << [tracker, t]
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
<%= call_hook :view_account_login_top %>
|
|
||||||
<div id="login-form">
|
<div id="login-form">
|
||||||
<% form_tag({:action=> "login"}) do %>
|
<% form_tag({:action=> "login"}) do %>
|
||||||
<%= back_url_hidden_field_tag %>
|
<%= back_url_hidden_field_tag %>
|
||||||
@@ -39,4 +38,3 @@
|
|||||||
<%= javascript_tag "Form.Element.focus('username');" %>
|
<%= javascript_tag "Form.Element.focus('username');" %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<%= call_hook :view_account_login_bottom %>
|
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
<div class="box tabular">
|
<div class="box tabular">
|
||||||
<p><label for="new_password"><%=l(:field_new_password)%> <span class="required">*</span></label>
|
<p><label for="new_password"><%=l(:field_new_password)%> <span class="required">*</span></label>
|
||||||
<%= password_field_tag 'new_password', nil, :size => 25 %><br />
|
<%= password_field_tag 'new_password', nil, :size => 25 %><br />
|
||||||
<em><%= l(:text_caracters_minimum, :count => Setting.password_min_length) %></em></p>
|
<em><%= l(:text_caracters_minimum, 4) %></em></p>
|
||||||
|
|
||||||
<p><label for="new_password_confirmation"><%=l(:field_password_confirmation)%> <span class="required">*</span></label>
|
<p><label for="new_password_confirmation"><%=l(:field_password_confirmation)%> <span class="required">*</span></label>
|
||||||
<%= password_field_tag 'new_password_confirmation', nil, :size => 25 %></p>
|
<%= password_field_tag 'new_password_confirmation', nil, :size => 25 %></p>
|
||||||
|
|||||||
@@ -1,5 +1,19 @@
|
|||||||
<div id="admin-menu">
|
<div id="admin-menu">
|
||||||
<ul>
|
<ul>
|
||||||
<%= render_menu :admin_menu %>
|
<li><%= link_to l(:label_project_plural), {:controller => 'admin', :action => 'projects'}, :class => 'projects' %></li>
|
||||||
</ul>
|
<li><%= link_to l(:label_user_plural), {:controller => 'users'}, :class => 'users' %></li>
|
||||||
|
<li><%= link_to l(:label_group_plural), {:controller => 'groups'}, :class => 'groups' %></li>
|
||||||
|
<li><%= link_to l(:label_role_and_permissions), {:controller => 'roles'}, :class => 'roles' %></li>
|
||||||
|
<li><%= link_to l(:label_tracker_plural), {:controller => 'trackers'}, :class => 'trackers' %></li>
|
||||||
|
<li><%= link_to l(:label_issue_status_plural), {:controller => 'issue_statuses'}, :class => 'issue_statuses' %></li>
|
||||||
|
<li><%= link_to l(:label_workflow), {:controller => 'workflows', :action => 'edit'}, :class => 'workflows' %></li>
|
||||||
|
<li><%= link_to l(:label_custom_field_plural), {:controller => 'custom_fields'}, :class => 'custom_fields' %></li>
|
||||||
|
<li><%= link_to l(:label_enumerations), {:controller => 'enumerations'}, :class => 'enumerations' %></li>
|
||||||
|
<li><%= link_to l(:label_settings), {:controller => 'settings'}, :class => 'settings' %></li>
|
||||||
|
<% menu_items_for(:admin_menu) do |item| -%>
|
||||||
|
<li><%= link_to h(item.caption), item.url, item.html_options %></li>
|
||||||
|
<% end -%>
|
||||||
|
<li><%= link_to l(:label_plugins), {:controller => 'admin', :action => 'plugins'}, :class => 'plugins' %></li>
|
||||||
|
<li><%= link_to l(:label_information_plural), {:controller => 'admin', :action => 'info'}, :class => 'info' %></li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<div class="contextual">
|
<div class="contextual">
|
||||||
<%= link_to l(:label_project_new), {:controller => 'projects', :action => 'new'}, :class => 'icon icon-add' %>
|
<%= link_to l(:label_project_new), {:controller => 'projects', :action => 'add'}, :class => 'icon icon-add' %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2><%=l(:label_project_plural)%></h2>
|
<h2><%=l(:label_project_plural)%></h2>
|
||||||
@@ -15,7 +15,6 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
|
|
||||||
<div class="autoscroll">
|
|
||||||
<table class="list">
|
<table class="list">
|
||||||
<thead><tr>
|
<thead><tr>
|
||||||
<th><%=l(:label_project)%></th>
|
<th><%=l(:label_project)%></th>
|
||||||
@@ -25,11 +24,11 @@
|
|||||||
<th></th>
|
<th></th>
|
||||||
</tr></thead>
|
</tr></thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<% project_tree(@projects) do |project, level| %>
|
<% for project in @projects %>
|
||||||
<tr class="<%= cycle("odd", "even") %> <%= project.css_classes %> <%= level > 0 ? "idnt idnt-#{level}" : nil %>">
|
<tr class="<%= cycle("odd", "even") %> <%= css_project_classes(project) %>">
|
||||||
<td class="name"><%= link_to_project(project, :action => 'settings') %></td>
|
<td class="name" style="padding-left: <%= project.level %>em;"><%= project.active? ? link_to(h(project.name), :controller => 'projects', :action => 'settings', :id => project) : h(project.name) %></td>
|
||||||
<td><%= textilizable project.short_description, :project => project %></td>
|
<td><%= textilizable project.short_description, :project => project %></td>
|
||||||
<td align="center"><%= checked_image project.is_public? %></td>
|
<td align="center"><%= image_tag 'true.png' if project.is_public? %></td>
|
||||||
<td align="center"><%= format_date(project.created_on) %></td>
|
<td align="center"><%= format_date(project.created_on) %></td>
|
||||||
<td class="buttons">
|
<td class="buttons">
|
||||||
<%= link_to(l(:button_archive), { :controller => 'projects', :action => 'archive', :id => project, :status => params[:status] }, :confirm => l(:text_are_you_sure), :method => :post, :class => 'icon icon-lock') if project.active? %>
|
<%= link_to(l(:button_archive), { :controller => 'projects', :action => 'archive', :id => project, :status => params[:status] }, :confirm => l(:text_are_you_sure), :method => :post, :class => 'icon icon-lock') if project.active? %>
|
||||||
@@ -41,6 +40,5 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
|
||||||
|
|
||||||
<% html_title(l(:label_project_plural)) -%>
|
<% html_title(l(:label_project_plural)) -%>
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
<span id="attachments_fields">
|
<span id="attachments_fields">
|
||||||
<%= file_field_tag 'attachments[1][file]', :size => 30, :id => nil -%><label class="inline"><span id="attachment_description_label_content"><%= l(:label_optional_description) %></span><%= text_field_tag 'attachments[1][description]', '', :size => 60, :id => nil %>
|
<%= file_field_tag 'attachments[1][file]', :size => 30, :id => nil -%>
|
||||||
</label>
|
<%= text_field_tag 'attachments[1][description]', '', :size => 60, :id => nil %>
|
||||||
|
<em><%= l(:label_optional_description) %></em>
|
||||||
</span>
|
</span>
|
||||||
<br />
|
<br />
|
||||||
<small><%= link_to l(:label_add_another_file), '#', :onclick => 'addFileField(); return false;' %>
|
<small><%= link_to l(:label_add_another_file), '#', :onclick => 'addFileField(); return false;' %>
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
<%= error_messages_for 'auth_source' %>
|
|
||||||
|
|
||||||
<div class="box">
|
|
||||||
<!--[form:auth_source]-->
|
|
||||||
<p><label for="auth_source_name"><%=l(:field_name)%> <span class="required">*</span></label>
|
|
||||||
<%= text_field 'auth_source', 'name' %></p>
|
|
||||||
|
|
||||||
<p><label for="auth_source_onthefly_register"><%=l(:field_onthefly)%></label>
|
|
||||||
<%= check_box 'auth_source', 'onthefly_register' %></p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!--[eoform:auth_source]-->
|
|
||||||
|
|
||||||
File renamed without changes.
File renamed without changes.
Loaded 100 of 687 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user