Commit 11e93ff3 authored by Holger Just's avatar Holger Just

Merge branch 'release-v3.0.0' into stable

parents ad996d78 3ecace90
......@@ -4,6 +4,7 @@
/config/configuration.yml
/config/database.yml
/config/email.yml
/config/setup_load_paths.rb
/config/initializers/session_store.rb
/coverage
/db/*.db
......
# -*- coding: utf-8 -*-
source :rubygems
gem "rails", "2.3.14"
gem "coderay", "~> 0.9.7"
gem "coderay", "~> 1.0.0"
gem "i18n", "~> 0.4.2"
gem "rubytree", "~> 0.5.2", :require => 'tree'
gem "rdoc", ">= 2.4.2"
gem "liquid", "~> 2.3.0"
gem "acts-as-taggable-on", "= 2.1.0"
# Needed only on RUBY_VERSION = 1.8, ruby 1.9+ compatible interpreters should bring their csv
gem "fastercsv", "~> 1.5.0", :platforms => [:ruby_18, :jruby, :mingw_18]
......
......@@ -19,6 +19,10 @@ class AdminController < ApplicationController
include SortHelper
menu_item :projects, :only => [:projects]
menu_item :plugins, :only => [:plugins]
menu_item :info, :only => [:info]
def index
@no_configuration_data = Redmine::DefaultData::Loader::no_data?
end
......
......@@ -65,6 +65,9 @@ class ApplicationController < ActionController::Base
filter_parameter_logging :password
rescue_from ActionController::InvalidAuthenticityToken, :with => :invalid_authenticity_token
# FIXME: This doesn't work with Rails >= 3.0 anymore
# Possible workaround: https://github.com/rails/rails/issues/671#issuecomment-1780159
rescue_from ActionController::RoutingError, :with => proc{render_404}
include Redmine::Search::Controller
include Redmine::MenuManager::MenuController
......@@ -75,8 +78,6 @@ class ApplicationController < ActionController::Base
end
def user_setup
# Check the settings cache for each request
Setting.check_cache
# Find the current user
User.current = find_current_user
end
......
......@@ -13,7 +13,8 @@
#++
class AutoCompletesController < ApplicationController
before_filter :find_project
before_filter :find_project, :only => :issues
before_filter :require_admin, :only => :projects
def issues
@issues = []
......@@ -33,6 +34,38 @@ class AutoCompletesController < ApplicationController
render :layout => false
end
def users
if params[:remove_group_members].present?
@group = Group.find(params[:remove_group_members])
@removed_users = @group.users
end
if params[:remove_watchers].present? && params[:klass].present?
watcher_class = params[:klass].constantize
if watcher_class.included_modules.include?(Redmine::Acts::Watchable) # check class is a watching class
@object = watcher_class.find(params[:remove_watchers])
@removed_users = @object.watcher_users
end
end
@removed_users ||= []
if params[:include_groups]
user_finder = Principal
else
user_finder = User
end
@users = user_finder.active.like(params[:q]).find(:all, :limit => 100) - @removed_users
render :layout => false
end
def projects
@principal = Principal.find(params[:id])
@projects = Project.active.like(params[:q]).find(:all, :limit => 100) - @principal.projects
render :layout => false
end
private
def find_project
......
......@@ -44,19 +44,32 @@ class DocumentsController < ApplicationController
def new
@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
if request.post?
if User.current.allowed_to?(:add_document_watchers, @project) && params[:document]['watcher_user_ids'].present?
@document.watcher_user_ids = params[:document]['watcher_user_ids']
end
if @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
end
end
end
def edit
@categories = DocumentCategory.all
if request.post? and @document.update_attributes(params[:document])
flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'show', :id => @document
if request.post?
if User.current.allowed_to?(:add_document_watchers, @project) && params[:document]['watcher_user_ids'].present?
@document.watcher_user_ids = params[:document]['watcher_user_ids']
end
if @document.update_attributes(params[:document])
flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'show', :id => @document
end
end
end
......@@ -69,7 +82,12 @@ class DocumentsController < ApplicationController
attachments = Attachment.attach_files(@document, params[:attachments])
render_attachment_warning_if_needed(@document)
Mailer.deliver_attachments_added(attachments[:files]) if attachments.present? && attachments[:files].present? && Setting.notified_events.include?('document_added')
if attachments.present? && attachments[:files].present? && Setting.notified_events.include?('document_added')
# TODO: refactor
attachments.first.container.recipients.each do |recipient|
Mailer.deliver_attachments_added(attachments[:files], recipient)
end
end
redirect_to :action => 'show', :id => @document
end
......
......@@ -42,7 +42,11 @@ class FilesController < ApplicationController
render_attachment_warning_if_needed(container)
if !attachments.empty? && !attachments[:files].blank? && Setting.notified_events.include?('file_added')
Mailer.deliver_attachments_added(attachments[:files])
# TODO: refactor
recipients = attachments[:files].first.container.project.notified_users.select {|user| user.allowed_to?(:view_files, container.project)}.collect {|u| u.mail}
recipients.each do |recipient|
Mailer.deliver_attachments_added(attachments[:files], recipient)
end
end
redirect_to project_files_path(@project)
end
......
......@@ -126,16 +126,19 @@ class GroupsController < ApplicationController
end
end
def autocomplete_for_user
@group = Group.find(params[:id])
@users = User.active.not_in_group(@group).like(params[:q]).all(:limit => 100)
render :layout => false
end
def edit_membership
@group = Group.find(params[:id])
@membership = Member.edit_membership(params[:membership_id], params[:membership], @group)
@membership.save if request.post?
if params[:project_ids] # Multiple memberships, one per project
params[:project_ids].each do |project_id|
@membership = Member.edit_membership(params[:membership_id], (params[:membership]|| {}).merge(:project_id => project_id), @group)
@membership.save if request.post?
end
else # Single membership
@membership = Member.edit_membership(params[:membership_id], params[:membership], @group)
@membership.save if request.post?
end
respond_to do |format|
if @membership.valid?
format.html { redirect_to :controller => 'groups', :action => 'edit', :id => @group, :tab => 'memberships' }
......
......@@ -12,6 +12,8 @@
# See doc/COPYRIGHT.rdoc for more details.
#++
require 'diff'
class JournalsController < ApplicationController
before_filter :find_journal, :only => [:edit, :diff]
before_filter :find_issue, :only => [:new]
......@@ -84,6 +86,22 @@ class JournalsController < ApplicationController
end
end
def diff
if valid_field?(params[:field])
from = @journal.changes[params[:field]][0]
to = @journal.changes[params[:field]][1]
@diff = Redmine::Helpers::Diff.new(to, from)
@issue = @journal.journaled
respond_to do |format|
format.html { }
format.js { render :layout => false }
end
else
render_404
end
end
private
def find_journal
......@@ -100,4 +118,9 @@ class JournalsController < ApplicationController
rescue ActiveRecord::RecordNotFound
render_404
end
# Is this a valid field for diff'ing?
def valid_field?(field)
field.to_s.strip == "description"
end
end
......@@ -14,6 +14,7 @@
class LdapAuthSourcesController < AuthSourcesController
menu_item :ldap_authentication, :only => [:index]
protected
def auth_source_class
......
......@@ -110,7 +110,7 @@ class MessagesController < ApplicationController
content = "#{ll(Setting.default_language, :text_user_wrote, user)}\\n> "
content << text.to_s.strip.gsub(%r{<pre>((.|\s)*?)</pre>}m, '[...]').gsub('"', '\"').gsub(/(\r?\n|\r\n?)/, "\\n> ") + "\\n\\n"
render(:update) { |page|
page << "$('reply_subject').value = \"#{subject}\";"
page << "$('message_subject').value = \"#{subject}\";"
page.<< "$('message_content').value = \"#{content}\";"
page.show 'reply'
page << "Form.Element.focus('message_content');"
......
......@@ -20,6 +20,7 @@ class QueriesController < ApplicationController
def new
@query = Query.new(params[:query])
@query.project = params[:query_is_for_all] ? nil : @project
@query.display_subprojects = params[:display_subprojects] if params[:display_subprojects].present?
@query.user = User.current
@query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
......
......@@ -72,8 +72,7 @@ class TimeEntryReportsController < ApplicationController
@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
while date_from <= @to.to_time
case @columns
when 'year'
@periods << "#{date_from.year}"
......@@ -161,6 +160,9 @@ class TimeEntryReportsController < ApplicationController
@available_criterias = { 'project' => {:sql => "#{TimeEntry.table_name}.project_id",
:klass => Project,
:label => :label_project},
'status' => {:sql => "#{Issue.table_name}.status_id",
:klass => IssueStatus,
:label => :field_status},
'version' => {:sql => "#{Issue.table_name}.fixed_version_id",
:klass => Version,
:label => :label_version},
......
......@@ -197,8 +197,16 @@ class UsersController < ApplicationController
def edit_membership
@membership = Member.edit_membership(params[:membership_id], params[:membership], @user)
@membership.save if request.post?
if params[:project_ids] # Multiple memberships, one per project
params[:project_ids].each do |project_id|
@membership = Member.edit_membership(params[:membership_id], (params[:membership] || {}).merge(:project_id => project_id), @user)
@membership.save if request.post?
end
else # Single membership
@membership = Member.edit_membership(params[:membership_id], params[:membership], @user)
@membership.save if request.post?
end
respond_to do |format|
if @membership.valid?
format.html { redirect_to :controller => 'users', :action => 'edit', :id => @user, :tab => 'memberships' }
......
......@@ -16,6 +16,7 @@ class WatchersController < ApplicationController
before_filter :find_project
before_filter :require_login, :check_project_privacy, :only => [:watch, :unwatch]
before_filter :authorize, :only => [:new, :destroy]
before_filter :authorize_access_to_object, :only => [:new, :destroy]
verify :method => :post,
:only => [ :watch, :unwatch ],
......@@ -34,9 +35,12 @@ class WatchersController < ApplicationController
end
def new
@watcher = Watcher.new(params[:watcher])
@watcher.watchable = @watched
@watcher.save if request.post?
params[:user_ids].each do |user_id|
@watcher = Watcher.new((params[:watcher] || {}).merge({:user_id => user_id}))
@watcher.watchable = @watched
@watcher.save if request.post?
end if params[:user_ids].present?
respond_to do |format|
format.html { redirect_to :back }
format.js do
......@@ -50,7 +54,7 @@ class WatchersController < ApplicationController
end
def destroy
@watched.set_watcher(User.find(params[:user_id]), false) if request.post?
@watched.set_watcher(Principal.find(params[:user_id]), false) if request.post?
respond_to do |format|
format.html { redirect_to :back }
format.js do
......@@ -94,4 +98,24 @@ private
rescue ::ActionController::RedirectBackError
render :text => (watching ? 'Watcher added.' : 'Watcher removed.'), :layout => true
end
def authorize_access_to_object
permission = ''
case params[:action]
when 'new'
permission << 'add_'
when 'destroy'
permission << 'delete_'
end
# Ends up like: :delete_wiki_page_watchers
permission << "#{@watched.class.name.underscore}_watchers"
if User.current.allowed_to?(permission.to_sym, @project)
return true
else
deny_access
end
end
end
#-- encoding: UTF-8
#-- copyright
# ChiliProject is a project management system.
#
# Copyright (C) 2010-2012 the ChiliProject Team
#
# 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.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
class BaseDrop < Liquid::Drop
def initialize(object)
@object = object unless object.respond_to?(:visible?) && !object.visible?
end
# Defines a Liquid method on the drop that is allowed to call the
# Ruby method directly. Best used for attributes.
#
# Based on Module#liquid_methods
def self.allowed_methods(*allowed_methods)
class_eval do
allowed_methods.each do |sym|
define_method sym do
if @object.respond_to?(:public_send)
@object.public_send(sym) rescue nil
else
@object.send(sym) rescue nil
end
end
end
end
end
end
#-- encoding: UTF-8
#-- copyright
# ChiliProject is a project management system.
#
# Copyright (C) 2010-2012 the ChiliProject Team
#
# 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.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
class IssueDrop < BaseDrop
allowed_methods :id
allowed_methods :subject
allowed_methods :description
allowed_methods :project
allowed_methods :tracker
allowed_methods :status
allowed_methods :due_date
allowed_methods :category
allowed_methods :assigned_to
allowed_methods :priority
allowed_methods :fixed_version
allowed_methods :author
allowed_methods :created_on
allowed_methods :updated_on
allowed_methods :start_date
allowed_methods :done_ratio
allowed_methods :estimated_hours
allowed_methods :parent
def custom_field(name)
return '' unless name.present?
custom_field = IssueCustomField.find_by_name(name.strip)
return '' unless custom_field.present?
custom_value = @object.custom_value_for(custom_field)
if custom_value.present?
return custom_value.value
else
return ''
end
end
# TODO: both required, method_missing for Ruby and before_method for Liquid
# Allows accessing custom fields by their name:
#
# - issue.the_name_of_player => CustomField(:name => "The name Of Player")
#
def before_method(method_sym)
if custom_field_with_matching_name = has_custom_field_with_matching_name?(method_sym)
custom_field(custom_field_with_matching_name.name)
else
super
end
end
# Allows accessing custom fields by their name:
#
# - issue.the_name_of_player => CustomField(:name => "The name Of Player")
#
def method_missing(method_sym, *arguments, &block)
if custom_field_with_matching_name = has_custom_field_with_matching_name?(method_sym)
custom_field(custom_field_with_matching_name.name)
else
super
end
end
private
def has_custom_field_with_matching_name?(method_sym)
custom_field_with_matching_name = @object.available_custom_fields.detect {|custom_field|
custom_field.name.downcase.underscore.gsub(' ','_') == method_sym.to_s