Compare commits

..

21 Commits
2.0.3 ... 1.3.0

Author SHA1 Message Date
Jean-Philippe Lang
3351ef22b2 tagged version 1.3.0
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/tags/1.3.0@8180 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-12-10 14:50:59 +00:00
Jean-Philippe Lang
e56dd42e64 CHANGELOG updated.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@8176 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-12-10 14:48:48 +00:00
Jean-Philippe Lang
a0c34ab916 Merged r8166 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@8174 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-12-10 14:41:48 +00:00
Jean-Philippe Lang
c8a6b01e9f Backported r8168 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@8173 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-12-10 14:39:26 +00:00
Jean-Philippe Lang
ff02412e75 Merged r8130 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@8161 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-12-10 12:20:47 +00:00
Jean-Philippe Lang
166c3c15dd Merged r8112 from trunk (#9748).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@8160 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-12-10 12:08:55 +00:00
Jean-Philippe Lang
65a7c7e96c Merged r8113 from trunk (#9738).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@8159 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-12-10 12:07:13 +00:00
Jean-Philippe Lang
9a8cf5fa60 Merged r8103 from trunk (#9737).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@8114 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-12-07 21:47:24 +00:00
Toshi MARUYAMA
bf0008cd4e Merged r8106 from trunk
remove trailing white-spaces from calendar-hr.js

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@8110 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-12-07 01:25:19 +00:00
Toshi MARUYAMA
b849b9a47e Merged r8105 from trunk
convert calendar-hr.js (Croatian) from Windows-1250 to UTF-8.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@8109 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-12-07 01:24:36 +00:00
Toshi MARUYAMA
011d9587a5 Merged r8099 from trunk
pdf: move note number to the head of line for single issue's PDF.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@8100 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-12-06 03:49:39 +00:00
Jean-Philippe Lang
b7d813047b Merged r8096 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@8097 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-12-05 19:55:23 +00:00
Toshi MARUYAMA
08784f551e Merged r8090 from trunk
pdf: add note number for single issue's PDF.

Contributed by Akiko T.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@8091 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-12-05 07:39:55 +00:00
Jean-Philippe Lang
faad143c03 Merged r8030 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@8032 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-12-02 22:31:14 +00:00
Toshi MARUYAMA
08b6d7b654 Merged r8022 from trunk
scm: git: mercurial: not show revision graph in sub directory.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@8023 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-12-01 23:46:26 +00:00
Toshi MARUYAMA
18a30fd942 Merged r8015 from trunk
remove trailing white-spaces except SQL from extra/svn/Redmine.pm.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@8016 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-12-01 05:31:19 +00:00
Toshi MARUYAMA
2813b93370 Merged r8013 from trunk
Japanese translation updated by Go MAEDA.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@8014 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-12-01 04:13:04 +00:00
Toshi MARUYAMA
930cf0b487 Merged r8002 from trunk
Bulgarian translation updated by Ivan Cenov.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@8004 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-11-30 08:25:27 +00:00
Toshi MARUYAMA
27ac3193e3 Merged r8001 from trunk
pdf: fix textilized outputs of coderay line numbers.

Contributed by Jun NAITOH.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@8003 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-11-30 08:25:01 +00:00
Jean-Philippe Lang
80f5d3d041 Set version to 1.3.0.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@7994 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-11-29 21:05:08 +00:00
Jean-Philippe Lang
a3f8fbaee6 1.3-stable branch added.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.3-stable@7992 e93f8b46-1217-0410-a6f0-8f06a7374b81
2011-11-29 21:02:26 +00:00
1362 changed files with 35675 additions and 31230 deletions

2
.gitignore vendored
View File

@@ -5,7 +5,6 @@
/config/database.yml
/config/email.yml
/config/initializers/session_store.rb
/config/initializers/secret_token.rb
/coverage
/db/*.db
/db/*.sqlite3
@@ -22,7 +21,6 @@
/tmp/sessions/*
/tmp/sockets/*
/tmp/test/*
/vendor/cache
/vendor/rails
*.rbc

View File

@@ -7,7 +7,6 @@ config/configuration.yml
config/database.yml
config/email.yml
config/initializers/session_store.rb
config/initializers/secret_token.rb
coverage
db/*.db
db/*.sqlite3
@@ -24,7 +23,6 @@ tmp/cache/*
tmp/sessions/*
tmp/sockets/*
tmp/test/*
vendor/cache
vendor/rails
*.rbc

90
Gemfile
View File

@@ -1,90 +0,0 @@
source 'http://rubygems.org'
gem 'rails', '3.2.6'
gem 'prototype-rails', '3.2.1'
gem "i18n", "~> 0.6.0"
gem "coderay", "~> 1.0.6"
gem "fastercsv", "~> 1.5.0", :platforms => [:mri_18, :mingw_18, :jruby]
gem "builder"
# Optional gem for LDAP authentication
group :ldap do
gem "net-ldap", "~> 0.3.1"
end
# Optional gem for OpenID authentication
group :openid do
gem "ruby-openid", "~> 2.1.4", :require => "openid"
gem "rack-openid"
end
# Optional gem for exporting the gantt to a PNG file, not supported with jruby
platforms :mri, :mingw do
group :rmagick do
# RMagick 2 supports ruby 1.9
# RMagick 1 would be fine for ruby 1.8 but Bundler does not support
# different requirements for the same gem on different platforms
gem "rmagick", ">= 2.0.0"
end
end
# Database gems
platforms :mri, :mingw do
group :postgresql do
gem "pg", ">= 0.11.0"
end
group :sqlite do
gem "sqlite3"
end
end
platforms :mri_18, :mingw_18 do
group :mysql do
gem "mysql"
end
end
platforms :mri_19, :mingw_19 do
group :mysql do
gem "mysql2", "~> 0.3.11"
end
end
platforms :jruby do
gem "jruby-openssl"
group :mysql do
gem "activerecord-jdbcmysql-adapter"
end
group :postgresql do
gem "activerecord-jdbcpostgresql-adapter"
end
group :sqlite do
gem "activerecord-jdbcsqlite3-adapter"
end
end
group :development do
gem "rdoc", ">= 2.4.2"
gem "yard"
end
group :test do
gem "shoulda", "~> 2.11"
gem "mocha"
end
local_gemfile = File.join(File.dirname(__FILE__), "Gemfile.local")
if File.exists?(local_gemfile)
puts "Loading Gemfile.local ..." if $DEBUG # `ruby -d` or `bundle -v`
instance_eval File.read(local_gemfile)
end
# Load plugins' Gemfiles
Dir.glob File.expand_path("../plugins/*/Gemfile", __FILE__) do |file|
puts "Loading #{file} ..." if $DEBUG # `ruby -d` or `bundle -v`
instance_eval File.read(file)
end

View File

@@ -1,7 +1,15 @@
#!/usr/bin/env rake
# Add your own tasks in files placed in lib/tasks ending in .rake,
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
# for example lib/tasks/switchtower.rake, and they will automatically be available to Rake.
require File.expand_path('../config/application', __FILE__)
require(File.join(File.dirname(__FILE__), 'config', 'boot'))
RedmineApp::Application.load_tasks
require 'rake'
require 'rake/testtask'
begin
require 'rdoc/task'
rescue LoadError
# RDoc is not available
end
require 'tasks/rails'

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -29,9 +29,6 @@ class AccountController < ApplicationController
else
authenticate_user
end
rescue AuthSourceException => e
logger.error "An error occured when authenticating #{params[:username]}: #{e.message}"
render_error :message => e.message
end
# Log out current user and redirect to welcome page
@@ -68,7 +65,7 @@ class AccountController < ApplicationController
# create a new token for password recovery
token = Token.new(:user => user, :action => "recovery")
if token.save
Mailer.lost_password(token).deliver
Mailer.deliver_lost_password(token)
flash[:notice] = l(:notice_account_lost_email_sent)
redirect_to :action => 'login'
return
@@ -84,8 +81,7 @@ class AccountController < ApplicationController
session[:auth_source_registration] = nil
@user = User.new(:language => Setting.default_language)
else
@user = User.new
@user.safe_attributes = params[:user]
@user = User.new(params[:user])
@user.admin = false
@user.register
if session[:auth_source_registration]
@@ -100,7 +96,7 @@ class AccountController < ApplicationController
end
else
@user.login = params[:user][:login]
@user.password, @user.password_confirmation = params[:user][:password], params[:user][:password_confirmation]
@user.password, @user.password_confirmation = params[:password], params[:password_confirmation]
case Setting.self_registration
when '1'
@@ -131,6 +127,14 @@ class AccountController < ApplicationController
private
def logout_user
if User.current.logged?
cookies.delete :autologin
Token.delete_all(["user_id = ? AND action = ?", User.current.id, 'autologin'])
self.logged_user = nil
end
end
def authenticate_user
if Setting.openid? && using_open_id?
open_id_authenticate(params[:openid_url])
@@ -153,7 +157,7 @@ class AccountController < ApplicationController
end
def open_id_authenticate(openid_url)
authenticate_with_open_id(openid_url, :required => [:nickname, :fullname, :email], :return_to => signin_url, :method => :post) do |result, identity_url, registration|
authenticate_with_open_id(openid_url, :required => [:nickname, :fullname, :email], :return_to => signin_url) do |result, identity_url, registration|
if result.successful?
user = User.find_or_initialize_by_identity_url(identity_url)
if user.new_record?
@@ -235,7 +239,7 @@ class AccountController < ApplicationController
def register_by_email_activation(user, &block)
token = Token.new(:user => user, :action => "register")
if user.save and token.save
Mailer.register(token).deliver
Mailer.deliver_register(token)
flash[:notice] = l(:notice_account_register_done)
redirect_to :action => 'login'
else
@@ -265,7 +269,7 @@ class AccountController < ApplicationController
def register_manually_by_administrator(user, &block)
if user.save
# Sends an email to the administrators
Mailer.account_activation_request(user).deliver
Mailer.deliver_account_activation_request(user)
account_pending
else
yield if block_given?

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -43,7 +43,7 @@ class ActivitiesController < ApplicationController
if events.empty? || stale?(:etag => [@activity.scope, @date_to, @date_from, @with_subprojects, @author, events.first, User.current, current_language])
respond_to do |format|
format.html {
@events_by_day = events.group_by {|event| User.current.time_to_date(event.event_datetime)}
@events_by_day = events.group_by(&:event_date)
render :layout => false if request.xhr?
}
format.atom {

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -17,10 +17,6 @@
class AdminController < ApplicationController
layout 'admin'
menu_item :projects, :only => :projects
menu_item :plugins, :only => :plugins
menu_item :info, :only => :info
before_filter :require_admin
helper :sort
include SortHelper
@@ -30,12 +26,14 @@ class AdminController < ApplicationController
end
def projects
@status = params[:status] || 1
scope = Project.status(@status)
scope = scope.like(params[:name]) if params[:name].present?
@projects = scope.all(:order => 'lft')
@status = params[:status] ? params[:status].to_i : 1
c = ARCondition.new(@status == 0 ? "status <> 0" : ["status = ?", @status])
unless params[:name].blank?
name = "%#{params[:name].strip.downcase}%"
c << ["LOWER(identifier) LIKE ? OR LOWER(name) LIKE ?", name, name]
end
@projects = Project.find :all, :order => 'lft',
:conditions => c.conditions
render :action => "projects", :layout => false if request.xhr?
end
@@ -63,7 +61,7 @@ class AdminController < ApplicationController
# Force ActionMailer to raise delivery errors so we can catch it
ActionMailer::Base.raise_delivery_errors = true
begin
@test = Mailer.test_email(User.current).deliver
@test = Mailer.deliver_test(User.current)
flash[:notice] = l(:notice_email_sent, User.current.mail)
rescue Exception => e
flash[:error] = l(:notice_email_error, e.message)
@@ -75,9 +73,11 @@ class AdminController < ApplicationController
def info
@db_adapter_name = ActiveRecord::Base.connection.adapter_name
@checklist = [
[:text_default_administrator_account_changed, User.default_admin_account_changed?],
[:text_default_administrator_account_changed,
User.find(:first,
:conditions => ["login=? and hashed_password=?", 'admin', User.hash_password('admin')]).nil?],
[:text_file_repository_writable, File.writable?(Attachment.storage_path)],
[:text_plugin_assets_writable, File.writable?(Redmine::Plugin.public_directory)],
[:text_plugin_assets_writable, File.writable?(Engines.public_directory)],
[:text_rmagick_available, Object.const_defined?(:Magick)]
]
end

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -22,20 +22,29 @@ class Unauthorized < Exception; end
class ApplicationController < ActionController::Base
include Redmine::I18n
class_attribute :accept_api_auth_actions
class_attribute :accept_rss_auth_actions
class_attribute :model_object
layout 'base'
exempt_from_layout 'builder', 'rsb'
protect_from_forgery
def handle_unverified_request
super
cookies.delete(:autologin)
end
# Remove broken cookie after upgrade from 0.8.x (#4292)
# See https://rails.lighthouseapp.com/projects/8994/tickets/3360
# TODO: remove it when Rails is fixed
before_filter :delete_broken_cookies
def delete_broken_cookies
if cookies['_redmine_session'] && cookies['_redmine_session'] !~ /--/
cookies.delete '_redmine_session'
redirect_to home_path
return false
end
end
before_filter :user_setup, :check_if_login_required, :set_localization
filter_parameter_logging :password
rescue_from ActionController::InvalidAuthenticityToken, :with => :invalid_authenticity_token
rescue_from ::Unauthorized, :with => :deny_access
@@ -44,6 +53,10 @@ class ApplicationController < ActionController::Base
include Redmine::MenuManager::MenuController
helper Redmine::MenuManager::MenuHelper
Redmine::Scm::Base.all.each do |scm|
require_dependency "repository/#{scm.underscore}"
end
def user_setup
# Check the settings cache for each request
Setting.check_cache
@@ -89,15 +102,6 @@ class ApplicationController < ActionController::Base
end
end
# Logs out current user
def logout_user
if User.current.logged?
cookies.delete :autologin
Token.delete_all(["user_id = ? AND action = ?", User.current.id, 'autologin'])
self.logged_user = nil
end
end
# check if login is globally required to access the application
def check_if_login_required
# no check needed if user is already logged in
@@ -205,7 +209,7 @@ class ApplicationController < ActionController::Base
end
def find_model_object
model = self.class.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
@@ -215,7 +219,7 @@ class ApplicationController < ActionController::Base
end
def self.model_object(model)
self.model_object = model
write_inheritable_attribute('model_object', model)
end
# Filter for bulk issue operations
@@ -232,11 +236,20 @@ class ApplicationController < ActionController::Base
render_404
end
# Check if project is unique before bulk operations
def check_project_uniqueness
unless @project
# TODO: let users bulk edit/move/destroy issues from different projects
render_error 'Can not bulk edit/move/destroy issues from different projects'
return false
end
end
# make sure that the user is a member of the project (or admin) if project is private
# used as a before_filter for actions that do not require any particular permission on the project
def check_project_privacy
if @project && @project.active?
if @project.visible?
if @project.is_public? || User.current.member_of?(@project) || User.current.admin?
true
else
deny_access
@@ -270,19 +283,6 @@ class ApplicationController < ActionController::Base
false
end
# Redirects to the request referer if present, redirects to args or call block otherwise.
def redirect_to_referer_or(*args, &block)
redirect_to :back
rescue ::ActionController::RedirectBackError
if args.any?
redirect_to *args
elsif block_given?
block.call
else
raise "#redirect_to_referer_or takes arguments or a block"
end
end
def render_403(options={})
@project = nil
render_error({:message => :notice_not_authorized, :status => 403}.merge(options))
@@ -349,11 +349,23 @@ class ApplicationController < ActionController::Base
:content_type => 'application/atom+xml'
end
# TODO: remove in Redmine 1.4
def self.accept_key_auth(*actions)
ActiveSupport::Deprecation.warn "ApplicationController.accept_key_auth is deprecated and will be removed in Redmine 1.4. Use accept_rss_auth (or accept_api_auth) instead."
accept_rss_auth(*actions)
end
# TODO: remove in Redmine 1.4
def accept_key_auth_actions
ActiveSupport::Deprecation.warn "ApplicationController.accept_key_auth_actions is deprecated and will be removed in Redmine 1.4. Use accept_rss_auth (or accept_api_auth) instead."
self.class.accept_rss_auth
end
def self.accept_rss_auth(*actions)
if actions.any?
self.accept_rss_auth_actions = actions
write_inheritable_attribute('accept_rss_auth_actions', actions)
else
self.accept_rss_auth_actions || []
read_inheritable_attribute('accept_rss_auth_actions') || []
end
end
@@ -363,9 +375,9 @@ class ApplicationController < ActionController::Base
def self.accept_api_auth(*actions)
if actions.any?
self.accept_api_auth_actions = actions
write_inheritable_attribute('accept_api_auth_actions', actions)
else
self.accept_api_auth_actions || []
read_inheritable_attribute('accept_api_auth_actions') || []
end
end
@@ -445,9 +457,9 @@ class ApplicationController < ActionController::Base
# Returns the API key present in the request
def api_key_from_request
if params[:key].present?
params[:key].to_s
params[:key]
elsif request.headers["X-Redmine-API-Key"].present?
request.headers["X-Redmine-API-Key"].to_s
request.headers["X-Redmine-API-Key"]
end
end
@@ -480,18 +492,35 @@ class ApplicationController < ActionController::Base
end
# Renders API response on validation failure
def render_validation_errors(objects)
if objects.is_a?(Array)
@error_messages = objects.map {|object| object.errors.full_messages}.flatten
else
@error_messages = objects.errors.full_messages
end
render :template => 'common/error_messages.api', :status => :unprocessable_entity, :layout => nil
def render_validation_errors(object)
options = { :status => :unprocessable_entity, :layout => false }
options.merge!(case params[:format]
when 'xml'; { :xml => object.errors }
when 'json'; { :json => {'errors' => object.errors} } # ActiveResource client compliance
else
raise "Unknown format #{params[:format]} in #render_validation_errors"
end
)
render options
end
# Overrides #_include_layout? so that #render with no arguments
# Overrides #default_template so that the api template
# is used automatically if it exists
def default_template(action_name = self.action_name)
if api_request?
begin
return self.view_paths.find_template(default_template_name(action_name), 'api')
rescue ::ActionView::MissingTemplate
# the api template was not found
# fallback to the default behaviour
end
end
super
end
# Overrides #pick_layout so that #render with no arguments
# doesn't use the layout for api requests
def _include_layout?(*args)
api_request? ? false : super
def pick_layout(*args)
api_request? ? nil : super
end
end

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -16,12 +16,11 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class AttachmentsController < ApplicationController
before_filter :find_project, :except => :upload
before_filter :file_readable, :read_authorize, :only => [:show, :download]
before_filter :find_project
before_filter :file_readable, :read_authorize, :except => :destroy
before_filter :delete_authorize, :only => :destroy
before_filter :authorize_global, :only => :upload
accept_api_auth :show, :download, :upload
accept_api_auth :show, :download
def show
respond_to do |format|
@@ -30,11 +29,6 @@ class AttachmentsController < ApplicationController
@diff = File.new(@attachment.diskfile, "rb").read
@diff_type = params[:type] || User.current.pref[:diff_type] || 'inline'
@diff_type = 'inline' unless %w(inline sbs).include?(@diff_type)
# Save diff type as user preference
if User.current.logged? && @diff_type != User.current.pref[:diff_type]
User.current.pref[:diff_type] = @diff_type
User.current.preference.save
end
render :action => 'diff'
elsif @attachment.is_text? && @attachment.filesize <= Setting.file_max_size_displayed.to_i.kilobyte
@content = File.new(@attachment.diskfile, "rb").read
@@ -59,36 +53,13 @@ class AttachmentsController < ApplicationController
end
def upload
# Make sure that API users get used to set this content type
# as it won't trigger Rails' automatic parsing of the request body for parameters
unless request.content_type == 'application/octet-stream'
render :nothing => true, :status => 406
return
end
@attachment = Attachment.new(:file => request.raw_post)
@attachment.author = User.current
@attachment.filename = Redmine::Utils.random_hex(16)
if @attachment.save
respond_to do |format|
format.api { render :action => 'upload', :status => :created }
end
else
respond_to do |format|
format.api { render_validation_errors(@attachment) }
end
end
end
verify :method => :delete, :only => :destroy
def destroy
if @attachment.container.respond_to?(:init_journal)
@attachment.container.init_journal(User.current)
end
# Make sure association callbacks are called
@attachment.container.attachments.delete(@attachment)
redirect_to_referer_or project_path(@project)
redirect_to :back
rescue ::ActionController::RedirectBackError
redirect_to :controller => 'projects', :action => 'show', :id => @project
end
private

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -17,31 +17,36 @@
class AuthSourcesController < ApplicationController
layout 'admin'
menu_item :ldap_authentication
before_filter :require_admin
# GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html)
verify :method => :post, :only => [ :destroy, :create, :update ],
:redirect_to => { :template => :index }
def index
@auth_source_pages, @auth_sources = paginate AuthSource, :per_page => 10
@auth_source_pages, @auth_sources = paginate auth_source_class.name.tableize, :per_page => 10
render "auth_sources/index"
end
def new
klass_name = params[:type] || 'AuthSourceLdap'
@auth_source = AuthSource.new_subclass_instance(klass_name, params[:auth_source])
@auth_source = auth_source_class.new
render 'auth_sources/new'
end
def create
@auth_source = AuthSource.new_subclass_instance(params[:type], params[:auth_source])
@auth_source = auth_source_class.new(params[:auth_source])
if @auth_source.save
flash[:notice] = l(:notice_successful_create)
redirect_to :action => 'index'
else
render :action => 'new'
render 'auth_sources/new'
end
end
def edit
@auth_source = AuthSource.find(params[:id])
render 'auth_sources/edit'
end
def update
@@ -50,17 +55,17 @@ class AuthSourcesController < ApplicationController
flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'index'
else
render :action => 'edit'
render 'auth_sources/edit'
end
end
def test_connection
@auth_source = AuthSource.find(params[:id])
@auth_method = AuthSource.find(params[:id])
begin
@auth_source.test_connection
@auth_method.test_connection
flash[:notice] = l(:notice_successful_connection)
rescue Exception => e
flash[:error] = l(:error_unable_to_connect, e.message)
rescue => text
flash[:error] = l(:error_unable_to_connect, text.message)
end
redirect_to :action => 'index'
end
@@ -73,4 +78,10 @@ class AuthSourcesController < ApplicationController
end
redirect_to :action => 'index'
end
protected
def auth_source_class
AuthSource
end
end

View File

@@ -1,20 +1,3 @@
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class AutoCompletesController < ApplicationController
before_filter :find_project

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -17,12 +17,15 @@
class BoardsController < ApplicationController
default_search_scope :messages
before_filter :find_project_by_project_id, :find_board_if_available, :authorize
before_filter :find_project, :find_board_if_available, :authorize
accept_rss_auth :index, :show
helper :messages
include MessagesHelper
helper :sort
include SortHelper
helper :watchers
include WatchersHelper
def index
@boards = @project.boards
@@ -43,11 +46,11 @@ class BoardsController < ApplicationController
@topic_count = @board.topics.count
@topic_pages = Paginator.new self, @topic_count, per_page_option, params['page']
@topics = @board.topics.reorder("#{Message.table_name}.sticky DESC").order(sort_clause).all(
@topics = @board.topics.find :all, :order => ["#{Message.table_name}.sticky DESC", sort_clause].compact.join(', '),
:include => [:author, {:last_reply => :author}],
:limit => @topic_pages.items_per_page,
:offset => @topic_pages.current.offset)
@message = Message.new(:board => @board)
:offset => @topic_pages.current.offset
@message = Message.new
render :action => 'show', :layout => !request.xhr?
}
format.atom {
@@ -59,31 +62,20 @@ class BoardsController < ApplicationController
end
end
def new
@board = @project.boards.build
@board.safe_attributes = params[:board]
end
verify :method => :post, :only => [ :destroy ], :redirect_to => { :action => :index }
def create
@board = @project.boards.build
@board.safe_attributes = params[:board]
if @board.save
def new
@board = Board.new(params[:board])
@board.project = @project
if request.post? && @board.save
flash[:notice] = l(:notice_successful_create)
redirect_to_settings_in_projects
else
render :action => 'new'
end
end
def edit
end
def update
@board.safe_attributes = params[:board]
if @board.save
if request.post? && @board.update_attributes(params[:board])
redirect_to_settings_in_projects
else
render :action => 'edit'
end
end
@@ -97,6 +89,12 @@ private
redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'boards'
end
def find_project
@project = Project.find(params[:project_id])
rescue ActiveRecord::RecordNotFound
render_404
end
def find_board_if_available
@board = @project.boards.find(params[:id]) if params[:id]
rescue ActiveRecord::RecordNotFound

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License

View File

@@ -1,20 +1,3 @@
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class CommentsController < ApplicationController
default_search_scope :news
model_object News
@@ -22,11 +5,9 @@ class CommentsController < ApplicationController
before_filter :find_project_from_association
before_filter :authorize
verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed }
def create
raise Unauthorized unless @news.commentable?
@comment = Comment.new
@comment.safe_attributes = params[:comment]
@comment = Comment.new(params[:comment])
@comment.author = User.current
if @news.comments << @comment
flash[:notice] = l(:label_comment_added)
@@ -35,6 +16,7 @@ class CommentsController < ApplicationController
redirect_to :controller => 'news', :action => 'show', :id => @news
end
verify :method => :delete, :only => :destroy, :render => {:nothing => true, :status => :method_not_allowed }
def destroy
@news.comments.find(params[:comment_id]).destroy
redirect_to :controller => 'news', :action => 'show', :id => @news
@@ -50,4 +32,5 @@ class CommentsController < ApplicationController
@comment = nil
@news
end
end

View File

@@ -1,31 +1,20 @@
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class ContextMenusController < ApplicationController
helper :watchers
helper :issues
def issues
@issues = Issue.visible.all(:conditions => {:id => params[:ids]}, :include => :project)
if (@issues.size == 1)
@issue = @issues.first
@allowed_statuses = @issue.new_statuses_allowed_to(User.current)
else
@allowed_statuses = @issues.map do |i|
i.new_statuses_allowed_to(User.current)
end.inject do |memo,s|
memo & s
end
end
@allowed_statuses = @issues.map(&:new_statuses_allowed_to).reduce(:&)
@projects = @issues.collect(&:project).compact.uniq
@project = @projects.first if @projects.size == 1
@@ -45,26 +34,14 @@ class ContextMenusController < ApplicationController
@trackers = @project.trackers
else
#when multiple projects, we only keep the intersection of each set
@assignables = @projects.map(&:assignable_users).reduce(:&)
@trackers = @projects.map(&:trackers).reduce(:&)
@assignables = @projects.map(&:assignable_users).inject{|memo,a| memo & a}
@trackers = @projects.map(&:trackers).inject{|memo,t| memo & t}
end
@priorities = IssuePriority.active.reverse
@statuses = IssueStatus.find(:all, :order => 'position')
@back = back_url
@options_by_custom_field = {}
if @can[:edit]
custom_fields = @issues.map(&:available_custom_fields).reduce(:&).select do |f|
%w(bool list user version).include?(f.field_format) && !f.multiple?
end
custom_fields.each do |field|
values = field.possible_values_options(@projects)
if values.any?
@options_by_custom_field[field] = values
end
end
end
render :layout => false
end

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -19,8 +19,6 @@ class CustomFieldsController < ApplicationController
layout 'admin'
before_filter :require_admin
before_filter :build_new_custom_field, :only => [:new, :create]
before_filter :find_custom_field, :only => [:edit, :update, :destroy]
def index
@custom_fields_by_type = CustomField.find(:all).group_by {|f| f.class.name }
@@ -28,51 +26,39 @@ class CustomFieldsController < ApplicationController
end
def new
end
@custom_field = begin
if params[:type].to_s.match(/.+CustomField$/)
params[:type].to_s.constantize.new(params[:custom_field])
end
rescue
end
(redirect_to(:action => 'index'); return) unless @custom_field.is_a?(CustomField)
def create
if request.post? and @custom_field.save
flash[:notice] = l(:notice_successful_create)
call_hook(:controller_custom_fields_new_after_save, :params => params, :custom_field => @custom_field)
redirect_to :action => 'index', :tab => @custom_field.class.name
else
render :action => 'new'
@trackers = Tracker.find(:all, :order => 'position')
end
end
def edit
end
def update
if request.put? and @custom_field.update_attributes(params[:custom_field])
@custom_field = CustomField.find(params[:id])
if request.post? and @custom_field.update_attributes(params[:custom_field])
flash[:notice] = l(:notice_successful_update)
call_hook(:controller_custom_fields_edit_after_save, :params => params, :custom_field => @custom_field)
redirect_to :action => 'index', :tab => @custom_field.class.name
else
render :action => 'edit'
@trackers = Tracker.find(:all, :order => 'position')
end
end
def destroy
@custom_field.destroy
@custom_field = CustomField.find(params[:id]).destroy
redirect_to :action => 'index', :tab => @custom_field.class.name
rescue
flash[:error] = l(:error_can_not_delete_custom_field)
redirect_to :action => 'index'
end
private
def build_new_custom_field
@custom_field = CustomField.new_subclass_instance(params[:type], params[:custom_field])
if @custom_field.nil?
render_404
end
end
def find_custom_field
@custom_field = CustomField.find(params[:id])
rescue ActiveRecord::RecordNotFound
render_404
end
end

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -18,9 +18,9 @@
class DocumentsController < ApplicationController
default_search_scope :documents
model_object Document
before_filter :find_project_by_project_id, :only => [:index, :new, :create]
before_filter :find_model_object, :except => [:index, :new, :create]
before_filter :find_project_from_association, :except => [:index, :new, :create]
before_filter :find_project, :only => [:index, :new]
before_filter :find_model_object, :except => [:index, :new]
before_filter :find_project_from_association, :except => [:index, :new]
before_filter :authorize
helper :attachments
@@ -47,38 +47,25 @@ class DocumentsController < ApplicationController
end
def new
@document = @project.documents.build
@document.safe_attributes = params[:document]
end
def create
@document = @project.documents.build
@document.safe_attributes = params[:document]
@document.save_attachments(params[:attachments])
if @document.save
@document = @project.documents.build(params[:document])
if request.post? and @document.save
attachments = Attachment.attach_files(@document, params[:attachments])
render_attachment_warning_if_needed(@document)
flash[:notice] = l(:notice_successful_create)
redirect_to :action => 'index', :project_id => @project
else
render :action => 'new'
end
end
def edit
end
def update
@document.safe_attributes = params[:document]
if request.put? and @document.save
@categories = DocumentCategory.active #TODO: use it in the views
if request.post? and @document.update_attributes(params[:document])
flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'show', :id => @document
else
render :action => 'edit'
end
end
def destroy
@document.destroy if request.delete?
@document.destroy
redirect_to :controller => 'documents', :action => 'index', :project_id => @project
end
@@ -86,9 +73,14 @@ class DocumentsController < ApplicationController
attachments = Attachment.attach_files(@document, params[:attachments])
render_attachment_warning_if_needed(@document)
if attachments.present? && attachments[:files].present? && Setting.notified_events.include?('document_added')
Mailer.attachments_added(attachments[:files]).deliver
end
Mailer.deliver_attachments_added(attachments[:files]) if attachments.present? && attachments[:files].present? && Setting.notified_events.include?('document_added')
redirect_to :action => 'show', :id => @document
end
private
def find_project
@project = Project.find(params[:project_id])
rescue ActiveRecord::RecordNotFound
render_404
end
end

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -19,19 +19,28 @@ class EnumerationsController < ApplicationController
layout 'admin'
before_filter :require_admin
before_filter :build_new_enumeration, :only => [:new, :create]
before_filter :find_enumeration, :only => [:edit, :update, :destroy]
helper :custom_fields
include CustomFieldsHelper
def index
end
verify :method => :post, :only => [ :destroy, :create, :update ],
:redirect_to => { :action => :index }
def new
begin
@enumeration = params[:type].constantize.new
rescue NameError
@enumeration = Enumeration.new
end
end
def create
if request.post? && @enumeration.save
@enumeration = Enumeration.new(params[:enumeration])
@enumeration.type = params[:enumeration][:type]
if @enumeration.save
flash[:notice] = l(:notice_successful_create)
redirect_to :action => 'index', :type => @enumeration.type
else
@@ -40,10 +49,13 @@ class EnumerationsController < ApplicationController
end
def edit
@enumeration = Enumeration.find(params[:id])
end
def update
if request.put? && @enumeration.update_attributes(params[:enumeration])
@enumeration = Enumeration.find(params[:id])
@enumeration.type = params[:enumeration][:type] if params[:enumeration][:type]
if @enumeration.update_attributes(params[:enumeration])
flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'index', :type => @enumeration.type
else
@@ -52,6 +64,7 @@ class EnumerationsController < ApplicationController
end
def destroy
@enumeration = Enumeration.find(params[:id])
if !@enumeration.in_use?
# No associated objects
@enumeration.destroy
@@ -64,22 +77,9 @@ class EnumerationsController < ApplicationController
return
end
end
@enumerations = @enumeration.class.all - [@enumeration]
end
private
def build_new_enumeration
class_name = params[:enumeration] && params[:enumeration][:type] || params[:type]
@enumeration = Enumeration.new_subclass_instance(class_name, params[:enumeration])
if @enumeration.nil?
render_404
end
end
def find_enumeration
@enumeration = Enumeration.find(params[:id])
rescue ActiveRecord::RecordNotFound
render_404
@enumerations = @enumeration.class.find(:all) - [@enumeration]
#rescue
# flash[:error] = 'Unable to delete enumeration'
# redirect_to :action => 'index'
end
end

View File

@@ -1,20 +1,3 @@
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class FilesController < ApplicationController
menu_item :files
@@ -46,7 +29,7 @@ class FilesController < ApplicationController
render_attachment_warning_if_needed(container)
if !attachments.empty? && !attachments[:files].blank? && Setting.notified_events.include?('file_added')
Mailer.attachments_added(attachments[:files]).deliver
Mailer.deliver_attachments_added(attachments[:files])
end
redirect_to project_files_path(@project)
end

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -20,7 +20,7 @@ class IssueCategoriesController < ApplicationController
model_object IssueCategory
before_filter :find_model_object, :except => [:index, :new, :create]
before_filter :find_project_from_association, :except => [:index, :new, :create]
before_filter :find_project_by_project_id, :only => [:index, :new, :create]
before_filter :find_project, :only => [:index, :new, :create]
before_filter :authorize
accept_api_auth :index, :show, :create, :update, :destroy
@@ -39,13 +39,12 @@ class IssueCategoriesController < ApplicationController
end
def new
@category = @project.issue_categories.build
@category.safe_attributes = params[:issue_category]
@category = @project.issue_categories.build(params[:issue_category])
end
verify :method => :post, :only => :create
def create
@category = @project.issue_categories.build
@category.safe_attributes = params[:issue_category]
@category = @project.issue_categories.build(params[:issue_category])
if @category.save
respond_to do |format|
format.html do
@@ -55,7 +54,7 @@ class IssueCategoriesController < ApplicationController
format.js do
# IE doesn't support the replace_html rjs method for select box options
render(:update) {|page| page.replace "issue_category_id",
content_tag('select', content_tag('option') + options_from_collection_for_select(@project.issue_categories, 'id', 'name', @category.id), :id => 'issue_category_id', :name => 'issue[category_id]')
content_tag('select', '<option></option>' + options_from_collection_for_select(@project.issue_categories, 'id', 'name', @category.id), :id => 'issue_category_id', :name => 'issue[category_id]')
}
end
format.api { render :action => 'show', :status => :created, :location => issue_category_path(@category) }
@@ -74,9 +73,9 @@ class IssueCategoriesController < ApplicationController
def edit
end
verify :method => :put, :only => :update
def update
@category.safe_attributes = params[:issue_category]
if @category.save
if @category.update_attributes(params[:issue_category])
respond_to do |format|
format.html {
flash[:notice] = l(:notice_successful_update)
@@ -92,6 +91,7 @@ class IssueCategoriesController < ApplicationController
end
end
verify :method => :delete, :only => :destroy
def destroy
@issue_count = @category.issues.size
if @issue_count == 0 || params[:todo] || api_request?
@@ -116,4 +116,10 @@ private
super
@category = @object
end
def find_project
@project = Project.find(params[:project_id])
rescue ActiveRecord::RecordNotFound
render_404
end
end

View File

@@ -0,0 +1,87 @@
# Redmine - project management software
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class IssueMovesController < ApplicationController
menu_item :issues
default_search_scope :issues
before_filter :find_issues, :check_project_uniqueness
before_filter :authorize
def new
prepare_for_issue_move
render :layout => false if request.xhr?
end
def create
prepare_for_issue_move
if request.post?
new_tracker = params[:new_tracker_id].blank? ? nil : @target_project.trackers.find_by_id(params[:new_tracker_id])
unsaved_issue_ids = []
moved_issues = []
@issues.each do |issue|
issue.reload
issue.init_journal(User.current)
issue.current_journal.notes = @notes if @notes.present?
call_hook(:controller_issues_move_before_save, { :params => params, :issue => issue, :target_project => @target_project, :copy => !!@copy })
if r = issue.move_to_project(@target_project, new_tracker, {:copy => @copy, :attributes => extract_changed_attributes_for_move(params)})
moved_issues << r
else
unsaved_issue_ids << issue.id
end
end
set_flash_from_bulk_issue_save(@issues, unsaved_issue_ids)
if params[:follow]
if @issues.size == 1 && moved_issues.size == 1
redirect_to :controller => 'issues', :action => 'show', :id => moved_issues.first
else
redirect_to :controller => 'issues', :action => 'index', :project_id => (@target_project || @project)
end
else
redirect_to :controller => 'issues', :action => 'index', :project_id => @project
end
return
end
end
private
def prepare_for_issue_move
@issues.sort!
@copy = params[:copy_options] && params[:copy_options][:copy]
@allowed_projects = Issue.allowed_target_projects_on_move
@target_project = @allowed_projects.detect {|p| p.id.to_s == params[:new_project_id]} if params[:new_project_id]
@target_project ||= @project
@trackers = @target_project.trackers
@available_statuses = Workflow.available_statuses(@project)
@notes = params[:notes]
@notes ||= ''
end
def extract_changed_attributes_for_move(params)
changed_attributes = {}
[:assigned_to_id, :status_id, :start_date, :due_date, :priority_id].each do |valid_attribute|
unless params[valid_attribute].blank?
changed_attributes[valid_attribute] = (params[valid_attribute] == 'none' ? nil : params[valid_attribute])
end
end
changed_attributes
end
end

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -39,10 +39,11 @@ class IssueRelationsController < ApplicationController
end
end
verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed }
def create
@relation = IssueRelation.new(params[:relation])
@relation.issue_from = @issue
if params[:relation] && m = params[:relation][:issue_to_id].to_s.strip.match(/^#?(\d+)$/)
if params[:relation] && m = params[:relation][:issue_to_id].to_s.match(/^#?(\d+)$/)
@relation.issue_to = Issue.visible.find_by_id(m[1].to_i)
end
saved = @relation.save
@@ -69,12 +70,13 @@ class IssueRelationsController < ApplicationController
end
end
verify :method => :delete, :only => :destroy, :render => {:nothing => true, :status => :method_not_allowed }
def destroy
raise Unauthorized unless @relation.deletable?
@relation.destroy
respond_to do |format|
format.html { redirect_to issue_path } # TODO : does this really work since @issue is always nil? What is it useful to?
format.html { redirect_to :controller => 'issues', :action => 'show', :id => @issue }
format.js { render(:update) {|page| page.remove "relation-#{@relation.id}"} }
format.api { head :ok }
end

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -62,6 +62,7 @@ class IssueStatusesController < ApplicationController
end
end
verify :method => :delete, :only => :destroy, :redirect_to => { :action => :index }
def destroy
IssueStatus.find(params[:id]).destroy
redirect_to :action => 'index'

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -20,7 +20,8 @@ class IssuesController < ApplicationController
default_search_scope :issues
before_filter :find_issue, :only => [:show, :edit, :update]
before_filter :find_issues, :only => [:bulk_edit, :bulk_update, :destroy]
before_filter :find_issues, :only => [:bulk_edit, :bulk_update, :move, :perform_move, :destroy]
before_filter :check_project_uniqueness, :only => [:move, :perform_move]
before_filter :find_project, :only => [:new, :create]
before_filter :authorize, :except => [:index]
before_filter :find_optional_project, :only => [:index]
@@ -53,6 +54,14 @@ class IssuesController < ApplicationController
helper :gantt
include Redmine::Export::PDF
verify :method => [:post, :delete],
:only => :destroy,
:render => { :nothing => true, :status => :method_not_allowed }
verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed }
verify :method => :post, :only => :bulk_update, :render => {:nothing => true, :status => :method_not_allowed }
verify :method => :put, :only => :update, :render => {:nothing => true, :status => :method_not_allowed }
def index
retrieve_query
sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
@@ -104,8 +113,10 @@ class IssuesController < ApplicationController
@journals.each_with_index {|j,i| j.indice = i+1}
@journals.reverse! if User.current.wants_comments_in_reverse_order?
@changesets = @issue.changesets.visible.all
@changesets.reverse! if User.current.wants_comments_in_reverse_order?
if User.current.allowed_to?(:view_changesets, @project)
@changesets = @issue.changesets.visible.all
@changesets.reverse! if User.current.wants_comments_in_reverse_order?
end
@relations = @issue.relations.select {|r| r.other_issue(@issue) && r.other_issue(@issue).visible? }
@allowed_statuses = @issue.new_statuses_allowed_to(User.current)
@@ -113,10 +124,7 @@ class IssuesController < ApplicationController
@priorities = IssuePriority.active
@time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
respond_to do |format|
format.html {
retrieve_previous_and_next_issue_ids
render :template => 'issues/show'
}
format.html { render :template => 'issues/show' }
format.api
format.atom { render :template => 'journals/index', :layout => false, :content_type => 'application/atom+xml' }
format.pdf { send_data(issue_to_pdf(@issue), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") }
@@ -128,30 +136,20 @@ class IssuesController < ApplicationController
def new
respond_to do |format|
format.html { render :action => 'new', :layout => !request.xhr? }
format.js {
render(:update) { |page|
if params[:project_change]
page.replace_html 'all_attributes', :partial => 'form'
else
page.replace_html 'attributes', :partial => 'attributes'
end
m = User.current.allowed_to?(:log_time, @issue.project) ? 'show' : 'hide'
page << "if ($('log_time')) {Element.#{m}('log_time');}"
}
}
format.js { render :partial => 'attributes' }
end
end
def create
call_hook(:controller_issues_new_before_save, { :params => params, :issue => @issue })
@issue.save_attachments(params[:attachments] || (params[:issue] && params[:issue][:uploads]))
if @issue.save
attachments = Attachment.attach_files(@issue, params[:attachments])
call_hook(:controller_issues_new_after_save, { :params => params, :issue => @issue})
respond_to do |format|
format.html {
render_attachment_warning_if_needed(@issue)
flash[:notice] = l(:notice_issue_successful_create, :id => "<a href='#{issue_path(@issue)}'>##{@issue.id}</a>")
redirect_to(params[:continue] ? { :action => 'new', :project_id => @issue.project, :issue => {:tracker_id => @issue.tracker, :parent_issue_id => @issue.parent_issue_id}.reject {|k,v| v.nil?} } :
redirect_to(params[:continue] ? { :action => 'new', :project_id => @project, :issue => {:tracker_id => @issue.tracker, :parent_issue_id => @issue.parent_issue_id}.reject {|k,v| v.nil?} } :
{ :action => 'show', :id => @issue })
}
format.api { render :action => 'show', :status => :created, :location => issue_url(@issue) }
@@ -166,7 +164,9 @@ class IssuesController < ApplicationController
end
def edit
return unless update_issue_from_params
update_issue_from_params
@journal = @issue.current_journal
respond_to do |format|
format.html { }
@@ -175,24 +175,9 @@ class IssuesController < ApplicationController
end
def update
return unless update_issue_from_params
@issue.save_attachments(params[:attachments] || (params[:issue] && params[:issue][:uploads]))
saved = false
begin
saved = @issue.save_issue_with_child_records(params, @time_entry)
rescue ActiveRecord::StaleObjectError
@conflict = true
if params[:last_journal_id]
if params[:last_journal_id].present?
last_journal_id = params[:last_journal_id].to_i
@conflict_journals = @issue.journals.all(:conditions => ["#{Journal.table_name}.id > ?", last_journal_id])
else
@conflict_journals = @issue.journals.all
end
end
end
update_issue_from_params
if saved
if @issue.save_issue_with_child_records(params, @time_entry)
render_attachment_warning_if_needed(@issue)
flash[:notice] = l(:notice_successful_update) unless @issue.current_journal.new_record?
@@ -201,6 +186,10 @@ class IssuesController < ApplicationController
format.api { head :ok }
end
else
render_attachment_warning_if_needed(@issue)
flash[:notice] = l(:notice_successful_update) unless @issue.current_journal.new_record?
@journal = @issue.current_journal
respond_to do |format|
format.html { render :action => 'edit' }
format.api { render_validation_errors(@issue) }
@@ -208,74 +197,32 @@ class IssuesController < ApplicationController
end
end
# Bulk edit/copy a set of issues
# Bulk edit a set of issues
def bulk_edit
@issues.sort!
@copy = params[:copy].present?
@notes = params[:notes]
if User.current.allowed_to?(:move_issues, @projects)
@allowed_projects = Issue.allowed_target_projects_on_move
if params[:issue]
@target_project = @allowed_projects.detect {|p| p.id.to_s == params[:issue][:project_id].to_s}
if @target_project
target_projects = [@target_project]
end
end
end
target_projects ||= @projects
if @copy
@available_statuses = [IssueStatus.default]
else
@available_statuses = @issues.map(&:new_statuses_allowed_to).reduce(:&)
end
@custom_fields = target_projects.map{|p|p.all_issue_custom_fields}.reduce(:&)
@assignables = target_projects.map(&:assignable_users).reduce(:&)
@trackers = target_projects.map(&:trackers).reduce(:&)
@versions = target_projects.map {|p| p.shared_versions.open}.reduce(:&)
@categories = target_projects.map {|p| p.issue_categories}.reduce(:&)
if @copy
@attachments_present = @issues.detect {|i| i.attachments.any?}.present?
end
@safe_attributes = @issues.map(&:safe_attribute_names).reduce(:&)
render :layout => false if request.xhr?
@available_statuses = @projects.map{|p|Workflow.available_statuses(p)}.inject{|memo,w|memo & w}
@custom_fields = @projects.map{|p|p.all_issue_custom_fields}.inject{|memo,c|memo & c}
@assignables = @projects.map(&:assignable_users).inject{|memo,a| memo & a}
@trackers = @projects.map(&:trackers).inject{|memo,t| memo & t}
end
def bulk_update
@issues.sort!
@copy = params[:copy].present?
attributes = parse_params_for_bulk_issue_attributes(params)
unsaved_issue_ids = []
moved_issues = []
@issues.each do |issue|
issue.reload
if @copy
issue = issue.copy({}, :attachments => params[:copy_attachments].present?)
end
journal = issue.init_journal(User.current, params[:notes])
issue.safe_attributes = attributes
call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue })
if issue.save
moved_issues << issue
else
unless issue.save
# Keep unsaved issue ids to display them in flash error
unsaved_issue_ids << issue.id
end
end
set_flash_from_bulk_issue_save(@issues, unsaved_issue_ids)
if params[:follow]
if @issues.size == 1 && moved_issues.size == 1
redirect_to :controller => 'issues', :action => 'show', :id => moved_issues.first
elsif moved_issues.map(&:project).uniq.size == 1
redirect_to :controller => 'issues', :action => 'index', :project_id => moved_issues.map(&:project).first
end
else
redirect_back_or_default({:controller => 'issues', :action => 'index', :project_id => @project})
end
redirect_back_or_default({:controller => 'issues', :action => 'index', :project_id => @project})
end
def destroy
@@ -327,58 +274,25 @@ private
end
def find_project
project_id = params[:project_id] || (params[:issue] && params[:issue][:project_id])
project_id = (params[:issue] && params[:issue][:project_id]) || params[:project_id]
@project = Project.find(project_id)
rescue ActiveRecord::RecordNotFound
render_404
end
def retrieve_previous_and_next_issue_ids
retrieve_query_from_session
if @query
sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
sort_update(@query.sortable_columns, 'issues_index_sort')
limit = 500
issue_ids = @query.issue_ids(:order => sort_clause, :limit => (limit + 1), :include => [:assigned_to, :tracker, :priority, :category, :fixed_version])
if (idx = issue_ids.index(@issue.id)) && idx < limit
if issue_ids.size < 500
@issue_position = idx + 1
@issue_count = issue_ids.size
end
@prev_issue_id = issue_ids[idx - 1] if idx > 0
@next_issue_id = issue_ids[idx + 1] if idx < (issue_ids.size - 1)
end
end
end
# Used by #edit and #update to set some common instance variables
# from the params
# TODO: Refactor, not everything in here is needed by #edit
def update_issue_from_params
@allowed_statuses = @issue.new_statuses_allowed_to(User.current)
@priorities = IssuePriority.active
@edit_allowed = User.current.allowed_to?(:edit_issues, @project)
@time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
@time_entry.attributes = params[:time_entry]
@notes = params[:notes] || (params[:issue].present? ? params[:issue][:notes] : nil)
@issue.init_journal(User.current, @notes)
issue_attributes = params[:issue]
if issue_attributes && params[:conflict_resolution]
case params[:conflict_resolution]
when 'overwrite'
issue_attributes = issue_attributes.dup
issue_attributes.delete(:lock_version)
when 'add_notes'
issue_attributes = {}
when 'cancel'
redirect_to issue_path(@issue)
return false
end
end
@issue.safe_attributes = issue_attributes
@priorities = IssuePriority.active
@allowed_statuses = @issue.new_statuses_allowed_to(User.current)
true
@issue.safe_attributes = params[:issue]
end
# TODO: Refactor, lots of extra code in here
@@ -386,16 +300,7 @@ private
def build_new_issue_from_params
if params[:id].blank?
@issue = Issue.new
if params[:copy_from]
begin
@copy_from = Issue.visible.find(params[:copy_from])
@copy_attachments = params[:copy_attachments].present? || request.get?
@issue.copy_from(@copy_from, :attachments => @copy_attachments)
rescue ActiveRecord::RecordNotFound
render_404
return
end
end
@issue.copy_from(params[:copy_from]) if params[:copy_from]
@issue.project = @project
else
@issue = @project.issues.visible.find(params[:id])
@@ -410,11 +315,14 @@ private
return false
end
@issue.start_date ||= Date.today if Setting.default_issue_start_date_to_creation_date?
@issue.safe_attributes = params[:issue]
if params[:issue].is_a?(Hash)
@issue.safe_attributes = params[:issue]
if User.current.allowed_to?(:add_issue_watchers, @project) && @issue.new_record?
@issue.watcher_user_ids = params[:issue]['watcher_user_ids']
end
end
@priorities = IssuePriority.active
@allowed_statuses = @issue.new_statuses_allowed_to(User.current, true)
@available_watchers = (@issue.project.users.sort + @issue.watcher_users).uniq
end
def check_for_default_issue_status
@@ -427,16 +335,7 @@ private
def parse_params_for_bulk_issue_attributes(params)
attributes = (params[:issue] || {}).reject {|k,v| v.blank?}
attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'}
if custom = attributes[:custom_field_values]
custom.reject! {|k,v| v.blank?}
custom.keys.each do |k|
if custom[k].is_a?(Array)
custom[k] << '' if custom[k].delete('__none__')
else
custom[k] = '' if custom[k] == '__none__'
end
end
end
attributes[:custom_field_values].reject! {|k,v| v.blank?} if attributes[:custom_field_values]
attributes
end
end

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -15,13 +15,11 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
require File.expand_path('../../../../test_helper', __FILE__)
class LdapAuthSourcesController < AuthSourcesController
class Redmine::InfoTest < ActiveSupport::TestCase
def test_environment
env = Redmine::Info.environment
protected
assert_kind_of String, env
assert_match 'Redmine version', env
def auth_source_class
AuthSourceLdap
end
end

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -18,6 +18,10 @@
class MailHandlerController < ActionController::Base
before_filter :check_credential
verify :method => :post,
:only => :index,
:render => { :nothing => true, :status => 405 }
# Submits an incoming email to MailHandler
def index
options = params.dup

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -17,54 +17,29 @@
class MembersController < ApplicationController
model_object Member
before_filter :find_model_object, :except => [:index, :create, :autocomplete]
before_filter :find_project_from_association, :except => [:index, :create, :autocomplete]
before_filter :find_project_by_project_id, :only => [:index, :create, :autocomplete]
before_filter :find_model_object, :except => [:new, :autocomplete_for_member]
before_filter :find_project_from_association, :except => [:new, :autocomplete_for_member]
before_filter :find_project, :only => [:new, :autocomplete_for_member]
before_filter :authorize
accept_api_auth :index, :show, :create, :update, :destroy
def index
@offset, @limit = api_offset_and_limit
@member_count = @project.member_principals.count
@member_pages = Paginator.new self, @member_count, @limit, params['page']
@offset ||= @member_pages.current.offset
@members = @project.member_principals.all(
:order => "#{Member.table_name}.id",
:limit => @limit,
:offset => @offset
)
respond_to do |format|
format.html { head 406 }
format.api
end
end
def show
respond_to do |format|
format.html { head 406 }
format.api
end
end
def create
def new
members = []
if params[:membership]
if params[:membership][:user_ids]
attrs = params[:membership].dup
user_ids = attrs.delete(:user_ids)
if params[:member] && request.post?
attrs = params[:member].dup
if (user_ids = attrs.delete(:user_ids))
user_ids.each do |user_id|
members << Member.new(:role_ids => params[:membership][:role_ids], :user_id => user_id)
members << Member.new(attrs.merge(:user_id => user_id))
end
else
members << Member.new(:role_ids => params[:membership][:role_ids], :user_id => params[:membership][:user_id])
members << Member.new(attrs)
end
@project.members << members
end
respond_to do |format|
if members.present? && members.all? {|m| m.valid? }
format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'members', :id => @project }
format.js {
render(:update) {|page|
page.replace_html "tab-content-members", :partial => 'projects/settings/members'
@@ -72,11 +47,8 @@ class MembersController < ApplicationController
members.each {|member| page.visual_effect(:highlight, "member-#{member.id}") }
}
}
format.api {
@member = members.first
render :action => 'show', :status => :created, :location => membership_url(@member)
}
else
format.js {
render(:update) {|page|
errors = members.collect {|m|
@@ -86,37 +58,28 @@ class MembersController < ApplicationController
page.alert(l(:notice_failed_to_save_members, :errors => errors.join(', ')))
}
}
format.api { render_validation_errors(members.first) }
end
end
end
def update
if params[:membership]
@member.role_ids = params[:membership][:role_ids]
end
saved = @member.save
respond_to do |format|
format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'members', :id => @project }
format.js {
render(:update) {|page|
page.replace_html "tab-content-members", :partial => 'projects/settings/members'
page << 'hideOnLoad()'
page.visual_effect(:highlight, "member-#{@member.id}")
def edit
if request.post? and @member.update_attributes(params[:member])
respond_to do |format|
format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'members', :id => @project }
format.js {
render(:update) {|page|
page.replace_html "tab-content-members", :partial => 'projects/settings/members'
page << 'hideOnLoad()'
page.visual_effect(:highlight, "member-#{@member.id}")
}
}
}
format.api {
if saved
head :ok
else
render_validation_errors(@member)
end
}
end
end
end
def destroy
if request.delete? && @member.deletable?
if request.post? && @member.deletable?
@member.destroy
end
respond_to do |format|
@@ -126,18 +89,11 @@ class MembersController < ApplicationController
page << 'hideOnLoad()'
}
}
format.api {
if @member.destroyed?
head :ok
else
head :unprocessable_entity
end
}
end
end
def autocomplete
@principals = Principal.active.not_member_of(@project).like(params[:q]).all(:limit => 100)
def autocomplete_for_member
@principals = Principal.active.like(params[:q]).find(:all, :limit => 100) - @project.principals
render :layout => false
end

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -22,6 +22,9 @@ class MessagesController < ApplicationController
before_filter :find_message, :except => [:new, :preview]
before_filter :authorize, :except => [:preview, :edit, :destroy]
verify :method => :post, :only => [ :reply, :destroy ], :redirect_to => { :action => :show }
verify :xhr => true, :only => :quote
helper :watchers
helper :attachments
include AttachmentsHelper
@@ -50,26 +53,26 @@ class MessagesController < ApplicationController
# Create a new topic
def new
@message = Message.new
@message = Message.new(params[:message])
@message.author = User.current
@message.board = @board
@message.safe_attributes = params[:message]
if request.post?
@message.save_attachments(params[:attachments])
if @message.save
call_hook(:controller_messages_new_after_save, { :params => params, :message => @message})
render_attachment_warning_if_needed(@message)
redirect_to :action => 'show', :id => @message
end
if params[:message] && User.current.allowed_to?(:edit_messages, @project)
@message.locked = params[:message]['locked']
@message.sticky = params[:message]['sticky']
end
if request.post? && @message.save
call_hook(:controller_messages_new_after_save, { :params => params, :message => @message})
attachments = Attachment.attach_files(@message, params[:attachments])
render_attachment_warning_if_needed(@message)
redirect_to :action => 'show', :id => @message
end
end
# Reply to a topic
def reply
@reply = Message.new
@reply = Message.new(params[:reply])
@reply.author = User.current
@reply.board = @board
@reply.safe_attributes = params[:reply]
@topic.children << @reply
if !@reply.new_record?
call_hook(:controller_messages_reply_after_save, { :params => params, :message => @reply})
@@ -82,8 +85,11 @@ class MessagesController < ApplicationController
# Edit a message
def edit
(render_403; return false) unless @message.editable_by?(User.current)
@message.safe_attributes = params[:message]
if request.post? && @message.save
if params[:message]
@message.locked = params[:message]['locked']
@message.sticky = params[:message]['sticky']
end
if request.post? && @message.update_attributes(params[:message])
attachments = Attachment.attach_files(@message, params[:attachments])
render_attachment_warning_if_needed(@message)
flash[:notice] = l(:notice_successful_update)
@@ -95,11 +101,10 @@ class MessagesController < ApplicationController
# Delete a messages
def destroy
(render_403; return false) unless @message.destroyable_by?(User.current)
r = @message.to_param
@message.destroy
redirect_to @message.parent.nil? ?
{ :controller => 'boards', :action => 'show', :project_id => @project, :id => @board } :
{ :action => 'show', :id => @message.parent, :r => r }
{ :action => 'show', :id => @message.parent, :r => @message }
end
def quote

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -35,6 +35,9 @@ class MyController < ApplicationController
'right' => ['issuesreportedbyme']
}.freeze
verify :xhr => true,
:only => [:add_block, :remove_block, :order_blocks]
def index
page
render :action => 'page'
@@ -65,24 +68,6 @@ class MyController < ApplicationController
end
end
# Destroys user's account
def destroy
@user = User.current
unless @user.own_account_deletable?
redirect_to :action => 'account'
return
end
if request.post? && params[:confirm]
@user.destroy
if @user.destroyed?
logout_user
flash[:notice] = l(:notice_account_deleted)
end
redirect_to home_path
end
end
# Manage user's password
def password
@user = User.current

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -20,14 +20,13 @@ class NewsController < ApplicationController
model_object News
before_filter :find_model_object, :except => [:new, :create, :index]
before_filter :find_project_from_association, :except => [:new, :create, :index]
before_filter :find_project_by_project_id, :only => [:new, :create]
before_filter :find_project, :only => [:new, :create]
before_filter :authorize, :except => [:index]
before_filter :find_optional_project, :only => :index
accept_rss_auth :index
accept_api_auth :index
helper :watchers
helper :attachments
def index
case params[:format]
@@ -68,14 +67,14 @@ class NewsController < ApplicationController
def create
@news = News.new(:project => @project, :author => User.current)
@news.safe_attributes = params[:news]
@news.save_attachments(params[:attachments])
if @news.save
render_attachment_warning_if_needed(@news)
flash[:notice] = l(:notice_successful_create)
redirect_to :controller => 'news', :action => 'index', :project_id => @project
else
render :action => 'new'
if request.post?
@news.attributes = params[:news]
if @news.save
flash[:notice] = l(:notice_successful_create)
redirect_to :controller => 'news', :action => 'index', :project_id => @project
else
render :action => 'new'
end
end
end
@@ -83,10 +82,7 @@ class NewsController < ApplicationController
end
def update
@news.safe_attributes = params[:news]
@news.save_attachments(params[:attachments])
if @news.save
render_attachment_warning_if_needed(@news)
if request.put? and @news.update_attributes(params[:news])
flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'show', :id => @news
else
@@ -99,7 +95,12 @@ class NewsController < ApplicationController
redirect_to :action => 'index', :project_id => @project
end
private
private
def find_project
@project = Project.find(params[:project_id])
rescue ActiveRecord::RecordNotFound
render_404
end
def find_optional_project
return true unless params[:project_id]

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License

View File

@@ -1,20 +1,3 @@
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class ProjectEnumerationsController < ApplicationController
before_filter :find_project_by_project_id
before_filter :authorize

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -29,7 +29,7 @@ class ProjectsController < ApplicationController
after_filter :only => [:create, :edit, :update, :archive, :unarchive, :destroy] do |controller|
if controller.request.post?
controller.send :expire_action, :controller => 'welcome', :action => 'robots'
controller.send :expire_action, :controller => 'welcome', :action => 'robots.txt'
end
end
@@ -66,10 +66,10 @@ class ProjectsController < ApplicationController
def new
@issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
@trackers = Tracker.all
@project = Project.new
@project.safe_attributes = params[:project]
@project = Project.new(params[:project])
end
verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed }
def create
@issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
@trackers = Tracker.all
@@ -176,12 +176,15 @@ class ProjectsController < ApplicationController
@issue_category ||= IssueCategory.new
@member ||= @project.members.new
@trackers = Tracker.all
@repository ||= @project.repository
@wiki ||= @project.wiki
end
def edit
end
# TODO: convert to PUT only
verify :method => [:post, :put], :only => :update, :render => {:nothing => true, :status => :method_not_allowed }
def update
@project.safe_attributes = params[:project]
if validate_parent_id && @project.save
@@ -204,6 +207,7 @@ class ProjectsController < ApplicationController
end
end
verify :method => :post, :only => :modules, :render => {:nothing => true, :status => :method_not_allowed }
def modules
@project.enabled_module_names = params[:enabled_module_names]
flash[:notice] = l(:notice_successful_update)
@@ -227,18 +231,29 @@ class ProjectsController < ApplicationController
# Delete @project
def destroy
@project_to_destroy = @project
if api_request? || params[:confirm]
@project_to_destroy.destroy
respond_to do |format|
format.html { redirect_to :controller => 'admin', :action => 'projects' }
format.api { head :ok }
if request.get?
# display confirmation view
else
if api_request? || params[:confirm]
@project_to_destroy.destroy
respond_to do |format|
format.html { redirect_to :controller => 'admin', :action => 'projects' }
format.api { head :ok }
end
end
end
# hide project in layout
@project = nil
end
private
private
def find_optional_project
return true unless params[:id]
@project = Project.find(params[:id])
authorize
rescue ActiveRecord::RecordNotFound
render_404
end
# Validates parent_id param according to user's permissions
# TODO: move it to Project model in a validation that depends on User.current

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -50,6 +50,7 @@ class QueriesController < ApplicationController
build_query_from_params
end
verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed }
def create
@query = Query.new(params[:query])
@query.user = User.current
@@ -69,6 +70,7 @@ class QueriesController < ApplicationController
def edit
end
verify :method => :put, :only => :update, :render => {:nothing => true, :status => :method_not_allowed }
def update
@query.attributes = params[:query]
@query.project = nil if params[:query_is_for_all]
@@ -84,6 +86,7 @@ class QueriesController < ApplicationController
end
end
verify :method => :delete, :only => :destroy, :render => {:nothing => true, :status => :method_not_allowed }
def destroy
@query.destroy
redirect_to :controller => 'issues', :action => 'index', :project_id => @project, :set_filter => 1

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -18,77 +18,52 @@
require 'SVG/Graph/Bar'
require 'SVG/Graph/BarHorizontal'
require 'digest/sha1'
require 'redmine/scm/adapters/abstract_adapter'
class ChangesetNotFound < Exception; end
class InvalidRevisionParam < Exception; end
class RepositoriesController < ApplicationController
menu_item :repository
menu_item :settings, :only => [:new, :create, :edit, :update, :destroy, :committers]
menu_item :settings, :only => :edit
default_search_scope :changesets
before_filter :find_project_by_project_id, :only => [:new, :create]
before_filter :find_repository, :only => [:edit, :update, :destroy, :committers]
before_filter :find_project_repository, :except => [:new, :create, :edit, :update, :destroy, :committers]
before_filter :find_changeset, :only => [:revision, :add_related_issue, :remove_related_issue]
before_filter :find_repository, :except => :edit
before_filter :find_project, :only => :edit
before_filter :authorize
accept_rss_auth :revisions
rescue_from Redmine::Scm::Adapters::CommandFailed, :with => :show_error_command_failed
def new
scm = params[:repository_scm] || (Redmine::Scm::Base.all & Setting.enabled_scm).first
@repository = Repository.factory(scm)
@repository.is_default = @project.repository.nil?
@repository.project = @project
render :layout => !request.xhr?
end
def create
attrs = pickup_extra_info
@repository = Repository.factory(params[:repository_scm], attrs[:attrs])
if attrs[:attrs_extra].keys.any?
@repository.merge_extra_info(attrs[:attrs_extra])
end
@repository.project = @project
if request.post? && @repository.save
redirect_to settings_project_path(@project, :tab => 'repositories')
else
render :action => 'new'
end
end
def edit
end
def update
attrs = pickup_extra_info
@repository.attributes = attrs[:attrs]
if attrs[:attrs_extra].keys.any?
@repository.merge_extra_info(attrs[:attrs_extra])
@repository = @project.repository
if !@repository && !params[:repository_scm].blank?
@repository = Repository.factory(params[:repository_scm])
@repository.project = @project if @repository
end
@repository.project = @project
if request.put? && @repository.save
redirect_to settings_project_path(@project, :tab => 'repositories')
else
render :action => 'edit'
if request.post? && @repository
p1 = params[:repository]
p = {}
p_extra = {}
p1.each do |k, v|
if k =~ /^extra_/
p_extra[k] = v
else
p[k] = v
end
end
@repository.attributes = p
@repository.merge_extra_info(p_extra)
@repository.save
end
end
def pickup_extra_info
p = {}
p_extra = {}
params[:repository].each do |k, v|
if k =~ /^extra_/
p_extra[k] = v
else
p[k] = v
render(:update) do |page|
page.replace_html "tab-content-repository",
:partial => 'projects/settings/repository'
if @repository && !@project.repository
@project.reload # needed to reload association
page.replace_html "main-menu", render_main_menu(@project)
end
end
{:attrs => p, :attrs_extra => p_extra}
end
private :pickup_extra_info
def committers
@committers = @repository.committers
@@ -101,13 +76,16 @@ class RepositoriesController < ApplicationController
# Build a hash with repository usernames as keys and corresponding user ids as values
@repository.committer_ids = params[:committers].values.inject({}) {|h, c| h[c.first] = c.last; h}
flash[:notice] = l(:notice_successful_update)
redirect_to settings_project_path(@project, :tab => 'repositories')
redirect_to :action => 'committers', :id => @project
end
end
def destroy
@repository.destroy if request.delete?
redirect_to settings_project_path(@project, :tab => 'repositories')
@repository.destroy
redirect_to :controller => 'projects',
:action => 'settings',
:id => @project,
:tab => 'repository'
end
def show
@@ -121,7 +99,6 @@ class RepositoriesController < ApplicationController
(show_error_not_found; return) unless @entries
@changesets = @repository.latest_changesets(@path, @rev)
@properties = @repository.properties(@path, @rev)
@repositories = @project.repositories
render :action => 'show'
end
end
@@ -152,15 +129,7 @@ class RepositoriesController < ApplicationController
end
end
def raw
entry_and_raw(true)
end
def entry
entry_and_raw(false)
end
def entry_and_raw(is_raw)
@entry = @repository.entry(@path, @rev)
(show_error_not_found; return) unless @entry
@@ -169,7 +138,7 @@ class RepositoriesController < ApplicationController
@content = @repository.cat(@path, @rev)
(show_error_not_found; return) unless @content
if is_raw ||
if 'raw' == params[:format] ||
(@content.size && @content.size > Setting.file_max_size_displayed.to_i.kilobyte) ||
! is_entry_text_data?(@content, @path)
# Force the download
@@ -185,7 +154,6 @@ class RepositoriesController < ApplicationController
@changeset = @repository.find_changeset_by_name(@rev)
end
end
private :entry_and_raw
def is_entry_text_data?(ent, path)
# UTF-16 contains "\x00".
@@ -218,56 +186,16 @@ class RepositoriesController < ApplicationController
end
def revision
raise ChangesetNotFound if @rev.blank?
@changeset = @repository.find_changeset_by_name(@rev)
raise ChangesetNotFound unless @changeset
respond_to do |format|
format.html
format.js {render :layout => false}
end
end
# Adds a related issue to a changeset
# POST /projects/:project_id/repository/(:repository_id/)revisions/:rev/issues
def add_related_issue
@issue = @changeset.find_referenced_issue_by_id(params[:issue_id])
if @issue && (!@issue.visible? || @changeset.issues.include?(@issue))
@issue = nil
end
if @issue
@changeset.issues << @issue
respond_to do |format|
format.js {
render :update do |page|
page.replace_html "related-issues", :partial => "related_issues"
page.visual_effect :highlight, "related-issue-#{@issue.id}"
end
}
end
else
respond_to do |format|
format.js {
render :update do |page|
page.alert(l(:label_issue) + ' ' + l('activerecord.errors.messages.invalid'))
end
}
end
end
end
# Removes a related issue from a changeset
# DELETE /projects/:project_id/repository/(:repository_id/)revisions/:rev/issues/:issue_id
def remove_related_issue
@issue = Issue.visible.find_by_id(params[:issue_id])
if @issue
@changeset.issues.delete(@issue)
end
respond_to do |format|
format.js {
render :update do |page|
page.remove "related-issue-#{@issue.id}"
end if @issue
}
end
rescue ChangesetNotFound
show_error_not_found
end
def diff
@@ -322,24 +250,14 @@ class RepositoriesController < ApplicationController
private
def find_repository
@repository = Repository.find(params[:id])
@project = @repository.project
rescue ActiveRecord::RecordNotFound
render_404
end
REV_PARAM_RE = %r{\A[a-f0-9]*\Z}i
def find_project_repository
def find_repository
@project = Project.find(params[:id])
if params[:repository_id].present?
@repository = @project.repositories.find_by_identifier_param(params[:repository_id])
else
@repository = @project.repository
end
@repository = @project.repository
(render_404; return false) unless @repository
@path = params[:path].is_a?(Array) ? params[:path].join('/') : params[:path].to_s
@path = params[:path].join('/') unless params[:path].nil?
@path ||= ''
@rev = params[:rev].blank? ? @repository.default_branch : params[:rev].to_s.strip
@rev_to = params[:rev_to]
@@ -354,13 +272,6 @@ class RepositoriesController < ApplicationController
show_error_not_found
end
def find_changeset
if @rev.present?
@changeset = @repository.find_changeset_by_name(@rev)
end
show_error_not_found unless @changeset
end
def show_error_not_found
render_error :message => l(:error_scm_not_found), :status => 404
end
@@ -374,17 +285,17 @@ class RepositoriesController < ApplicationController
@date_to = Date.today
@date_from = @date_to << 11
@date_from = Date.civil(@date_from.year, @date_from.month, 1)
commits_by_day = Changeset.count(
commits_by_day = repository.changesets.count(
:all, :group => :commit_date,
:conditions => ["repository_id = ? AND commit_date BETWEEN ? AND ?", repository.id, @date_from, @date_to])
:conditions => ["commit_date BETWEEN ? AND ?", @date_from, @date_to])
commits_by_month = [0] * 12
commits_by_day.each {|c| commits_by_month[(@date_to.month - c.first.to_date.month) % 12] += c.last }
commits_by_day.each {|c| commits_by_month[c.first.to_date.months_ago] += c.last }
changes_by_day = Change.count(
:all, :group => :commit_date, :include => :changeset,
:conditions => ["#{Changeset.table_name}.repository_id = ? AND #{Changeset.table_name}.commit_date BETWEEN ? AND ?", repository.id, @date_from, @date_to])
changes_by_day = repository.changes.count(
:all, :group => :commit_date,
:conditions => ["commit_date BETWEEN ? AND ?", @date_from, @date_to])
changes_by_month = [0] * 12
changes_by_day.each {|c| changes_by_month[(@date_to.month - c.first.to_date.month) % 12] += c.last }
changes_by_day.each {|c| changes_by_month[c.first.to_date.months_ago] += c.last }
fields = []
12.times {|m| fields << month_name(((Date.today.month - 1 - m) % 12) + 1)}
@@ -415,10 +326,10 @@ class RepositoriesController < ApplicationController
end
def graph_commits_per_author(repository)
commits_by_author = Changeset.count(:all, :group => :committer, :conditions => ["repository_id = ?", repository.id])
commits_by_author = repository.changesets.count(:all, :group => :committer)
commits_by_author.to_a.sort! {|x, y| x.last <=> y.last}
changes_by_author = Change.count(:all, :group => :committer, :include => :changeset, :conditions => ["#{Changeset.table_name}.repository_id = ?", repository.id])
changes_by_author = repository.changes.count(:all, :group => :committer)
h = changes_by_author.inject({}) {|o, i| o[i.first] = i.last; o}
fields = commits_by_author.collect {|r| r.first}
@@ -455,3 +366,18 @@ class RepositoriesController < ApplicationController
end
end
class Date
def months_ago(date = Date.today)
(date.year - self.year)*12 + (date.month - self.month)
end
def weeks_ago(date = Date.today)
(date.year - self.year)*52 + (date.cweek - self.cweek)
end
end
class String
def with_leading_slash
starts_with?('/') ? self : "/#{self}"
end
end

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -18,31 +18,19 @@
class RolesController < ApplicationController
layout 'admin'
before_filter :require_admin, :except => :index
before_filter :require_admin_or_api_request, :only => :index
before_filter :find_role, :only => [:edit, :update, :destroy]
accept_api_auth :index
before_filter :require_admin
verify :method => :post, :only => [ :destroy ],
:redirect_to => { :action => :index }
def index
respond_to do |format|
format.html {
@role_pages, @roles = paginate :roles, :per_page => 25, :order => 'builtin, position'
render :action => "index", :layout => false if request.xhr?
}
format.api {
@roles = Role.givable.all
}
end
@role_pages, @roles = paginate :roles, :per_page => 25, :order => 'builtin, position'
render :action => "index", :layout => false if request.xhr?
end
def new
# Prefills the form with 'Non member' role permissions
@role = Role.new(params[:role] || {:permissions => Role.non_member.permissions})
@roles = Role.sorted.all
end
def create
@role = Role.new(params[:role])
if request.post? && @role.save
# workflow copy
if !params[:copy_workflow_from].blank? && (copy_from = Role.find_by_id(params[:copy_workflow_from]))
@@ -51,24 +39,23 @@ class RolesController < ApplicationController
flash[:notice] = l(:notice_successful_create)
redirect_to :action => 'index'
else
@roles = Role.sorted.all
render :action => 'new'
@permissions = @role.setable_permissions
@roles = Role.find :all, :order => 'builtin, position'
end
end
def edit
end
def update
if request.put? and @role.update_attributes(params[:role])
@role = Role.find(params[:id])
if request.post? and @role.update_attributes(params[:role])
flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'index'
else
render :action => 'edit'
@permissions = @role.setable_permissions
end
end
def destroy
@role = Role.find(params[:id])
@role.destroy
redirect_to :action => 'index'
rescue
@@ -76,8 +63,8 @@ class RolesController < ApplicationController
redirect_to :action => 'index'
end
def permissions
@roles = Role.sorted.all
def report
@roles = Role.find(:all, :order => 'builtin, position')
@permissions = Redmine::AccessControl.permissions.select { |p| !p.public? }
if request.post?
@roles.each do |role|
@@ -88,12 +75,4 @@ class RolesController < ApplicationController
redirect_to :action => 'index'
end
end
private
def find_role
@role = Role.find(params[:id])
rescue ActiveRecord::RecordNotFound
render_404
end
end

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -34,7 +34,7 @@ class SearchController < ApplicationController
when 'my_projects'
User.current.memberships.collect(&:project)
when 'subprojects'
@project ? (@project.self_and_descendants.active.all) : nil
@project ? (@project.self_and_descendants.active) : nil
else
@project
end

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -17,7 +17,6 @@
class SettingsController < ApplicationController
layout 'admin'
menu_item :plugins, :only => :plugin
before_filter :require_admin
@@ -52,12 +51,12 @@ class SettingsController < ApplicationController
def plugin
@plugin = Redmine::Plugin.find(params[:id])
if request.post?
Setting.send "plugin_#{@plugin.id}=", params[:settings]
Setting["plugin_#{@plugin.id}"] = params[:settings]
flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'plugin', :id => @plugin.id
else
@partial = @plugin.settings[:partial]
@settings = Setting.send "plugin_#{@plugin.id}"
@settings = Setting["plugin_#{@plugin.id}"]
end
rescue Redmine::PluginNotFound
render_404

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -19,16 +19,9 @@ class SysController < ActionController::Base
before_filter :check_enabled
def projects
p = Project.active.has_module(:repository).find(
:all,
:include => :repository,
:order => "#{Project.table_name}.identifier"
)
p = Project.active.has_module(:repository).find(:all, :include => :repository, :order => 'identifier')
# extra_info attribute from repository breaks activeresource client
render :xml => p.to_xml(
:only => [:id, :identifier, :name, :is_public, :status],
:include => {:repository => {:only => [:id, :url]}}
)
render :xml => p.to_xml(:only => [:id, :identifier, :name, :is_public, :status], :include => {:repository => {:only => [:id, :url]}})
end
def create_project_repository
@@ -37,10 +30,9 @@ class SysController < ActionController::Base
render :nothing => true, :status => 409
else
logger.info "Repository for #{project.name} was reported to be created by #{request.remote_ip}."
repository = Repository.factory(params[:vendor], params[:repository])
repository.project = project
if repository.save
render :xml => {repository.class.name.underscore.gsub('/', '-') => {:id => repository.id, :url => repository.url}}, :status => 201
project.repository = Repository.factory(params[:vendor], params[:repository])
if project.repository && project.repository.save
render :xml => project.repository.to_xml(:only => [:id, :url]), :status => 201
else
render :nothing => true, :status => 422
end
@@ -49,22 +41,14 @@ class SysController < ActionController::Base
def fetch_changesets
projects = []
scope = Project.active.has_module(:repository)
if params[:id]
project = nil
if params[:id].to_s =~ /^\d*$/
project = scope.find(params[:id])
else
project = scope.find_by_identifier(params[:id])
end
raise ActiveRecord::RecordNotFound unless project
projects << project
projects << Project.active.has_module(:repository).find(params[:id])
else
projects = scope.all
projects = Project.active.has_module(:repository).find(:all, :include => :repository)
end
projects.each do |project|
project.repositories.each do |repository|
repository.fetch_changesets
if project.repository
project.repository.fetch_changesets
end
end
render :nothing => true, :status => 200

View File

@@ -0,0 +1,209 @@
class TimeEntryReportsController < ApplicationController
menu_item :issues
before_filter :find_optional_project
before_filter :load_available_criterias
helper :sort
include SortHelper
helper :issues
helper :timelog
include TimelogHelper
helper :custom_fields
include CustomFieldsHelper
def report
@criterias = params[:criterias] || []
@criterias = @criterias.select{|criteria| @available_criterias.has_key? criteria}
@criterias.uniq!
@criterias = @criterias[0,3]
@columns = (params[:columns] && %w(year month week day).include?(params[:columns])) ? params[:columns] : 'month'
retrieve_date_range
unless @criterias.empty?
sql_select = @criterias.collect{|criteria| @available_criterias[criteria][:sql] + " AS " + criteria}.join(', ')
sql_group_by = @criterias.collect{|criteria| @available_criterias[criteria][:sql]}.join(', ')
sql_condition = ''
if @project.nil?
sql_condition = Project.allowed_to_condition(User.current, :view_time_entries)
elsif @issue.nil?
sql_condition = @project.project_condition(Setting.display_subprojects_issues?)
else
sql_condition = "#{Issue.table_name}.root_id = #{@issue.root_id} AND #{Issue.table_name}.lft >= #{@issue.lft} AND #{Issue.table_name}.rgt <= #{@issue.rgt}"
end
sql = "SELECT #{sql_select}, tyear, tmonth, tweek, spent_on, SUM(hours) AS hours"
sql << " FROM #{TimeEntry.table_name}"
sql << time_report_joins
sql << " WHERE"
sql << " (%s) AND" % sql_condition
sql << " (spent_on BETWEEN '%s' AND '%s')" % [ActiveRecord::Base.connection.quoted_date(@from), ActiveRecord::Base.connection.quoted_date(@to)]
sql << " GROUP BY #{sql_group_by}, tyear, tmonth, tweek, spent_on"
@hours = ActiveRecord::Base.connection.select_all(sql)
@hours.each do |row|
case @columns
when 'year'
row['year'] = row['tyear']
when 'month'
row['month'] = "#{row['tyear']}-#{row['tmonth']}"
when 'week'
row['week'] = "#{row['tyear']}-#{row['tweek']}"
when 'day'
row['day'] = "#{row['spent_on']}"
end
end
@total_hours = @hours.inject(0) {|s,k| s = s + k['hours'].to_f}
@periods = []
# Date#at_beginning_of_ not supported in Rails 1.2.x
date_from = @from.to_time
# 100 columns max
while date_from <= @to.to_time && @periods.length < 100
case @columns
when 'year'
@periods << "#{date_from.year}"
date_from = (date_from + 1.year).at_beginning_of_year
when 'month'
@periods << "#{date_from.year}-#{date_from.month}"
date_from = (date_from + 1.month).at_beginning_of_month
when 'week'
@periods << "#{date_from.year}-#{date_from.to_date.cweek}"
date_from = (date_from + 7.day).at_beginning_of_week
when 'day'
@periods << "#{date_from.to_date}"
date_from = date_from + 1.day
end
end
end
respond_to do |format|
format.html { render :layout => !request.xhr? }
format.csv { send_data(report_to_csv(@criterias, @periods, @hours), :type => 'text/csv; header=present', :filename => 'timelog.csv') }
end
end
private
# TODO: duplicated in TimelogController
def find_optional_project
if !params[:issue_id].blank?
@issue = Issue.find(params[:issue_id])
@project = @issue.project
elsif !params[:project_id].blank?
@project = Project.find(params[:project_id])
end
deny_access unless User.current.allowed_to?(:view_time_entries, @project, :global => true)
end
# Retrieves the date range based on predefined ranges or specific from/to param dates
# TODO: duplicated in TimelogController
def retrieve_date_range
@free_period = false
@from, @to = nil, nil
if params[:period_type] == '1' || (params[:period_type].nil? && !params[:period].nil?)
case params[:period].to_s
when 'today'
@from = @to = Date.today
when 'yesterday'
@from = @to = Date.today - 1
when 'current_week'
@from = Date.today - (Date.today.cwday - 1)%7
@to = @from + 6
when 'last_week'
@from = Date.today - 7 - (Date.today.cwday - 1)%7
@to = @from + 6
when '7_days'
@from = Date.today - 7
@to = Date.today
when 'current_month'
@from = Date.civil(Date.today.year, Date.today.month, 1)
@to = (@from >> 1) - 1
when 'last_month'
@from = Date.civil(Date.today.year, Date.today.month, 1) << 1
@to = (@from >> 1) - 1
when '30_days'
@from = Date.today - 30
@to = Date.today
when 'current_year'
@from = Date.civil(Date.today.year, 1, 1)
@to = Date.civil(Date.today.year, 12, 31)
end
elsif params[:period_type] == '2' || (params[:period_type].nil? && (!params[:from].nil? || !params[:to].nil?))
begin; @from = params[:from].to_s.to_date unless params[:from].blank?; rescue; end
begin; @to = params[:to].to_s.to_date unless params[:to].blank?; rescue; end
@free_period = true
else
# default
end
@from, @to = @to, @from if @from && @to && @from > @to
@from ||= (TimeEntry.earilest_date_for_project(@project) || Date.today)
@to ||= (TimeEntry.latest_date_for_project(@project) || Date.today)
end
def load_available_criterias
@available_criterias = { 'project' => {:sql => "#{TimeEntry.table_name}.project_id",
:klass => Project,
:label => :label_project},
'version' => {:sql => "#{Issue.table_name}.fixed_version_id",
:klass => Version,
:label => :label_version},
'category' => {:sql => "#{Issue.table_name}.category_id",
:klass => IssueCategory,
:label => :field_category},
'member' => {:sql => "#{TimeEntry.table_name}.user_id",
:klass => User,
:label => :label_member},
'tracker' => {:sql => "#{Issue.table_name}.tracker_id",
:klass => Tracker,
:label => :label_tracker},
'activity' => {:sql => "#{TimeEntry.table_name}.activity_id",
:klass => TimeEntryActivity,
:label => :label_activity},
'issue' => {:sql => "#{TimeEntry.table_name}.issue_id",
:klass => Issue,
:label => :label_issue}
}
# Add list and boolean custom fields as available criterias
custom_fields = (@project.nil? ? IssueCustomField.for_all : @project.all_issue_custom_fields)
custom_fields.select {|cf| %w(list bool).include? cf.field_format }.each do |cf|
@available_criterias["cf_#{cf.id}"] = {:sql => "(SELECT c.value FROM #{CustomValue.table_name} c WHERE c.custom_field_id = #{cf.id} AND c.customized_type = 'Issue' AND c.customized_id = #{Issue.table_name}.id)",
:format => cf.field_format,
:label => cf.name}
end if @project
# Add list and boolean time entry custom fields
TimeEntryCustomField.find(:all).select {|cf| %w(list bool).include? cf.field_format }.each do |cf|
@available_criterias["cf_#{cf.id}"] = {:sql => "(SELECT c.value FROM #{CustomValue.table_name} c WHERE c.custom_field_id = #{cf.id} AND c.customized_type = 'TimeEntry' AND c.customized_id = #{TimeEntry.table_name}.id)",
:format => cf.field_format,
:label => cf.name}
end
# Add list and boolean time entry activity custom fields
TimeEntryActivityCustomField.find(:all).select {|cf| %w(list bool).include? cf.field_format }.each do |cf|
@available_criterias["cf_#{cf.id}"] = {:sql => "(SELECT c.value FROM #{CustomValue.table_name} c WHERE c.custom_field_id = #{cf.id} AND c.customized_type = 'Enumeration' AND c.customized_id = #{TimeEntry.table_name}.activity_id)",
:format => cf.field_format,
:label => cf.name}
end
call_hook(:controller_timelog_available_criterias, { :available_criterias => @available_criterias, :project => @project })
@available_criterias
end
def time_report_joins
sql = ''
sql << " LEFT JOIN #{Issue.table_name} ON #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id"
sql << " LEFT JOIN #{Project.table_name} ON #{TimeEntry.table_name}.project_id = #{Project.table_name}.id"
# TODO: rename hook
call_hook(:controller_timelog_time_report_joins, {:sql => sql} )
sql
end
end

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -17,16 +17,11 @@
class TimelogController < ApplicationController
menu_item :issues
before_filter :find_project_for_new_time_entry, :only => [:create]
before_filter :find_project, :only => [:new, :create]
before_filter :find_time_entry, :only => [:show, :edit, :update]
before_filter :find_time_entries, :only => [:bulk_edit, :bulk_update, :destroy]
before_filter :authorize, :except => [:new, :index, :report]
before_filter :find_optional_project, :only => [:index, :report]
before_filter :find_optional_project_for_new_time_entry, :only => [:new]
before_filter :authorize_global, :only => [:new, :index, :report]
before_filter :authorize, :except => [:index]
before_filter :find_optional_project, :only => [:index]
accept_rss_auth :index
accept_api_auth :index, :show, :create, :update, :destroy
@@ -39,76 +34,67 @@ class TimelogController < ApplicationController
def index
sort_init 'spent_on', 'desc'
sort_update 'spent_on' => ['spent_on', "#{TimeEntry.table_name}.created_on"],
sort_update 'spent_on' => 'spent_on',
'user' => 'user_id',
'activity' => 'activity_id',
'project' => "#{Project.table_name}.name",
'issue' => 'issue_id',
'hours' => 'hours'
retrieve_date_range
scope = TimeEntry.visible.spent_between(@from, @to)
cond = ARCondition.new
if @issue
scope = scope.on_issue(@issue)
cond << "#{Issue.table_name}.root_id = #{@issue.root_id} AND #{Issue.table_name}.lft >= #{@issue.lft} AND #{Issue.table_name}.rgt <= #{@issue.rgt}"
elsif @project
scope = scope.on_project(@project, Setting.display_subprojects_issues?)
cond << @project.project_condition(Setting.display_subprojects_issues?)
end
retrieve_date_range
cond << ['spent_on BETWEEN ? AND ?', @from, @to]
respond_to do |format|
format.html {
# Paginate results
@entry_count = scope.count
@entry_count = TimeEntry.visible.count(:include => [:project, :issue], :conditions => cond.conditions)
@entry_pages = Paginator.new self, @entry_count, per_page_option, params['page']
@entries = scope.all(
:include => [:project, :activity, :user, {:issue => :tracker}],
:order => sort_clause,
:limit => @entry_pages.items_per_page,
:offset => @entry_pages.current.offset
)
@total_hours = scope.sum(:hours).to_f
@entries = TimeEntry.visible.find(:all,
:include => [:project, :activity, :user, {:issue => :tracker}],
:conditions => cond.conditions,
:order => sort_clause,
:limit => @entry_pages.items_per_page,
:offset => @entry_pages.current.offset)
@total_hours = TimeEntry.visible.sum(:hours, :include => [:project, :issue], :conditions => cond.conditions).to_f
render :layout => !request.xhr?
}
format.api {
@entry_count = scope.count
@entry_count = TimeEntry.visible.count(:include => [:project, :issue], :conditions => cond.conditions)
@offset, @limit = api_offset_and_limit
@entries = scope.all(
:include => [:project, :activity, :user, {:issue => :tracker}],
:order => sort_clause,
:limit => @limit,
:offset => @offset
)
@entries = TimeEntry.visible.find(:all,
:include => [:project, :activity, :user, {:issue => :tracker}],
:conditions => cond.conditions,
:order => sort_clause,
:limit => @limit,
:offset => @offset)
}
format.atom {
entries = scope.all(
:include => [:project, :activity, :user, {:issue => :tracker}],
:order => "#{TimeEntry.table_name}.created_on DESC",
:limit => Setting.feeds_limit.to_i
)
entries = TimeEntry.visible.find(:all,
:include => [:project, :activity, :user, {:issue => :tracker}],
:conditions => cond.conditions,
:order => "#{TimeEntry.table_name}.created_on DESC",
:limit => Setting.feeds_limit.to_i)
render_feed(entries, :title => l(:label_spent_time))
}
format.csv {
# Export all entries
@entries = scope.all(
:include => [:project, :activity, :user, {:issue => [:tracker, :assigned_to, :priority]}],
:order => sort_clause
)
@entries = TimeEntry.visible.find(:all,
:include => [:project, :activity, :user, {:issue => [:tracker, :assigned_to, :priority]}],
:conditions => cond.conditions,
:order => sort_clause)
send_data(entries_to_csv(@entries), :type => 'text/csv; header=present', :filename => 'timelog.csv')
}
end
end
def report
retrieve_date_range
@report = Redmine::Helpers::TimeReport.new(@project, @issue, params[:criteria], params[:columns], @from, @to)
respond_to do |format|
format.html { render :layout => !request.xhr? }
format.csv { send_data(report_to_csv(@report), :type => 'text/csv; header=present', :filename => 'timelog.csv') }
end
end
def show
respond_to do |format|
# TODO: Implement html response
@@ -119,49 +105,44 @@ class TimelogController < ApplicationController
def new
@time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => User.current.today)
@time_entry.safe_attributes = params[:time_entry]
@time_entry.attributes = params[:time_entry]
call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
render :action => 'edit'
end
verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed }
def create
@time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => User.current.today)
@time_entry.safe_attributes = params[:time_entry]
@time_entry.attributes = params[:time_entry]
call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
if @time_entry.save
respond_to do |format|
format.html {
flash[:notice] = l(:notice_successful_create)
if params[:continue]
if params[:project_id]
redirect_to :action => 'new', :project_id => @time_entry.project, :issue_id => @time_entry.issue,
:time_entry => {:issue_id => @time_entry.issue_id, :activity_id => @time_entry.activity_id},
:back_url => params[:back_url]
else
redirect_to :action => 'new',
:time_entry => {:project_id => @time_entry.project_id, :issue_id => @time_entry.issue_id, :activity_id => @time_entry.activity_id},
:back_url => params[:back_url]
end
else
redirect_back_or_default :action => 'index', :project_id => @time_entry.project
end
flash[:notice] = l(:notice_successful_update)
redirect_back_or_default :action => 'index', :project_id => @time_entry.project
}
format.api { render :action => 'show', :status => :created, :location => time_entry_url(@time_entry) }
end
else
respond_to do |format|
format.html { render :action => 'new' }
format.html { render :action => 'edit' }
format.api { render_validation_errors(@time_entry) }
end
end
end
def edit
@time_entry.safe_attributes = params[:time_entry]
@time_entry.attributes = params[:time_entry]
call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
end
verify :method => :put, :only => :update, :render => {:nothing => true, :status => :method_not_allowed }
def update
@time_entry.safe_attributes = params[:time_entry]
@time_entry.attributes = params[:time_entry]
call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
@@ -192,7 +173,7 @@ class TimelogController < ApplicationController
unsaved_time_entry_ids = []
@time_entries.each do |time_entry|
time_entry.reload
time_entry.safe_attributes = attributes
time_entry.attributes = attributes
call_hook(:controller_time_entries_bulk_edit_before_save, { :params => params, :time_entry => time_entry })
unless time_entry.save
# Keep unsaved time_entry ids to display them in flash error
@@ -203,31 +184,32 @@ class TimelogController < ApplicationController
redirect_back_or_default({:controller => 'timelog', :action => 'index', :project_id => @projects.first})
end
verify :method => :delete, :only => :destroy, :render => {:nothing => true, :status => :method_not_allowed }
def destroy
destroyed = TimeEntry.transaction do
@time_entries.each do |t|
@time_entries.each do |t|
begin
unless t.destroy && t.destroyed?
raise ActiveRecord::Rollback
respond_to do |format|
format.html {
flash[:error] = l(:notice_unable_delete_time_entry)
redirect_to :back
}
format.api { render_validation_errors(t) }
end
return
end
rescue ::ActionController::RedirectBackError
redirect_to :action => 'index', :project_id => @projects.first
return
end
end
respond_to do |format|
format.html {
if destroyed
flash[:notice] = l(:notice_successful_delete)
else
flash[:error] = l(:notice_unable_delete_time_entry)
end
flash[:notice] = l(:notice_successful_delete)
redirect_back_or_default(:action => 'index', :project_id => @projects.first)
}
format.api {
if destroyed
head :ok
else
render_validation_errors(@time_entries)
end
}
format.api { head :ok }
end
end
@@ -263,25 +245,20 @@ private
end
end
def find_optional_project_for_new_time_entry
if (project_id = (params[:project_id] || params[:time_entry] && params[:time_entry][:project_id])).present?
@project = Project.find(project_id)
end
def find_project
if (issue_id = (params[:issue_id] || params[:time_entry] && params[:time_entry][:issue_id])).present?
@issue = Issue.find(issue_id)
@project ||= @issue.project
@project = @issue.project
elsif (project_id = (params[:project_id] || params[:time_entry] && params[:time_entry][:project_id])).present?
@project = Project.find(project_id)
else
render_404
return false
end
rescue ActiveRecord::RecordNotFound
render_404
end
def find_project_for_new_time_entry
find_optional_project_for_new_time_entry
if @project.nil?
render_404
end
end
def find_optional_project
if !params[:issue_id].blank?
@issue = Issue.find(params[:issue_id])
@@ -289,6 +266,7 @@ private
elsif !params[:project_id].blank?
@project = Project.find(params[:project_id])
end
deny_access unless User.current.allowed_to?(:view_time_entries, @project, :global => true)
end
# Retrieves the date range based on predefined ranges or specific from/to param dates
@@ -333,6 +311,8 @@ private
end
@from, @to = @to, @from if @from && @to && @from > @to
@from ||= (TimeEntry.earilest_date_for_project(@project) || Date.today)
@to ||= (TimeEntry.latest_date_for_project(@project) || Date.today)
end
def parse_params_for_bulk_time_entry_attributes(params)

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -71,6 +71,7 @@ class TrackersController < ApplicationController
render :action => 'edit'
end
verify :method => :delete, :only => :destroy, :redirect_to => { :action => :index }
def destroy
@tracker = Tracker.find(params[:id])
unless @tracker.issues.empty?

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -38,17 +38,23 @@ class UsersController < ApplicationController
@limit = per_page_option
end
@status = params[:status] || 1
scope = User
scope = scope.in_group(params[:group_id].to_i) if params[:group_id].present?
scope = User.logged.status(@status)
scope = scope.like(params[:name]) if params[:name].present?
scope = scope.in_group(params[:group_id]) if params[:group_id].present?
@status = params[:status] ? params[:status].to_i : 1
c = ARCondition.new(@status == 0 ? "status <> 0" : ["status = ?", @status])
@user_count = scope.count
unless params[:name].blank?
name = "%#{params[:name].strip.downcase}%"
c << ["LOWER(login) LIKE ? OR LOWER(firstname) LIKE ? OR LOWER(lastname) LIKE ? OR LOWER(mail) LIKE ?", name, name, name, name]
end
@user_count = scope.count(:conditions => c.conditions)
@user_pages = Paginator.new self, @user_count, @limit, params['page']
@offset ||= @user_pages.current.offset
@users = scope.find :all,
:order => sort_clause,
:conditions => c.conditions,
:limit => @limit,
:offset => @offset
@@ -86,6 +92,7 @@ class UsersController < ApplicationController
@auth_sources = AuthSource.find(:all)
end
verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed }
def create
@user = User.new(:language => Setting.default_language, :mail_notification => Setting.default_notification_option)
@user.safe_attributes = params[:user]
@@ -93,13 +100,15 @@ class UsersController < ApplicationController
@user.login = params[:user][:login]
@user.password, @user.password_confirmation = params[:user][:password], params[:user][:password_confirmation] unless @user.auth_source_id
# TODO: Similar to My#account
@user.pref.attributes = params[:pref]
@user.pref[:no_self_notified] = (params[:no_self_notified] == '1')
if @user.save
@user.pref.attributes = params[:pref]
@user.pref[:no_self_notified] = (params[:no_self_notified] == '1')
@user.pref.save
@user.notified_project_ids = (@user.mail_notification == 'selected' ? params[:notified_project_ids] : [])
Mailer.account_information(@user, params[:user][:password]).deliver if params[:send_information]
Mailer.deliver_account_information(@user, params[:user][:password]) if params[:send_information]
respond_to do |format|
format.html {
@@ -128,6 +137,7 @@ class UsersController < ApplicationController
@membership ||= Member.new
end
verify :method => :put, :only => :update, :render => {:nothing => true, :status => :method_not_allowed }
def update
@user.admin = params[:user][:admin] if params[:user][:admin]
@user.login = params[:user][:login] if params[:user][:login]
@@ -146,15 +156,15 @@ class UsersController < ApplicationController
@user.notified_project_ids = (@user.mail_notification == 'selected' ? params[:notified_project_ids] : [])
if was_activated
Mailer.account_activated(@user).deliver
Mailer.deliver_account_activated(@user)
elsif @user.active? && params[:send_information] && !params[:user][:password].blank? && @user.auth_source_id.nil?
Mailer.account_information(@user, params[:user][:password]).deliver
Mailer.deliver_account_information(@user, params[:user][:password])
end
respond_to do |format|
format.html {
flash[:notice] = l(:notice_successful_update)
redirect_to_referer_or edit_user_path(@user)
redirect_to :back
}
format.api { head :ok }
end
@@ -169,19 +179,22 @@ class UsersController < ApplicationController
format.api { render_validation_errors(@user) }
end
end
rescue ::ActionController::RedirectBackError
redirect_to :controller => 'users', :action => 'edit', :id => @user
end
verify :method => :delete, :only => :destroy, :render => {:nothing => true, :status => :method_not_allowed }
def destroy
@user.destroy
respond_to do |format|
format.html { redirect_to_referer_or(users_url) }
format.html { redirect_to(users_url) }
format.api { head :ok }
end
end
def edit_membership
@membership = Member.edit_membership(params[:membership_id], params[:membership], @user)
@membership.save
@membership.save if request.post?
respond_to do |format|
if @membership.valid?
format.html { redirect_to :controller => 'users', :action => 'edit', :id => @user, :tab => 'memberships' }
@@ -203,7 +216,7 @@ class UsersController < ApplicationController
def destroy_membership
@membership = Member.find(params[:membership_id])
if @membership.deletable?
if request.post? && @membership.deletable?
@membership.destroy
end
respond_to do |format|

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -23,7 +23,7 @@ class VersionsController < ApplicationController
before_filter :find_project, :only => [:index, :new, :create, :close_completed]
before_filter :authorize
accept_api_auth :index, :show, :create, :update, :destroy
accept_api_auth :index, :create, :update, :destroy
helper :custom_fields
helper :projects
@@ -39,19 +39,17 @@ class VersionsController < ApplicationController
@versions = @project.shared_versions || []
@versions += @project.rolled_up_versions.visible if @with_subprojects
@versions = @versions.uniq.sort
unless params[:completed]
@completed_versions = @versions.select {|version| version.closed? || version.completed? }
@versions -= @completed_versions
end
@versions.reject! {|version| version.closed? || version.completed? } unless params[:completed]
@issues_by_version = {}
if @selected_tracker_ids.any? && @versions.any?
issues = Issue.visible.all(
:include => [:project, :status, :tracker, :priority, :fixed_version],
:conditions => {:tracker_id => @selected_tracker_ids, :project_id => project_ids, :fixed_version_id => @versions.map(&:id)},
:order => "#{Project.table_name}.lft, #{Tracker.table_name}.position, #{Issue.table_name}.id"
)
@issues_by_version = issues.group_by(&:fixed_version)
unless @selected_tracker_ids.empty?
@versions.each do |version|
issues = version.fixed_issues.visible.find(:all,
:include => [:project, :status, :tracker, :priority],
:conditions => {:tracker_id => @selected_tracker_ids, :project_id => project_ids},
:order => "#{Project.table_name}.lft, #{Tracker.table_name}.position, #{Issue.table_name}.id")
@issues_by_version[version] = issues
end
end
@versions.reject! {|version| !project_ids.include?(version.project_id) && @issues_by_version[version].blank?}
}
@@ -74,26 +72,20 @@ class VersionsController < ApplicationController
def new
@version = @project.versions.build
@version.safe_attributes = params[:version]
respond_to do |format|
format.html
format.js do
render :update do |page|
page.replace_html 'ajax-modal', :partial => 'versions/new_modal'
page << "showModal('ajax-modal', '600px');"
page << "Form.Element.focus('version_name');"
end
end
if params[:version]
attributes = params[:version].dup
attributes.delete('sharing') unless attributes.nil? || @version.allowed_sharings.include?(attributes['sharing'])
@version.attributes = attributes
end
end
def create
# TODO: refactor with code above in #new
@version = @project.versions.build
if params[:version]
attributes = params[:version].dup
attributes.delete('sharing') unless attributes.nil? || @version.allowed_sharings.include?(attributes['sharing'])
@version.safe_attributes = attributes
@version.attributes = attributes
end
if request.post?
@@ -104,11 +96,9 @@ class VersionsController < ApplicationController
redirect_back_or_default :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
end
format.js do
render(:update) {|page|
page << 'hideModal();'
# IE doesn't support the replace_html rjs method for select box options
page.replace "issue_fixed_version_id",
content_tag('select', content_tag('option') + version_options_for_select(@project.shared_versions.open, @version), :id => 'issue_fixed_version_id', :name => 'issue[fixed_version_id]')
# IE doesn't support the replace_html rjs method for select box options
render(:update) {|page| page.replace "issue_fixed_version_id",
content_tag('select', '<option></option>' + version_options_for_select(@project.shared_versions.open, @version), :id => 'issue_fixed_version_id', :name => 'issue[fixed_version_id]')
}
end
format.api do
@@ -119,10 +109,7 @@ class VersionsController < ApplicationController
respond_to do |format|
format.html { render :action => 'new' }
format.js do
render :update do |page|
page.replace_html 'ajax-modal', :partial => 'versions/new_modal'
page << "Form.Element.focus('version_name');"
end
render(:update) {|page| page.alert(@version.errors.full_messages.join('\n')) }
end
format.api { render_validation_errors(@version) }
end
@@ -137,8 +124,7 @@ class VersionsController < ApplicationController
if request.put? && params[:version]
attributes = params[:version].dup
attributes.delete('sharing') unless @version.allowed_sharings.include?(attributes['sharing'])
@version.safe_attributes = attributes
if @version.save
if @version.update_attributes(attributes)
respond_to do |format|
format.html {
flash[:notice] = l(:notice_successful_update)
@@ -162,6 +148,7 @@ class VersionsController < ApplicationController
redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
end
verify :method => :delete, :only => :destroy, :render => {:nothing => true, :status => :method_not_allowed }
def destroy
if @version.fixed_issues.empty?
@version.destroy

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -20,6 +20,10 @@ class WatchersController < ApplicationController
before_filter :require_login, :check_project_privacy, :only => [:watch, :unwatch]
before_filter :authorize, :only => [:new, :destroy]
verify :method => :post,
:only => [ :watch, :unwatch ],
:render => { :nothing => true, :status => :method_not_allowed }
def watch
if @watched.respond_to?(:visible?) && !@watched.visible?(User.current)
render_403
@@ -33,50 +37,19 @@ class WatchersController < ApplicationController
end
def new
@watcher = Watcher.new(params[:watcher])
@watcher.watchable = @watched
@watcher.save if request.post?
respond_to do |format|
format.html { redirect_to :back }
format.js do
render :update do |page|
page.replace_html 'ajax-modal', :partial => 'watchers/new', :locals => {:watched => @watched}
page << "showModal('ajax-modal', '400px');"
page << "$('ajax-modal').addClassName('new-watcher');"
end
end
end
end
def create
if params[:watcher].is_a?(Hash) && request.post?
user_ids = params[:watcher][:user_ids] || [params[:watcher][:user_id]]
user_ids.each do |user_id|
Watcher.create(:watchable => @watched, :user_id => user_id)
end
end
respond_to do |format|
format.html { redirect_to_referer_or {render :text => 'Watcher added.', :layout => true}}
format.js do
render :update do |page|
page.replace_html 'ajax-modal', :partial => 'watchers/new', :locals => {:watched => @watched}
page.replace_html 'watchers', :partial => 'watchers/watchers', :locals => {:watched => @watched}
end
end
end
end
def append
if params[:watcher].is_a?(Hash)
user_ids = params[:watcher][:user_ids] || [params[:watcher][:user_id]]
users = User.active.find_all_by_id(user_ids)
respond_to do |format|
format.js do
render :update do |page|
users.each do |user|
page << %|$$("#issue_watcher_user_ids_#{user.id}").each(function(el){el.remove();});|
end
page.insert_html :bottom, 'watchers_inputs', :text => watchers_checkboxes(nil, users, true)
end
end
end
end
rescue ::ActionController::RedirectBackError
render :text => 'Watcher added.', :layout => true
end
def destroy
@@ -91,24 +64,12 @@ class WatchersController < ApplicationController
end
end
def autocomplete_for_user
@users = User.active.like(params[:q]).find(:all, :limit => 100)
if @watched
@users -= @watched.watcher_users
end
render :layout => false
end
private
def find_project
if params[:object_type] && params[:object_id]
klass = Object.const_get(params[:object_type].camelcase)
return false unless klass.respond_to?('watched_by')
@watched = klass.find(params[:object_id])
@project = @watched.project
elsif params[:project_id]
@project = Project.visible.find_by_param(params[:project_id])
end
klass = Object.const_get(params[:object_type].camelcase)
return false unless klass.respond_to?('watched_by')
@watched = klass.find(params[:object_id])
@project = @watched.project
rescue
render_404
end
@@ -116,13 +77,17 @@ private
def set_watcher(user, watching)
@watched.set_watcher(user, watching)
respond_to do |format|
format.html { redirect_to_referer_or {render :text => (watching ? 'Watcher added.' : 'Watcher removed.'), :layout => true}}
format.html { redirect_to :back }
format.js do
render(:update) do |page|
c = watcher_css(@watched)
page << %|$$(".#{c}").each(function(el){el.innerHTML="#{escape_javascript watcher_link(@watched, user)}"});|
page.select(".#{c}").each do |item|
page.replace_html item, watcher_link(@watched, user)
end
end
end
end
rescue ::ActionController::RedirectBackError
render :text => (watching ? 'Watcher added.' : 'Watcher removed.'), :layout => true
end
end

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -73,7 +73,7 @@ class WikiController < ApplicationController
@content = @page.content_for_version(params[:version])
if User.current.allowed_to?(:export_wiki_pages, @project)
if params[:format] == 'pdf'
send_data(wiki_page_to_pdf(@page, @project), :type => 'application/pdf', :filename => "#{@page.title}.pdf")
send_data(wiki_to_pdf(@page, @project), :type => 'application/pdf', :filename => "#{@page.title}.pdf")
return
elsif params[:format] == 'html'
export = render_to_string :action => 'export', :layout => false
@@ -95,12 +95,7 @@ class WikiController < ApplicationController
# edit an existing page or a new one
def edit
return render_403 unless editable?
if @page.new_record?
@page.content = WikiContent.new(:page => @page)
if params[:parent].present?
@page.parent = @page.wiki.find_page(params[:parent].to_s)
end
end
@page.content = WikiContent.new(:page => @page) if @page.new_record?
@content = @page.content_for_version(params[:version])
@content.text = initial_page_content(@page) if @content.text.blank?
@@ -118,11 +113,11 @@ class WikiController < ApplicationController
end
end
verify :method => :put, :only => :update, :render => {:nothing => true, :status => :method_not_allowed }
# Creates a new page or updates an existing one
def update
return render_403 unless editable?
@page.content = WikiContent.new(:page => @page) if @page.new_record?
@page.safe_attributes = params[:wiki_page]
@content = @page.content_for_version(params[:version])
@content.text = initial_page_content(@page) if @content.text.blank?
@@ -132,12 +127,11 @@ class WikiController < ApplicationController
if !@page.new_record? && params[:content].present? && @content.text == params[:content][:text]
attachments = Attachment.attach_files(@page, params[:attachments])
render_attachment_warning_if_needed(@page)
# don't save content if text wasn't changed
@page.save
# don't save if text wasn't changed
redirect_to :action => 'show', :project_id => @project, :id => @page.title
return
end
@content.comments = params[:content][:comments]
@text = params[:content][:text]
if params[:section].present? && Redmine::WikiFormatting.supports_section_edit?
@@ -149,8 +143,8 @@ class WikiController < ApplicationController
@content.text = @text
end
@content.author = User.current
@page.content = @content
if @page.save
# if page is new @page.save will also save content, but not if page isn't a new record
if (@page.new_record? ? @page.save : @content.save)
attachments = Attachment.attach_files(@page, params[:attachments])
render_attachment_warning_if_needed(@page)
call_hook(:controller_wiki_edit_after_save, { :params => params, :page => @page})
@@ -163,8 +157,6 @@ class WikiController < ApplicationController
# Optimistic locking exception
flash.now[:error] = l(:notice_locking_conflict)
render :action => 'edit'
rescue ActiveRecord::RecordNotSaved
render :action => 'edit'
end
# rename a page
@@ -179,6 +171,7 @@ class WikiController < ApplicationController
end
end
verify :method => :post, :only => :protect, :redirect_to => { :action => :show }
def protect
@page.update_attribute :protected, params[:protected]
redirect_to :action => 'show', :project_id => @project, :id => @page.title
@@ -208,6 +201,7 @@ class WikiController < ApplicationController
render_404 unless @annotate
end
verify :method => :delete, :only => [:destroy], :redirect_to => { :action => :show }
# Removes a wiki page and its history
# Children can be either set as root pages, removed or reassigned to another parent page
def destroy
@@ -237,17 +231,14 @@ class WikiController < ApplicationController
redirect_to :action => 'index', :project_id => @project
end
# Export wiki to a single pdf or html file
# Export wiki to a single html file
def export
@pages = @wiki.pages.all(:order => 'title', :include => [:content, :attachments], :limit => 75)
respond_to do |format|
format.html {
export = render_to_string :action => 'export_multiple', :layout => false
send_data(export, :type => 'text/html', :filename => "wiki.html")
}
format.pdf {
send_data(wiki_pages_to_pdf(@pages, @project), :type => 'application/pdf', :filename => "#{@project.identifier}.pdf")
}
if User.current.allowed_to?(:export_wiki_pages, @project)
@pages = @wiki.pages.find :all, :order => 'title'
export = render_to_string :action => 'export_multiple', :layout => false
send_data(export, :type => 'text/html', :filename => "wiki.html")
else
redirect_to :action => 'show', :project_id => @project, :id => nil
end
end

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -22,7 +22,7 @@ class WikisController < ApplicationController
# Create or update a project's wiki
def edit
@wiki = @project.wiki || Wiki.new(:project => @project)
@wiki.safe_attributes = params[:wiki]
@wiki.attributes = params[:wiki]
@wiki.save if request.post?
render(:update) {|page| page.replace_html "tab-content-wiki", :partial => 'projects/settings/wiki'}
end

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License

View File

@@ -1,7 +1,5 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License

View File

@@ -1,7 +1,5 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -20,6 +18,6 @@
module AdminHelper
def project_status_options_for_select(selected)
options_for_select([[l(:label_all), ''],
[l(:status_active), '1']], selected.to_s)
[l(:status_active), 1]], selected)
end
end

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -85,8 +85,8 @@ module ApplicationHelper
s = link_to "#{h(issue.tracker)} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue},
:class => issue.css_classes,
:title => title
s << h(": #{subject}") if subject
s = h("#{issue.project} - ") + s if options[:project]
s << ": #{h subject}" if subject
s = "#{h issue.project} - " + s if options[:project]
s
end
@@ -97,29 +97,21 @@ module ApplicationHelper
def link_to_attachment(attachment, options={})
text = options.delete(:text) || attachment.filename
action = options.delete(:download) ? 'download' : 'show'
opt_only_path = {}
opt_only_path[:only_path] = (options[:only_path] == false ? false : true)
options.delete(:only_path)
link_to(h(text),
{:controller => 'attachments', :action => action,
:id => attachment, :filename => attachment.filename}.merge(opt_only_path),
:id => attachment, :filename => attachment.filename },
options)
end
# Generates a link to a SCM revision
# Options:
# * :text - Link text (default to the formatted revision)
def link_to_revision(revision, repository, options={})
if repository.is_a?(Project)
repository = repository.repository
end
def link_to_revision(revision, project, options={})
text = options.delete(:text) || format_revision(revision)
rev = revision.respond_to?(:identifier) ? revision.identifier : revision
link_to(
h(text),
{:controller => 'repositories', :action => 'revision', :id => repository.project, :repository_id => repository.identifier_param, :rev => rev},
:title => l(:label_revision_id, format_revision(revision))
)
link_to(h(text), {:controller => 'repositories', :action => 'revision', :id => project, :rev => rev},
:title => l(:label_revision_id, format_revision(revision)))
end
# Generates a link to a message
@@ -178,12 +170,11 @@ module ApplicationHelper
end
def format_activity_day(date)
date == User.current.today ? l(:label_today).titleize : format_date(date)
date == Date.today ? l(:label_today).titleize : format_date(date)
end
def format_activity_description(text)
h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')
).gsub(/[\r\n]+/, "<br />").html_safe
h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')).gsub(/[\r\n]+/, "<br />")
end
def format_version_name(version)
@@ -220,7 +211,7 @@ module ApplicationHelper
def render_flash_messages
s = ''
flash.each do |k,v|
s << content_tag('div', v.html_safe, :class => "flash #{k}", :id => "flash_#{k}")
s << content_tag('div', v, :class => "flash #{k}")
end
s.html_safe
end
@@ -253,7 +244,7 @@ module ApplicationHelper
def project_tree_options_for_select(projects, options = {})
s = ''
project_tree(projects) do |project, level|
name_prefix = (level > 0 ? ('&nbsp;' * 2 * level + '&#187; ').html_safe : '')
name_prefix = (level > 0 ? ('&nbsp;' * 2 * level + '&#187; ') : '')
tag_options = {:value => project.id}
if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project))
tag_options[:selected] = 'selected'
@@ -308,9 +299,6 @@ module ApplicationHelper
# Returns a string for users/groups option tags
def principals_options_for_select(collection, selected=nil)
s = ''
if collection.include?(User.current)
s << content_tag('option', "<< #{l(:label_me)} >>".html_safe, :value => User.current.id)
end
groups = ''
collection.sort.each do |element|
selected_attribute = ' selected="selected"' if option_value_selected?(element, selected)
@@ -319,7 +307,7 @@ module ApplicationHelper
unless groups.empty?
s << %(<optgroup label="#{h(l(:label_group_plural))}">#{groups}</optgroup>)
end
s.html_safe
s
end
# Truncates and returns the string as a single line
@@ -337,10 +325,6 @@ module ApplicationHelper
end
end
def anchor(text)
text.to_s.gsub(' ', '_')
end
def html_hours(text)
text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>').html_safe
end
@@ -352,25 +336,18 @@ module ApplicationHelper
def time_tag(time)
text = distance_of_time_in_words(Time.now, time)
if @project
link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => User.current.time_to_date(time)}, :title => format_time(time))
link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => time.to_date}, :title => format_time(time))
else
content_tag('acronym', text, :title => format_time(time))
end
end
def syntax_highlight_lines(name, content)
lines = []
syntax_highlight(name, content).each_line { |line| lines << line }
lines
end
def syntax_highlight(name, content)
Redmine::SyntaxHighlighting.highlight_by_filename(content, name)
end
def to_path_param(path)
str = path.to_s.split(%r{[/\\]}).select{|p| !p.blank?}.join("/")
str.blank? ? nil : str
path.to_s.split(%r{[/\\]}).select {|p| !p.blank?}
end
def pagination_links_full(paginator, count=nil, options={})
@@ -399,7 +376,7 @@ module ApplicationHelper
unless count.nil?
html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})"
if per_page_links != false && links = per_page_links(paginator.items_per_page, count)
if per_page_links != false && links = per_page_links(paginator.items_per_page)
html << " | #{links}"
end
end
@@ -407,23 +384,11 @@ module ApplicationHelper
html.html_safe
end
def per_page_links(selected=nil, item_count=nil)
values = Setting.per_page_options_array
if item_count && values.any?
if item_count > values.first
max = values.detect {|value| value >= item_count} || item_count
else
max = item_count
end
values = values.select {|value| value <= max || value == selected}
end
if values.empty? || (values.size == 1 && values.first == selected)
return nil
end
links = values.collect do |n|
def per_page_links(selected=nil)
links = Setting.per_page_options_array.collect do |n|
n == selected ? n : link_to_content_update(n, params.merge(:per_page => n))
end
l(:label_display_per_page, links.join(', '))
links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
end
def reorder_links(name, url, method = :post)
@@ -492,8 +457,8 @@ module ApplicationHelper
css << 'theme-' + theme.name
end
css << 'controller-' + controller_name
css << 'action-' + action_name
css << 'controller-' + params[:controller]
css << 'action-' + params[:action]
css.join(' ')
end
@@ -525,22 +490,18 @@ module ApplicationHelper
text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr)
@parsed_headings = []
@heading_anchors = {}
@current_section = 0 if options[:edit_section_links]
parse_sections(text, project, obj, attr, only_path, options)
text = parse_non_pre_blocks(text) do |text|
[:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links, :parse_macros].each do |method_name|
[:parse_sections, :parse_inline_attachments, :parse_wiki_links, :parse_redmine_links, :parse_macros, :parse_headings].each do |method_name|
send method_name, text, project, obj, attr, only_path, options
end
end
parse_headings(text, project, obj, attr, only_path, options)
if @parsed_headings.any?
replace_toc(text, @parsed_headings)
end
text.html_safe
text
end
def parse_non_pre_blocks(text)
@@ -569,7 +530,7 @@ module ApplicationHelper
while tag = tags.pop
parsed << "</#{tag}>"
end
parsed
parsed.html_safe
end
def parse_inline_attachments(text, project, obj, attr, only_path, options)
@@ -586,9 +547,9 @@ module ApplicationHelper
if !desc.blank? && alttext.blank?
alt = " title=\"#{desc}\" alt=\"#{desc}\""
end
"src=\"#{image_url}\"#{alt}"
"src=\"#{image_url}\"#{alt}".html_safe
else
m
m.html_safe
end
end
end
@@ -632,18 +593,16 @@ module ApplicationHelper
when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export
else
wiki_page_id = page.present? ? Wiki.titleize(page) : nil
parent = wiki_page.nil? && obj.is_a?(WikiContent) && obj.page && project == link_project ? obj.page.title : nil
url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project,
:id => wiki_page_id, :anchor => anchor, :parent => parent)
url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project, :id => wiki_page_id, :anchor => anchor)
end
end
link_to(title.present? ? title.html_safe : h(page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
else
# project or wiki doesn't exist
all
all.html_safe
end
else
all
all.html_safe
end
end
end
@@ -681,35 +640,26 @@ module ApplicationHelper
# identifier:version:1.0.0
# identifier:source:some/file
def parse_redmine_links(text, project, obj, attr, only_path, options)
text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-_]+):)?(attachment|document|version|forum|news|message|project|commit|source|export)?(((#)|((([a-z0-9\-]+)\|)?(r)))((\d+)((#note)?-(\d+))?)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]][^A-Za-z0-9_/])|,|\s|\]|<|$)}) do |m|
leading, esc, project_prefix, project_identifier, prefix, repo_prefix, repo_identifier, sep, identifier, comment_suffix, comment_id = $1, $2, $3, $4, $5, $10, $11, $8 || $12 || $18, $14 || $19, $15, $17
text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-]+):)?(attachment|document|version|forum|news|commit|source|export|message|project)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|,|\s|\]|<|$)}) do |m|
leading, esc, project_prefix, project_identifier, prefix, sep, identifier = $1, $2, $3, $4, $5, $7 || $9, $8 || $10
link = nil
if project_identifier
project = Project.visible.find_by_identifier(project_identifier)
end
if esc.nil?
if prefix.nil? && sep == 'r'
if project
repository = nil
if repo_identifier
repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
else
repository = project.repository
end
# project.changesets.visible raises an SQL error because of a double join on repositories
if repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(repository.id, identifier))
link = link_to(h("#{project_prefix}#{repo_prefix}r#{identifier}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :repository_id => repository.identifier_param, :rev => changeset.revision},
:class => 'changeset',
:title => truncate_single_line(changeset.comments, :length => 100))
end
# project.changesets.visible raises an SQL error because of a double join on repositories
if project && project.repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(project.repository.id, identifier))
link = link_to(h("#{project_prefix}r#{identifier}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
:class => 'changeset',
:title => truncate_single_line(changeset.comments, :length => 100))
end
elsif sep == '#'
oid = identifier.to_i
case prefix
when nil
if issue = Issue.visible.find_by_id(oid, :include => :status)
anchor = comment_id ? "note-#{comment_id}" : nil
link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid, :anchor => anchor},
link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
:class => issue.css_classes,
:title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
end
@@ -766,34 +716,22 @@ module ApplicationHelper
link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
:class => 'news'
end
when 'commit', 'source', 'export'
if project
repository = nil
if name =~ %r{^(([a-z0-9\-]+)\|)(.+)$}
repo_prefix, repo_identifier, name = $1, $2, $3
repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
else
repository = project.repository
end
if prefix == 'commit'
if repository && (changeset = Changeset.visible.find(:first, :conditions => ["repository_id = ? AND scmid LIKE ?", repository.id, "#{name}%"]))
link = link_to h("#{project_prefix}#{repo_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :repository_id => repository.identifier_param, :rev => changeset.identifier},
:class => 'changeset',
:title => truncate_single_line(h(changeset.comments), :length => 100)
end
else
if repository && User.current.allowed_to?(:browse_repository, project)
name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
path, rev, anchor = $1, $3, $5
link = link_to h("#{project_prefix}#{prefix}:#{repo_prefix}#{name}"), {:controller => 'repositories', :action => 'entry', :id => project, :repository_id => repository.identifier_param,
:path => to_path_param(path),
:rev => rev,
:anchor => anchor,
:format => (prefix == 'export' ? 'raw' : nil)},
:class => (prefix == 'export' ? 'source download' : 'source')
end
end
repo_prefix = nil
when 'commit'
if project && project.repository && (changeset = Changeset.visible.find(:first, :conditions => ["repository_id = ? AND scmid LIKE ?", project.repository.id, "#{name}%"]))
link = link_to h("#{project_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.identifier},
:class => 'changeset',
:title => truncate_single_line(h(changeset.comments), :length => 100)
end
when 'source', 'export'
if project && project.repository && User.current.allowed_to?(:browse_repository, project)
name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
path, rev, anchor = $1, $3, $5
link = link_to h("#{project_prefix}#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project,
:path => to_path_param(path),
:rev => rev,
:anchor => anchor,
:format => (prefix == 'export' ? 'raw' : nil)},
:class => (prefix == 'export' ? 'source download' : 'source')
end
when 'attachment'
attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
@@ -808,7 +746,7 @@ module ApplicationHelper
end
end
end
(leading + (link || "#{project_prefix}#{prefix}#{repo_prefix}#{sep}#{identifier}#{comment_suffix}"))
(leading + (link || "#{project_prefix}#{prefix}#{sep}#{identifier}")).html_safe
end
end
@@ -817,15 +755,14 @@ module ApplicationHelper
def parse_sections(text, project, obj, attr, only_path, options)
return unless options[:edit_section_links]
text.gsub!(HEADING_RE) do
heading = $1
@current_section += 1
if @current_section > 1
content_tag('div',
link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)),
:class => 'contextual',
:title => l(:button_edit_section)) + heading.html_safe
:title => l(:button_edit_section)) + $1
else
heading
$1
end
end
end
@@ -841,11 +778,6 @@ module ApplicationHelper
anchor = sanitize_anchor_name(item)
# used for single-file wiki export
anchor = "#{obj.page.title}_#{anchor}" if options[:wiki_links] == :anchor && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version))
@heading_anchors[anchor] ||= 0
idx = (@heading_anchors[anchor] += 1)
if idx > 1
anchor = "#{anchor}-#{idx}"
end
@parsed_headings << [level, anchor, item]
"<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>"
end
@@ -931,57 +863,25 @@ module ApplicationHelper
end
def labelled_tabular_form_for(*args, &proc)
ActiveSupport::Deprecation.warn "ApplicationHelper#labelled_tabular_form_for is deprecated and will be removed in Redmine 1.5. Use #labelled_form_for instead."
args << {} unless args.last.is_a?(Hash)
options = args.last
options[:html] ||= {}
options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
options.merge!({:builder => TabularFormBuilder})
form_for(*args, &proc)
end
def labelled_form_for(*args, &proc)
args << {} unless args.last.is_a?(Hash)
options = args.last
if args.first.is_a?(Symbol)
options.merge!(:as => args.shift)
end
options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
options.merge!({:builder => TabularFormBuilder})
form_for(*args, &proc)
end
def labelled_fields_for(*args, &proc)
args << {} unless args.last.is_a?(Hash)
options = args.last
options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
fields_for(*args, &proc)
end
def labelled_remote_form_for(*args, &proc)
args << {} unless args.last.is_a?(Hash)
options = args.last
options.merge!({:builder => Redmine::Views::LabelledFormBuilder, :remote => true})
form_for(*args, &proc)
end
def error_messages_for(*objects)
html = ""
objects = objects.map {|o| o.is_a?(String) ? instance_variable_get("@#{o}") : o}.compact
errors = objects.map {|o| o.errors.full_messages}.flatten
if errors.any?
html << "<div id='errorExplanation'><ul>\n"
errors.each do |error|
html << "<li>#{h error}</li>\n"
end
html << "</ul></div>\n"
end
html.html_safe
end
def back_url_hidden_field_tag
back_url = params[:back_url] || request.env['HTTP_REFERER']
back_url = CGI.unescape(back_url.to_s)
hidden_field_tag('back_url', CGI.escape(back_url), :id => nil) unless back_url.blank?
hidden_field_tag('back_url', CGI.escape(back_url)) unless back_url.blank?
end
def check_all_links(form_name)
@@ -1028,6 +928,22 @@ module ApplicationHelper
javascript_tag "new ContextMenu('#{ url_for(url) }')"
end
def context_menu_link(name, url, options={})
options[:class] ||= ''
if options.delete(:selected)
options[:class] << ' icon-checked disabled'
options[:disabled] = true
end
if options.delete(:disabled)
options.delete(:method)
options.delete(:confirm)
options.delete(:onclick)
options[:class] << ' disabled'
url = '#'
end
link_to h(name), url, options
end
def calendar_for(field_id)
include_calendar_headers_tags
image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
@@ -1058,59 +974,6 @@ module ApplicationHelper
end
end
# Overrides Rails' stylesheet_link_tag with themes and plugins support.
# Examples:
# stylesheet_link_tag('styles') # => picks styles.css from the current theme or defaults
# stylesheet_link_tag('styles', :plugin => 'foo) # => picks styles.css from plugin's assets
#
def stylesheet_link_tag(*sources)
options = sources.last.is_a?(Hash) ? sources.pop : {}
plugin = options.delete(:plugin)
sources = sources.map do |source|
if plugin
"/plugin_assets/#{plugin}/stylesheets/#{source}"
elsif current_theme && current_theme.stylesheets.include?(source)
current_theme.stylesheet_path(source)
else
source
end
end
super sources, options
end
# Overrides Rails' image_tag with themes and plugins support.
# Examples:
# image_tag('image.png') # => picks image.png from the current theme or defaults
# image_tag('image.png', :plugin => 'foo) # => picks image.png from plugin's assets
#
def image_tag(source, options={})
if plugin = options.delete(:plugin)
source = "/plugin_assets/#{plugin}/images/#{source}"
elsif current_theme && current_theme.images.include?(source)
source = current_theme.image_path(source)
end
super source, options
end
# Overrides Rails' javascript_include_tag with plugins support
# Examples:
# javascript_include_tag('scripts') # => picks scripts.js from defaults
# javascript_include_tag('scripts', :plugin => 'foo) # => picks scripts.js from plugin's assets
#
def javascript_include_tag(*sources)
options = sources.last.is_a?(Hash) ? sources.pop : {}
if plugin = options.delete(:plugin)
sources = sources.map do |source|
if plugin
"/plugin_assets/#{plugin}/javascripts/#{source}"
else
source
end
end
end
super sources, options
end
def content_for(name, content = nil, &block)
@has_content ||= {}
@has_content[name] = true
@@ -1121,14 +984,6 @@ module ApplicationHelper
(@has_content && @has_content[name]) || false
end
def sidebar_content?
has_content?(:sidebar) || view_layouts_base_sidebar_hook_response.present?
end
def view_layouts_base_sidebar_hook_response
@view_layouts_base_sidebar_hook_response ||= call_hook(:view_layouts_base_sidebar)
end
def email_delivery_enabled?
!!ActionMailer::Base.perform_deliveries
end
@@ -1137,7 +992,7 @@ module ApplicationHelper
# +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 = { })
if Setting.gravatar_enabled?
options.merge!({:ssl => (request && request.ssl?), :default => Setting.gravatar_default})
options.merge!({:ssl => (defined?(request) && request.ssl?), :default => Setting.gravatar_default})
email = nil
if user.respond_to?(:mail)
email = user.mail
@@ -1156,7 +1011,7 @@ module ApplicationHelper
# Returns the javascript tags that are included in the html layout head
def javascript_heads
tags = javascript_include_tag('prototype', 'effects', 'dragdrop', 'controls', 'rails', 'application')
tags = javascript_include_tag(:defaults)
unless User.current.pref.warn_on_leaving_unsaved == '0'
tags << "\n".html_safe + javascript_tag("Event.observe(window, 'load', function(){ new WarnLeavingUnsaved('#{escape_javascript( l(:text_warn_on_leaving_unsaved) )}'); });")
end

View File

@@ -1,7 +1,5 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License

View File

@@ -1,7 +1,5 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -18,7 +16,4 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module AuthSourcesHelper
def auth_source_partial_name(auth_source)
"form_#{auth_source.class.name.underscore}"
end
end

View File

@@ -1,7 +1,5 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License

View File

@@ -1,22 +1,3 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module CalendarsHelper
def link_to_previous_month(year, month, options={})
target_year, target_month = if month == 1

View File

@@ -1,43 +0,0 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module ContextMenusHelper
def context_menu_link(name, url, options={})
options[:class] ||= ''
if options.delete(:selected)
options[:class] << ' icon-checked disabled'
options[:disabled] = true
end
if options.delete(:disabled)
options.delete(:method)
options.delete(:confirm)
options.delete(:onclick)
options[:class] << ' disabled'
url = '#'
end
link_to h(name), url, options
end
def bulk_update_custom_field_context_menu_link(field, text, value)
context_menu_link h(text),
{:controller => 'issues', :action => 'bulk_update', :ids => @issues.collect(&:id), :issue => {'custom_field_values' => {field.id => value}}, :back_url => @back},
:method => :post,
:selected => (@issue && @issue.custom_field_value(field) == value)
end
end

View File

@@ -1,7 +1,5 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -36,39 +34,24 @@ module CustomFieldsHelper
def custom_field_tag(name, custom_value)
custom_field = custom_value.custom_field
field_name = "#{name}[custom_field_values][#{custom_field.id}]"
field_name << "[]" if custom_field.multiple?
field_id = "#{name}_custom_field_values_#{custom_field.id}"
tag_options = {:id => field_id, :class => "#{custom_field.field_format}_cf"}
field_format = Redmine::CustomFieldFormat.find_by_name(custom_field.field_format)
case field_format.try(:edit_as)
when "date"
text_field_tag(field_name, custom_value.value, tag_options.merge(:size => 10)) +
text_field_tag(field_name, custom_value.value, :id => field_id, :size => 10) +
calendar_for(field_id)
when "text"
text_area_tag(field_name, custom_value.value, tag_options.merge(:rows => 3))
text_area_tag(field_name, custom_value.value, :id => field_id, :rows => 3, :style => 'width:90%')
when "bool"
hidden_field_tag(field_name, '0') + check_box_tag(field_name, '1', custom_value.true?, tag_options)
hidden_field_tag(field_name, '0') + check_box_tag(field_name, '1', custom_value.true?, :id => field_id)
when "list"
blank_option = ''.html_safe
unless custom_field.multiple?
if custom_field.is_required?
unless custom_field.default_value.present?
blank_option = content_tag('option', "--- #{l(:actionview_instancetag_blank_option)} ---", :value => '')
end
else
blank_option = content_tag('option')
end
end
s = select_tag(field_name, blank_option + options_for_select(custom_field.possible_values_options(custom_value.customized), custom_value.value),
tag_options.merge(:multiple => custom_field.multiple?))
if custom_field.multiple?
s << hidden_field_tag(field_name, '')
end
s
blank_option = custom_field.is_required? ?
(custom_field.default_value.blank? ? "<option value=\"\">--- #{l(:actionview_instancetag_blank_option)} ---</option>" : '') :
'<option></option>'
select_tag(field_name, blank_option + options_for_select(custom_field.possible_values_options(custom_value.customized), custom_value.value), :id => field_id)
else
text_field_tag(field_name, custom_value.value, tag_options)
text_field_tag(field_name, custom_value.value, :id => field_id)
end
end
@@ -76,7 +59,8 @@ module CustomFieldsHelper
def custom_field_label_tag(name, custom_value)
content_tag "label", h(custom_value.custom_field.name) +
(custom_value.custom_field.is_required? ? " <span class=\"required\">*</span>".html_safe : ""),
:for => "#{name}_custom_field_values_#{custom_value.custom_field.id}"
:for => "#{name}_custom_field_values_#{custom_value.custom_field.id}",
:class => (custom_value.errors.empty? ? nil : "error" )
end
# Return custom field tag with its label tag
@@ -86,30 +70,22 @@ module CustomFieldsHelper
def custom_field_tag_for_bulk_edit(name, custom_field, projects=nil)
field_name = "#{name}[custom_field_values][#{custom_field.id}]"
field_name << "[]" if custom_field.multiple?
field_id = "#{name}_custom_field_values_#{custom_field.id}"
tag_options = {:id => field_id, :class => "#{custom_field.field_format}_cf"}
field_format = Redmine::CustomFieldFormat.find_by_name(custom_field.field_format)
case field_format.try(:edit_as)
when "date"
text_field_tag(field_name, '', tag_options.merge(:size => 10)) +
text_field_tag(field_name, '', :id => field_id, :size => 10) +
calendar_for(field_id)
when "text"
text_area_tag(field_name, '', tag_options.merge(:rows => 3))
text_area_tag(field_name, '', :id => field_id, :rows => 3, :style => 'width:90%')
when "bool"
select_tag(field_name, options_for_select([[l(:label_no_change_option), ''],
[l(:general_text_yes), '1'],
[l(:general_text_no), '0']]), tag_options)
[l(:general_text_no), '0']]), :id => field_id)
when "list"
options = []
options << [l(:label_no_change_option), ''] unless custom_field.multiple?
options << [l(:label_none), '__none__'] unless custom_field.is_required?
options += custom_field.possible_values_options(projects)
select_tag(field_name, options_for_select(options), tag_options.merge(:multiple => custom_field.multiple?))
select_tag(field_name, options_for_select([[l(:label_no_change_option), '']] + custom_field.possible_values_options(projects)), :id => field_id)
else
text_field_tag(field_name, '', tag_options)
text_field_tag(field_name, '', :id => field_id)
end
end
@@ -121,11 +97,7 @@ module CustomFieldsHelper
# Return a string used to display a custom value
def format_value(value, field_format)
if value.is_a?(Array)
value.collect {|v| format_value(v, field_format)}.compact.sort.join(', ')
else
Redmine::CustomFieldFormat.format_value(value, field_format)
end
Redmine::CustomFieldFormat.format_value(value, field_format) # Proxy
end
# Return an array of custom field formats which can be used in select_tag
@@ -137,18 +109,8 @@ module CustomFieldsHelper
def render_api_custom_values(custom_values, api)
api.array :custom_fields do
custom_values.each do |custom_value|
attrs = {:id => custom_value.custom_field_id, :name => custom_value.custom_field.name}
attrs.merge!(:multiple => true) if custom_value.custom_field.multiple?
api.custom_field attrs do
if custom_value.value.is_a?(Array)
api.array :value do
custom_value.value.each do |value|
api.value value unless value.blank?
end
end
else
api.value custom_value.value
end
api.custom_field :id => custom_value.custom_field_id, :name => custom_value.custom_field.name do
api.value custom_value.value
end
end
end unless custom_values.empty?

View File

@@ -1,7 +1,5 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License

View File

@@ -1,7 +1,5 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License

View File

@@ -1,7 +1,5 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License

View File

@@ -1,7 +1,5 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License

View File

@@ -1,7 +1,5 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License

View File

@@ -0,0 +1,2 @@
module IssueMovesHelper
end

View File

@@ -1,7 +1,5 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License

View File

@@ -1,7 +1,5 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License

View File

@@ -1,7 +1,5 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -48,13 +46,13 @@ module IssuesHelper
@cached_label_priority ||= l(:field_priority)
@cached_label_project ||= l(:field_project)
link_to_issue(issue) + "<br /><br />".html_safe +
"<strong>#{@cached_label_project}</strong>: #{link_to_project(issue.project)}<br />".html_safe +
"<strong>#{@cached_label_status}</strong>: #{h(issue.status.name)}<br />".html_safe +
"<strong>#{@cached_label_start_date}</strong>: #{format_date(issue.start_date)}<br />".html_safe +
"<strong>#{@cached_label_due_date}</strong>: #{format_date(issue.due_date)}<br />".html_safe +
"<strong>#{@cached_label_assigned_to}</strong>: #{h(issue.assigned_to)}<br />".html_safe +
"<strong>#{@cached_label_priority}</strong>: #{h(issue.priority.name)}".html_safe
(link_to_issue(issue) + "<br /><br />" +
"<strong>#{@cached_label_project}</strong>: #{link_to_project(issue.project)}<br />" +
"<strong>#{@cached_label_status}</strong>: #{h(issue.status.name)}<br />" +
"<strong>#{@cached_label_start_date}</strong>: #{format_date(issue.start_date)}<br />" +
"<strong>#{@cached_label_due_date}</strong>: #{format_date(issue.due_date)}<br />" +
"<strong>#{@cached_label_assigned_to}</strong>: #{h(issue.assigned_to)}<br />" +
"<strong>#{@cached_label_priority}</strong>: #{h(issue.priority.name)}").html_safe
end
def issue_heading(issue)
@@ -88,7 +86,7 @@ module IssuesHelper
content_tag('td', progress_bar(child.done_ratio, :width => '80px')),
:class => "issue issue-#{child.id} hascontextmenu #{level > 0 ? "idnt idnt-#{level}" : nil}")
end
s << '</table></form>'
s << '</form></table>'
s.html_safe
end
@@ -131,11 +129,14 @@ module IssuesHelper
def sidebar_queries
unless @sidebar_queries
@sidebar_queries = Query.visible.all(
:order => "#{Query.table_name}.name ASC",
# Project specific queries and global queries
:conditions => (@project.nil? ? ["project_id IS NULL"] : ["project_id IS NULL OR project_id = ?", @project.id])
)
# User can see public queries and his own queries
visible = ARCondition.new(["is_public = ? OR user_id = ?", true, (User.current.logged? ? User.current.id : 0)])
# Project specific queries and global queries
visible << (@project.nil? ? ["project_id IS NULL"] : ["project_id IS NULL OR project_id = ?", @project.id])
@sidebar_queries = Query.find(:all,
:select => 'id, name, is_public',
:order => "name ASC",
:conditions => visible.conditions)
end
@sidebar_queries
end
@@ -146,14 +147,12 @@ module IssuesHelper
content_tag('h3', h(title)) +
queries.collect {|query|
css = 'query'
css << ' selected' if query == @query
link_to(h(query.name), url_params.merge(:query_id => query), :class => css)
}.join('<br />').html_safe
link_to(h(query.name), url_params.merge(:query_id => query))
}.join('<br />')
end
def render_sidebar_queries
out = ''.html_safe
out = ''
queries = sidebar_queries.select {|q| !q.is_public?}
out << query_links(l(:label_my_queries), queries) if queries.any?
queries = sidebar_queries.select {|q| q.is_public?}
@@ -161,76 +160,36 @@ module IssuesHelper
out
end
# Returns the textual representation of a journal details
# as an array of strings
def details_to_strings(details, no_html=false, options={})
options[:only_path] = (options[:only_path] == false ? false : true)
strings = []
values_by_field = {}
details.each do |detail|
if detail.property == 'cf'
field_id = detail.prop_key
field = CustomField.find_by_id(field_id)
if field && field.multiple?
values_by_field[field_id] ||= {:added => [], :deleted => []}
if detail.old_value
values_by_field[field_id][:deleted] << detail.old_value
end
if detail.value
values_by_field[field_id][:added] << detail.value
end
next
end
end
strings << show_detail(detail, no_html, options)
end
values_by_field.each do |field_id, changes|
detail = JournalDetail.new(:property => 'cf', :prop_key => field_id)
if changes[:added].any?
detail.value = changes[:added]
strings << show_detail(detail, no_html, options)
elsif changes[:deleted].any?
detail.old_value = changes[:deleted]
strings << show_detail(detail, no_html, options)
end
end
strings
end
# Returns the textual representation of a single journal detail
def show_detail(detail, no_html=false, options={})
multiple = false
def show_detail(detail, no_html=false)
case detail.property
when 'attr'
field = detail.prop_key.to_s.gsub(/\_id$/, "")
label = l(("field_" + field).to_sym)
case detail.prop_key
when 'due_date', 'start_date'
case
when ['due_date', 'start_date'].include?(detail.prop_key)
value = format_date(detail.value.to_date) if detail.value
old_value = format_date(detail.old_value.to_date) if detail.old_value
when 'project_id', 'status_id', 'tracker_id', 'assigned_to_id',
'priority_id', 'category_id', 'fixed_version_id'
when ['project_id', 'status_id', 'tracker_id', 'assigned_to_id', 'priority_id', 'category_id', 'fixed_version_id'].include?(detail.prop_key)
value = find_name_by_reflection(field, detail.value)
old_value = find_name_by_reflection(field, detail.old_value)
when 'estimated_hours'
when detail.prop_key == 'estimated_hours'
value = "%0.02f" % detail.value.to_f unless detail.value.blank?
old_value = "%0.02f" % detail.old_value.to_f unless detail.old_value.blank?
when 'parent_id'
when detail.prop_key == 'parent_id'
label = l(:field_parent_issue)
value = "##{detail.value}" unless detail.value.blank?
old_value = "##{detail.old_value}" unless detail.old_value.blank?
when 'is_private'
when detail.prop_key == 'is_private'
value = l(detail.value == "0" ? :general_text_No : :general_text_Yes) unless detail.value.blank?
old_value = l(detail.old_value == "0" ? :general_text_No : :general_text_Yes) unless detail.old_value.blank?
end
when 'cf'
custom_field = CustomField.find_by_id(detail.prop_key)
if custom_field
multiple = custom_field.multiple?
label = custom_field.name
value = format_value(detail.value, custom_field.field_format) if detail.value
old_value = format_value(detail.old_value, custom_field.field_format) if detail.old_value
@@ -238,8 +197,7 @@ module IssuesHelper
when 'attachment'
label = l(:label_attachment)
end
call_hook(:helper_issues_show_detail_after_setting,
{:detail => detail, :label => label, :value => value, :old_value => old_value })
call_hook(:helper_issues_show_detail_after_setting, {:detail => detail, :label => label, :value => value, :old_value => old_value })
label ||= detail.prop_key
value ||= detail.value
@@ -249,16 +207,9 @@ module IssuesHelper
label = content_tag('strong', label)
old_value = content_tag("i", h(old_value)) if detail.old_value
old_value = content_tag("strike", old_value) if detail.old_value and detail.value.blank?
if detail.property == 'attachment' && !value.blank? && atta = Attachment.find_by_id(detail.prop_key)
if detail.property == 'attachment' && !value.blank? && a = Attachment.find_by_id(detail.prop_key)
# Link to the attachment if it has not been removed
value = link_to_attachment(atta, :download => true, :only_path => options[:only_path])
if options[:only_path] != false && atta.is_text?
value += link_to(
image_tag('magnifier.png'),
:controller => 'attachments', :action => 'show',
:id => atta, :filename => atta.filename
)
end
value = link_to_attachment(a)
else
value = content_tag("i", h(value)) if value
end
@@ -268,27 +219,24 @@ module IssuesHelper
s = l(:text_journal_changed_no_detail, :label => label)
unless no_html
diff_link = link_to 'diff',
{:controller => 'journals', :action => 'diff', :id => detail.journal_id,
:detail_id => detail.id, :only_path => options[:only_path]},
{:controller => 'journals', :action => 'diff', :id => detail.journal_id, :detail_id => detail.id},
:title => l(:label_view_diff)
s << " (#{ diff_link })"
end
s.html_safe
elsif detail.value.present?
s
elsif !detail.value.blank?
case detail.property
when 'attr', 'cf'
if detail.old_value.present?
l(:text_journal_changed, :label => label, :old => old_value, :new => value).html_safe
elsif multiple
l(:text_journal_added, :label => label, :value => value).html_safe
if !detail.old_value.blank?
l(:text_journal_changed, :label => label, :old => old_value, :new => value)
else
l(:text_journal_set_to, :label => label, :value => value).html_safe
l(:text_journal_set_to, :label => label, :value => value)
end
when 'attachment'
l(:text_journal_added, :label => label, :value => value).html_safe
l(:text_journal_added, :label => label, :value => value)
end
else
l(:text_journal_deleted, :label => label, :old => old_value).html_safe
l(:text_journal_deleted, :label => label, :old => old_value)
end
end
@@ -329,16 +277,16 @@ module IssuesHelper
issues.each do |issue|
col_values = columns.collect do |column|
s = if column.is_a?(QueryCustomFieldColumn)
cv = issue.custom_field_values.detect {|v| v.custom_field_id == column.custom_field.id}
cv = issue.custom_values.detect {|v| v.custom_field_id == column.custom_field.id}
show_value(cv)
else
value = column.value(issue)
value = issue.send(column.name)
if value.is_a?(Date)
format_date(value)
elsif value.is_a?(Time)
format_time(value)
elsif value.is_a?(Float)
("%.2f" % value).gsub('.', decimal_separator)
value.to_s.gsub('.', decimal_separator)
else
value
end

View File

@@ -1,7 +1,5 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -30,11 +28,11 @@ module JournalsHelper
{ :controller => 'journals', :action => 'edit', :id => journal },
:title => l(:button_edit)) if editable
end
content << content_tag('div', links.join(' ').html_safe, :class => 'contextual') unless links.empty?
content << content_tag('div', links.join(' '), :class => 'contextual') unless links.empty?
content << textilizable(journal, :notes)
css_classes = "wiki"
css_classes << " editable" if editable
content_tag('div', content.html_safe, :id => "journal-#{journal.id}-notes", :class => css_classes)
content_tag('div', content, :id => "journal-#{journal.id}-notes", :class => css_classes)
end
def link_to_in_place_notes_editor(text, field_id, url, options={})

View File

@@ -1,7 +1,5 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License

View File

@@ -1,7 +1,5 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License

View File

@@ -1,7 +1,5 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License

View File

@@ -1,7 +1,5 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License

View File

@@ -1,7 +1,5 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License

View File

@@ -1,7 +1,5 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -30,7 +28,7 @@ module ProjectsHelper
{:name => 'versions', :action => :manage_versions, :partial => 'projects/settings/versions', :label => :label_version_plural},
{:name => 'categories', :action => :manage_categories, :partial => 'projects/settings/issue_categories', :label => :label_issue_category_plural},
{:name => 'wiki', :action => :manage_wiki, :partial => 'projects/settings/wiki', :label => :label_wiki},
{:name => 'repositories', :action => :manage_repository, :partial => 'projects/settings/repositories', :label => :label_repository_plural},
{:name => 'repository', :action => :manage_repository, :partial => 'projects/settings/repository', :label => :label_repository},
{:name => 'boards', :action => :manage_boards, :partial => 'projects/settings/boards', :label => :label_board_plural},
{:name => 'activities', :action => :manage_project_activities, :partial => 'projects/settings/activities', :label => :enumeration_activities}
]

View File

@@ -1,7 +1,5 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -31,14 +29,7 @@ module QueriesHelper
def column_content(column, issue)
value = column.value(issue)
if value.is_a?(Array)
value.collect {|v| column_value(column, issue, v)}.compact.sort.join(', ').html_safe
else
column_value(column, issue, value)
end
end
def column_value(column, issue, value)
case value.class.name
when 'String'
if column.name == :subject
@@ -53,8 +44,6 @@ module QueriesHelper
when 'Fixnum', 'Float'
if column.name == :done_ratio
progress_bar(value, :width => '80px')
elsif column.name == :spent_hours
sprintf "%.2f", value
else
h(value.to_s)
end
@@ -99,23 +88,6 @@ module QueriesHelper
end
end
def retrieve_query_from_session
if session[:query]
if session[:query][:id]
@query = Query.find_by_id(session[:query][:id])
return unless @query
else
@query = Query.new(:name => "_", :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names])
end
if session[:query].has_key?(:project_id)
@query.project_id = session[:query][:project_id]
else
@query.project = @project
end
@query
end
end
def build_query_from_params
if params[:fields] || params[:f]
@query.filters = {}

View File

@@ -1,7 +1,5 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License

View File

@@ -1,7 +1,5 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -46,17 +44,17 @@ module RepositoriesHelper
end
def render_changeset_changes
changes = @changeset.filechanges.find(:all, :limit => 1000, :order => 'path').collect do |change|
changes = @changeset.changes.find(:all, :limit => 1000, :order => 'path').collect do |change|
case change.action
when 'A'
# Detects moved/copied files
if !change.from_path.blank?
change.action =
@changeset.filechanges.detect {|c| c.action == 'D' && c.path == change.from_path} ? 'R' : 'C'
@changeset.changes.detect {|c| c.action == 'D' && c.path == change.from_path} ? 'R' : 'C'
end
change
when 'D'
@changeset.filechanges.detect {|c| c.from_path == change.path} ? nil : change
@changeset.changes.detect {|c| c.from_path == change.path} ? nil : change
else
change
end
@@ -92,26 +90,22 @@ module RepositoriesHelper
text = link_to(h(text), :controller => 'repositories',
:action => 'show',
:id => @project,
:repository_id => @repository.identifier_param,
:path => path_param,
:rev => @changeset.identifier)
output << "<li class='#{style}'>#{text}"
output << "<li class='#{style}'>#{text}</li>"
output << render_changes_tree(s)
output << "</li>"
elsif c = tree[file][:c]
style << " change-#{c.action}"
path_param = to_path_param(@repository.relative_path(c.path))
text = link_to(h(text), :controller => 'repositories',
:action => 'entry',
:id => @project,
:repository_id => @repository.identifier_param,
:path => path_param,
:rev => @changeset.identifier) unless c.action == 'D'
text << " - #{h(c.revision)}" unless c.revision.blank?
text << ' ('.html_safe + link_to(l(:label_diff), :controller => 'repositories',
:action => 'diff',
:id => @project,
:repository_id => @repository.identifier_param,
:path => path_param,
:rev => @changeset.identifier) + ') '.html_safe if c.action == 'M'
text << ' '.html_safe + content_tag('span', h(c.from_path), :class => 'copied-from') unless c.from_path.blank?
@@ -142,10 +136,13 @@ module RepositoriesHelper
options_for_select(scm_options, repository.class.name.demodulize),
:disabled => (repository && !repository.new_record?),
:onchange => remote_function(
:url => new_project_repository_path(@project),
:method => :get,
:update => 'content',
:with => "Form.serialize(this.form)")
:url => {
:controller => 'repositories',
:action => 'edit',
:id => @project
},
:method => :get,
:with => "Form.serialize(this.form)")
)
end
@@ -256,63 +253,59 @@ module RepositoriesHelper
'<br />'.html_safe + l(:text_scm_path_encoding_note))
end
def index_commits(commits, heads)
def index_commits(commits, heads, href_proc = nil)
return nil if commits.nil? or commits.first.parents.nil?
map = {}
commit_hashes = []
refs_map = {}
heads.each do |head|
refs_map[head.scmid] ||= []
refs_map[head.scmid] << head
href_proc ||= Proc.new {|x|x}
heads.each{|r| refs_map[r.scmid] ||= []; refs_map[r.scmid] << r}
commits.reverse.each_with_index do |c, i|
h = {}
h[:parents] = c.parents.collect do |p|
[p.scmid, 0, 0]
end
h[:rdmid] = i
h[:space] = 0
h[:refs] = refs_map[c.scmid].join(" ") if refs_map.include? c.scmid
h[:scmid] = c.scmid
h[:href] = href_proc.call(c.scmid)
commit_hashes << h
map[c.scmid] = h
end
commits_by_scmid = {}
commits.reverse.each_with_index do |commit, commit_index|
commits_by_scmid[commit.scmid] = {
:parent_scmids => commit.parents.collect { |parent| parent.scmid },
:rdmid => commit_index,
:refs => refs_map.include?(commit.scmid) ? refs_map[commit.scmid].join(" ") : nil,
:scmid => commit.scmid,
:href => block_given? ? yield(commit.scmid) : commit.scmid
}
heads.sort! do |a,b|
a.to_s <=> b.to_s
end
heads.sort! { |head1, head2| head1.to_s <=> head2.to_s }
space = nil
heads.each do |head|
if commits_by_scmid.include? head.scmid
space = index_head((space || -1) + 1, head, commits_by_scmid)
j = 0
heads.each do |h|
if map.include? h.scmid then
j = mark_chain(j += 1, map[h.scmid], map)
end
end
# when no head matched anything use first commit
space ||= index_head(0, commits.first, commits_by_scmid)
return commits_by_scmid, space
if j == 0 then
mark_chain(j += 1, map.values.first, map)
end
map
end
def index_head(space, commit, commits_by_scmid)
stack = [[space, commits_by_scmid[commit.scmid]]]
max_space = space
def mark_chain(mark, commit, map)
stack = [[mark, commit]]
markmax = mark
until stack.empty?
space, commit = stack.pop
commit[:space] = space if commit[:space].nil?
space -= 1
commit[:parent_scmids].each_with_index do |parent_scmid, parent_index|
parent_commit = commits_by_scmid[parent_scmid]
if parent_commit and parent_commit[:space].nil?
stack.unshift [space += 1, parent_commit]
current = stack.pop
m, commit = current
commit[:space] = m if commit[:space] == 0
m1 = m - 1
commit[:parents].each_with_index do |p, i|
psha = p[0]
if map.include? psha and map[psha][:space] == 0 then
stack << [m1 += 1, map[psha]] if i == 0
stack = [[m1 += 1, map[psha]]] + stack if i > 0
end
end
max_space = space if max_space < space
markmax = m1 if markmax < m1
end
max_space
markmax
end
end

View File

@@ -1,7 +1,5 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License

View File

@@ -1,7 +1,5 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -37,7 +35,7 @@ module SearchHelper
result << content_tag('span', h(words), :class => "highlight token-#{t}")
end
end
result.html_safe
result
end
def type_label(t)
@@ -63,8 +61,6 @@ module SearchHelper
links << link_to(h(text), :q => params[:q], :titles_only => params[:titles_only],
:all_words => params[:all_words], :scope => params[:scope], t => 1)
end
('<ul>'.html_safe +
links.map {|link| content_tag('li', link)}.join(' ').html_safe +
'</ul>'.html_safe) unless links.empty?
('<ul>' + links.map {|link| content_tag('li', link)}.join(' ') + '</ul>') unless links.empty?
end
end

View File

@@ -1,7 +1,5 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License

View File

@@ -1,5 +1,3 @@
# encoding: utf-8
#
# Helpers to sort tables using clickable column headers.
#
# Author: Stuart Rackham <srackham@methods.co.nz>, March 2005.
@@ -160,8 +158,7 @@ module SortHelper
# sort_clause.
# - criteria can be either an array or a hash of allowed keys
#
def sort_update(criteria, sort_name=nil)
sort_name ||= self.sort_name
def sort_update(criteria)
@sort_criteria = SortCriteria.new
@sort_criteria.available_criteria = criteria
@sort_criteria.from_param(params[:sort] || session[sort_name])

View File

@@ -1,7 +1,5 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -118,7 +116,7 @@ module TimelogHelper
entry.hours.to_s.gsub('.', decimal_separator),
entry.comments
]
fields += custom_fields.collect {|f| show_value(entry.custom_field_values.detect {|v| v.custom_field_id == f.id}) }
fields += custom_fields.collect {|f| show_value(entry.custom_value_for(f)) }
csv << fields.collect {|c| Redmine::CodesetUtil.from_utf8(
c.to_s,
@@ -128,10 +126,10 @@ module TimelogHelper
export
end
def format_criteria_value(criteria_options, value)
def format_criteria_value(criteria, value)
if value.blank?
"[#{l(:label_none)}]"
elsif k = criteria_options[:klass]
l(:label_none)
elsif k = @available_criterias[criteria][:klass]
obj = k.find_by_id(value.to_i)
if obj.is_a?(Issue)
obj.visible? ? "#{obj.tracker} ##{obj.id}: #{obj.subject}" : "##{obj.id}"
@@ -139,28 +137,28 @@ module TimelogHelper
obj
end
else
format_value(value, criteria_options[:format])
format_value(value, @available_criterias[criteria][:format])
end
end
def report_to_csv(report)
def report_to_csv(criterias, periods, hours)
decimal_separator = l(:general_csv_decimal_separator)
export = FCSV.generate(:col_sep => l(:general_csv_separator)) do |csv|
# Column headers
headers = report.criteria.collect {|criteria| l(report.available_criteria[criteria][:label]) }
headers += report.periods
headers = criterias.collect {|criteria| l(@available_criterias[criteria][:label]) }
headers += periods
headers << l(:label_total)
csv << headers.collect {|c| Redmine::CodesetUtil.from_utf8(
c.to_s,
l(:general_csv_encoding) ) }
# Content
report_criteria_to_csv(csv, report.available_criteria, report.columns, report.criteria, report.periods, report.hours)
report_criteria_to_csv(csv, criterias, periods, hours)
# Total row
str_total = Redmine::CodesetUtil.from_utf8(l(:label_total), l(:general_csv_encoding))
row = [ str_total ] + [''] * (report.criteria.size - 1)
row = [ str_total ] + [''] * (criterias.size - 1)
total = 0
report.periods.each do |period|
sum = sum_hours(select_hours(report.hours, report.columns, period.to_s))
periods.each do |period|
sum = sum_hours(select_hours(hours, @columns, period.to_s))
total += sum
row << (sum > 0 ? ("%.2f" % sum).gsub('.',decimal_separator) : '')
end
@@ -170,26 +168,26 @@ module TimelogHelper
export
end
def report_criteria_to_csv(csv, available_criteria, columns, criteria, periods, hours, level=0)
def report_criteria_to_csv(csv, criterias, periods, hours, level=0)
decimal_separator = l(:general_csv_decimal_separator)
hours.collect {|h| h[criteria[level]].to_s}.uniq.each do |value|
hours_for_value = select_hours(hours, criteria[level], value)
hours.collect {|h| h[criterias[level]].to_s}.uniq.each do |value|
hours_for_value = select_hours(hours, criterias[level], value)
next if hours_for_value.empty?
row = [''] * level
row << Redmine::CodesetUtil.from_utf8(
format_criteria_value(available_criteria[criteria[level]], value).to_s,
format_criteria_value(criterias[level], value).to_s,
l(:general_csv_encoding) )
row += [''] * (criteria.length - level - 1)
row += [''] * (criterias.length - level - 1)
total = 0
periods.each do |period|
sum = sum_hours(select_hours(hours_for_value, columns, period.to_s))
sum = sum_hours(select_hours(hours_for_value, @columns, period.to_s))
total += sum
row << (sum > 0 ? ("%.2f" % sum).gsub('.',decimal_separator) : '')
end
row << ("%.2f" % total).gsub('.',decimal_separator)
csv << row
if criteria.length > level + 1
report_criteria_to_csv(csv, available_criteria, columns, criteria, periods, hours_for_value, level + 1)
if criterias.length > level + 1
report_criteria_to_csv(csv, criterias, periods, hours_for_value, level + 1)
end
end
end

View File

@@ -1,7 +1,5 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License

View File

@@ -1,7 +1,5 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -21,9 +19,9 @@ module UsersHelper
def users_status_options_for_select(selected)
user_count_by_status = User.count(:group => 'status').to_hash
options_for_select([[l(:label_all), ''],
["#{l(:status_active)} (#{user_count_by_status[1].to_i})", '1'],
["#{l(:status_registered)} (#{user_count_by_status[2].to_i})", '2'],
["#{l(:status_locked)} (#{user_count_by_status[3].to_i})", '3']], selected.to_s)
["#{l(:status_active)} (#{user_count_by_status[1].to_i})", 1],
["#{l(:status_registered)} (#{user_count_by_status[2].to_i})", 2],
["#{l(:status_locked)} (#{user_count_by_status[3].to_i})", 3]], selected)
end
# Options for the new membership projects combo-box

View File

@@ -1,7 +1,5 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License

View File

@@ -1,7 +1,5 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -45,36 +43,22 @@ module WatchersHelper
# Returns a comma separated list of users watching the given object
def watchers_list(object)
remove_allowed = User.current.allowed_to?("delete_#{object.class.name.underscore}_watchers".to_sym, object.project)
content = ''.html_safe
lis = object.watcher_users.collect do |user|
s = ''.html_safe
s << avatar(user, :size => "16").to_s
s << link_to_user(user, :class => 'user')
s = avatar(user, :size => "16").to_s + link_to_user(user, :class => 'user').to_s
if remove_allowed
url = {:controller => 'watchers',
:action => 'destroy',
:object_type => object.class.to_s.underscore,
:object_id => object.id,
:user_id => user}
s << ' '
s << link_to_remote(image_tag('delete.png'),
s += ' ' + link_to_remote(image_tag('delete.png'),
{:url => url},
:href => url_for(url),
:style => "vertical-align: middle",
:class => "delete")
end
content << content_tag('li', s)
"<li>#{ s }</li>"
end
content.present? ? content_tag('ul', content) : content
end
def watchers_checkboxes(object, users, checked=nil)
users.map do |user|
c = checked.nil? ? object.watched_by?(user) : checked
tag = check_box_tag 'issue[watcher_user_ids][]', user.id, c, :id => nil
content_tag 'label', "#{tag} #{h(user)}".html_safe,
:id => "issue_watcher_user_ids_#{user.id}",
:class => "floating"
end.join.html_safe
lis.empty? ? "" : "<ul>#{ lis.join("\n") }</ul>"
end
end

View File

@@ -1,7 +1,5 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License

View File

@@ -1,7 +1,5 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -21,14 +19,14 @@ module WikiHelper
def wiki_page_options_for_select(pages, selected = nil, parent = nil, level = 0)
pages = pages.group_by(&:parent) unless pages.is_a?(Hash)
s = ''.html_safe
s = ''
if pages.has_key?(parent)
pages[parent].each do |page|
attrs = "value='#{page.id}'"
attrs << " selected='selected'" if selected == page
indent = (level > 0) ? ('&nbsp;' * level * 2 + '&#187; ') : ''
indent = (level > 0) ? ('&nbsp;' * level * 2 + '&#187; ') : nil
s << content_tag('option', (indent + h(page.pretty_title)).html_safe, :value => page.id.to_s, :selected => selected == page) +
s << "<option #{attrs}>#{indent}#{h page.pretty_title}</option>\n" +
wiki_page_options_for_select(pages, selected, page, level + 1)
end
end

View File

@@ -1,7 +1,5 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -21,7 +21,7 @@ class Attachment < ActiveRecord::Base
belongs_to :container, :polymorphic => true
belongs_to :author, :class_name => "User", :foreign_key => "author_id"
validates_presence_of :filename, :author
validates_presence_of :container, :filename, :author
validates_length_of :filename, :maximum => 255
validates_length_of :disk_filename, :maximum => 255
validate :validate_max_file_size
@@ -44,22 +44,14 @@ class Attachment < ActiveRecord::Base
"LEFT JOIN #{Project.table_name} ON #{Document.table_name}.project_id = #{Project.table_name}.id"}
cattr_accessor :storage_path
@@storage_path = Redmine::Configuration['attachments_storage_path'] || File.join(Rails.root, "files")
@@storage_path = Redmine::Configuration['attachments_storage_path'] || "#{Rails.root}/files"
before_save :files_to_final_location
after_destroy :delete_from_disk
# Returns an unsaved copy of the attachment
def copy(attributes=nil)
copy = self.class.new
copy.attributes = self.attributes.dup.except("id", "downloads")
copy.attributes = attributes if attributes
copy
end
def validate_max_file_size
if @temp_file && self.filesize > Setting.attachment_max_size.to_i.kilobytes
errors.add(:base, l(:error_attachment_too_big, :max_size => Setting.attachment_max_size.to_i.kilobytes))
if self.filesize > Setting.attachment_max_size.to_i.kilobytes
errors.add(:base, :too_long, :count => Setting.attachment_max_size.to_i.kilobytes)
end
end
@@ -67,33 +59,21 @@ class Attachment < ActiveRecord::Base
unless incoming_file.nil?
@temp_file = incoming_file
if @temp_file.size > 0
if @temp_file.respond_to?(:original_filename)
self.filename = @temp_file.original_filename
self.filename.force_encoding("UTF-8") if filename.respond_to?(:force_encoding)
end
if @temp_file.respond_to?(:content_type)
self.content_type = @temp_file.content_type.to_s.chomp
end
if content_type.blank? && filename.present?
self.filename = sanitize_filename(@temp_file.original_filename)
self.disk_filename = Attachment.disk_filename(filename)
self.content_type = @temp_file.content_type.to_s.chomp
if content_type.blank?
self.content_type = Redmine::MimeType.of(filename)
end
self.filesize = @temp_file.size
end
end
end
def file
nil
end
def filename=(arg)
write_attribute :filename, sanitize_filename(arg.to_s)
if new_record? && disk_filename.blank?
self.disk_filename = Attachment.disk_filename(filename)
end
filename
end
# Copies the temporary file to its final location
# and computes its MD5 hash
def files_to_final_location
@@ -101,15 +81,10 @@ class Attachment < ActiveRecord::Base
logger.info("Saving attachment '#{self.diskfile}' (#{@temp_file.size} bytes)")
md5 = Digest::MD5.new
File.open(diskfile, "wb") do |f|
if @temp_file.respond_to?(:read)
buffer = ""
while (buffer = @temp_file.read(8192))
f.write(buffer)
md5.update(buffer)
end
else
f.write(@temp_file)
md5.update(@temp_file)
buffer = ""
while (buffer = @temp_file.read(8192))
f.write(buffer)
md5.update(buffer)
end
end
self.digest = md5.hexdigest
@@ -121,16 +96,14 @@ class Attachment < ActiveRecord::Base
end
end
# Deletes the file from the file system if it's not referenced by other attachments
# Deletes file on the disk
def delete_from_disk
if Attachment.first(:conditions => ["disk_filename = ? AND id <> ?", disk_filename, id]).nil?
delete_from_disk!
end
File.delete(diskfile) if !filename.blank? && File.exist?(diskfile)
end
# Returns file's location on disk
def diskfile
File.join(self.class.storage_path, disk_filename.to_s)
"#{@@storage_path}/#{self.disk_filename}"
end
def increment_download
@@ -138,15 +111,15 @@ class Attachment < ActiveRecord::Base
end
def project
container.try(:project)
container.project
end
def visible?(user=User.current)
container && container.attachments_visible?(user)
container.attachments_visible?(user)
end
def deletable?(user=User.current)
container && container.attachments_deletable?(user)
container.attachments_deletable?(user)
end
def image?
@@ -166,53 +139,41 @@ class Attachment < ActiveRecord::Base
File.readable?(diskfile)
end
# Returns the attachment token
def token
"#{id}.#{digest}"
end
# Finds an attachment that matches the given token and that has no container
def self.find_by_token(token)
if token.to_s =~ /^(\d+)\.([0-9a-f]+)$/
attachment_id, attachment_digest = $1, $2
attachment = Attachment.first(:conditions => {:id => attachment_id, :digest => attachment_digest})
if attachment && attachment.container.nil?
attachment
end
end
end
# Bulk attaches a set of files to an object
#
# Returns a Hash of the results:
# :files => array of the attached files
# :unsaved => array of the files that could not be attached
def self.attach_files(obj, attachments)
result = obj.save_attachments(attachments, User.current)
obj.attach_saved_attachments
result
attached = []
if attachments && attachments.is_a?(Hash)
attachments.each_value do |attachment|
file = attachment['file']
next unless file && file.size > 0
a = Attachment.create(:container => obj,
:file => file,
:description => attachment['description'].to_s.strip,
:author => User.current)
obj.attachments << a
if a.new_record?
obj.unsaved_attachments ||= []
obj.unsaved_attachments << a
else
attached << a
end
end
end
{:files => attached, :unsaved => obj.unsaved_attachments}
end
def self.latest_attach(attachments, filename)
attachments.sort_by(&:created_on).reverse.detect {
attachments.sort_by(&:created_on).reverse.detect {
|att| att.filename.downcase == filename.downcase
}
end
def self.prune(age=1.day)
attachments = Attachment.all(:conditions => ["created_on < ? AND (container_type IS NULL OR container_type = '')", Time.now - age])
attachments.each(&:destroy)
end
private
# Physically deletes the file from the file system
def delete_from_disk!
if disk_filename.present? && File.exist?(diskfile)
File.delete(diskfile)
end
end
private
def sanitize_filename(value)
# get only the filename, not the whole path
just_filename = value.gsub(/^.*(\\|\/)/, '')

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -15,12 +15,7 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
# Generic exception for when the AuthSource can not be reached
# (eg. can not connect to the LDAP)
class AuthSourceException < Exception; end
class AuthSource < ActiveRecord::Base
include Redmine::SubclassFactory
include Redmine::Ciphering
has_many :users

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -15,51 +15,40 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
require 'iconv'
require 'net/ldap'
require 'net/ldap/dn'
require 'iconv'
class AuthSourceLdap < AuthSource
validates_presence_of :host, :port, :attr_login
validates_length_of :name, :host, :maximum => 60, :allow_nil => true
validates_length_of :account, :account_password, :base_dn, :filter, :maximum => 255, :allow_blank => true
validates_length_of :account, :account_password, :base_dn, :maximum => 255, :allow_nil => true
validates_length_of :attr_login, :attr_firstname, :attr_lastname, :attr_mail, :maximum => 30, :allow_nil => true
validates_numericality_of :port, :only_integer => true
validate :validate_filter
before_validation :strip_ldap_attributes
def self.human_attribute_name(attribute_key_name, *args)
attr_name = attribute_key_name.to_s
if attr_name == "filter"
attr_name = "ldap_filter"
end
super(attr_name, *args)
end
def initialize(attributes=nil, *args)
super
def after_initialize
self.port = 389 if self.port == 0
end
def authenticate(login, password)
return nil if login.blank? || password.blank?
attrs = get_user_dn(login, password)
attrs = get_user_dn(login)
if attrs && attrs[:dn] && authenticate_dn(attrs[:dn], password)
logger.debug "Authentication successful for '#{login}'" if logger && logger.debug?
return attrs.except(:dn)
end
rescue Net::LDAP::LdapError => e
raise AuthSourceException.new(e.message)
rescue Net::LDAP::LdapError => text
raise "LdapError: " + text
end
# test the connection to the LDAP
def test_connection
ldap_con = initialize_ldap_con(self.account, self.account_password)
ldap_con.open { }
rescue Net::LDAP::LdapError => e
raise "LdapError: " + e.message
rescue Net::LDAP::LdapError => text
raise "LdapError: " + text
end
def auth_method_name
@@ -68,20 +57,6 @@ class AuthSourceLdap < AuthSource
private
def ldap_filter
if filter.present?
Net::LDAP::Filter.construct(filter)
end
rescue Net::LDAP::LdapError
nil
end
def validate_filter
if filter.present? && ldap_filter.nil?
errors.add(:filter, :invalid)
end
end
def strip_ldap_attributes
[:attr_login, :attr_firstname, :attr_lastname, :attr_mail].each do |attr|
write_attribute(attr, read_attribute(attr).strip) unless read_attribute(attr).nil?
@@ -125,24 +100,14 @@ class AuthSourceLdap < AuthSource
end
# Get the user's dn and any attributes for them, given their login
def get_user_dn(login, password)
ldap_con = nil
if self.account && self.account.include?("$login")
ldap_con = initialize_ldap_con(self.account.sub("$login", Net::LDAP::DN.escape(login)), password)
else
ldap_con = initialize_ldap_con(self.account, self.account_password)
end
def get_user_dn(login)
ldap_con = initialize_ldap_con(self.account, self.account_password)
login_filter = Net::LDAP::Filter.eq( self.attr_login, login )
object_filter = Net::LDAP::Filter.eq( "objectClass", "*" )
attrs = {}
search_filter = object_filter & login_filter
if f = ldap_filter
search_filter = search_filter & f
end
ldap_con.search( :base => self.base_dn,
:filter => search_filter,
:filter => object_filter & login_filter,
:attributes=> search_attributes) do |entry|
if onthefly_register?

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -16,7 +16,6 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class Board < ActiveRecord::Base
include Redmine::SafeAttributes
belongs_to :project
has_many :topics, :class_name => 'Message', :conditions => "#{Message.table_name}.parent_id IS NULL", :order => "#{Message.table_name}.created_on DESC"
has_many :messages, :dependent => :destroy, :order => "#{Message.table_name}.created_on DESC"
@@ -28,11 +27,9 @@ class Board < ActiveRecord::Base
validates_length_of :name, :maximum => 30
validates_length_of :description, :maximum => 255
scope :visible, lambda {|*args| { :include => :project,
named_scope :visible, lambda {|*args| { :include => :project,
:conditions => Project.allowed_to_condition(args.shift || User.current, :view_messages, *args) } }
safe_attributes 'name', 'description', 'move_to'
def visible?(user=User.current)
!user.nil? && user.allowed_to?(:view_messages, project)
end

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -20,7 +20,7 @@ require 'iconv'
class Changeset < ActiveRecord::Base
belongs_to :repository
belongs_to :user
has_many :filechanges, :class_name => 'Change', :dependent => :delete_all
has_many :changes, :dependent => :delete_all
has_and_belongs_to_many :issues
has_and_belongs_to_many :parents,
:class_name => "Changeset",
@@ -31,10 +31,10 @@ class Changeset < ActiveRecord::Base
:join_table => "#{table_name_prefix}changeset_parents#{table_name_suffix}",
:association_foreign_key => 'changeset_id', :foreign_key => 'parent_id'
acts_as_event :title => Proc.new {|o| o.title},
acts_as_event :title => Proc.new {|o| "#{l(:label_revision)} #{o.format_identifier}" + (o.short_comments.blank? ? '' : (': ' + o.short_comments))},
:description => :long_comments,
:datetime => :committed_on,
:url => Proc.new {|o| {:controller => 'repositories', :action => 'revision', :id => o.repository.project, :repository_id => o.repository.identifier_param, :rev => o.identifier}}
:url => Proc.new {|o| {:controller => 'repositories', :action => 'revision', :id => o.repository.project, :rev => o.identifier}}
acts_as_searchable :columns => 'comments',
:include => {:repository => :project},
@@ -49,8 +49,7 @@ class Changeset < ActiveRecord::Base
validates_uniqueness_of :revision, :scope => :repository_id
validates_uniqueness_of :scmid, :scope => :repository_id, :allow_nil => true
scope :visible,
lambda {|*args| { :include => {:repository => :project},
named_scope :visible, lambda {|*args| { :include => {:repository => :project},
:conditions => Project.allowed_to_condition(args.shift || User.current, :view_changesets, *args) } }
after_create :scan_for_issues
@@ -152,26 +151,12 @@ class Changeset < ActiveRecord::Base
@long_comments || split_comments.last
end
def text_tag(ref_project=nil)
tag = if scmid?
def text_tag
if scmid?
"commit:#{scmid}"
else
"r#{revision}"
end
if repository && repository.identifier.present?
tag = "#{repository.identifier}|#{tag}"
end
if ref_project && project && ref_project != project
tag = "#{project.identifier}:#{tag}"
end
tag
end
# Returns the title used for the changeset in the activity/search results
def title
repo = (repository && repository.identifier.present?) ? " (#{repository.identifier})" : ''
comm = short_comments.blank? ? '' : (': ' + short_comments)
"#{l(:label_revision)} #{format_identifier}#{repo}#{comm}"
end
# Returns the previous changeset
@@ -199,14 +184,14 @@ class Changeset < ActiveRecord::Base
:from_revision => change[:from_revision])
end
private
# Finds an issue that can be referenced by the commit message
# i.e. an issue that belong to the repository project, a subproject or a parent project
def find_referenced_issue_by_id(id)
return nil if id.blank?
issue = Issue.find_by_id(id.to_i, :include => :project)
if Setting.commit_cross_project_ref?
# all issues can be referenced/fixed
elsif issue
# issue that belong to the repository project, a subproject or a parent project only
if issue
unless issue.project &&
(project == issue.project || project.is_ancestor_of?(issue.project) ||
project.is_descendant_of?(issue.project))
@@ -216,8 +201,6 @@ class Changeset < ActiveRecord::Base
issue
end
private
def fix_issue(issue)
status = IssueStatus.find_by_id(Setting.commit_fix_status_id.to_i)
if status.nil?
@@ -230,7 +213,7 @@ class Changeset < ActiveRecord::Base
# don't change the status is the issue is closed
return if issue.status && issue.status.is_closed?
journal = issue.init_journal(user || User.anonymous, ll(Setting.default_language, :text_status_changed_by_changeset, text_tag(issue.project)))
journal = issue.init_journal(user || User.anonymous, ll(Setting.default_language, :text_status_changed_by_changeset, text_tag))
issue.status = status
unless Setting.commit_fix_done_ratio.blank?
issue.done_ratio = Setting.commit_fix_done_ratio.to_i
@@ -249,7 +232,7 @@ class Changeset < ActiveRecord::Base
:hours => hours,
:issue => issue,
:spent_on => commit_date,
:comments => l(:text_time_logged_by_changeset, :value => text_tag(issue.project),
:comments => l(:text_time_logged_by_changeset, :value => text_tag,
:locale => Setting.default_language)
)
time_entry.activity = log_time_activity unless log_time_activity.nil?

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2012 Jean-Philippe Lang
# Copyright (C) 2006-2011 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -16,11 +16,8 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class Comment < ActiveRecord::Base
include Redmine::SafeAttributes
belongs_to :commented, :polymorphic => true, :counter_cache => true
belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
validates_presence_of :commented, :author, :comments
safe_attributes 'comments'
end

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