Compare commits

..

34 Commits

Author SHA1 Message Date
Felix Schäfer 4424841784 Bump version to v2.9.0 2013-01-29 23:13:50 +01:00
Felix Schäfer 2b8ec7c80f Update Changelog for v2.9.0 2013-01-29 23:13:16 +01:00
Felix Schäfer 066f616210 Remove Rails patches which are already included in Rails 2.3.16 #1219 2013-01-29 23:11:27 +01:00
Felix Schäfer 8ea58b6fd5 Bump Rails version to 2.3.16 #1219 2013-01-29 23:11:07 +01:00
Holger Just bfdc43ba73 Bump version to v2.8.1 2013-01-16 23:29:13 +01:00
Holger Just 2b909243ee Update Changelog for v2.8.1 2013-01-16 23:28:51 +01:00
Holger Just b7a82ac691 Fix for CVE-2013-0155 in Rails 2013-01-16 23:27:30 +01:00
Holger Just 964d19cc57 Bump version to 2.8.0 2013-01-09 14:11:46 +01:00
Holger Just dd945c78c3 Update Changelog for v2.8.0 2013-01-09 14:11:18 +01:00
Holger Just 41e349888b Remove Rails patches which are already included in Rails 2.3.15 #1200 2013-01-09 14:11:18 +01:00
Holger Just dca36c222a Bump Rails version to 2.3.15 #1200 2013-01-09 14:11:18 +01:00
Holger Just 524ef942d9 Bump version to v2.7.4 2013-01-06 23:56:43 +01:00
Holger Just e06dd303db Update Changelog for v2.7.4 2013-01-06 23:56:23 +01:00
Holger Just e2bc4e905a Update Copyright for 2013
We programmers have a nice new years tradition: We revisit all of
our projects and add 1 to a small number near a "(c)".

-- Volker Dusch
https://twitter.com/__edorian/status/153801913442373633
2013-01-06 23:52:16 +01:00
Holger Just 6ece1687de Fix XSS vulnerabilities in Rails (CVE-2012-3464, CVE-2012-3465) #1113 #1114 2013-01-06 23:50:49 +01:00
Holger Just 6d87b8b297 SQL Injection Vulnerability in Ruby on Rails (CVE-2012-5664) #1195 2013-01-06 23:50:32 +01:00
Holger Just bd509a4008 Bump version to 2.7.3 2012-06-13 10:29:12 +02:00
Holger Just b0ec4c140d Update changelog for v2.7.3 2012-06-13 10:28:55 +02:00
Holger Just e178f1ce9c Fix SQL injection via nested hashes in conditions. CVE-2012-2695 #1037 2012-06-13 10:27:30 +02:00
Holger Just c3d3bec47f Fix SQL injection via nested hashes in conditions (CVE-2012-2694) #1036 2012-06-13 10:27:21 +02:00
Holger Just 8d56d32774 Bump to 2.7.2 2012-06-09 18:17:46 +02:00
Holger Just 4456440535 Update changelog for v2.7.2 2012-06-09 18:17:14 +02:00
Holger Just f959b9bdb9 [#1025] Fix Rails vulnerability (CVE-2012-2660) 2012-06-09 18:03:41 +02:00
Holger Just 9d32e68ec0 Bump version to 2.7.1 2012-04-04 14:09:08 +02:00
Jean-Philippe Lang 80289c5a70 Set user_id as a protected attribute (#922). 2012-04-04 14:06:01 +02:00
Jean-Philippe Lang 902c624b47 Prevent mass-assignment vulnerability when adding/updating a wiki (#922). 2012-04-04 14:06:00 +02:00
Jean-Philippe Lang aee7d7315b Prevent mass-assignment vulnerability when adding/updating a version (#922). 2012-04-04 14:05:41 +02:00
Jean-Philippe Lang 1f10817444 Prevent mass-assignment vulnerability when adding/updating a time entry (#922). 2012-04-04 13:39:37 +02:00
Jean-Philippe Lang ea3ff66b8e Use safe_attributes= just like in #create. (#922) 2012-04-04 13:39:37 +02:00
Jean-Philippe Lang ee99b2de03 Prevent mass-assignment vulnerability when adding/updating a news (#922). 2012-04-04 13:39:37 +02:00
Jean-Philippe Lang 4c322d379e Prevent mass-assignment vulnerability when adding/updating a forum message (#922). 2012-04-04 13:39:36 +02:00
Jean-Philippe Lang f12b9fca08 Prevent mass-assignment vulnerability when adding a project member (#922). 2012-04-04 13:39:36 +02:00
Jean-Philippe Lang 296b3173ef Prevent mass-assignment vulnerability when adding/updating an issue category (#922). 2012-04-04 13:39:20 +02:00
Jean-Philippe Lang c651ba1a98 Prevent mass-assignment vulnerability when adding/updating a document (#922).
Conflicts:

	app/controllers/documents_controller.rb
2012-04-04 13:30:21 +02:00
867 changed files with 12505 additions and 15077 deletions

4
.gitignore vendored
View File

@ -4,7 +4,6 @@
/config/configuration.yml /config/configuration.yml
/config/database.yml /config/database.yml
/config/email.yml /config/email.yml
/config/setup_load_paths.rb
/config/initializers/session_store.rb /config/initializers/session_store.rb
/coverage /coverage
/db/*.db /db/*.db
@ -29,6 +28,3 @@ doc/app
/Gemfile.lock /Gemfile.lock
/Gemfile.local /Gemfile.local
/.rvmrc* /.rvmrc*
/*.iml
/.idea
.rbx

View File

@ -1,59 +0,0 @@
language: ruby
rvm:
- 1.8.7
- 1.9.2
- 1.9.3
- rbx-18mode
env:
- "TEST_SUITE=units RAILS_ENV=test DB=mysql BUNDLE_WITHOUT=rmagick:mysql2:postgres:sqlite"
- "TEST_SUITE=units RAILS_ENV=test DB=mysql2 BUNDLE_WITHOUT=rmagick:mysql:postgres:sqlite"
- "TEST_SUITE=units RAILS_ENV=test DB=postgres BUNDLE_WITHOUT=rmagick:mysql:mysql2:sqlite"
- "TEST_SUITE=units RAILS_ENV=test DB=sqlite BUNDLE_WITHOUT=rmagick:mysql:mysql2:postgres"
- "TEST_SUITE=functionals RAILS_ENV=test DB=mysql BUNDLE_WITHOUT=rmagick:mysql2:postgres:sqlite"
- "TEST_SUITE=functionals RAILS_ENV=test DB=mysql2 BUNDLE_WITHOUT=rmagick:mysql:postgres:sqlite"
- "TEST_SUITE=functionals RAILS_ENV=test DB=postgres BUNDLE_WITHOUT=rmagick:mysql:mysql2:sqlite"
- "TEST_SUITE=functionals RAILS_ENV=test DB=sqlite BUNDLE_WITHOUT=rmagick:mysql:mysql2:postgres"
- "TEST_SUITE=integration RAILS_ENV=test DB=mysql BUNDLE_WITHOUT=rmagick:mysql2:postgres:sqlite"
- "TEST_SUITE=integration RAILS_ENV=test DB=mysql2 BUNDLE_WITHOUT=rmagick:mysql:postgres:sqlite"
- "TEST_SUITE=integration RAILS_ENV=test DB=postgres BUNDLE_WITHOUT=rmagick:mysql:mysql2:sqlite"
- "TEST_SUITE=integration RAILS_ENV=test DB=sqlite BUNDLE_WITHOUT=rmagick:mysql:mysql2:postgres"
matrix:
exclude:
- rvm: 1.9.2
env: "TEST_SUITE=units RAILS_ENV=test DB=mysql BUNDLE_WITHOUT=rmagick:mysql2:postgres:sqlite"
- rvm: 1.9.2
env: "TEST_SUITE=functionals RAILS_ENV=test DB=mysql BUNDLE_WITHOUT=rmagick:mysql2:postgres:sqlite"
- rvm: 1.9.2
env: "TEST_SUITE=integration RAILS_ENV=test DB=mysql BUNDLE_WITHOUT=rmagick:mysql2:postgres:sqlite"
- rvm: 1.9.3
env: "TEST_SUITE=units RAILS_ENV=test DB=mysql BUNDLE_WITHOUT=rmagick:mysql2:postgres:sqlite"
- rvm: 1.9.3
env: "TEST_SUITE=functionals RAILS_ENV=test DB=mysql BUNDLE_WITHOUT=rmagick:mysql2:postgres:sqlite"
- rvm: 1.9.3
env: "TEST_SUITE=integration RAILS_ENV=test DB=mysql BUNDLE_WITHOUT=rmagick:mysql2:postgres:sqlite"
- rvm: rbx-18mode
env: "TEST_SUITE=units RAILS_ENV=test DB=mysql BUNDLE_WITHOUT=rmagick:mysql2:postgres:sqlite"
- rvm: rbx-18mode
env: "TEST_SUITE=functionals RAILS_ENV=test DB=mysql BUNDLE_WITHOUT=rmagick:mysql2:postgres:sqlite"
- rvm: rbx-18mode
env: "TEST_SUITE=integration RAILS_ENV=test DB=mysql BUNDLE_WITHOUT=rmagick:mysql2:postgres:sqlite"
allow_failures:
- rvm: rbx-18mode
before_install:
- "sudo apt-get update -qq"
- "sudo apt-get --no-install-recommends install bzr cvs git mercurial subversion"
before_script:
- "rvm rubygems 1.8.25" # Rubygems 2.0.x fails with Rails 2.3
- "rake ci:travis:prepare"
- "rm -rf tmp/test/darcs_repository" # Don't test Darcs on Travis. It breaks there :(
script: "bundle exec rake test:$TEST_SUITE"
branches:
only:
- unstable
- master
- stable
- /^stable-.*$/
- /^release-.*$/
notifications:
email: false
irc: "irc.freenode.org#chiliproject"

View File

@ -1,33 +0,0 @@
For the impatient: [report][cpo_new-issue], confirm, claim,
[fork][gh_chiliproject], [branch][cpo_contribute-code-branch],
[write][cpo_code-standards], [test][cpo_code-review], push.
The short version:
* Make sure the issue you are working on is reported and confirmed, add a note
to the issue to claim your intention to work on it.
* Fork [ChiliProject on GitHub][gh_chiliproject]
* Create a new branch from `master` with a descriptive name prefixed by the
issue ID (Example: `123-change_background_from_black_to_blue`).
* Make changes according to our [Code Standards][cpo_code-standards].
1. Be sure to include tests as necessary.
1. Make sure to not break existing tests.
1. Please try to make sure your code is going to pass a [Code
Review][cpo_code-review] prior to submitting the patch. If in doubt, just ask.
* Either upload your branch to GitHub and send a pull request to the
ChiliProject repository, or attach a patch to the issue on ChiliProject. If
you send a pull request on GitHub, remember to link to the pull request in the
issue you create on ChiliProject and to link to the issue on ChiliProject in
the pull request on GitHub.
* Make sure you watch the corresponding issue in case any discussion arises or
improvements are needed.
The long version is on the [Contribute Code][cpo_contribute-code] page.
[cpo_new-issue]: https://www.chiliproject.org/projects/chiliproject/issues/new
[cpo_contribute-code-branch]: https://www.chiliproject.org/projects/chiliproject/wiki/Contribute_Code#Branch
[cpo_contribute-code]: https://www.chiliproject.org/projects/chiliproject/wiki/Contribute_Code
[cpo_code-standards]: https://www.chiliproject.org/projects/chiliproject/wiki/Code_Standards
[cpo_code-review]: https://www.chiliproject.org/projects/chiliproject/wiki/Code_Review
[gh_chiliproject]: https://github.com/chiliproject/chiliproject

27
Gemfile
View File

@ -1,32 +1,23 @@
# -*- coding: utf-8 -*- source :rubygems
source "https://rubygems.org"
gem "rails", "2.3.18" gem "rails", "2.3.16"
gem "json", "~> 1.7.7" gem "coderay", "~> 0.9.7"
gem "coderay", "~> 1.0.0"
gem "i18n", "~> 0.4.2" gem "i18n", "~> 0.4.2"
gem "rubytree", "~> 0.5.2", :require => 'tree' gem "rubytree", "~> 0.5.2", :require => 'tree'
gem "rdoc", ">= 2.4.2" gem "rdoc", ">= 2.4.2"
gem "liquid", "~> 2.3.0"
gem "acts-as-taggable-on", "= 2.1.0"
gem 'gravatarify', '~> 3.0.0'
# Needed only on RUBY_VERSION = 1.8, ruby 1.9+ compatible interpreters should bring their csv # 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] gem "fastercsv", "~> 1.5.0", :platforms => [:ruby_18, :jruby, :mingw_18]
gem "tzinfo", "~> 0.3.31" # Fixes #903. Not required for Rails >= 3.2
group :test do group :test do
gem 'shoulda', '~> 2.10.3' gem 'shoulda', '~> 2.10.3'
# Shoulda doesn't work nice on 1.9.3 and seems to need test-unit explicitely…
gem 'test-unit', :platforms => [:mri_19]
gem 'edavis10-object_daddy', :require => 'object_daddy' gem 'edavis10-object_daddy', :require => 'object_daddy'
gem 'mocha', '0.12.1' gem 'mocha'
# capybara 2 drops ruby 1.8.7 compatibility gem 'capybara'
gem 'capybara', '< 2.0.0'
end end
group :ldap do group :ldap do
gem "net-ldap", '~> 0.3.1' gem "net-ldap", '~> 0.2.2'
end end
group :openid do group :openid do
@ -57,13 +48,13 @@ end
# orders of magnitude compared to their native counterparts. You have been # orders of magnitude compared to their native counterparts. You have been
# warned. # warned.
platforms :mri, :mingw, :rbx do platforms :mri, :mingw do
group :mysql2 do group :mysql2 do
gem "mysql2", "~> 0.2.7" gem "mysql2", "~> 0.2.7"
end end
group :postgres do group :postgres do
gem "pg" gem "pg", "~> 0.9.0"
# gem "postgres-pr" # gem "postgres-pr"
end end
end end
@ -79,7 +70,7 @@ platforms :mri_18, :mingw_18 do
end end
end end
platforms :mri_19, :mingw_19, :rbx do platforms :mri_19, :mingw_19 do
group :sqlite do group :sqlite do
gem "sqlite3" gem "sqlite3"
end end

View File

@ -1,4 +1,4 @@
= ChiliProject {<img src="https://travis-ci.org/chiliproject/chiliproject.png?branch=master" />}[http://travis-ci.org/chiliproject/chiliproject] = ChiliProject
ChiliProject is a web based project management system. It supports your team throughout the complete project life cycle, from setting up and discussing a project plan, over tracking issues and reporting work progress to collaboratively sharing knowledge. ChiliProject is a web based project management system. It supports your team throughout the complete project life cycle, from setting up and discussing a project plan, over tracking issues and reporting work progress to collaboratively sharing knowledge.

View File

@ -8,5 +8,3 @@ require 'rake/testtask'
require 'rdoc/task' require 'rdoc/task'
require 'tasks/rails' require 'tasks/rails'
# Load rake tasks from plugins in chiliproject_plugins
Dir["#{RAILS_ROOT}/vendor/chiliproject_plugins/*/lib/tasks/**/*.rake"].sort.each { |ext| load ext }

View File

@ -37,7 +37,7 @@ class AccountController < ApplicationController
def lost_password def lost_password
redirect_to(home_url) && return unless Setting.lost_password? redirect_to(home_url) && return unless Setting.lost_password?
if params[:token] if params[:token]
@token = Token.find_by_action_and_value("recovery", params[:token].to_s) @token = Token.find_by_action_and_value("recovery", params[:token])
redirect_to(home_url) && return unless @token and !@token.expired? redirect_to(home_url) && return unless @token and !@token.expired?
@user = @token.user @user = @token.user
if request.post? if request.post?
@ -53,7 +53,7 @@ class AccountController < ApplicationController
return return
else else
if request.post? if request.post?
user = User.find_by_mail(params[:mail].to_s) user = User.find_by_mail(params[:mail])
# user not found in db # user not found in db
(flash.now[:error] = l(:notice_account_unknown_email); return) unless user (flash.now[:error] = l(:notice_account_unknown_email); return) unless user
# user uses an external authentification # user uses an external authentification
@ -109,7 +109,7 @@ class AccountController < ApplicationController
# Token based account activation # Token based account activation
def activate def activate
redirect_to(home_url) && return unless Setting.self_registration? && params[:token] redirect_to(home_url) && return unless Setting.self_registration? && params[:token]
token = Token.find_by_action_and_value('register', params[:token].to_s) token = Token.find_by_action_and_value('register', params[:token])
redirect_to(home_url) && return unless token and !token.expired? redirect_to(home_url) && return unless token and !token.expired?
user = token.user user = token.user
redirect_to(home_url) && return unless user.registered? redirect_to(home_url) && return unless user.registered?

View File

@ -33,7 +33,7 @@ class ActivitiesController < ApplicationController
:with_subprojects => @with_subprojects, :with_subprojects => @with_subprojects,
:author => @author) :author => @author)
@activity.scope_select {|t| !params["show_#{t}"].nil?} @activity.scope_select {|t| !params["show_#{t}"].nil?}
@activity.scope = (@author.nil? ? :default : :all) if @activity.scope.empty? unless params[:set_filter] @activity.scope = (@author.nil? ? :default : :all) if @activity.scope.empty?
events = @activity.events(@date_from, @date_to) events = @activity.events(@date_from, @date_to)

View File

@ -19,10 +19,6 @@ class AdminController < ApplicationController
include SortHelper include SortHelper
menu_item :projects, :only => [:projects]
menu_item :plugins, :only => [:plugins]
menu_item :info, :only => [:info]
def index def index
@no_configuration_data = Redmine::DefaultData::Loader::no_data? @no_configuration_data = Redmine::DefaultData::Loader::no_data?
end end
@ -77,7 +73,7 @@ class AdminController < ApplicationController
def info def info
@db_adapter_name = ActiveRecord::Base.connection.adapter_name @db_adapter_name = ActiveRecord::Base.connection.adapter_name
@checklist = [ @checklist = [
[:text_default_administrator_account_changed, !User.find_by_login("admin").try(:check_password?, "admin")], [: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_file_repository_writable, File.writable?(Attachment.storage_path)],
[:text_plugin_assets_writable, File.writable?(Engines.public_directory)], [:text_plugin_assets_writable, File.writable?(Engines.public_directory)],
[:text_rmagick_available, Object.const_defined?(:Magick)] [:text_rmagick_available, Object.const_defined?(:Magick)]

View File

@ -31,6 +31,18 @@ class ApplicationController < ActionController::Base
cookies.delete(:autologin) cookies.delete(:autologin)
end 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['_chiliproject_session'] && cookies['_chiliproject_session'] !~ /--/
cookies.delete '_chiliproject_session'
redirect_to home_path
return false
end
end
# FIXME: Remove this when all of Rack and Rails have learned how to # FIXME: Remove this when all of Rack and Rails have learned how to
# properly use encodings # properly use encodings
before_filter :params_filter before_filter :params_filter
@ -52,16 +64,7 @@ class ApplicationController < ActionController::Base
before_filter :user_setup, :check_if_login_required, :set_localization before_filter :user_setup, :check_if_login_required, :set_localization
filter_parameter_logging :password filter_parameter_logging :password
# FIXME: This doesn't work with Rails >= 3.0 anymore rescue_from ActionController::InvalidAuthenticityToken, :with => :invalid_authenticity_token
# Possible workaround: https://github.com/rails/rails/issues/671#issuecomment-1780159
rescue_from ActionController::RoutingError, :with => proc{
# manually apply basic before_filters which aren't applied by default here
user_setup
check_if_login_required
set_localization
render_404
}
include Redmine::Search::Controller include Redmine::Search::Controller
include Redmine::MenuManager::MenuController include Redmine::MenuManager::MenuController
@ -72,6 +75,8 @@ class ApplicationController < ActionController::Base
end end
def user_setup def user_setup
# Check the settings cache for each request
Setting.check_cache
# Find the current user # Find the current user
User.current = find_current_user User.current = find_current_user
end end
@ -89,11 +94,11 @@ class ApplicationController < ActionController::Base
user user
elsif params[:format] == 'atom' && params[:key] && accept_key_auth_actions.include?(params[:action]) elsif params[:format] == 'atom' && params[:key] && accept_key_auth_actions.include?(params[:action])
# RSS key authentication does not start a session # RSS key authentication does not start a session
User.find_by_rss_key(params[:key].to_s) User.find_by_rss_key(params[:key])
elsif Setting.rest_api_enabled? && api_request? elsif Setting.rest_api_enabled? && api_request?
if (key = api_key_from_request) && accept_key_auth_actions.include?(params[:action]) if (key = api_key_from_request) && accept_key_auth_actions.include?(params[:action])
# Use API key # Use API key
User.find_by_api_key(key.to_s) User.find_by_api_key(key)
else else
# HTTP Basic, either username/password or API key/random # HTTP Basic, either username/password or API key/random
authenticate_with_http_basic do |username, password| authenticate_with_http_basic do |username, password|
@ -330,6 +335,13 @@ class ApplicationController < ActionController::Base
request.xhr? ? false : 'base' request.xhr? ? false : 'base'
end end
def invalid_authenticity_token
if api_request?
logger.error "Form authenticity token is missing or is invalid. API calls must include a proper Content-type header (text/xml or text/json)."
end
render_error "Invalid form authenticity token."
end
def render_feed(items, options={}) def render_feed(items, options={})
@items = items || [] @items = items || []
@items.sort! {|x,y| y.event_datetime <=> x.event_datetime } @items.sort! {|x,y| y.event_datetime <=> x.event_datetime }

View File

@ -13,8 +13,7 @@
#++ #++
class AutoCompletesController < ApplicationController class AutoCompletesController < ApplicationController
before_filter :find_project, :only => :issues before_filter :find_project
before_filter :require_admin, :only => :projects
def issues def issues
@issues = [] @issues = []
@ -34,38 +33,6 @@ class AutoCompletesController < ApplicationController
render :layout => false render :layout => false
end 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 private
def find_project def find_project

View File

@ -21,10 +21,7 @@ class CommentsController < ApplicationController
verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed } verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed }
def create def create
raise Unauthorized unless @news.commentable? @comment = Comment.new(params[:comment])
@comment = Comment.new
@comment.safe_attributes = params[:comment]
@comment.author = User.current @comment.author = User.current
if @news.comments << @comment if @news.comments << @comment
flash[:notice] = l(:label_comment_added) flash[:notice] = l(:label_comment_added)

View File

@ -45,34 +45,21 @@ class DocumentsController < ApplicationController
def new def new
@document = @project.documents.build @document = @project.documents.build
@document.safe_attributes = params[:document] @document.safe_attributes = params[:document]
if request.post? if request.post? && @document.save
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]) attachments = Attachment.attach_files(@document, params[:attachments])
render_attachment_warning_if_needed(@document) render_attachment_warning_if_needed(@document)
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
redirect_to :action => 'index', :project_id => @project redirect_to :action => 'index', :project_id => @project
end end
end end
end
def edit def edit
@categories = DocumentCategory.all @categories = DocumentCategory.all
if request.post? and @document.update_attributes(params[: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) flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'show', :id => @document redirect_to :action => 'show', :id => @document
end end
end end
end
def destroy def destroy
@document.destroy @document.destroy
@ -83,12 +70,7 @@ class DocumentsController < ApplicationController
attachments = Attachment.attach_files(@document, params[:attachments]) attachments = Attachment.attach_files(@document, params[:attachments])
render_attachment_warning_if_needed(@document) render_attachment_warning_if_needed(@document)
if attachments.present? && attachments[:files].present? && Setting.notified_events.include?('document_added') Mailer.deliver_attachments_added(attachments[:files]) if attachments.present? && attachments[:files].present? && Setting.notified_events.include?('document_added')
# TODO: refactor
@document.recipients.each do |recipient|
Mailer.deliver_attachments_added(attachments[:files], recipient)
end
end
redirect_to :action => 'show', :id => @document redirect_to :action => 'show', :id => @document
end end

View File

@ -42,11 +42,7 @@ class FilesController < ApplicationController
render_attachment_warning_if_needed(container) render_attachment_warning_if_needed(container)
if !attachments.empty? && !attachments[:files].blank? && Setting.notified_events.include?('file_added') if !attachments.empty? && !attachments[:files].blank? && Setting.notified_events.include?('file_added')
# TODO: refactor Mailer.deliver_attachments_added(attachments[:files])
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 end
redirect_to project_files_path(@project) redirect_to project_files_path(@project)
end end

View File

@ -126,19 +126,16 @@ class GroupsController < ApplicationController
end end
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 def edit_membership
@group = Group.find(params[:id]) @group = Group.find(params[:id])
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 = Member.edit_membership(params[:membership_id], params[:membership], @group)
@membership.save if request.post? @membership.save if request.post?
end
respond_to do |format| respond_to do |format|
if @membership.valid? if @membership.valid?
format.html { redirect_to :controller => 'groups', :action => 'edit', :id => @group, :tab => 'memberships' } format.html { redirect_to :controller => 'groups', :action => 'edit', :id => @group, :tab => 'memberships' }

View File

@ -12,8 +12,6 @@
# See doc/COPYRIGHT.rdoc for more details. # See doc/COPYRIGHT.rdoc for more details.
#++ #++
require 'diff'
class JournalsController < ApplicationController class JournalsController < ApplicationController
before_filter :find_journal, :only => [:edit, :diff] before_filter :find_journal, :only => [:edit, :diff]
before_filter :find_issue, :only => [:new] before_filter :find_issue, :only => [:new]
@ -86,22 +84,6 @@ class JournalsController < ApplicationController
end end
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 private
def find_journal def find_journal
@ -118,9 +100,4 @@ class JournalsController < ApplicationController
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
render_404 render_404
end end
# Is this a valid field for diff'ing?
def valid_field?(field)
field.to_s.strip == "description"
end
end end

View File

@ -14,7 +14,6 @@
class LdapAuthSourcesController < AuthSourcesController class LdapAuthSourcesController < AuthSourcesController
menu_item :ldap_authentication, :only => [:index]
protected protected
def auth_source_class def auth_source_class

View File

@ -107,7 +107,7 @@ class MessagesController < ApplicationController
content = "#{ll(Setting.default_language, :text_user_wrote, user)}\\n> " 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" content << text.to_s.strip.gsub(%r{<pre>((.|\s)*?)</pre>}m, '[...]').gsub('"', '\"').gsub(/(\r?\n|\r\n?)/, "\\n> ") + "\\n\\n"
render(:update) { |page| render(:update) { |page|
page << "$('message_subject').value = \"#{subject}\";" page << "$('reply_subject').value = \"#{subject}\";"
page.<< "$('message_content').value = \"#{content}\";" page.<< "$('message_content').value = \"#{content}\";"
page.show 'reply' page.show 'reply'
page << "Form.Element.focus('message_content');" page << "Form.Element.focus('message_content');"

View File

@ -20,7 +20,6 @@ class QueriesController < ApplicationController
def new def new
@query = Query.new(params[:query]) @query = Query.new(params[:query])
@query.project = params[:query_is_for_all] ? nil : @project @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.user = User.current
@query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin? @query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
@ -43,7 +42,6 @@ class QueriesController < ApplicationController
@query.add_filters(params[:fields] || params[:f], params[:operators] || params[:op], params[:values] || params[:v]) if params[:fields] || params[:f] @query.add_filters(params[:fields] || params[:f], params[:operators] || params[:op], params[:values] || params[:v]) if params[:fields] || params[:f]
@query.attributes = params[:query] @query.attributes = params[:query]
@query.project = nil if params[:query_is_for_all] @query.project = nil if params[:query_is_for_all]
@query.display_subprojects = params[:display_subprojects] if params[:display_subprojects].present?
@query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin? @query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
@query.group_by ||= params[:group_by] @query.group_by ||= params[:group_by]
@query.column_names = params[:c] if params[:c] @query.column_names = params[:c] if params[:c]

View File

@ -72,7 +72,8 @@ class TimeEntryReportsController < ApplicationController
@periods = [] @periods = []
# Date#at_beginning_of_ not supported in Rails 1.2.x # Date#at_beginning_of_ not supported in Rails 1.2.x
date_from = @from.to_time date_from = @from.to_time
while date_from <= @to.to_time # 100 columns max
while date_from <= @to.to_time && @periods.length < 100
case @columns case @columns
when 'year' when 'year'
@periods << "#{date_from.year}" @periods << "#{date_from.year}"
@ -160,9 +161,6 @@ class TimeEntryReportsController < ApplicationController
@available_criterias = { 'project' => {:sql => "#{TimeEntry.table_name}.project_id", @available_criterias = { 'project' => {:sql => "#{TimeEntry.table_name}.project_id",
:klass => Project, :klass => Project,
:label => :label_project}, :label => :label_project},
'status' => {:sql => "#{Issue.table_name}.status_id",
:klass => IssueStatus,
:label => :field_status},
'version' => {:sql => "#{Issue.table_name}.fixed_version_id", 'version' => {:sql => "#{Issue.table_name}.fixed_version_id",
:klass => Version, :klass => Version,
:label => :label_version}, :label => :label_version},

View File

@ -197,16 +197,8 @@ class UsersController < ApplicationController
def edit_membership def edit_membership
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 = Member.edit_membership(params[:membership_id], params[:membership], @user)
@membership.save if request.post? @membership.save if request.post?
end
respond_to do |format| respond_to do |format|
if @membership.valid? if @membership.valid?
format.html { redirect_to :controller => 'users', :action => 'edit', :id => @user, :tab => 'memberships' } format.html { redirect_to :controller => 'users', :action => 'edit', :id => @user, :tab => 'memberships' }

View File

@ -16,7 +16,6 @@ class WatchersController < ApplicationController
before_filter :find_project before_filter :find_project
before_filter :require_login, :check_project_privacy, :only => [:watch, :unwatch] before_filter :require_login, :check_project_privacy, :only => [:watch, :unwatch]
before_filter :authorize, :only => [:new, :destroy] before_filter :authorize, :only => [:new, :destroy]
before_filter :authorize_access_to_object, :only => [:new, :destroy]
verify :method => :post, verify :method => :post,
:only => [ :watch, :unwatch ], :only => [ :watch, :unwatch ],
@ -35,12 +34,9 @@ class WatchersController < ApplicationController
end end
def new def new
params[:user_ids].each do |user_id| @watcher = Watcher.new(params[:watcher])
@watcher = Watcher.new((params[:watcher] || {}).merge({:user_id => user_id}))
@watcher.watchable = @watched @watcher.watchable = @watched
@watcher.save if request.post? @watcher.save if request.post?
end if params[:user_ids].present?
respond_to do |format| respond_to do |format|
format.html { redirect_to :back } format.html { redirect_to :back }
format.js do format.js do
@ -54,7 +50,7 @@ class WatchersController < ApplicationController
end end
def destroy def destroy
@watched.set_watcher(Principal.find(params[:user_id]), false) if request.post? @watched.set_watcher(User.find(params[:user_id]), false) if request.post?
respond_to do |format| respond_to do |format|
format.html { redirect_to :back } format.html { redirect_to :back }
format.js do format.js do
@ -98,24 +94,4 @@ private
rescue ::ActionController::RedirectBackError rescue ::ActionController::RedirectBackError
render :text => (watching ? 'Watcher added.' : 'Watcher removed.'), :layout => true render :text => (watching ? 'Watcher added.' : 'Watcher removed.'), :layout => true
end 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 end

View File

@ -1,37 +0,0 @@
#-- encoding: UTF-8
#-- copyright
# ChiliProject is a project management system.
#
# Copyright (C) 2010-2013 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

View File

@ -1,79 +0,0 @@
#-- encoding: UTF-8
#-- copyright
# ChiliProject is a project management system.
#
# Copyright (C) 2010-2013 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
}
end
end

View File

@ -1,17 +0,0 @@
#-- encoding: UTF-8
#-- copyright
# ChiliProject is a project management system.
#
# Copyright (C) 2010-2013 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 IssueStatusDrop < BaseDrop
allowed_methods :name
end

View File

@ -1,17 +0,0 @@
#-- encoding: UTF-8
#-- copyright
# ChiliProject is a project management system.
#
# Copyright (C) 2010-2013 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 PrincipalDrop < BaseDrop
allowed_methods :name
end

View File

@ -1,17 +0,0 @@
#-- encoding: UTF-8
#-- copyright
# ChiliProject is a project management system.
#
# Copyright (C) 2010-2013 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 ProjectDrop < BaseDrop
allowed_methods :name, :identifier
end

View File

@ -1,17 +0,0 @@
#-- encoding: UTF-8
#-- copyright
# ChiliProject is a project management system.
#
# Copyright (C) 2010-2013 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 TrackerDrop < BaseDrop
allowed_methods :name
end

View File

@ -1,17 +0,0 @@
#-- encoding: UTF-8
#-- copyright
# ChiliProject is a project management system.
#
# Copyright (C) 2010-2013 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 WikiPageDrop < BaseDrop
allowed_methods :title
end

View File

@ -16,8 +16,9 @@ require 'forwardable'
require 'cgi' require 'cgi'
module ApplicationHelper module ApplicationHelper
include Redmine::WikiFormatting::Macros::Definitions
include Redmine::I18n include Redmine::I18n
include Gravatarify::Helper include GravatarHelper::PublicMethods
extend Forwardable extend Forwardable
def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter
@ -223,15 +224,17 @@ module ApplicationHelper
end end
# Renders the project quick-jump box # Renders the project quick-jump box
def render_project_jump_box(projects = [], html_options = {}) def render_project_jump_box
projects ||= User.current.memberships.collect(&:project).compact.uniq projects = User.current.memberships.collect(&:project).compact.uniq
if projects.any? if projects.any?
# option_tags = content_tag :option, l(:label_jump_to_a_project), :value => "" s = '<select onchange="if (this.value != \'\') { window.location = this.value; }">' +
option_tags = (content_tag :option, "", :value => "" ) "<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
option_tags << project_tree_options_for_select(projects, :selected => @project) do |p| '<option value="" disabled="disabled">---</option>'
s << project_tree_options_for_select(projects, :selected => @project) do |p|
{ :value => url_for(:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item) } { :value => url_for(:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item) }
end end
select_tag "", option_tags, html_options.merge({ :onchange => "if (this.value != \'\') { window.location = this.value; }" }) s << '</select>'
s
end end
end end
@ -285,15 +288,7 @@ module ApplicationHelper
def principals_check_box_tags(name, principals) def principals_check_box_tags(name, principals)
s = '' s = ''
principals.sort.each do |principal| principals.sort.each do |principal|
s << "<label style='display:block;'>#{ check_box_tag name, principal.id, false } #{h principal}</label>\n" s << "<label>#{ check_box_tag name, principal.id, false } #{h principal}</label>\n"
end
s
end
def projects_check_box_tags(name, projects)
s = ''
projects.each do |project|
s << "<label>#{ check_box_tag name, project.id, false } #{h project}</label>\n"
end end
s s
end end
@ -394,9 +389,7 @@ module ApplicationHelper
end end
def page_header_title def page_header_title
if @page_header_title.present? if @project.nil? || @project.new_record?
h(@page_header_title)
elsif @project.nil? || @project.new_record?
h(Setting.app_title) h(Setting.app_title)
else else
b = [] b = []
@ -436,9 +429,8 @@ module ApplicationHelper
css << 'theme-' + theme.name css << 'theme-' + theme.name
end end
css << 'project-' + @project.id.to_s if @project.present? css << 'controller-' + params[:controller]
css << 'controller-' + params[:controller] if params[:controller] css << 'action-' + params[:action]
css << 'action-' + params[:action] if params[:action]
css.join(' ') css.join(' ')
end end
@ -455,55 +447,23 @@ module ApplicationHelper
case args.size case args.size
when 1 when 1
obj = options[:object] obj = options[:object]
input_text = args.shift text = args.shift
when 2 when 2
obj = args.shift obj = args.shift
attr = args.shift attr = args.shift
input_text = obj.send(attr).to_s text = obj.send(attr).to_s
else else
raise ArgumentError, 'invalid arguments to textilizable' raise ArgumentError, 'invalid arguments to textilizable'
end end
return '' if input_text.blank? return '' if text.blank?
project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil) project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
only_path = options.delete(:only_path) == false ? false : true only_path = options.delete(:only_path) == false ? false : true
begin text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr) { |macro, args| exec_macro(macro, obj, args) }
text = ChiliProject::Liquid::Legacy.run_macros(input_text)
liquid_template = ChiliProject::Liquid::Template.parse(text)
liquid_variables = get_view_instance_variables_for_liquid
liquid_variables.merge!({'current_user' => User.current})
liquid_variables.merge!({'toc' => '{{toc}}'}) # Pass toc through to replace later
liquid_variables.merge!(ChiliProject::Liquid::Variables.all)
# Pass :view in a register so this view (with helpers) can be used inside of a tag
text = liquid_template.render(liquid_variables, :registers => {:view => self, :object => obj, :attribute => attr})
# Add Liquid errors to the log
if Rails.logger && Rails.logger.debug?
msg = ""
liquid_template.errors.each do |exception|
msg << "[Liquid Error] #{exception.message}\n:\n#{exception.backtrace.join("\n")}"
msg << "\n\n"
end
Rails.logger.debug msg
end
rescue Liquid::SyntaxError => exception
msg = "[Liquid Syntax Error] #{exception.message}"
if Rails.logger && Rails.logger.debug?
log_msg = "#{msg}\n"
log_msg << exception.backtrace.collect{ |str| " #{str}" }.join("\n")
log_msg << "\n\n"
Rails.logger.debug log_msg
end
# Skip Liquid if there is a syntax error
text = content_tag(:div, msg, :class => "flash error")
text << h(input_text)
end
@parsed_headings = [] @parsed_headings = []
text = parse_non_pre_blocks(text) do |text| text = parse_non_pre_blocks(text) do |text|
[:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links, :parse_headings, :parse_relative_urls].each do |method_name| [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links, :parse_headings].each do |method_name|
send method_name, text, project, obj, attr, only_path, options send method_name, text, project, obj, attr, only_path, options
end end
end end
@ -544,41 +504,6 @@ module ApplicationHelper
parsed parsed
end end
RELATIVE_LINK_RE = %r{
<a
(?:
(\shref=
(?: # the href and link
(?:'(\/[^>]+?)')|
(?:"(\/[^>]+?)")
)
)|
[^>]
)*
>
[^<]*?<\/a> # content and closing link tag.
}x unless const_defined?(:RELATIVE_LINK_RE)
def parse_relative_urls(text, project, obj, attr, only_path, options)
return if only_path
text.gsub!(RELATIVE_LINK_RE) do |m|
href, relative_url = $1, $2 || $3
next m unless href.present?
if defined?(request) && request.present?
# we have a request!
protocol, host_with_port = request.protocol, request.host_with_port
elsif @controller
# use the same methods as url_for in the Mailer
url_opts = @controller.class.default_url_options
next m unless url_opts && url_opts[:protocol] && url_opts[:host]
protocol, host_with_port = "#{url_opts[:protocol]}://", url_opts[:host]
else
next m
end
m.sub href, " href=\"#{protocol}#{host_with_port}#{relative_url}\""
end
end
def parse_inline_attachments(text, project, obj, attr, only_path, options) def parse_inline_attachments(text, project, obj, attr, only_path, options)
# when using an image link, try to use an attachment, if possible # when using an image link, try to use an attachment, if possible
if options[:attachments] || (obj && obj.respond_to?(:attachments)) if options[:attachments] || (obj && obj.respond_to?(:attachments))
@ -787,7 +712,7 @@ module ApplicationHelper
end end
end end
TOC_RE = /<p>\{%\s*toc(_right|_left)?\s*%\}<\/p>/i unless const_defined?(:TOC_RE) TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
# Renders the TOC with given headings # Renders the TOC with given headings
def replace_toc(text, headings) def replace_toc(text, headings)
@ -795,14 +720,10 @@ module ApplicationHelper
if headings.empty? if headings.empty?
'' ''
else else
toc_class = 'toc' div_class = 'toc'
toc_class << ' right' if $1 == '_right' div_class << ' right' if $1 == '>'
toc_class << ' left' if $1 == '_left' div_class << ' left' if $1 == '<'
out = "<ul class=\"#{div_class}\"><li>"
out = "<fieldset class=\"header_collapsible collapsible #{toc_class}\">"
out << "<legend onclick=\"toggleFieldset(this);\"><span>#{l(:label_toc)}</span></legend>"
out << "<div>"
out << "<ul class=\"toc\"><li>"
root = headings.map(&:first).min root = headings.map(&:first).min
current = root current = root
started = false started = false
@ -820,7 +741,6 @@ module ApplicationHelper
end end
out << '</li></ul>' * (current - root) out << '</li></ul>' * (current - root)
out << '</li></ul>' out << '</li></ul>'
out << '</div></fieldset>'
end end
end end
end end
@ -852,7 +772,7 @@ module ApplicationHelper
def back_url_hidden_field_tag def back_url_hidden_field_tag
back_url = params[:back_url] || request.env['HTTP_REFERER'] back_url = params[:back_url] || request.env['HTTP_REFERER']
back_url = CGI.unescape(back_url.to_s) 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 end
def check_all_links(form_name) def check_all_links(form_name)
@ -868,10 +788,12 @@ module ApplicationHelper
pcts << (100 - pcts[1] - pcts[0]) pcts << (100 - pcts[1] - pcts[0])
width = options[:width] || '100px;' width = options[:width] || '100px;'
legend = options[:legend] || '' legend = options[:legend] || ''
content_tag('div', content_tag('table',
content_tag('div', '', :style => "width: #{pcts[0]}%;", :class => 'closed ui-progressbar-value ui-widget-header ui-corner-left') + content_tag('tr',
content_tag('div', '', :style => "width: #{pcts[1]}%;", :class => 'done ui-progressbar-value ui-widget-header'), (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : '') +
:class => 'progress ui-progressbar ui-widget ui-widget-content ui-corner-all', :style => "width: #{width};") + (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : '') +
(pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : '')
), :class => 'progress', :style => "width: #{width};") +
content_tag('p', legend, :class => 'pourcent') content_tag('p', legend, :class => 'pourcent')
end end
@ -884,7 +806,7 @@ module ApplicationHelper
def context_menu(url) def context_menu(url)
unless @context_menu_included unless @context_menu_included
content_for :header_tags do content_for :header_tags do
javascript_include_tag('context_menu.jquery') + javascript_include_tag('context_menu') +
stylesheet_link_tag('context_menu') stylesheet_link_tag('context_menu')
end end
if l(:direction) == 'rtl' if l(:direction) == 'rtl'
@ -894,7 +816,7 @@ module ApplicationHelper
end end
@context_menu_included = true @context_menu_included = true
end end
javascript_tag "jQuery(document).ContextMenu('#{ url_for(url) }')" javascript_tag "new ContextMenu('#{ url_for(url) }')"
end end
def context_menu_link(name, url, options={}) def context_menu_link(name, url, options={})
@ -914,25 +836,33 @@ module ApplicationHelper
end end
def calendar_for(field_id) def calendar_for(field_id)
javascript_tag("jQuery('##{field_id}').datepicker(datepickerSettings)") include_calendar_headers_tags
image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
end end
def jquery_datepicker_settings def include_calendar_headers_tags
start_of_week = Setting.start_of_week.to_s unless @calendar_headers_tags_included
start_of_week_string = start_of_week.present? ? "firstDay: '#{start_of_week}', " : '' @calendar_headers_tags_included = true
script = javascript_tag("var datepickerSettings = {" + content_for :header_tags do
start_of_week_string + start_of_week = case Setting.start_of_week.to_i
"showOn: 'both', " + when 1
"buttonImage: '" + path_to_image('/images/calendar.png') + "', " + 'Calendar._FD = 1;' # Monday
"buttonImageOnly: true, " + when 7
"showButtonPanel: true, " + 'Calendar._FD = 0;' # Sunday
"dateFormat: 'yy-mm-dd' " + when 6
"}") 'Calendar._FD = 6;' # Saturday
unless current_language == :en else
jquery_locale = l("jquery.ui", :default => current_language.to_s) '' # use language
script << javascript_include_tag("libs/ui/i18n/jquery.ui.datepicker-#{jquery_locale}.js") end
javascript_include_tag('calendar/calendar') +
javascript_include_tag("calendar/lang/calendar-#{current_language.to_s.downcase}.js") +
javascript_tag(start_of_week) +
javascript_include_tag('calendar/calendar-setup') +
stylesheet_link_tag('calendar')
end
end end
script
end end
def content_for(name, content = nil, &block) def content_for(name, content = nil, &block)
@ -945,29 +875,10 @@ module ApplicationHelper
(@has_content && @has_content[name]) || false (@has_content && @has_content[name]) || false
end end
# Returns the gravatar image tag for the given email
# +email+ is a string with an email address
def gravatar(email, options={})
gravatarify_options = {}
gravatarify_options[:secure] = options.delete :ssl
[:default, :size, :rating, :filetype].each {|key| gravatarify_options[key] = options.delete key}
# Default size is 50x50 px
gravatarify_options[:size] ||= 50
options[:class] ||= 'gravatar'
gravatarify_options[:html] = options
gravatar_tag email, gravatarify_options
end
# Returns the avatar image tag for the given +user+ if avatars are enabled # Returns the avatar image tag for the given +user+ if avatars are enabled
# +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>') # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
def avatar(user, options = { }) def avatar(user, options = { })
if Setting.gravatar_enabled? if Setting.gravatar_enabled?
if user.is_a?(Group)
size = options[:size] || 50
size = "#{size}x#{size}" # image_tag uses WxH
options[:class] ||= 'gravatar'
return image_tag("group.png", options.merge(:size => size))
end
options.merge!({:ssl => (defined?(request) && request.ssl?), :default => Setting.gravatar_default}) options.merge!({:ssl => (defined?(request) && request.ssl?), :default => Setting.gravatar_default})
email = nil email = nil
if user.respond_to?(:mail) if user.respond_to?(:mail)
@ -987,7 +898,6 @@ module ApplicationHelper
unless User.current.pref.warn_on_leaving_unsaved == '0' unless User.current.pref.warn_on_leaving_unsaved == '0'
tags << "\n" + javascript_tag("Event.observe(window, 'load', function(){ new WarnLeavingUnsaved('#{escape_javascript( l(:text_warn_on_leaving_unsaved) )}'); });") tags << "\n" + javascript_tag("Event.observe(window, 'load', function(){ new WarnLeavingUnsaved('#{escape_javascript( l(:text_warn_on_leaving_unsaved) )}'); });")
end end
tags << jquery_datepicker_settings
tags tags
end end
@ -1025,72 +935,6 @@ module ApplicationHelper
end end
end end
# Expands the current menu item using JavaScript based on the params
def expand_current_menu
current_menu_class =
case
when params[:controller] == "timelog"
"reports"
when params[:controller] == 'projects' && params[:action] == 'changelog'
"reports"
when params[:controller] == 'issues' && ['calendar','gantt'].include?(params[:action])
"reports"
when params[:controller] == 'projects' && params[:action] == 'roadmap'
'roadmap'
when params[:controller] == 'versions' && params[:action] == 'show'
'roadmap'
when params[:controller] == 'projects' && params[:action] == 'settings'
'settings'
when params[:controller] == 'contracts' || params[:controller] == 'deliverables'
'contracts'
else
params[:controller]
end
javascript_tag("jQuery.menu_expand({ menuItem: '.#{current_menu_class}' });")
end
# Menu items for the main top menu
def main_top_menu_items
split_top_menu_into_main_or_more_menus[:main]
end
# Menu items for the more top menu
def more_top_menu_items
split_top_menu_into_main_or_more_menus[:more]
end
def help_menu_item
split_top_menu_into_main_or_more_menus[:help]
end
# Split the :top_menu into separate :main and :more items
def split_top_menu_into_main_or_more_menus
unless @top_menu_split
items_for_main_level = []
items_for_more_level = []
help_menu = nil
menu_items_for(:top_menu) do |item|
if item.name == :home || item.name == :my_page
items_for_main_level << item
elsif item.name == :help
help_menu = item
elsif item.name == :projects
# Remove, present in layout
else
items_for_more_level << item
end
end
@top_menu_split = {
:main => items_for_main_level,
:more => items_for_more_level,
:help => help_menu
}
end
@top_menu_split
end
private private
def wiki_helper def wiki_helper
@ -1102,20 +946,4 @@ module ApplicationHelper
def link_to_content_update(text, url_params = {}, html_options = {}) def link_to_content_update(text, url_params = {}, html_options = {})
link_to(text, url_params, html_options) link_to(text, url_params, html_options)
end end
def get_view_instance_variables_for_liquid
internal_variables = %w{
@output_buffer @cookies @helpers @real_format @assigns_added @assigns
@view_paths @controller
}
self.instance_variables.collect(&:to_s).reject do |ivar|
ivar.match(/^@_/) || # Rails "internal" variables: @_foo
ivar.match(/^@template/) ||
internal_variables.include?(ivar)
end.inject({}) do |acc,ivar|
acc[ivar.sub('@','')] = instance_variable_get(ivar)
acc
end
end
end end

View File

@ -36,7 +36,8 @@ module CustomFieldsHelper
field_format = Redmine::CustomFieldFormat.find_by_name(custom_field.field_format) field_format = Redmine::CustomFieldFormat.find_by_name(custom_field.field_format)
case field_format.try(:edit_as) case field_format.try(:edit_as)
when "date" when "date"
date_field_tag(field_name, custom_value.value, :id => field_id, :size => 10) text_field_tag(field_name, custom_value.value, :id => field_id, :size => 10) +
calendar_for(field_id)
when "text" when "text"
text_area_tag(field_name, custom_value.value, :id => field_id, :rows => 3, :style => 'width:90%') text_area_tag(field_name, custom_value.value, :id => field_id, :rows => 3, :style => 'width:90%')
when "bool" when "bool"
@ -70,7 +71,8 @@ module CustomFieldsHelper
field_format = Redmine::CustomFieldFormat.find_by_name(custom_field.field_format) field_format = Redmine::CustomFieldFormat.find_by_name(custom_field.field_format)
case field_format.try(:edit_as) case field_format.try(:edit_as)
when "date" when "date"
date_field_tag(field_name, '', :id => field_id, :size => 10) text_field_tag(field_name, '', :id => field_id, :size => 10) +
calendar_for(field_id)
when "text" when "text"
text_area_tag(field_name, '', :id => field_id, :rows => 3, :style => 'width:90%') text_area_tag(field_name, '', :id => field_id, :rows => 3, :style => 'width:90%')
when "bool" when "bool"

View File

@ -52,14 +52,13 @@ module IssuesHelper
"<strong>#{@cached_label_priority}</strong>: #{h(issue.priority.name)}" "<strong>#{@cached_label_priority}</strong>: #{h(issue.priority.name)}"
end end
# TODO: deprecate and/or remove
def render_issue_subject_with_tree(issue) def render_issue_subject_with_tree(issue)
s = '' s = ''
ancestors = issue.root? ? [] : issue.ancestors.all ancestors = issue.root? ? [] : issue.ancestors.all
ancestors.each do |ancestor| ancestors.each do |ancestor|
s << '<div>' + content_tag('h2', link_to_issue(ancestor)) s << '<div>' + content_tag('p', link_to_issue(ancestor))
end end
s << '<div class="subject">' + content_tag('h2', h(issue.subject)) s << '<div>' + content_tag('h3', h(issue.subject))
s << '</div>' * (ancestors.size + 1) s << '</div>' * (ancestors.size + 1)
s s
end end
@ -79,22 +78,6 @@ module IssuesHelper
s s
end end
def render_parents_and_subtree(issue)
return if issue.leaf? && !issue.parent
s = '<form><table id="issue_tree" class="list">'
issue_list(issue.self_and_ancestors.sort_by(&:lft) + issue.descendants.sort_by(&:lft)) do |el, level|
s << content_tag('tr',
content_tag('td', check_box_tag("ids[]", el.id, false, :id => nil), :class => 'checkbox') +
content_tag('td', link_to_issue(el, :truncate => 60), :class => 'subject') +
content_tag('td', h(el.status)) +
content_tag('td', link_to_user(el.assigned_to)) +
content_tag('td', progress_bar(el.done_ratio, :width => '80px')),
:class => "issue issue-#{el.id} #{"self" if el == issue} hascontextmenu #{level > 0 ? "idnt idnt-#{level}" : nil}")
end
s << '</table></form>'
s
end
def render_custom_fields_rows(issue) def render_custom_fields_rows(issue)
return if issue.custom_field_values.empty? return if issue.custom_field_values.empty?
ordered_values = [] ordered_values = []

View File

@ -27,34 +27,24 @@ module JournalsHelper
def render_journal(model, journal, options = {}) def render_journal(model, journal, options = {})
return "" if journal.initial? return "" if journal.initial?
journal_content = render_journal_details(journal, :label_updated_time_by)
journal_classes = journal.css_classes journal_content += render_notes(model, journal, options) unless journal.notes.blank?
journal_content = render_journal_details(journal, :label_updated_time_by, model, options) content_tag "div", journal_content, { :id => "change-#{journal.id}", :class => journal.css_classes }
avatar = avatar(journal.user, :size => "40")
unless avatar.blank?
profile_wrap = content_tag("div", avatar, {:class => "profile-wrap"}, false)
journal_content = profile_wrap + journal_content
journal_classes << " has-avatar"
end end
content_tag("div", journal_content, :id => "change-#{journal.id}", :class => journal_classes) # This renders a journal entry wiht a header and details
end def render_journal_details(journal, header_label = :label_updated_time_by)
# This renders a journal entry with a header and details
def render_journal_details(journal, header_label = :label_updated_time_by, model=nil, options={})
header = <<-HTML header = <<-HTML
<h4> <h4>
<div class="journal-link" style="float:right;">#{link_to "##{journal.anchor}", :anchor => "note-#{journal.anchor}"}</div> <div style="float:right;">#{link_to "##{journal.anchor}", :anchor => "note-#{journal.anchor}"}</div>
#{authoring journal.created_at, journal.user, :label => header_label} #{avatar(journal.user, :size => "24")}
#{content_tag('a', '', :name => "note-#{journal.anchor}")} #{content_tag('a', '', :name => "note-#{journal.anchor}")}
#{authoring journal.created_at, journal.user, :label => header_label}
</h4> </h4>
HTML HTML
header << render_notes(model, journal, options) unless journal.notes.blank?
if journal.details.any? if journal.details.any?
details = content_tag "ul", :class => "journal-attributes details" do details = content_tag "ul", :class => "details" do
journal.details.collect do |detail| journal.details.collect do |detail|
if d = journal.render_detail(detail) if d = journal.render_detail(detail)
content_tag("li", d) content_tag("li", d)
@ -63,7 +53,7 @@ module JournalsHelper
end end
end end
content_tag "div", "#{header}#{details}", :class => "journal-details" content_tag("div", "#{header}#{details}", :id => "change-#{journal.id}", :class => "journal")
end end
def render_notes(model, journal, options={}) def render_notes(model, journal, options={})

View File

@ -84,12 +84,11 @@ module QueriesHelper
end end
end end
@query.group_by = params[:group_by] @query.group_by = params[:group_by]
@query.display_subprojects = params[:display_subprojects] if params[:display_subprojects]
@query.column_names = params[:c] || (params[:query] && params[:query][:column_names]) @query.column_names = params[:c] || (params[:query] && params[:query][:column_names])
session[:query] = {:project_id => @query.project_id, :filters => @query.filters, :group_by => @query.group_by, :column_names => @query.column_names, :display_subprojects => @query.display_subprojects} session[:query] = {:project_id => @query.project_id, :filters => @query.filters, :group_by => @query.group_by, :column_names => @query.column_names}
else else
@query = Query.find_by_id(session[:query][:id]) if session[:query][:id] @query = Query.find_by_id(session[:query][:id]) if session[:query][:id]
@query ||= Query.new(:name => "_", :project => @project, :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names], :display_subprojects => session[:query][:display_subprojects]) @query ||= Query.new(:name => "_", :project => @project, :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names])
@query.project = @project @query.project = @project
end end
end end

View File

@ -89,12 +89,12 @@ class Changeset < ActiveRecord::Base
# Attribute reader for committer that encodes the committer string to # Attribute reader for committer that encodes the committer string to
# the repository log encoding (e.g. UTF-8) # the repository log encoding (e.g. UTF-8)
def committer def committer
self.class.to_utf8(read_attribute(:committer), repository_encoding) self.class.to_utf8(read_attribute(:committer), repository.repo_log_encoding)
end end
def before_create def before_create
self.committer = self.class.to_utf8(self.committer, repository_encoding) self.committer = self.class.to_utf8(self.committer, repository.repo_log_encoding)
self.comments = self.class.normalize_comments(self.comments, repository_encoding) self.comments = self.class.normalize_comments(self.comments, repository.repo_log_encoding)
self.user = repository.find_committer_user(self.committer) self.user = repository.find_committer_user(self.committer)
end end

View File

@ -13,11 +13,8 @@
#++ #++
class Comment < ActiveRecord::Base class Comment < ActiveRecord::Base
include Redmine::SafeAttributes
belongs_to :commented, :polymorphic => true, :counter_cache => true belongs_to :commented, :polymorphic => true, :counter_cache => true
belongs_to :author, :class_name => 'User', :foreign_key => 'author_id' belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
validates_presence_of :commented, :author, :comments validates_presence_of :commented, :author, :comments
safe_attributes 'comments'
end end

View File

@ -25,7 +25,6 @@ class Document < ActiveRecord::Base
end) end)
acts_as_searchable :columns => ['title', "#{table_name}.description"], :include => :project acts_as_searchable :columns => ['title', "#{table_name}.description"], :include => :project
acts_as_watchable
validates_presence_of :project, :title, :category validates_presence_of :project, :title, :category
validates_length_of :title, :maximum => 60 validates_length_of :title, :maximum => 60
@ -41,9 +40,7 @@ class Document < ActiveRecord::Base
def after_initialize def after_initialize
if new_record? if new_record?
# FIXME: on Rails 3 use this instead self.category ||= DocumentCategory.default
# self.category ||= DocumentCategory.default
self.category_id = DocumentCategory.default.id if self.category_id == 0
end end
end end
@ -54,10 +51,4 @@ class Document < ActiveRecord::Base
end end
@updated_on @updated_on
end end
def recipients
mails = super # from acts_as_event
mails += watcher_recipients
mails.uniq
end
end end

View File

@ -14,10 +14,6 @@
class DocumentObserver < ActiveRecord::Observer class DocumentObserver < ActiveRecord::Observer
def after_create(document) def after_create(document)
if Setting.notified_events.include?('document_added') Mailer.deliver_document_added(document) if Setting.notified_events.include?('document_added')
document.recipients.each do |recipient|
Mailer.deliver_document_added(document, recipient)
end
end
end end
end end

View File

@ -22,11 +22,6 @@ class Group < Principal
validates_uniqueness_of :lastname, :case_sensitive => false validates_uniqueness_of :lastname, :case_sensitive => false
validates_length_of :lastname, :maximum => 30 validates_length_of :lastname, :maximum => 30
# Returns an array of all of the email addresses of the group's users
def mails
users.collect(&:mail)
end
def to_s def to_s
lastname.to_s lastname.to_s
end end
@ -48,9 +43,4 @@ class Group < Principal
:conditions => ["#{Member.table_name}.user_id = ? AND #{MemberRole.table_name}.inherited_from IN (?)", user.id, member.member_role_ids]).each(&:destroy) :conditions => ["#{Member.table_name}.user_id = ? AND #{MemberRole.table_name}.inherited_from IN (?)", user.id, member.member_role_ids]).each(&:destroy)
end end
end end
def self.human_attribute_name(attribute_name)
attribute_name = "name" if attribute_name == "lastname"
super(attribute_name)
end
end end

View File

@ -103,10 +103,6 @@ class Issue < ActiveRecord::Base
(usr || User.current).allowed_to?(:view_issues, self.project) (usr || User.current).allowed_to?(:view_issues, self.project)
end end
def to_liquid
IssueDrop.new(self)
end
def after_initialize def after_initialize
if new_record? if new_record?
# set default values for new records only # set default values for new records only
@ -368,7 +364,7 @@ class Issue < ActiveRecord::Base
def attachment_removed(obj) def attachment_removed(obj)
init_journal(User.current) init_journal(User.current)
create_journal create_journal
last_journal.update_attribute(:changes, {"attachments_" + obj.id.to_s => [obj.filename, nil]}) last_journal.update_attribute(:changes, {"attachments_" + obj.id.to_s => [obj.filename, nil]}.to_yaml)
end end
# Return true if the issue is closed, otherwise false # Return true if the issue is closed, otherwise false
@ -707,15 +703,6 @@ class Issue < ActiveRecord::Base
projects projects
end end
# Overrides Redmine::Acts::Journalized::Permissions
#
# The default assumption is that journals have the same permissions
# as the journaled object, issue notes have separate permissions though
def journal_editable_by?(journal, user)
return true if journal.user == user && user.allowed_to?(:edit_own_issue_notes, project)
user.allowed_to? :edit_issue_notes, project
end
private private
def update_nested_set_attributes def update_nested_set_attributes

View File

@ -17,9 +17,7 @@ class IssueObserver < ActiveRecord::Observer
def after_create(issue) def after_create(issue)
if self.send_notification if self.send_notification
(issue.recipients + issue.watcher_recipients).uniq.each do |recipient| Mailer.deliver_issue_add(issue) if Setting.notified_events.include?('issue_added')
Mailer.deliver_issue_add(issue, recipient)
end
end end
clear_notification clear_notification
end end

View File

@ -28,10 +28,6 @@ class IssueStatus < ActiveRecord::Base
IssueStatus.update_all("is_default=#{connection.quoted_false}", ['id <> ?', id]) if self.is_default? IssueStatus.update_all("is_default=#{connection.quoted_false}", ['id <> ?', id]) if self.is_default?
end end
def to_liquid
IssueStatusDrop.new(self)
end
# Returns the default status for new issues # Returns the default status for new issues
def self.default def self.default
find(:first, :conditions =>["is_default=?", true]) find(:first, :conditions =>["is_default=?", true])

View File

@ -76,7 +76,7 @@ class Journal < ActiveRecord::Base
end end
def editable_by?(user) def editable_by?(user)
journaled.journal_editable_by?(self, user) journaled.journal_editable_by?(user)
end end
def details def details

View File

@ -27,15 +27,11 @@ class JournalObserver < ActiveRecord::Observer
if journal.initial? if journal.initial?
if Setting.notified_events.include?('wiki_content_added') if Setting.notified_events.include?('wiki_content_added')
(wiki_content.recipients + wiki_page.wiki.watcher_recipients).uniq.each do |recipient| Mailer.deliver_wiki_content_added(wiki_content)
Mailer.deliver_wiki_content_added(wiki_content, recipient)
end
end end
else else
if Setting.notified_events.include?('wiki_content_updated') if Setting.notified_events.include?('wiki_content_updated')
(wiki_content.recipients + wiki_page.wiki.watcher_recipients + wiki_page.watcher_recipients).uniq.each do |recipient| Mailer.deliver_wiki_content_updated(wiki_content)
Mailer.deliver_wiki_content_updated(wiki_content, recipient)
end
end end
end end
end end
@ -47,10 +43,7 @@ class JournalObserver < ActiveRecord::Observer
(Setting.notified_events.include?('issue_note_added') && journal.notes.present?) || (Setting.notified_events.include?('issue_note_added') && journal.notes.present?) ||
(Setting.notified_events.include?('issue_status_updated') && journal.new_status.present?) || (Setting.notified_events.include?('issue_status_updated') && journal.new_status.present?) ||
(Setting.notified_events.include?('issue_priority_updated') && journal.new_value_for('priority_id').present?) (Setting.notified_events.include?('issue_priority_updated') && journal.new_value_for('priority_id').present?)
issue = journal.issue Mailer.deliver_issue_edit(journal)
(issue.recipients + issue.watcher_recipients).uniq.each do |recipient|
Mailer.deliver_issue_edit(journal, recipient)
end
end end
end end

View File

@ -69,7 +69,6 @@ class MailHandler < ActionMailer::Base
else else
# Default behaviour, emails from unknown users are ignored # Default behaviour, emails from unknown users are ignored
logger.info "MailHandler: ignoring email from unknown user [#{sender_email}]" if logger && logger.info logger.info "MailHandler: ignoring email from unknown user [#{sender_email}]" if logger && logger.info
Mailer.deliver_mail_handler_unauthorized_action(user, email.subject.to_s, :to => sender_email) if Setting.mail_handler_confirmation_on_failure
return false return false
end end
end end
@ -103,15 +102,12 @@ class MailHandler < ActionMailer::Base
rescue ActiveRecord::RecordInvalid => e rescue ActiveRecord::RecordInvalid => e
# TODO: send a email to the user # TODO: send a email to the user
logger.error e.message if logger logger.error e.message if logger
Mailer.deliver_mail_handler_missing_information(user, email.subject.to_s, e.message) if Setting.mail_handler_confirmation_on_failure
false false
rescue MissingInformation => e rescue MissingInformation => e
logger.error "MailHandler: missing information from #{user}: #{e.message}" if logger logger.error "MailHandler: missing information from #{user}: #{e.message}" if logger
Mailer.deliver_mail_handler_missing_information(user, email.subject.to_s, e.message) if Setting.mail_handler_confirmation_on_failure
false false
rescue UnauthorizedAction => e rescue UnauthorizedAction => e
logger.error "MailHandler: unauthorized attempt from #{user}" if logger logger.error "MailHandler: unauthorized attempt from #{user}" if logger
Mailer.deliver_mail_handler_unauthorized_action(user, email.subject.to_s) if Setting.mail_handler_confirmation_on_failure
false false
end end
@ -145,7 +141,6 @@ class MailHandler < ActionMailer::Base
issue.save! issue.save!
add_attachments(issue) add_attachments(issue)
logger.info "MailHandler: issue ##{issue.id} created by #{user}" if logger && logger.info logger.info "MailHandler: issue ##{issue.id} created by #{user}" if logger && logger.info
Mailer.deliver_mail_handler_confirmation(issue, user, issue.subject) if Setting.mail_handler_confirmation_on_success
issue issue
end end
@ -167,7 +162,6 @@ class MailHandler < ActionMailer::Base
add_attachments(issue) add_attachments(issue)
issue.save! issue.save!
logger.info "MailHandler: issue ##{issue.id} updated by #{user}" if logger && logger.info logger.info "MailHandler: issue ##{issue.id} updated by #{user}" if logger && logger.info
Mailer.deliver_mail_handler_confirmation(issue.last_journal, user, email.subject) if Setting.mail_handler_confirmation_on_success
issue.last_journal issue.last_journal
end end
@ -196,7 +190,6 @@ class MailHandler < ActionMailer::Base
reply.board = message.board reply.board = message.board
message.children << reply message.children << reply
add_attachments(reply) add_attachments(reply)
Mailer.deliver_mail_handler_confirmation(message, user, reply.subject) if Setting.mail_handler_confirmation_on_success
reply reply
else else
logger.info "MailHandler: ignoring reply from [#{sender_email}] to a locked topic" if logger && logger.info logger.info "MailHandler: ignoring reply from [#{sender_email}] to a locked topic" if logger && logger.info

View File

@ -30,19 +30,20 @@ class Mailer < ActionMailer::Base
{ :host => h, :protocol => Setting.protocol } { :host => h, :protocol => Setting.protocol }
end end
# Builds a tmail object used to email a recipient of the added issue. # Builds a tmail object used to email recipients of the added issue.
# #
# Example: # Example:
# issue_add(issue, 'user@example.com') => tmail object # issue_add(issue) => tmail object
# Mailer.deliver_issue_add(issue, 'user@example.com') => sends an email to 'user@example.com' # Mailer.deliver_issue_add(issue) => sends an email to issue recipients
def issue_add(issue, recipient) def issue_add(issue)
redmine_headers 'Project' => issue.project.identifier, redmine_headers 'Project' => issue.project.identifier,
'Issue-Id' => issue.id, 'Issue-Id' => issue.id,
'Issue-Author' => issue.author.login, 'Issue-Author' => issue.author.login,
'Type' => "Issue" 'Type' => "Issue"
redmine_headers 'Issue-Assignee' => issue.assigned_to.login if issue.assigned_to redmine_headers 'Issue-Assignee' => issue.assigned_to.login if issue.assigned_to
message_id issue message_id issue
recipients [recipient] recipients issue.recipients
cc(issue.watcher_recipients - @recipients)
subject "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] (#{issue.status.name}) #{issue.subject}" subject "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] (#{issue.status.name}) #{issue.subject}"
body :issue => issue, body :issue => issue,
:issue_url => url_for(:controller => 'issues', :action => 'show', :id => issue) :issue_url => url_for(:controller => 'issues', :action => 'show', :id => issue)
@ -52,9 +53,9 @@ class Mailer < ActionMailer::Base
# Builds a tmail object used to email recipients of the edited issue. # Builds a tmail object used to email recipients of the edited issue.
# #
# Example: # Example:
# issue_edit(journal, 'user@example.com') => tmail object # issue_edit(journal) => tmail object
# Mailer.deliver_issue_edit(journal, 'user@example.com') => sends an email to issue recipients # Mailer.deliver_issue_edit(journal) => sends an email to issue recipients
def issue_edit(journal, recipient) def issue_edit(journal)
issue = journal.journaled.reload issue = journal.journaled.reload
redmine_headers 'Project' => issue.project.identifier, redmine_headers 'Project' => issue.project.identifier,
'Issue-Id' => issue.id, 'Issue-Id' => issue.id,
@ -64,7 +65,9 @@ class Mailer < ActionMailer::Base
message_id journal message_id journal
references issue references issue
@author = journal.user @author = journal.user
recipients [recipient] recipients issue.recipients
# Watchers in cc
cc(issue.watcher_recipients - @recipients)
s = "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] " s = "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] "
s << "(#{issue.status.name}) " if journal.details['status_id'] s << "(#{issue.status.name}) " if journal.details['status_id']
s << issue.subject s << issue.subject
@ -90,12 +93,12 @@ class Mailer < ActionMailer::Base
# Builds a tmail object used to email users belonging to the added document's project. # Builds a tmail object used to email users belonging to the added document's project.
# #
# Example: # Example:
# document_added(document, 'test@example.com') => tmail object # document_added(document) => tmail object
# Mailer.deliver_document_added(document, 'test@example.com') => sends an email to the document's project recipients # Mailer.deliver_document_added(document) => sends an email to the document's project recipients
def document_added(document, recipient) def document_added(document)
redmine_headers 'Project' => document.project.identifier, redmine_headers 'Project' => document.project.identifier,
'Type' => "Document" 'Type' => "Document"
recipients [recipient] recipients document.recipients
subject "[#{document.project.name}] #{l(:label_document_new)}: #{document.title}" subject "[#{document.project.name}] #{l(:label_document_new)}: #{document.title}"
body :document => document, body :document => document,
:document_url => url_for(:controller => 'documents', :action => 'show', :id => document) :document_url => url_for(:controller => 'documents', :action => 'show', :id => document)
@ -107,7 +110,7 @@ class Mailer < ActionMailer::Base
# Example: # Example:
# attachments_added(attachments) => tmail object # attachments_added(attachments) => tmail object
# Mailer.deliver_attachments_added(attachments) => sends an email to the project's recipients # Mailer.deliver_attachments_added(attachments) => sends an email to the project's recipients
def attachments_added(attachments, recipient) def attachments_added(attachments)
container = attachments.first.container container = attachments.first.container
added_to = '' added_to = ''
added_to_url = '' added_to_url = ''
@ -115,14 +118,16 @@ class Mailer < ActionMailer::Base
when 'Project' when 'Project'
added_to_url = url_for(:controller => 'files', :action => 'index', :project_id => container) added_to_url = url_for(:controller => 'files', :action => 'index', :project_id => container)
added_to = "#{l(:label_project)}: #{container}" added_to = "#{l(:label_project)}: #{container}"
recipients container.project.notified_users.select {|user| user.allowed_to?(:view_files, container.project)}.collect {|u| u.mail}
when 'Version' when 'Version'
added_to_url = url_for(:controller => 'files', :action => 'index', :project_id => container.project) added_to_url = url_for(:controller => 'files', :action => 'index', :project_id => container.project)
added_to = "#{l(:label_version)}: #{container.name}" added_to = "#{l(:label_version)}: #{container.name}"
recipients container.project.notified_users.select {|user| user.allowed_to?(:view_files, container.project)}.collect {|u| u.mail}
when 'Document' when 'Document'
added_to_url = url_for(:controller => 'documents', :action => 'show', :id => container.id) added_to_url = url_for(:controller => 'documents', :action => 'show', :id => container.id)
added_to = "#{l(:label_document)}: #{container.title}" added_to = "#{l(:label_document)}: #{container.title}"
recipients container.recipients
end end
recipients [recipient]
redmine_headers 'Project' => container.project.identifier, redmine_headers 'Project' => container.project.identifier,
'Type' => "Attachment" 'Type' => "Attachment"
subject "[#{container.project.name}] #{l(:label_attachment_new)}" subject "[#{container.project.name}] #{l(:label_attachment_new)}"
@ -137,11 +142,11 @@ class Mailer < ActionMailer::Base
# Example: # Example:
# news_added(news) => tmail object # news_added(news) => tmail object
# Mailer.deliver_news_added(news) => sends an email to the news' project recipients # Mailer.deliver_news_added(news) => sends an email to the news' project recipients
def news_added(news, recipient) def news_added(news)
redmine_headers 'Project' => news.project.identifier, redmine_headers 'Project' => news.project.identifier,
'Type' => "News" 'Type' => "News"
message_id news message_id news
recipients [recipient] recipients news.recipients
subject "[#{news.project.name}] #{l(:label_news)}: #{news.title}" subject "[#{news.project.name}] #{l(:label_news)}: #{news.title}"
body :news => news, body :news => news,
:news_url => url_for(:controller => 'news', :action => 'show', :id => news) :news_url => url_for(:controller => 'news', :action => 'show', :id => news)
@ -171,13 +176,14 @@ class Mailer < ActionMailer::Base
# Example: # Example:
# message_posted(message) => tmail object # message_posted(message) => tmail object
# Mailer.deliver_message_posted(message) => sends an email to the recipients # Mailer.deliver_message_posted(message) => sends an email to the recipients
def message_posted(message, recipient) def message_posted(message)
redmine_headers 'Project' => message.project.identifier, redmine_headers 'Project' => message.project.identifier,
'Topic-Id' => (message.parent_id || message.id), 'Topic-Id' => (message.parent_id || message.id),
'Type' => "Forum" 'Type' => "Forum"
message_id message message_id message
references message.parent unless message.parent.nil? references message.parent unless message.parent.nil?
recipients [recipient] recipients(message.recipients)
cc((message.root.watcher_recipients + message.board.watcher_recipients).uniq - @recipients)
subject "[#{message.board.project.name} - #{message.board.name} - msg#{message.root.id}] #{message.subject}" subject "[#{message.board.project.name} - #{message.board.name} - msg#{message.root.id}] #{message.subject}"
body :message => message, body :message => message,
:message_url => url_for({ :controller => 'messages', :action => 'show', :board_id => message.board, :id => message.root, :r => message, :anchor => "message-#{message.id}" }) :message_url => url_for({ :controller => 'messages', :action => 'show', :board_id => message.board, :id => message.root, :r => message, :anchor => "message-#{message.id}" })
@ -189,12 +195,13 @@ class Mailer < ActionMailer::Base
# Example: # Example:
# wiki_content_added(wiki_content) => tmail object # wiki_content_added(wiki_content) => tmail object
# Mailer.deliver_wiki_content_added(wiki_content) => sends an email to the project's recipients # Mailer.deliver_wiki_content_added(wiki_content) => sends an email to the project's recipients
def wiki_content_added(wiki_content, recipient) def wiki_content_added(wiki_content)
redmine_headers 'Project' => wiki_content.project.identifier, redmine_headers 'Project' => wiki_content.project.identifier,
'Wiki-Page-Id' => wiki_content.page.id, 'Wiki-Page-Id' => wiki_content.page.id,
'Type' => "Wiki" 'Type' => "Wiki"
message_id wiki_content message_id wiki_content
recipients [recipient] recipients wiki_content.recipients
cc(wiki_content.page.wiki.watcher_recipients - recipients)
subject "[#{wiki_content.project.name}] #{l(:mail_subject_wiki_content_added, :id => wiki_content.page.pretty_title)}" subject "[#{wiki_content.project.name}] #{l(:mail_subject_wiki_content_added, :id => wiki_content.page.pretty_title)}"
body :wiki_content => wiki_content, body :wiki_content => wiki_content,
:wiki_content_url => url_for(:controller => 'wiki', :action => 'show', :project_id => wiki_content.project, :id => wiki_content.page.title) :wiki_content_url => url_for(:controller => 'wiki', :action => 'show', :project_id => wiki_content.project, :id => wiki_content.page.title)
@ -206,12 +213,13 @@ class Mailer < ActionMailer::Base
# Example: # Example:
# wiki_content_updated(wiki_content) => tmail object # wiki_content_updated(wiki_content) => tmail object
# Mailer.deliver_wiki_content_updated(wiki_content) => sends an email to the project's recipients # Mailer.deliver_wiki_content_updated(wiki_content) => sends an email to the project's recipients
def wiki_content_updated(wiki_content, recipient) def wiki_content_updated(wiki_content)
redmine_headers 'Project' => wiki_content.project.identifier, redmine_headers 'Project' => wiki_content.project.identifier,
'Wiki-Page-Id' => wiki_content.page.id, 'Wiki-Page-Id' => wiki_content.page.id,
'Type' => "Wiki" 'Type' => "Wiki"
message_id wiki_content message_id wiki_content
recipients [recipient] recipients wiki_content.recipients
cc(wiki_content.page.wiki.watcher_recipients + wiki_content.page.watcher_recipients - recipients)
subject "[#{wiki_content.project.name}] #{l(:mail_subject_wiki_content_updated, :id => wiki_content.page.pretty_title)}" subject "[#{wiki_content.project.name}] #{l(:mail_subject_wiki_content_updated, :id => wiki_content.page.pretty_title)}"
body :wiki_content => wiki_content, body :wiki_content => wiki_content,
:wiki_content_url => url_for(:controller => 'wiki', :action => 'show', :project_id => wiki_content.project, :id => wiki_content.page.title), :wiki_content_url => url_for(:controller => 'wiki', :action => 'show', :project_id => wiki_content.project, :id => wiki_content.page.title),
@ -285,44 +293,6 @@ class Mailer < ActionMailer::Base
render_multipart('register', body) render_multipart('register', body)
end end
def mail_handler_confirmation(object, user, email_subject)
recipients user.mail
case
when object.is_a?(Issue)
project = object.project.name
url = url_for(:controller => 'issues', :action => 'show', :id => object.id)
when object.is_a?(Journal)
project = object.project.name
url = url_for(:controller => 'issues', :action => 'show', :id => object.issue.id)
when object.class == Message
project = object.project.name
url = url_for(object.event_url)
else
project = ''
url = ''
end
subject "[#{project}] #{l(:label_mail_handler_confirmation, :subject => email_subject)}"
body(:object => object,
:url => url)
render_multipart('mail_handler_confirmation', body)
end
def mail_handler_unauthorized_action(user, email_subject, options={})
recipients options[:to] || user.mail
subject l(:label_mail_handler_failure, :subject => email_subject)
body({})
render_multipart('mail_handler_unauthorized_action', body)
end
def mail_handler_missing_information(user, email_subject, error_message)
recipients user.mail
subject l(:label_mail_handler_failure, :subject => email_subject)
body({:errors => error_message.to_s})
render_multipart('mail_handler_missing_information', body)
end
def test(user) def test(user)
redmine_headers 'Type' => "Test" redmine_headers 'Type' => "Test"
set_language_if_valid(user.language) set_language_if_valid(user.language)
@ -425,7 +395,7 @@ class Mailer < ActionMailer::Base
# Removes the current user from the recipients and cc # Removes the current user from the recipients and cc
# if he doesn't want to receive notifications about what he does # if he doesn't want to receive notifications about what he does
@author ||= User.current @author ||= User.current
if @author && @author.mail && @author.pref[:no_self_notified] if @author.pref[:no_self_notified]
recipients((recipients.is_a?(Array) ? recipients : [recipients]) - [@author.mail]) if recipients.present? recipients((recipients.is_a?(Array) ? recipients : [recipients]) - [@author.mail]) if recipients.present?
cc((cc.is_a?(Array) ? cc : [cc]) - [@author.mail]) if cc.present? cc((cc.is_a?(Array) ? cc : [cc]) - [@author.mail]) if cc.present?
end end
@ -433,6 +403,13 @@ class Mailer < ActionMailer::Base
notified_users = [recipients, cc].flatten.compact.uniq notified_users = [recipients, cc].flatten.compact.uniq
# Rails would log recipients only, not cc and bcc # Rails would log recipients only, not cc and bcc
mylogger.info "Sending email notification to: #{notified_users.join(', ')}" if mylogger mylogger.info "Sending email notification to: #{notified_users.join(', ')}" if mylogger
# Blind carbon copy recipients
if Setting.bcc_recipients?
bcc(notified_users)
recipients []
cc []
end
super super
end end

View File

@ -41,6 +41,7 @@ class Message < ActiveRecord::Base
acts_as_watchable acts_as_watchable
attr_protected :locked, :sticky
validates_presence_of :board, :subject, :content validates_presence_of :board, :subject, :content
validates_length_of :subject, :maximum => 255 validates_length_of :subject, :maximum => 255
@ -50,7 +51,7 @@ class Message < ActiveRecord::Base
:conditions => Project.allowed_to_condition(args.first || User.current, :view_messages) } } :conditions => Project.allowed_to_condition(args.first || User.current, :view_messages) } }
safe_attributes 'subject', 'content' safe_attributes 'subject', 'content'
safe_attributes 'locked', 'sticky', 'board_id', safe_attributes 'locked', 'sticky',
:if => lambda {|message, user| :if => lambda {|message, user|
user.allowed_to?(:edit_messages, message.project) user.allowed_to?(:edit_messages, message.project)
} }
@ -80,15 +81,9 @@ class Message < ActiveRecord::Base
end end
def after_destroy def after_destroy
parent.reset_last_reply_id! if parent
board.reset_counters! board.reset_counters!
end end
def reset_last_reply_id!
clid = children.present? ? children.last.id : nil
self.update_attribute(:last_reply_id, clid)
end
def sticky=(arg) def sticky=(arg)
write_attribute :sticky, (arg == true || arg.to_s == '1' ? 1 : 0) write_attribute :sticky, (arg == true || arg.to_s == '1' ? 1 : 0)
end end

View File

@ -14,13 +14,6 @@
class MessageObserver < ActiveRecord::Observer class MessageObserver < ActiveRecord::Observer
def after_create(message) def after_create(message)
if Setting.notified_events.include?('message_posted') Mailer.deliver_message_posted(message) if Setting.notified_events.include?('message_posted')
recipients = message.recipients
recipients += message.root.watcher_recipients
recipients += message.board.watcher_recipients
recipients.uniq.each do |recipient|
Mailer.deliver_message_posted(message, recipient)
end
end
end end
end end

View File

@ -22,8 +22,7 @@ class News < ActiveRecord::Base
validates_length_of :title, :maximum => 60 validates_length_of :title, :maximum => 60
validates_length_of :summary, :maximum => 255 validates_length_of :summary, :maximum => 255
acts_as_journalized :event_url => Proc.new {|o| {:controller => 'news', :action => 'show', :id => o.journaled_id} }, acts_as_journalized :event_url => Proc.new {|o| {:controller => 'news', :action => 'show', :id => o.journaled_id} }
:event_description => :description
acts_as_searchable :columns => ["#{table_name}.title", "#{table_name}.summary", "#{table_name}.description"], :include => :project acts_as_searchable :columns => ["#{table_name}.title", "#{table_name}.summary", "#{table_name}.description"], :include => :project
acts_as_watchable acts_as_watchable
@ -40,11 +39,6 @@ class News < ActiveRecord::Base
!user.nil? && user.allowed_to?(:view_news, project) !user.nil? && user.allowed_to?(:view_news, project)
end end
# Returns true if the news can be commented by user
def commentable?(user=User.current)
user.allowed_to?(:comment_news, project)
end
# returns latest news for projects visible by user # returns latest news for projects visible by user
def self.latest(user = User.current, count = 5) def self.latest(user = User.current, count = 5)
find(:all, :limit => count, :conditions => Project.allowed_to_condition(user, :view_news), :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC") find(:all, :limit => count, :conditions => Project.allowed_to_condition(user, :view_news), :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC")

View File

@ -14,10 +14,6 @@
class NewsObserver < ActiveRecord::Observer class NewsObserver < ActiveRecord::Observer
def after_create(news) def after_create(news)
if Setting.notified_events.include?('news_added') Mailer.deliver_news_added(news) if Setting.notified_events.include?('news_added')
news.recipients.each do |recipient|
Mailer.deliver_news_added(news, recipient)
end
end
end end
end end

View File

@ -31,10 +31,6 @@ class Principal < ActiveRecord::Base
before_create :set_default_empty_values before_create :set_default_empty_values
def to_liquid
PrincipalDrop.new(self)
end
def name(formatter = nil) def name(formatter = nil)
to_s to_s
end end
@ -48,82 +44,6 @@ class Principal < ActiveRecord::Base
end end
end end
def active?
true
end
def logged?
true # TODO: should all principals default to logged or not?
end
# Return true if the user is allowed to do the specified action on a specific context
# Action can be:
# * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
# * a permission Symbol (eg. :edit_project)
# Context can be:
# * a project : returns true if user is allowed to do the specified action on this project
# * a group of projects : returns true if user is allowed on every project
# * nil with options[:global] set : check if user has at least one role allowed for this action,
# or falls back to Non Member / Anonymous permissions depending if the user is logged
def allowed_to?(action, context, options={})
if context && context.is_a?(Project)
# No action allowed on archived projects
return false unless context.active?
# No action allowed on disabled modules
return false unless context.allows_to?(action)
# Admin users are authorized for anything else
return true if admin?
roles = roles_for_project(context)
return false unless roles
roles.detect {|role| (context.is_public? || role.member?) && role.allowed_to?(action)}
elsif context && context.is_a?(Array)
# Authorize if user is authorized on every element of the array
context.map do |project|
allowed_to?(action,project,options)
end.inject do |memo,allowed|
memo && allowed
end
elsif options[:global]
# Admin users are always authorized
return true if admin?
# authorize if user has at least one role that has this permission
roles = memberships.collect {|m| m.roles}.flatten.uniq
roles.detect {|r| r.allowed_to?(action)} || (self.logged? ? Role.non_member.allowed_to?(action) : Role.anonymous.allowed_to?(action))
else
false
end
end
# Is the user allowed to do the specified action on any project?
# See allowed_to? for the actions and valid options.
def allowed_to_globally?(action, options)
allowed_to?(action, nil, options.reverse_merge(:global => true))
end
# Return user's roles for project
def roles_for_project(project)
roles = []
# No role on archived projects
return roles unless project && project.active?
if logged?
# Find project membership
membership = memberships.detect {|m| m.project_id == project.id}
if membership
roles = membership.roles
else
@role_non_member ||= Role.non_member
roles << @role_non_member
end
else
@role_anonymous ||= Role.anonymous
roles << @role_anonymous
end
roles
end
protected protected
# Make sure we don't try to insert NULL values (see #4632) # Make sure we don't try to insert NULL values (see #4632)

View File

@ -82,16 +82,6 @@ class Project < ActiveRecord::Base
named_scope :active, { :conditions => "#{Project.table_name}.status = #{STATUS_ACTIVE}"} named_scope :active, { :conditions => "#{Project.table_name}.status = #{STATUS_ACTIVE}"}
named_scope :all_public, { :conditions => { :is_public => true } } named_scope :all_public, { :conditions => { :is_public => true } }
named_scope :visible, lambda { { :conditions => Project.visible_by(User.current) } } named_scope :visible, lambda { { :conditions => Project.visible_by(User.current) } }
named_scope :like, lambda {|q|
s = "%#{q.to_s.strip.downcase}%"
{
:conditions => ["LOWER(name) LIKE ?", s]
}
}
def to_liquid
ProjectDrop.new(self)
end
def initialize(attributes = nil) def initialize(attributes = nil)
super super
@ -141,11 +131,6 @@ class Project < ActiveRecord::Base
end end
end end
# Is the project visible to the current user
def visible?
User.current.allowed_to?(:view_project, self)
end
def self.allowed_to_condition(user, permission, options={}) def self.allowed_to_condition(user, permission, options={})
base_statement = "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}" base_statement = "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"
if perm = Redmine::AccessControl.permission(permission) if perm = Redmine::AccessControl.permission(permission)
@ -631,7 +616,7 @@ class Project < ActiveRecord::Base
while (ancestors.any? && !project.is_descendant_of?(ancestors.last)) while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
ancestors.pop ancestors.pop
end end
yield project, ancestors.size if block_given? yield project, ancestors.size
ancestors << project ancestors << project
end end
end end

View File

@ -12,7 +12,64 @@
# See doc/COPYRIGHT.rdoc for more details. # See doc/COPYRIGHT.rdoc for more details.
#++ #++
class QueryColumn
attr_accessor :name, :sortable, :groupable, :default_order
include Redmine::I18n
def initialize(name, options={})
self.name = name
self.sortable = options[:sortable]
self.groupable = options[:groupable] || false
if groupable == true
self.groupable = name.to_s
end
self.default_order = options[:default_order]
@caption_key = options[:caption] || "field_#{name}"
end
def caption
l(@caption_key)
end
# Returns true if the column is sortable, otherwise false
def sortable?
!sortable.nil?
end
def value(issue)
issue.send name
end
end
class QueryCustomFieldColumn < QueryColumn
def initialize(custom_field)
self.name = "cf_#{custom_field.id}".to_sym
self.sortable = custom_field.order_statement || false
if %w(list date bool int).include?(custom_field.field_format)
self.groupable = custom_field.order_statement
end
self.groupable ||= false
@cf = custom_field
end
def caption
@cf.name
end
def custom_field
@cf
end
def value(issue)
cv = issue.custom_values.detect {|v| v.custom_field_id == @cf.id}
cv && @cf.cast_value(cv.value)
end
end
class Query < ActiveRecord::Base class Query < ActiveRecord::Base
class StatementInvalid < ::ActiveRecord::StatementInvalid
end
belongs_to :project belongs_to :project
belongs_to :user belongs_to :user
@ -33,7 +90,6 @@ class Query < ActiveRecord::Base
"*" => :label_all, "*" => :label_all,
">=" => :label_greater_or_equal, ">=" => :label_greater_or_equal,
"<=" => :label_less_or_equal, "<=" => :label_less_or_equal,
"><" => :label_between,
"<t+" => :label_in_less_than, "<t+" => :label_in_less_than,
">t+" => :label_in_more_than, ">t+" => :label_in_more_than,
"t+" => :label_in, "t+" => :label_in,
@ -51,8 +107,8 @@ class Query < ActiveRecord::Base
:list_status => [ "o", "=", "!", "c", "*" ], :list_status => [ "o", "=", "!", "c", "*" ],
:list_optional => [ "=", "!", "!*", "*" ], :list_optional => [ "=", "!", "!*", "*" ],
:list_subprojects => [ "*", "!*", "=" ], :list_subprojects => [ "*", "!*", "=" ],
:date => [ "=", ">=", "<=", "><", "<t+", ">t+", "t+", "t", "w", ">t-", "<t-", "t-" ], :date => [ "<t+", ">t+", "t+", "t", "w", ">t-", "<t-", "t-" ],
:date_past => [ "=", ">=", "<=", "><", ">t-", "<t-", "t-", "t", "w" ], :date_past => [ ">t-", "<t-", "t-", "t", "w" ],
:string => [ "=", "~", "!", "!~" ], :string => [ "=", "~", "!", "!~" ],
:text => [ "~", "!~" ], :text => [ "~", "!~" ],
:integer => [ "=", ">=", "<=", "!*", "*" ] } :integer => [ "=", ">=", "<=", "!*", "*" ] }
@ -83,7 +139,6 @@ class Query < ActiveRecord::Base
def initialize(attributes = nil) def initialize(attributes = nil)
super attributes super attributes
self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} } self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} }
self.display_subprojects ||= Setting.display_subprojects_issues?
end end
def after_initialize def after_initialize
@ -190,7 +245,7 @@ class Query < ActiveRecord::Base
def add_filter(field, operator, values) def add_filter(field, operator, values)
# values must be an array # values must be an array
return unless values.nil? || values.is_a?(Array) return unless values and values.is_a? Array # and !values.first.empty?
# check if field is defined as an available filter # check if field is defined as an available filter
if available_filters.has_key? field if available_filters.has_key? field
filter_options = available_filters[field] filter_options = available_filters[field]
@ -199,7 +254,7 @@ class Query < ActiveRecord::Base
# allowed_values = values & ([""] + (filter_options[:values] || []).collect {|val| val[1]}) # allowed_values = values & ([""] + (filter_options[:values] || []).collect {|val| val[1]})
# filters[field] = {:operator => operator, :values => allowed_values } if (allowed_values.first and !allowed_values.first.empty?) or ["o", "c", "!*", "*", "t"].include? operator # filters[field] = {:operator => operator, :values => allowed_values } if (allowed_values.first and !allowed_values.first.empty?) or ["o", "c", "!*", "*", "t"].include? operator
#end #end
filters[field] = {:operator => operator, :values => (values || ['']) } filters[field] = {:operator => operator, :values => values }
end end
end end
@ -211,9 +266,9 @@ class Query < ActiveRecord::Base
# Add multiple filters using +add_filter+ # Add multiple filters using +add_filter+
def add_filters(fields, operators, values) def add_filters(fields, operators, values)
if fields.is_a?(Array) && operators.is_a?(Hash) && (values.nil? || values.is_a?(Hash)) if fields.is_a?(Array) && operators.is_a?(Hash) && values.is_a?(Hash)
fields.each do |field| fields.each do |field|
add_filter(field, operators[field], values && values[field]) add_filter(field, operators[field], values[field])
end end
end end
end end
@ -222,10 +277,6 @@ class Query < ActiveRecord::Base
filters and filters[field] filters and filters[field]
end end
def type_for(field)
available_filters[field][:type] if available_filters.has_key?(field)
end
def operator_for(field) def operator_for(field)
has_filter?(field) ? filters[field][:operator] : nil has_filter?(field) ? filters[field][:operator] : nil
end end
@ -234,10 +285,6 @@ class Query < ActiveRecord::Base
has_filter?(field) ? filters[field][:values] : nil has_filter?(field) ? filters[field][:values] : nil
end end
def value_for(field, index=0)
(values_for(field) || [])[index]
end
def label_for(field) def label_for(field)
label = available_filters[field][:name] if available_filters.has_key?(field) label = available_filters[field][:name] if available_filters.has_key?(field)
label ||= field.gsub(/\_id$/, "") label ||= field.gsub(/\_id$/, "")
@ -363,7 +410,7 @@ class Query < ActiveRecord::Base
# all subprojects # all subprojects
ids += project.descendants.collect(&:id) ids += project.descendants.collect(&:id)
end end
elsif display_subprojects? elsif Setting.display_subprojects_issues?
ids += project.descendants.collect(&:id) ids += project.descendants.collect(&:id)
end end
project_clauses << "#{Project.table_name}.id IN (%s)" % ids.join(',') project_clauses << "#{Project.table_name}.id IN (%s)" % ids.join(',')
@ -383,10 +430,8 @@ class Query < ActiveRecord::Base
next unless v and !v.empty? next unless v and !v.empty?
operator = operator_for(field) operator = operator_for(field)
# "me" value substitution # "me" value subsitution
if %w(assigned_to_id author_id watcher_id).include?(field) || if %w(assigned_to_id author_id watcher_id).include?(field)
# user custom fields
available_filters.has_key?(field) && available_filters[field][:format] == 'user'
v.push(User.current.logged? ? User.current.id.to_s : "0") if v.delete("me") v.push(User.current.logged? ? User.current.id.to_s : "0") if v.delete("me")
end end
@ -474,7 +519,7 @@ class Query < ActiveRecord::Base
def issue_count def issue_count
Issue.count(:include => [:status, :project], :conditions => statement) Issue.count(:include => [:status, :project], :conditions => statement)
rescue ::ActiveRecord::StatementInvalid => e rescue ::ActiveRecord::StatementInvalid => e
raise Query::StatementInvalid.new(e.message) raise StatementInvalid.new(e.message)
end end
# Returns the issue count by group or nil if query is not grouped # Returns the issue count by group or nil if query is not grouped
@ -494,7 +539,7 @@ class Query < ActiveRecord::Base
end end
r r
rescue ::ActiveRecord::StatementInvalid => e rescue ::ActiveRecord::StatementInvalid => e
raise Query::StatementInvalid.new(e.message) raise StatementInvalid.new(e.message)
end end
# Returns the issues # Returns the issues
@ -509,7 +554,7 @@ class Query < ActiveRecord::Base
:limit => options[:limit], :limit => options[:limit],
:offset => options[:offset] :offset => options[:offset]
rescue ::ActiveRecord::StatementInvalid => e rescue ::ActiveRecord::StatementInvalid => e
raise Query::StatementInvalid.new(e.message) raise StatementInvalid.new(e.message)
end end
# Returns the journals # Returns the journals
@ -521,7 +566,7 @@ class Query < ActiveRecord::Base
:limit => options[:limit], :limit => options[:limit],
:offset => options[:offset] :offset => options[:offset]
rescue ::ActiveRecord::StatementInvalid => e rescue ::ActiveRecord::StatementInvalid => e
raise Query::StatementInvalid.new(e.message) raise StatementInvalid.new(e.message)
end end
# Returns the versions # Returns the versions
@ -530,7 +575,7 @@ class Query < ActiveRecord::Base
Version.find :all, :include => :project, Version.find :all, :include => :project,
:conditions => Query.merge_conditions(project_statement, options[:conditions]) :conditions => Query.merge_conditions(project_statement, options[:conditions])
rescue ::ActiveRecord::StatementInvalid => e rescue ::ActiveRecord::StatementInvalid => e
raise Query::StatementInvalid.new(e.message) raise StatementInvalid.new(e.message)
end end
private private
@ -540,16 +585,12 @@ class Query < ActiveRecord::Base
sql = '' sql = ''
case operator case operator
when "=" when "="
if [:date, :date_past].include?(type_for(field)) if value.present?
sql = date_clause(db_table, db_field, (Date.parse(value.first) rescue nil), (Date.parse(value.first) rescue nil))
else
if value.any?
sql = "#{db_table}.#{db_field} IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")" sql = "#{db_table}.#{db_field} IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")"
else else
# IN an empty set # empty set of allowed values produces no result
sql = "0=1" sql = "0=1"
end end
end
when "!" when "!"
if value.present? if value.present?
sql = "(#{db_table}.#{db_field} IS NULL OR #{db_table}.#{db_field} NOT IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + "))" sql = "(#{db_table}.#{db_field} IS NULL OR #{db_table}.#{db_field} NOT IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + "))"
@ -564,58 +605,42 @@ class Query < ActiveRecord::Base
sql = "#{db_table}.#{db_field} IS NOT NULL" sql = "#{db_table}.#{db_field} IS NOT NULL"
sql << " AND #{db_table}.#{db_field} <> ''" if is_custom_filter sql << " AND #{db_table}.#{db_field} <> ''" if is_custom_filter
when ">=" when ">="
if [:date, :date_past].include?(type_for(field))
sql = date_clause(db_table, db_field, (Date.parse(value.first) rescue nil), nil)
else
if is_custom_filter if is_custom_filter
sql = "CAST(#{db_table}.#{db_field} AS decimal(60,3)) >= #{value.first.to_i}" sql = "#{db_table}.#{db_field} != '' AND CAST(#{db_table}.#{db_field} AS decimal(60,4)) >= #{value.first.to_f}"
else else
sql = "#{db_table}.#{db_field} >= #{value.first.to_i}" sql = "#{db_table}.#{db_field} >= #{value.first.to_f}"
end
end end
when "<=" when "<="
if [:date, :date_past].include?(type_for(field))
sql = date_clause(db_table, db_field, nil, (Date.parse(value.first) rescue nil))
else
if is_custom_filter if is_custom_filter
sql = "CAST(#{db_table}.#{db_field} AS decimal(60,3)) <= #{value.first.to_i}" sql = "#{db_table}.#{db_field} != '' AND CAST(#{db_table}.#{db_field} AS decimal(60,4)) <= #{value.first.to_f}"
else else
sql = "#{db_table}.#{db_field} <= #{value.first.to_i}" sql = "#{db_table}.#{db_field} <= #{value.first.to_f}"
end
end
when "><"
if [:date, :date_past].include?(type_for(field))
sql = date_clause(db_table, db_field, (Date.parse(value[0]) rescue nil), (Date.parse(value[1]) rescue nil))
else
if is_custom_filter
sql = "CAST(#{db_table}.#{db_field} AS decimal(60,3)) BETWEEN #{value[0].to_i} AND #{value[1].to_i}"
else
sql = "#{db_table}.#{db_field} BETWEEN #{value[0].to_i} AND #{value[1].to_i}"
end
end end
when "o" when "o"
sql = "#{IssueStatus.table_name}.is_closed=#{connection.quoted_false}" if field == "status_id" sql = "#{IssueStatus.table_name}.is_closed=#{connection.quoted_false}" if field == "status_id"
when "c" when "c"
sql = "#{IssueStatus.table_name}.is_closed=#{connection.quoted_true}" if field == "status_id" sql = "#{IssueStatus.table_name}.is_closed=#{connection.quoted_true}" if field == "status_id"
when ">t-" when ">t-"
sql = relative_date_clause(db_table, db_field, - value.first.to_i, 0) sql = date_range_clause(db_table, db_field, - value.first.to_i, 0)
when "<t-" when "<t-"
sql = relative_date_clause(db_table, db_field, nil, - value.first.to_i) sql = date_range_clause(db_table, db_field, nil, - value.first.to_i)
when "t-" when "t-"
sql = relative_date_clause(db_table, db_field, - value.first.to_i, - value.first.to_i) sql = date_range_clause(db_table, db_field, - value.first.to_i, - value.first.to_i)
when ">t+" when ">t+"
sql = relative_date_clause(db_table, db_field, value.first.to_i, nil) sql = date_range_clause(db_table, db_field, value.first.to_i, nil)
when "<t+" when "<t+"
sql = relative_date_clause(db_table, db_field, 0, value.first.to_i) sql = date_range_clause(db_table, db_field, 0, value.first.to_i)
when "t+" when "t+"
sql = relative_date_clause(db_table, db_field, value.first.to_i, value.first.to_i) sql = date_range_clause(db_table, db_field, value.first.to_i, value.first.to_i)
when "t" when "t"
sql = relative_date_clause(db_table, db_field, 0, 0) sql = date_range_clause(db_table, db_field, 0, 0)
when "w" when "w"
first_day_of_week = l(:general_first_day_of_week).to_i from = l(:general_first_day_of_week) == '7' ?
day_of_week = Date.today.cwday # week starts on sunday
days_ago = (day_of_week >= first_day_of_week ? day_of_week - first_day_of_week : day_of_week + 7 - first_day_of_week) ((Date.today.cwday == 7) ? Time.now.at_beginning_of_day : Time.now.at_beginning_of_week - 1.day) :
sql = relative_date_clause(db_table, db_field, - days_ago, - days_ago + 6) # week starts on monday (Rails default)
Time.now.at_beginning_of_week
sql = "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date(from), connection.quoted_date(from + 7.days)]
when "~" when "~"
sql = "LOWER(#{db_table}.#{db_field}) LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'" sql = "LOWER(#{db_table}.#{db_field}) LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'"
when "!~" when "!~"
@ -642,32 +667,23 @@ class Query < ActiveRecord::Base
options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]], :order => 20 } options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]], :order => 20 }
when "user", "version" when "user", "version"
next unless project next unless project
values = field.possible_values_options(project) options = { :type => :list_optional, :values => field.possible_values_options(project), :order => 20}
if User.current.logged? && field.field_format == 'user'
values.unshift ["<< #{l(:label_me)} >>", "me"]
end
options = { :type => :list_optional, :values => values, :order => 20}
else else
options = { :type => :string, :order => 20 } options = { :type => :string, :order => 20 }
end end
@available_filters["cf_#{field.id}"] = options.merge({ :name => field.name, :format => field.field_format }) @available_filters["cf_#{field.id}"] = options.merge({ :name => field.name })
end end
end end
# Returns a SQL clause for a date or datetime field. # Returns a SQL clause for a date or datetime field.
def date_clause(table, field, from, to) def date_range_clause(table, field, from, to)
s = [] s = []
if from if from
s << ("#{table}.#{field} > '%s'" % [connection.quoted_date((from - 1).to_time.end_of_day)]) s << ("#{table}.#{field} > '%s'" % [connection.quoted_date((Date.yesterday + from).to_time.end_of_day)])
end end
if to if to
s << ("#{table}.#{field} <= '%s'" % [connection.quoted_date(to.to_time.end_of_day)]) s << ("#{table}.#{field} <= '%s'" % [connection.quoted_date((Date.today + to).to_time.end_of_day)])
end end
s.join(' AND ') s.join(' AND ')
end end
# Returns a SQL clause for a date or datetime field using relative dates.
def relative_date_clause(table, field, days_from, days_to)
date_clause(table, field, (days_from ? Date.today + days_from : nil), (days_to ? Date.today + days_to : nil))
end
end end

View File

@ -1,16 +0,0 @@
#-- encoding: UTF-8
#-- copyright
# ChiliProject is a project management system.
#
# Copyright (C) 2010-2013 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 Query::StatementInvalid < ActiveRecord::StatementInvalid
end

View File

@ -1,42 +0,0 @@
#-- encoding: UTF-8
#-- copyright
# ChiliProject is a project management system.
#
# Copyright (C) 2010-2013 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 QueryColumn
attr_accessor :name, :sortable, :groupable, :default_order
include Redmine::I18n
def initialize(name, options={})
self.name = name
self.sortable = options[:sortable]
self.groupable = options[:groupable] || false
if groupable == true
self.groupable = name.to_s
end
self.default_order = options[:default_order]
@caption_key = options[:caption] || "field_#{name}"
end
def caption
l(@caption_key)
end
# Returns true if the column is sortable, otherwise false
def sortable?
!sortable.nil?
end
def value(issue)
issue.send name
end
end

View File

@ -1,40 +0,0 @@
#-- encoding: UTF-8
#-- copyright
# ChiliProject is a project management system.
#
# Copyright (C) 2010-2013 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 QueryCustomFieldColumn < QueryColumn
def initialize(custom_field)
self.name = "cf_#{custom_field.id}".to_sym
self.sortable = custom_field.order_statement || false
if %w(list date bool int).include?(custom_field.field_format)
self.groupable = custom_field.order_statement
end
self.groupable ||= false
@cf = custom_field
end
def caption
@cf.name
end
def custom_field
@cf
end
def value(issue)
cv = issue.custom_values.detect {|v| v.custom_field_id == @cf.id}
cv && @cf.cast_value(cv.value)
end
end

View File

@ -12,7 +12,7 @@
# See doc/COPYRIGHT.rdoc for more details. # See doc/COPYRIGHT.rdoc for more details.
#++ #++
require_dependency 'redmine/scm/adapters/bazaar_adapter' require 'redmine/scm/adapters/bazaar_adapter'
class Repository::Bazaar < Repository class Repository::Bazaar < Repository
attr_protected :root_url attr_protected :root_url

View File

@ -12,7 +12,7 @@
# See doc/COPYRIGHT.rdoc for more details. # See doc/COPYRIGHT.rdoc for more details.
#++ #++
require_dependency 'redmine/scm/adapters/cvs_adapter' require 'redmine/scm/adapters/cvs_adapter'
require 'digest/sha1' require 'digest/sha1'
class Repository::Cvs < Repository class Repository::Cvs < Repository

View File

@ -12,7 +12,7 @@
# See doc/COPYRIGHT.rdoc for more details. # See doc/COPYRIGHT.rdoc for more details.
#++ #++
require_dependency 'redmine/scm/adapters/darcs_adapter' require 'redmine/scm/adapters/darcs_adapter'
class Repository::Darcs < Repository class Repository::Darcs < Repository
validates_presence_of :url, :log_encoding validates_presence_of :url, :log_encoding

View File

@ -12,7 +12,7 @@
# See doc/COPYRIGHT.rdoc for more details. # See doc/COPYRIGHT.rdoc for more details.
#++ #++
require_dependency 'redmine/scm/adapters/filesystem_adapter' require 'redmine/scm/adapters/filesystem_adapter'
class Repository::Filesystem < Repository class Repository::Filesystem < Repository
attr_protected :root_url attr_protected :root_url

View File

@ -12,7 +12,7 @@
# See doc/COPYRIGHT.rdoc for more details. # See doc/COPYRIGHT.rdoc for more details.
#++ #++
require_dependency 'redmine/scm/adapters/git_adapter' require 'redmine/scm/adapters/git_adapter'
class Repository::Git < Repository class Repository::Git < Repository
attr_protected :root_url attr_protected :root_url

View File

@ -12,7 +12,7 @@
# See doc/COPYRIGHT.rdoc for more details. # See doc/COPYRIGHT.rdoc for more details.
#++ #++
require_dependency 'redmine/scm/adapters/mercurial_adapter' require 'redmine/scm/adapters/mercurial_adapter'
class Repository::Mercurial < Repository class Repository::Mercurial < Repository
# sort changesets by revision number # sort changesets by revision number

View File

@ -12,7 +12,7 @@
# See doc/COPYRIGHT.rdoc for more details. # See doc/COPYRIGHT.rdoc for more details.
#++ #++
require_dependency 'redmine/scm/adapters/subversion_adapter' require 'redmine/scm/adapters/subversion_adapter'
class Repository::Subversion < Repository class Repository::Subversion < Repository
attr_protected :root_url attr_protected :root_url

View File

@ -97,17 +97,13 @@ class Setting < ActiveRecord::Base
# Returns the value of the setting named name # Returns the value of the setting named name
def self.[](name) def self.[](name)
if use_caching? Marshal.load(Rails.cache.fetch("chiliproject/setting/#{name}") {Marshal.dump(find_or_default(name).value)})
Marshal.load(Rails.cache.fetch(self.cache_key(name)) {Marshal.dump(find_or_default(name).value)})
else
find_or_default(name).value
end
end end
def self.[]=(name, v) def self.[]=(name, v)
setting = find_or_default(name) setting = find_or_default(name)
setting.value = (v ? v : "") setting.value = (v ? v : "")
Rails.cache.delete self.cache_key(name) Rails.cache.delete "chiliproject/setting/#{name}"
setting.save setting.save
setting.value setting.value
end end
@ -141,42 +137,23 @@ class Setting < ActiveRecord::Base
Object.const_defined?(:OpenID) && self[:openid].to_i > 0 Object.const_defined?(:OpenID) && self[:openid].to_i > 0
end end
# Deprecation Warning: This method is no longer available. There is no # Checks if settings have changed since the values were read
# replacement. # and clears the cache hash if it's the case
# Called once per request
def self.check_cache def self.check_cache
# DEPRECATED SINCE 3.0.0beta2 settings_updated_on = Setting.maximum(:updated_on)
ActiveSupport::Deprecation.warn "The Setting.check_cache method is " + cache_cleared_on = Rails.cache.read('chiliproject/setting-cleared_on')
"deprecated and will be removed in the future. There should be no " + cache_cleared_on = cache_cleared_on ? Marshal.load(cache_cleared_on) : Time.now
"replacement for this functionality needed." if settings_updated_on && cache_cleared_on <= settings_updated_on
clear_cache
end
end end
# Clears all of the Setting caches # Clears all of the Setting caches
def self.clear_cache def self.clear_cache
# DEPRECATED SINCE 3.0.0beta2 Rails.cache.delete_matched( /^chiliproject\/setting\/.+$/ )
ActiveSupport::Deprecation.warn "The Setting.clear_cache method is " + Rails.cache.write('chiliproject/setting-cleared_on', Marshal.dump(Time.now))
"deprecated and will be removed in the future. There should be no " + logger.info 'Settings cache cleared.' if logger
"replacement for this functionality needed. To sweep the whole " +
"cache Rails.cache.clear may be used. To invalidate the Settings " +
"only, you may use Setting.first.try(:touch)"
end
# Temporarily deactivate settings caching in the block scope
def self.uncached
cache_setting = self.use_caching
self.use_caching = false
yield
ensure
self.use_caching = cache_setting
end
# Check if Setting caching should be performed
def self.use_caching?
!Thread.current['chiliproject/settings/do_not_use_caching']
end
# Dis-/En-able Setting caching. This is mainly intended to be used in tests
def self.use_caching=(new_value)
Thread.current['chiliproject/settings/do_not_use_caching'] = !new_value
end end
private private
@ -185,13 +162,7 @@ private
def self.find_or_default(name) def self.find_or_default(name)
name = name.to_s name = name.to_s
raise "There's no setting named #{name}" unless @@available_settings.has_key?(name) raise "There's no setting named #{name}" unless @@available_settings.has_key?(name)
find_by_name(name) or new do |s| setting = find_by_name(name)
s.name = name setting ||= new(:name => name, :value => @@available_settings[name]['default']) if @@available_settings.has_key? name
s.value = @@available_settings[name]['default']
end
end
def self.cache_key(name)
"chiliproject/setting/#{Setting.maximum(:updated_on).to_i}/#{name}"
end end
end end

View File

@ -35,10 +35,6 @@ class Tracker < ActiveRecord::Base
name <=> tracker.name name <=> tracker.name
end end
def to_liquid
TrackerDrop.new(self)
end
def self.all def self.all
find(:all, :order => 'position') find(:all, :order => 'position')
end end

View File

@ -64,9 +64,10 @@ class User < Principal
validates_uniqueness_of :mail, :if => Proc.new { |user| !user.mail.blank? }, :case_sensitive => false validates_uniqueness_of :mail, :if => Proc.new { |user| !user.mail.blank? }, :case_sensitive => false
# Login must contain lettres, numbers, underscores only # Login must contain lettres, numbers, underscores only
validates_format_of :login, :with => /^[a-z0-9_\-@\.]*$/i validates_format_of :login, :with => /^[a-z0-9_\-@\.]*$/i
validates_length_of :login, :firstname, :lastname, :maximum => 255 validates_length_of :login, :maximum => 30
validates_length_of :firstname, :lastname, :maximum => 30
validates_format_of :mail, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :allow_nil => true validates_format_of :mail, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :allow_nil => true
validates_length_of :mail, :maximum => 255, :allow_nil => true validates_length_of :mail, :maximum => 60, :allow_nil => true
validates_confirmation_of :password, :allow_nil => true validates_confirmation_of :password, :allow_nil => true
validates_inclusion_of :mail_notification, :in => MAIL_NOTIFICATION_OPTIONS.collect(&:first), :allow_blank => true validates_inclusion_of :mail_notification, :in => MAIL_NOTIFICATION_OPTIONS.collect(&:first), :allow_blank => true
validates_inclusion_of :status, :in => [STATUS_ANONYMOUS, STATUS_ACTIVE, STATUS_REGISTERED, STATUS_LOCKED] validates_inclusion_of :status, :in => [STATUS_ANONYMOUS, STATUS_ACTIVE, STATUS_REGISTERED, STATUS_LOCKED]
@ -344,6 +345,27 @@ class User < Principal
!logged? !logged?
end end
# Return user's roles for project
def roles_for_project(project)
roles = []
# No role on archived projects
return roles unless project && project.active?
if logged?
# Find project membership
membership = memberships.detect {|m| m.project_id == project.id}
if membership
roles = membership.roles
else
@role_non_member ||= Role.non_member
roles << @role_non_member
end
else
@role_anonymous ||= Role.anonymous
roles << @role_anonymous
end
roles
end
# Return true if the user is a member of project # Return true if the user is a member of project
def member_of?(project) def member_of?(project)
!roles_for_project(project).detect {|role| role.member?}.nil? !roles_for_project(project).detect {|role| role.member?}.nil?
@ -366,6 +388,53 @@ class User < Principal
@projects_by_role @projects_by_role
end end
# Return true if the user is allowed to do the specified action on a specific context
# Action can be:
# * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
# * a permission Symbol (eg. :edit_project)
# Context can be:
# * a project : returns true if user is allowed to do the specified action on this project
# * a group of projects : returns true if user is allowed on every project
# * nil with options[:global] set : check if user has at least one role allowed for this action,
# or falls back to Non Member / Anonymous permissions depending if the user is logged
def allowed_to?(action, context, options={})
if context && context.is_a?(Project)
# No action allowed on archived projects
return false unless context.active?
# No action allowed on disabled modules
return false unless context.allows_to?(action)
# Admin users are authorized for anything else
return true if admin?
roles = roles_for_project(context)
return false unless roles
roles.detect {|role| (context.is_public? || role.member?) && role.allowed_to?(action)}
elsif context && context.is_a?(Array)
# Authorize if user is authorized on every element of the array
context.map do |project|
allowed_to?(action,project,options)
end.inject do |memo,allowed|
memo && allowed
end
elsif options[:global]
# Admin users are always authorized
return true if admin?
# authorize if user has at least one role that has this permission
roles = memberships.collect {|m| m.roles}.flatten.uniq
roles.detect {|r| r.allowed_to?(action)} || (self.logged? ? Role.non_member.allowed_to?(action) : Role.anonymous.allowed_to?(action))
else
false
end
end
# Is the user allowed to do the specified action on any project?
# See allowed_to? for the actions and valid options.
def allowed_to_globally?(action, options)
allowed_to?(action, nil, options.reverse_merge(:global => true))
end
safe_attributes 'login', safe_attributes 'login',
'firstname', 'firstname',
'lastname', 'lastname',
@ -403,8 +472,6 @@ class User < Principal
when 'only_my_events' when 'only_my_events'
if object.is_a?(Issue) && (object.author == self || object.assigned_to == self) if object.is_a?(Issue) && (object.author == self || object.assigned_to == self)
true true
elsif object.respond_to?(:watched_by?) && object.watched_by?(self) # Make it clear that we always want to be notified about things we watch in this case
true
else else
false false
end end

View File

@ -27,7 +27,7 @@ class Version < ActiveRecord::Base
validates_presence_of :name validates_presence_of :name
validates_uniqueness_of :name, :scope => [:project_id] validates_uniqueness_of :name, :scope => [:project_id]
validates_length_of :name, :maximum => 60 validates_length_of :name, :maximum => 60
validates_format_of :start_date, :effective_date, :with => /^\d{4}-\d{2}-\d{2}$/, :message => :not_a_date, :allow_nil => true validates_format_of :effective_date, :with => /^\d{4}-\d{2}-\d{2}$/, :message => :not_a_date, :allow_nil => true
validates_inclusion_of :status, :in => VERSION_STATUSES validates_inclusion_of :status, :in => VERSION_STATUSES
validates_inclusion_of :sharing, :in => VERSION_SHARINGS validates_inclusion_of :sharing, :in => VERSION_SHARINGS
@ -37,7 +37,6 @@ class Version < ActiveRecord::Base
safe_attributes 'name', safe_attributes 'name',
'description', 'description',
'start_date',
'effective_date', 'effective_date',
'due_date', 'due_date',
'wiki_page_title', 'wiki_page_title',

View File

@ -14,7 +14,7 @@
class Watcher < ActiveRecord::Base class Watcher < ActiveRecord::Base
belongs_to :watchable, :polymorphic => true belongs_to :watchable, :polymorphic => true
belongs_to :user, :class_name => 'Principal', :foreign_key => 'user_id' belongs_to :user
validates_presence_of :user validates_presence_of :user
validates_uniqueness_of :user_id, :scope => [:watchable_type, :watchable_id] validates_uniqueness_of :user_id, :scope => [:watchable_type, :watchable_id]

View File

@ -98,7 +98,7 @@ class WikiContent < ActiveRecord::Base
changes.delete("text") changes.delete("text")
changes["data"] = hash[:text] changes["data"] = hash[:text]
changes["compression"] = hash[:compression] changes["compression"] = hash[:compression]
update_attribute(:changes, changes) update_attribute(:changes, changes.to_yaml)
end end
def text def text

View File

@ -53,10 +53,6 @@ class WikiPage < ActiveRecord::Base
end end
end end
def to_liquid
WikiPageDrop.new(self)
end
def visible?(user=User.current) def visible?(user=User.current)
!user.nil? && user.allowed_to?(:view_wiki_pages, project) !user.nil? && user.allowed_to?(:view_wiki_pages, project)
end end

View File

@ -86,8 +86,8 @@ class Workflow < ActiveRecord::Base
else else
transaction do transaction do
delete_all :tracker_id => target_tracker.id, :role_id => target_role.id delete_all :tracker_id => target_tracker.id, :role_id => target_role.id
connection.insert "INSERT INTO #{Workflow.table_name} (tracker_id, role_id, old_status_id, new_status_id, author, assignee)" + connection.insert "INSERT INTO #{Workflow.table_name} (tracker_id, role_id, old_status_id, new_status_id)" +
" SELECT #{target_tracker.id}, #{target_role.id}, old_status_id, new_status_id, author, assignee" + " SELECT #{target_tracker.id}, #{target_role.id}, old_status_id, new_status_id" +
" FROM #{Workflow.table_name}" + " FROM #{Workflow.table_name}" +
" WHERE tracker_id = #{source_tracker.id} AND role_id = #{source_role.id}" " WHERE tracker_id = #{source_tracker.id} AND role_id = #{source_role.id}"
end end

View File

@ -1,35 +0,0 @@
<div id="nav-login-content">
<% form_tag({:controller => "account", :action=> "login"}) do %>
<%= hidden_field_tag 'back_url', CGI.escape(request.url), :id => nil %>
<table>
<tr>
<td><label for="username-pulldown"><%= l(:field_login) %></label></td>
<td><label for="password-pulldown"><%= l(:field_password) %></label></td>
<td></td>
</tr>
<tr>
<td><%= text_field_tag 'username', nil, :tabindex => '1', :id => 'username-pulldown' %></td>
<td><%= password_field_tag 'password', nil, :tabindex => '1', :id => 'password-pulldown' %></td>
<td><input type="submit" name="login" value="<%=l(:button_login)%>" tabindex="1"/></td>
</tr>
</table>
<div id = "optional_login_fields" style = "top = 10px; white-space:nowrap">
<% if Setting.openid? %>
<%= text_field_tag "openid_url", nil, :placeholder => l(:field_identity_url), :tabindex => '1' %>
<% end %>
<% if Setting.autologin? %>
<label for="autologin"><%= check_box_tag 'autologin', 1, false, :tabindex => 1 %> <%= l(:label_stay_logged_in) %></label>
<% end %>
<% if Setting.lost_password? %>
<%= link_to l(:label_password_lost), {:controller => 'account', :action => 'lost_password'}, :tabindex => 1 %>
<% end %>
<% if !User.current.logged? && Setting.self_registration? %>
<%= "|" if Setting.lost_password? %>
<%= link_to l(:label_register), { :controller => 'account', :action => 'register' }, :tabindex => 1 %>
<% end %>
</div>
<% end %>
</div>

View File

@ -5,7 +5,7 @@
<table> <table>
<tr> <tr>
<td align="right"><label for="username"><%=l(:field_login)%>:</label></td> <td align="right"><label for="username"><%=l(:field_login)%>:</label></td>
<td align="left"><%= text_field_tag 'username', nil, :tabindex => '2' %></td> <td align="left"><%= text_field_tag 'username', nil, :tabindex => '1' %></td>
</tr> </tr>
<tr> <tr>
<td align="right"><label for="password"><%=l(:field_password)%>:</label></td> <td align="right"><label for="password"><%=l(:field_password)%>:</label></td>

View File

@ -1,6 +1,6 @@
<h2><%=l(:label_register)%> <%=link_to l(:label_login_with_open_id_option), signin_url if Setting.openid? %></h2> <h2><%=l(:label_register)%> <%=link_to l(:label_login_with_open_id_option), signin_url if Setting.openid? %></h2>
<% form_tag({:action => 'register'}, :class => "tabular", :autocomplete => :off) do %> <% form_tag({:action => 'register'}, :class => "tabular") do %>
<%= error_messages_for 'user' %> <%= error_messages_for 'user' %>
<div class="box"> <div class="box">

View File

@ -42,7 +42,6 @@
<% content_for :sidebar do %> <% content_for :sidebar do %>
<% form_tag({}, :method => :get) do %> <% form_tag({}, :method => :get) do %>
<h3><%= l(:label_activity) %></h3> <h3><%= l(:label_activity) %></h3>
<%= hidden_field_tag "set_filter", 1, :id => nil %>
<p><% @activity.event_types.each do |t| %> <p><% @activity.event_types.each do |t| %>
<%= check_box_tag "show_#{t}", 1, @activity.scope.include?(t) %> <%= check_box_tag "show_#{t}", 1, @activity.scope.include?(t) %>
<label for="show_<%=t%>"><%= link_to(l("label_#{t.singularize}_plural"), {"show_#{t}" => 1, :user_id => params[:user_id]})%></label> <label for="show_<%=t%>"><%= link_to(l("label_#{t.singularize}_plural"), {"show_#{t}" => 1, :user_id => params[:user_id]})%></label>

View File

@ -1 +1,5 @@
<%= render_menu :admin_menu %> <div id="admin-menu">
<ul>
<%= render_menu :admin_menu %>
</ul>
</div>

View File

@ -1,3 +1,5 @@
<h2><%=l(:label_administration)%></h2>
<div id="admin-index"> <div id="admin-index">
<%= render :partial => 'no_data' if @no_configuration_data %> <%= render :partial => 'no_data' if @no_configuration_data %>
<%= render :partial => 'menu' %> <%= render :partial => 'menu' %>

View File

@ -1 +0,0 @@
<%= projects_check_box_tags 'project_ids[]', @projects %>

View File

@ -71,13 +71,3 @@
<%= auto_discovery_link_tag(:atom, {:format => 'atom', :key => User.current.rss_key}, :title => "#{@project}: #{@board}") %> <%= auto_discovery_link_tag(:atom, {:format => 'atom', :key => User.current.rss_key}, :title => "#{@project}: #{@board}") %>
<%= stylesheet_link_tag 'scm' %> <%= stylesheet_link_tag 'scm' %>
<% end %> <% end %>
<% content_for :sidebar do %>
<% if User.current.allowed_to?(:add_board_watchers, @project) ||
(@board.watchers.present? && User.current.allowed_to?(:view_board_watchers, @project)) %>
<div id="watchers">
<%= render :partial => 'watchers/watchers', :locals => {:watched => @board} %>
</div>
<% end %>
<% end %>

View File

@ -1,55 +1,52 @@
<ul class="menu"> <ul>
<%= call_hook(:view_issues_context_menu_start, {:issues => @issues, :can => @can, :back => @back }) %> <%= call_hook(:view_issues_context_menu_start, {:issues => @issues, :can => @can, :back => @back }) %>
<% if !@issue.nil? -%> <% if !@issue.nil? -%>
<li class="edit"><%= context_menu_link l(:button_edit), {:controller => 'issues', :action => 'edit', :id => @issue}, <li><%= context_menu_link l(:button_edit), {:controller => 'issues', :action => 'edit', :id => @issue},
:class => 'icon-edit', :disabled => !@can[:edit] %></li> :class => 'icon-edit', :disabled => !@can[:edit] %></li>
<% else %> <% else %>
<li class="edit"><%= context_menu_link l(:button_edit), {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id)}, <li><%= context_menu_link l(:button_edit), {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id)},
:class => 'icon-edit', :disabled => !@can[:edit] %></li> :class => 'icon-edit', :disabled => !@can[:edit] %></li>
<% end %> <% end %>
<% if @allowed_statuses.present? %> <% if @allowed_statuses.present? %>
<li class="folder status"> <li class="folder">
<a href="#" class="context_item" onclick="return false;"><%= l(:field_status) %></a> <a href="#" class="submenu" onclick="return false;"><%= l(:field_status) %></a>
<ul> <ul>
<% @statuses.each do |s| -%> <% @statuses.each do |s| -%>
<li><%= context_menu_link s.name, {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {:status_id => s}, :back_url => @back}, :method => :post, <li><%= context_menu_link s.name, {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {:status_id => s}, :back_url => @back}, :method => :post,
:selected => (@issue && s == @issue.status), :disabled => !(@can[:update] && @allowed_statuses.include?(s)) %></li> :selected => (@issue && s == @issue.status), :disabled => !(@can[:update] && @allowed_statuses.include?(s)) %></li>
<% end -%> <% end -%>
</ul> </ul>
<div class="submenu"></div>
</li> </li>
<% end %> <% end %>
<% unless @trackers.nil? %> <% unless @trackers.nil? %>
<li class="folder tracker"> <li class="folder">
<a href="#" class="context_item"><%= l(:field_tracker) %></a> <a href="#" class="submenu"><%= l(:field_tracker) %></a>
<ul> <ul>
<% @trackers.each do |t| -%> <% @trackers.each do |t| -%>
<li><%= context_menu_link t.name, {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {'tracker_id' => t}, :back_url => @back}, :method => :post, <li><%= context_menu_link t.name, {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {'tracker_id' => t}, :back_url => @back}, :method => :post,
:selected => (@issue && t == @issue.tracker), :disabled => !@can[:edit] %></li> :selected => (@issue && t == @issue.tracker), :disabled => !@can[:edit] %></li>
<% end -%> <% end -%>
</ul> </ul>
<div class="submenu"></div>
</li> </li>
<% end %> <% end %>
<li class="folder priority"> <li class="folder">
<a href="#" class="context_item"><%= l(:field_priority) %></a> <a href="#" class="submenu"><%= l(:field_priority) %></a>
<ul> <ul>
<% @priorities.each do |p| -%> <% @priorities.each do |p| -%>
<li><%= context_menu_link p.name, {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {'priority_id' => p}, :back_url => @back}, :method => :post, <li><%= context_menu_link p.name, {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {'priority_id' => p}, :back_url => @back}, :method => :post,
:selected => (@issue && p == @issue.priority), :disabled => (!@can[:edit] || @issues.detect {|i| !i.leaf?}) %></li> :selected => (@issue && p == @issue.priority), :disabled => (!@can[:edit] || @issues.detect {|i| !i.leaf?}) %></li>
<% end -%> <% end -%>
</ul> </ul>
<div class="submenu"></div>
</li> </li>
<% #TODO: allow editing versions when multiple projects %> <% #TODO: allow editing versions when multiple projects %>
<% unless @project.nil? || @project.shared_versions.open.empty? -%> <% unless @project.nil? || @project.shared_versions.open.empty? -%>
<li class="folder fixed_version"> <li class="folder">
<a href="#" class="context_item"><%= l(:field_fixed_version) %></a> <a href="#" class="submenu"><%= l(:field_fixed_version) %></a>
<ul> <ul>
<% @project.shared_versions.open.sort.each do |v| -%> <% @project.shared_versions.open.sort.each do |v| -%>
<li><%= context_menu_link format_version_name(v), {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {'fixed_version_id' => v}, :back_url => @back}, :method => :post, <li><%= context_menu_link format_version_name(v), {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {'fixed_version_id' => v}, :back_url => @back}, :method => :post,
@ -58,13 +55,11 @@
<li><%= context_menu_link l(:label_none), {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {'fixed_version_id' => 'none'}, :back_url => @back}, :method => :post, <li><%= context_menu_link l(:label_none), {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {'fixed_version_id' => 'none'}, :back_url => @back}, :method => :post,
:selected => (@issue && @issue.fixed_version.nil?), :disabled => !@can[:update] %></li> :selected => (@issue && @issue.fixed_version.nil?), :disabled => !@can[:update] %></li>
</ul> </ul>
<div class="submenu"></div>
</li> </li>
<% end %> <% end %>
<% if @assignables.present? -%> <% if @assignables.present? -%>
<li class="folder assigned"> <li class="folder">
<a href="#" class="context_item"><%= l(:field_assigned_to) %></a> <a href="#" class="submenu"><%= l(:field_assigned_to) %></a>
<ul> <ul>
<% @assignables.each do |u| -%> <% @assignables.each do |u| -%>
<li><%= context_menu_link u.name, {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {'assigned_to_id' => u}, :back_url => @back}, :method => :post, <li><%= context_menu_link u.name, {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {'assigned_to_id' => u}, :back_url => @back}, :method => :post,
@ -73,13 +68,11 @@
<li><%= context_menu_link l(:label_nobody), {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {'assigned_to_id' => 'none'}, :back_url => @back}, :method => :post, <li><%= context_menu_link l(:label_nobody), {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {'assigned_to_id' => 'none'}, :back_url => @back}, :method => :post,
:selected => (@issue && @issue.assigned_to.nil?), :disabled => !@can[:update] %></li> :selected => (@issue && @issue.assigned_to.nil?), :disabled => !@can[:update] %></li>
</ul> </ul>
<div class="submenu"></div>
</li> </li>
<% end %> <% end %>
<% unless @project.nil? || @project.issue_categories.empty? -%> <% unless @project.nil? || @project.issue_categories.empty? -%>
<li class="folder"> <li class="folder">
<a href="#" class="context_item"><%= l(:field_category) %></a> <a href="#" class="submenu"><%= l(:field_category) %></a>
<ul> <ul>
<% @project.issue_categories.each do |u| -%> <% @project.issue_categories.each do |u| -%>
<li><%= context_menu_link u.name, {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {'category_id' => u}, :back_url => @back}, :method => :post, <li><%= context_menu_link u.name, {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {'category_id' => u}, :back_url => @back}, :method => :post,
@ -88,30 +81,28 @@
<li><%= context_menu_link l(:label_none), {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {'category_id' => 'none'}, :back_url => @back}, :method => :post, <li><%= context_menu_link l(:label_none), {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {'category_id' => 'none'}, :back_url => @back}, :method => :post,
:selected => (@issue && @issue.category.nil?), :disabled => !@can[:update] %></li> :selected => (@issue && @issue.category.nil?), :disabled => !@can[:update] %></li>
</ul> </ul>
<div class="submenu"></div>
</li> </li>
<% end -%> <% end -%>
<% if Issue.use_field_for_done_ratio? %> <% if Issue.use_field_for_done_ratio? %>
<li class="folder done_ratio"> <li class="folder">
<a href="#" class="context_item"><%= l(:field_done_ratio) %></a> <a href="#" class="submenu"><%= l(:field_done_ratio) %></a>
<ul> <ul>
<% (0..10).map{|x|x*10}.each do |p| -%> <% (0..10).map{|x|x*10}.each do |p| -%>
<li><%= context_menu_link "#{p}%", {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {'done_ratio' => p}, :back_url => @back}, :method => :post, <li><%= context_menu_link "#{p}%", {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {'done_ratio' => p}, :back_url => @back}, :method => :post,
:selected => (@issue && p == @issue.done_ratio), :disabled => (!@can[:edit] || @issues.detect {|i| !i.leaf?}) %></li> :selected => (@issue && p == @issue.done_ratio), :disabled => (!@can[:edit] || @issues.detect {|i| !i.leaf?}) %></li>
<% end -%> <% end -%>
</ul> </ul>
<div class="submenu"></div>
</li> </li>
<% end %> <% end %>
<% if !@issue.nil? %> <% if !@issue.nil? %>
<% if @can[:log_time] -%> <% if @can[:log_time] -%>
<li class="log_time"><%= context_menu_link l(:button_log_time), {:controller => 'timelog', :action => 'new', :issue_id => @issue}, <li><%= context_menu_link l(:button_log_time), {:controller => 'timelog', :action => 'new', :issue_id => @issue},
:class => 'context_item' %></li> :class => 'icon-time-add' %></li>
<% end %> <% end %>
<% if User.current.logged? %> <% if User.current.logged? %>
<li class="watch"><%= watcher_link(@issue, User.current) %></li> <li><%= watcher_link(@issue, User.current) %></li>
<% end %> <% end %>
<% end %> <% end %>
@ -119,13 +110,12 @@
<li><%= context_menu_link l(:button_duplicate), {:controller => 'issues', :action => 'new', :project_id => @project, :copy_from => @issue}, <li><%= context_menu_link l(:button_duplicate), {:controller => 'issues', :action => 'new', :project_id => @project, :copy_from => @issue},
:class => 'icon-duplicate', :disabled => !@can[:copy] %></li> :class => 'icon-duplicate', :disabled => !@can[:copy] %></li>
<% end %> <% end %>
<li><%= context_menu_link l(:button_copy), new_issue_move_path(:ids => @issues.collect(&:id), :copy_options => {:copy => 't'}),
<li class="move"><%= context_menu_link l(:button_move), new_issue_move_path(:ids => @issues.collect(&:id)), :class => 'icon-copy', :disabled => !@can[:move] %></li>
:class => 'context_item', :disabled => !@can[:move] %></li> <li><%= context_menu_link l(:button_move), new_issue_move_path(:ids => @issues.collect(&:id)),
<li class="copy"><%= context_menu_link l(:button_copy), new_issue_move_path(:ids => @issues.collect(&:id), :copy_options => {:copy => 't'}), :class => 'icon-move', :disabled => !@can[:move] %></li>
:class => 'context_item' %></li> <li><%= context_menu_link l(:button_delete), {:controller => 'issues', :action => 'destroy', :ids => @issues.collect(&:id), :back_url => @back},
<li class="delete"><%= context_menu_link l(:button_delete), {:controller => 'issues', :action => 'destroy', :ids => @issues.collect(&:id)}, :method => :post, :confirm => l(:text_issues_destroy_confirmation), :class => 'icon-del', :disabled => !@can[:delete] %></li>
:method => :post, :confirm => l(:text_issues_destroy_confirmation), :class => 'context_item', :disabled => !@can[:delete] %></li>
<%= call_hook(:view_issues_context_menu_end, {:issues => @issues, :can => @can, :back => @back }) %> <%= call_hook(:view_issues_context_menu_end, {:issues => @issues, :can => @can, :back => @back }) %>
</ul> </ul>

View File

@ -9,15 +9,6 @@
<p><label for="document_description"><%=l(:field_description)%></label> <p><label for="document_description"><%=l(:field_description)%></label>
<%= text_area 'document', 'description', :cols => 60, :rows => 15, :class => 'wiki-edit' %></p> <%= text_area 'document', 'description', :cols => 60, :rows => 15, :class => 'wiki-edit' %></p>
<% if User.current.allowed_to?(:add_document_watchers, @project) -%>
<p id="watchers_form"><label><%= l(:label_document_watchers) %></label>
<% @document.project.users.sort.each do |user| -%>
<label class="floating"><%= check_box_tag 'document[watcher_user_ids][]', user.id, @document.watched_by?(user) %> <%=h user %></label>
<% end -%>
</p>
<% end %>
<!--[eoform:document]--> <!--[eoform:document]-->
</div> </div>

View File

@ -27,16 +27,6 @@
<% html_title h(@document.title) -%> <% html_title h(@document.title) -%>
<% content_for :sidebar do %>
<% if User.current.allowed_to?(:add_document_watchers, @project) ||
(@document.watchers.present? && User.current.allowed_to?(:view_document_watchers, @project)) %>
<div id="watchers">
<%= render :partial => 'watchers/watchers', :locals => {:watched => @document} %>
</div>
<% end %>
<% end %>
<% content_for :header_tags do %> <% content_for :header_tags do %>
<%= stylesheet_link_tag 'scm' %> <%= stylesheet_link_tag 'scm' %>
<% end %> <% end %>

View File

@ -72,7 +72,7 @@ t_height = g_height + headers_height
<p class="warning"><%= l(:notice_gantt_chart_truncated, :max => @gantt.max_rows) %></p> <p class="warning"><%= l(:notice_gantt_chart_truncated, :max => @gantt.max_rows) %></p>
<% end %> <% end %>
<table style="width:100%; border:0; border-collapse: collapse;"> <table width="100%" style="border:0; border-collapse: collapse;">
<tr> <tr>
<td style="width:<%= subject_width %>px; padding:0px;"> <td style="width:<%= subject_width %>px; padding:0px;">
@ -98,7 +98,7 @@ month_f = @gantt.date_from
left = 0 left = 0
height = (show_weeks ? header_heigth : header_heigth + g_height) height = (show_weeks ? header_heigth : header_heigth + g_height)
@gantt.months.times do @gantt.months.times do
width = (((month_f >> 1) - month_f) * zoom - 1).to_i width = ((month_f >> 1) - month_f) * zoom - 1
%> %>
<div style="left:<%= left %>px;width:<%= width %>px;height:<%= height %>px;" class="gantt_hdr"> <div style="left:<%= left %>px;width:<%= width %>px;height:<%= height %>px;" class="gantt_hdr">
<%= link_to h("#{month_f.year}-#{month_f.month}"), @gantt.params.merge(:year => month_f.year, :month => month_f.month), :title => "#{month_name(month_f.month)} #{month_f.year}"%> <%= link_to h("#{month_f.year}-#{month_f.month}"), @gantt.params.merge(:year => month_f.year, :month => month_f.month), :title => "#{month_name(month_f.month)} #{month_f.year}"%>
@ -176,7 +176,7 @@ if Date.today >= @gantt.date_from and Date.today <= @gantt.date_to %>
</tr> </tr>
</table> </table>
<table style="width:100%"> <table width="100%">
<tr> <tr>
<td align="left"><%= link_to_content_update('&#171; ' + l(:label_previous), params.merge(@gantt.params_previous)) %></td> <td align="left"><%= link_to_content_update('&#171; ' + l(:label_previous), params.merge(@gantt.params_previous)) %></td>
<td align="right"><%= link_to_content_update(l(:label_next) + ' &#187;', params.merge(@gantt.params_next)) %></td> <td align="right"><%= link_to_content_update(l(:label_next) + ' &#187;', params.merge(@gantt.params_next)) %></td>

View File

@ -41,5 +41,17 @@
</div> </div>
<div class="splitcontentright"> <div class="splitcontentright">
<%= render :partial => 'members/membership_assignment', :locals => {:principal => @group, :projects => projects - @group.projects, :roles => roles } %> <% if projects.any? %>
<fieldset><legend><%=l(:label_project_new)%></legend>
<% remote_form_for(:membership, :url => { :action => 'edit_membership', :id => @group }) do %>
<%= label_tag "membership_project_id", l(:description_choose_project), :class => "hidden-for-sighted" %>
<%= select_tag 'membership[project_id]', options_for_membership_project_select(@group, projects) %>
<p><%= l(:label_role_plural) %>:
<% roles.each do |role| %>
<label><%= check_box_tag 'membership[role_ids][]', role.id %> <%=h role %></label>
<% end %></p>
<p><%= submit_tag l(:button_add) %></p>
<% end %>
</fieldset>
<% end %>
</div> </div>

View File

@ -33,7 +33,7 @@
<%= observe_field(:user_search, <%= observe_field(:user_search,
:frequency => 0.5, :frequency => 0.5,
:update => :users, :update => :users,
:url => auto_complete_users_path(:remove_group_members => @group), :url => { :controller => 'groups', :action => 'autocomplete_for_user', :id => @group },
:with => 'q') :with => 'q')
%> %>

View File

@ -19,7 +19,6 @@
<td class="buttons"><%= link_to l(:button_delete), group, :confirm => l(:text_are_you_sure), :method => :delete, :class => 'icon icon-del' %></td> <td class="buttons"><%= link_to l(:button_delete), group, :confirm => l(:text_are_you_sure), :method => :delete, :class => 'icon icon-del' %></td>
</tr> </tr>
<% end %> <% end %>
</tbody>
</table> </table>
<% else %> <% else %>
<p class="nodata"><%= l(:label_no_data) %></p> <p class="nodata"><%= l(:label_no_data) %></p>

View File

@ -12,7 +12,7 @@
<% html_title "Wiki Syntax Quick Reference" %> <% html_title "Wiki Syntax Quick Reference" %>
<h1>Wiki Syntax Quick Reference</h1> <h1>Wiki Syntax Quick Reference</h1>
<table style="width:100%"> <table width="100%">
<tr><th colspan="3">Font Styles</th></tr> <tr><th colspan="3">Font Styles</th></tr>
<tr><th><img src="../images/jstoolbar/bt_strong.png" style="border: 1px solid #bbb;" alt="Strong" /></th><td width="50%">*Strong*</td><td width="50%"><strong>Strong</strong></td></tr> <tr><th><img src="../images/jstoolbar/bt_strong.png" style="border: 1px solid #bbb;" alt="Strong" /></th><td width="50%">*Strong*</td><td width="50%"><strong>Strong</strong></td></tr>
<tr><th><img src="../images/jstoolbar/bt_em.png" style="border: 1px solid #bbb;" alt="Italic" /></th><td>_Italic_</td><td><em>Italic</em></td></tr> <tr><th><img src="../images/jstoolbar/bt_em.png" style="border: 1px solid #bbb;" alt="Italic" /></th><td>_Italic_</td><td><em>Italic</em></td></tr>

View File

@ -13,22 +13,20 @@
} }
a.new { color: #b73535; } a.new { color: #b73535; }
.syntaxhl .line-numbers { padding: 2px 4px 2px 4px; background-color: #eee; margin:0 } .CodeRay .c { color:#666; }
.syntaxhl .comment { color:#666; }
.syntaxhl .class { color:#B06; font-weight:bold } .CodeRay .cl { color:#B06; font-weight:bold }
.syntaxhl .delimiter { color:black } .CodeRay .dl { color:black }
.syntaxhl .function { color:#06B; font-weight:bold } .CodeRay .fu { color:#06B; font-weight:bold }
.syntaxhl .inline { background: #eee } .CodeRay .il { background: #eee }
.syntaxhl .inline .inline-delimiter { font-weight: bold; color: #888 } .CodeRay .il .idl { font-weight: bold; color: #888 }
.syntaxhl .instance-variable { color:#33B } .CodeRay .iv { color:#33B }
.syntaxhl .reserved { color:#080; font-weight:bold } .CodeRay .r { color:#080; font-weight:bold }
.syntaxhl .string { background-color:#fff0f0; color: #D20; }
.syntaxhl .string .delimiter { color:#710 }
.CodeRay .s { background-color:#fff0f0 }
.CodeRay .s .dl { color:#710 }
<% end %> <% end %>
<% html_title "Wiki Formatting" %> <% html_title "Wiki Formatting" %>
@ -238,7 +236,7 @@ To go live, all you need to add is a database and a web server.
<h2><a name="13" class="wiki-page"></a>Code highlighting</h2> <h2><a name="13" class="wiki-page"></a>Code highlighting</h2>
<p>The default code highlighting relies on <a href="http://coderay.rubychan.de/" class="external">CodeRay</a>, a fast syntax highlighting library written completely in Ruby. It currently supports c, cpp, css, delphi, groovy, html, java, javascript, json, php, python, rhtml, ruby, scheme, sql, xml and yaml languages.</p> <p>Code highlightment relies on <a href="http://coderay.rubychan.de/" class="external">CodeRay</a>, a fast syntax highlighting library written completely in Ruby. It currently supports c, cpp, css, delphi, groovy, html, java, javascript, json, php, python, rhtml, ruby, scheme, sql, xml and yaml languages.</p>
<p>You can highlight code in your wiki page using this syntax:</p> <p>You can highlight code in your wiki page using this syntax:</p>
@ -250,14 +248,15 @@ To go live, all you need to add is a database and a web server.
<p>Example:</p> <p>Example:</p>
<pre><code class="ruby syntaxhl"><span class="line-numbers"> 1</span> <span class="comment"># The Greeter class</span> <pre><code class="ruby CodeRay"><span class="no"> 1</span> <span class="c"># The Greeter class</span>
<span class="line-numbers"> 2</span> <span class="reserved">class</span> <span class="class">Greeter</span> <span class="no"> 2</span> <span class="r">class</span> <span class="cl">Greeter</span>
<span class="line-numbers"> 3</span> <span class="reserved">def</span> <span class="function">initialize</span>(name) <span class="no"> 3</span> <span class="r">def</span> <span class="fu">initialize</span>(name)
<span class="line-numbers"> 4</span> <span class="instance-variable">@name</span> = name.capitalize <span class="no"> 4</span> <span class="iv">@name</span> = name.capitalize
<span class="line-numbers"> 5</span> <span class="reserved">end</span> <span class="no"> 5</span> <span class="r">end</span>
<span class="line-numbers"> 6</span> <span class="no"> 6</span>
<span class="line-numbers"> 7</span> <span class="reserved">def</span> <span class="function">salute</span> <span class="no"> 7</span> <span class="r">def</span> <span class="fu">salute</span>
<span class="line-numbers"> 8</span> puts <span class="string"><span class="delimiter">"</span><span class="content">Hello </span><span class="inline"><span class="inline-delimiter">#{</span><span class="instance-variable">@name</span><span class="inline-delimiter">}</span></span><span class="content">!</span><span class="delimiter">"</span></span> <span class="no"> 8</span> puts <span class="s"><span class="dl">"</span><span class="k">Hello </span><span class="il"><span class="idl">#{</span><span class="iv">@name</span><span class="idl">}</span></span><span class="k">!</span><span class="dl">"</span></span>
<span class="line-numbers"> 9</span> <span class="reserved">end</span> <span class="no"> 9</span> <span class="r">end</span>
<span class="line-numbers"><strong>10</strong></span> <span class="reserved">end</span></code> <span class="no"><strong>10</strong></span> <span class="r">end</span>
</code>
</pre> </pre>

View File

@ -46,12 +46,12 @@
<div class="splitcontentright"> <div class="splitcontentright">
<p> <p>
<label for='start_date'><%= l(:field_start_date) %></label> <label for='start_date'><%= l(:field_start_date) %></label>
<%= date_field_tag 'start_date', '', :size => 10 %> <%= text_field_tag 'start_date', '', :size => 10 %><%= calendar_for('start_date') %>
</p> </p>
<p> <p>
<label for='due_date'><%= l(:field_due_date) %></label> <label for='due_date'><%= l(:field_due_date) %></label>
<%= date_field_tag 'due_date', '', :size => 10 %> <%= text_field_tag 'due_date', '', :size => 10 %><%= calendar_for('due_date') %>
</p> </p>
</div> </div>

View File

@ -31,8 +31,8 @@
</div> </div>
<div class="splitcontentright"> <div class="splitcontentright">
<p><%= f.date_field :start_date, :size => 10, :disabled => !@issue.leaf? %><%= calendar_for('issue_start_date') if @issue.leaf? %></p> <p><%= f.text_field :start_date, :size => 10, :disabled => !@issue.leaf? %><%= calendar_for('issue_start_date') if @issue.leaf? %></p>
<p><%= f.date_field :due_date, :size => 10, :disabled => !@issue.leaf? %><%= calendar_for('issue_due_date') if @issue.leaf? %></p> <p><%= f.text_field :due_date, :size => 10, :disabled => !@issue.leaf? %><%= calendar_for('issue_due_date') if @issue.leaf? %></p>
<p><%= f.text_field :estimated_hours, :size => 3, :disabled => !@issue.leaf? %> <%= l(:field_hours) %></p> <p><%= f.text_field :estimated_hours, :size => 3, :disabled => !@issue.leaf? %> <%= l(:field_hours) %></p>
<% if @issue.leaf? && Issue.use_field_for_done_ratio? %> <% if @issue.leaf? && Issue.use_field_for_done_ratio? %>
<p><%= f.select :done_ratio, ((0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %></p> <p><%= f.select :done_ratio, ((0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %></p>

View File

@ -38,7 +38,7 @@
<%= call_hook(:view_issues_edit_notes_bottom, { :issue => @issue, :notes => @notes, :form => f }) %> <%= call_hook(:view_issues_edit_notes_bottom, { :issue => @issue, :notes => @notes, :form => f }) %>
</fieldset> </fieldset>
<fieldset id="attachments" class="header_collapsible collapsible collapsed"><legend onclick="toggleFieldset(this);"><%=l(:label_attachment_plural)%></legend> <fieldset id="attachments" class="collapsible collapsed borders"><legend onclick="toggleFieldset(this);"><%=l(:label_attachment_plural)%></legend>
<div style="display: none;"> <div style="display: none;">
<%= render :partial => 'attachments/form' %> <%= render :partial => 'attachments/form' %>
</div> </div>

View File

@ -1,9 +1,10 @@
<% form_tag({}) do -%> <% form_tag({}) do -%>
<%= hidden_field_tag 'back_url', url_for(params), :id => nil %> <%= hidden_field_tag 'back_url', url_for(params) %>
<div class="autoscroll"> <div class="autoscroll">
<table class="list issues"> <table class="list issues">
<thead><tr> <thead><tr>
<th class="checkbox hide-when-print"><%= link_to image_tag('toggle_check.png'), {}, :title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}" %> <th class="checkbox hide-when-print"><%= link_to image_tag('toggle_check.png'), {}, :onclick => 'toggleIssuesSelection(Element.up(this, "form")); return false;',
:title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}" %>
</th> </th>
<%= sort_header_tag('id', :caption => '#', :default_order => 'desc') %> <%= sort_header_tag('id', :caption => '#', :default_order => 'desc') %>
<% query.columns.each do |column| %> <% query.columns.each do |column| %>

View File

@ -1,9 +1,10 @@
<p> <div class="contextual">
<strong><%=l(:label_related_issues)%></strong> <% if authorize_for('issue_relations', 'new') %>
<% if authorize_for('issue_relations', 'new') %> <%= toggle_link l(:button_add), 'new-relation-form', {:focus => 'relation_issue_to_id'} %>
(<%= toggle_link l(:button_add), 'new-relation-form', {:focus => 'relation_issue_to_id'} %>) <% end %>
<% end %> </div>
</p>
<p><strong><%=l(:label_related_issues)%></strong></p>
<% if @relations.present? %> <% if @relations.present? %>
<table style="width:100%"> <table style="width:100%">

View File

@ -1,7 +1,17 @@
<h3><%= l(:label_issue_plural) %></h3> <h3><%= l(:label_issue_plural) %></h3>
<%= link_to l(:label_issue_view_all), { :controller => 'issues', :action => 'index', :project_id => @project, :set_filter => 1 } %><br />
<% if @project %>
<%= link_to l(:field_summary), :controller => 'reports', :action => 'issue_report', :id => @project %><br />
<% end %>
<%= call_hook(:view_issues_sidebar_issues_bottom) %> <%= call_hook(:view_issues_sidebar_issues_bottom) %>
<% if User.current.allowed_to?(:view_calendar, @project, :global => true) %>
<%= link_to(l(:label_calendar), :controller => 'calendars', :action => 'show', :project_id => @project) %><br />
<% end %>
<% if User.current.allowed_to?(:view_gantt, @project, :global => true) %>
<%= link_to(l(:label_gantt), :controller => 'gantts', :action => 'show', :project_id => @project) %><br />
<% end %>
<%= call_hook(:view_issues_sidebar_planning_bottom) %> <%= call_hook(:view_issues_sidebar_planning_bottom) %>
<%= render_sidebar_queries unless @project %> <%= render_sidebar_queries %>
<%= call_hook(:view_issues_sidebar_queries_bottom) %> <%= call_hook(:view_issues_sidebar_queries_bottom) %>

View File

@ -1,11 +0,0 @@
<% if !@issue.leaf? || @issue.parent || User.current.allowed_to?(:manage_subtasks, @project) %>
<hr />
<p>
<strong><%= l(:label_issue_hierarchy) %></strong>
<% if User.current.allowed_to?(:manage_subtasks, @project) %>
(<%= link_to(l(:label_subtask_add), {:controller => 'issues', :action => 'new', :project_id => @project, :issue => {:parent_issue_id => @issue}}) %>)
<% end %>
</p>
<% end %>
<%= render_parents_and_subtree @issue %>

View File

@ -65,11 +65,11 @@
<% end %> <% end %>
<p> <p>
<label for='issue_start_date'><%= l(:field_start_date) %></label> <label for='issue_start_date'><%= l(:field_start_date) %></label>
<%= date_field_tag 'issue[start_date]', '', :size => 10 %> <%= text_field_tag 'issue[start_date]', '', :size => 10 %><%= calendar_for('issue_start_date') %>
</p> </p>
<p> <p>
<label for='issue_due_date'><%= l(:field_due_date) %></label> <label for='issue_due_date'><%= l(:field_due_date) %></label>
<%= date_field_tag 'issue[due_date]', '', :size => 10 %> <%= text_field_tag 'issue[due_date]', '', :size => 10 %><%= calendar_for('issue_due_date') %>
</p> </p>
<% if Issue.use_field_for_done_ratio? %> <% if Issue.use_field_for_done_ratio? %>
<p> <p>

View File

@ -1,29 +1,23 @@
<div class="title-bar">
<h2><%= @query.new_record? ? l(:label_issue_plural) : h(@query.name) %></h2>
<div class="title-bar-extras">
<div class="contextual"> <div class="contextual">
<% if !@query.new_record? && @query.editable_by?(User.current) %> <% if !@query.new_record? && @query.editable_by?(User.current) %>
<%= link_to l(:button_edit), {:controller => 'queries', :action => 'edit', :id => @query}, :class => 'icon icon-edit' %> <%= link_to l(:button_edit), {:controller => 'queries', :action => 'edit', :id => @query}, :class => 'icon icon-edit' %>
<%= link_to l(:button_delete), {:controller => 'queries', :action => 'destroy', :id => @query}, :confirm => l(:text_are_you_sure), :method => :post, :class => 'icon icon-del' %> <%= link_to l(:button_delete), {:controller => 'queries', :action => 'destroy', :id => @query}, :confirm => l(:text_are_you_sure), :method => :post, :class => 'icon icon-del' %>
<% end %> <% end %>
<%= render :partial => 'queries/new_issue_button' %>
</div> </div>
<h2><%= @query.new_record? ? l(:label_issue_plural) : h(@query.name) %></h2>
<% html_title(@query.new_record? ? l(:label_issue_plural) : h(@query.name)) %> <% html_title(@query.new_record? ? l(:label_issue_plural) : h(@query.name)) %>
<% form_tag({ :controller => 'queries', :action => 'new' }, :id => 'query_form') do %> <% form_tag({ :controller => 'queries', :action => 'new' }, :id => 'query_form') do %>
<%= hidden_field_tag('project_id', @project.to_param) if @project %> <%= hidden_field_tag('project_id', @project.to_param) if @project %>
<div id="query_form_content" class="hide-when-print"> <div id="query_form_content" class="hide-when-print">
<fieldset id="filters" class="header_collapsible collapsible <%= @query.new_record? ? "" : "collapsed" %>"> <fieldset id="filters" class="collapsible <%= @query.new_record? ? "" : "collapsed" %>">
<legend onclick="toggleFieldset(this);"><%= l(:label_filter_plural) %></legend> <legend onclick="toggleFieldset(this);"><%= l(:label_filter_plural) %></legend>
<div class="filter-fields" style="<%= @query.new_record? ? "" : "display: none;" %>"> <div style="<%= @query.new_record? ? "" : "display: none;" %>">
<%= render :partial => 'queries/filters', :locals => {:query => @query} %> <%= render :partial => 'queries/filters', :locals => {:query => @query} %>
</div> </div>
</fieldset> </fieldset>
<fieldset id="column_options" class="header_collapsible collapsible collapsed"> <fieldset class="collapsible collapsed">
<legend onclick="toggleFieldset(this);"><%= l(:label_options) %></legend> <legend onclick="toggleFieldset(this);"><%= l(:label_options) %></legend>
<div style="display: none;"> <div style="display: none;">
<table> <table>
@ -35,20 +29,6 @@
<td><label for='group_by'><%= l(:field_group_by) %></label></td> <td><label for='group_by'><%= l(:field_group_by) %></label></td>
<td><%= select_tag('group_by', options_for_select([[]] + @query.groupable_columns.collect {|c| [c.caption, c.name.to_s]}, @query.group_by)) %></td> <td><%= select_tag('group_by', options_for_select([[]] + @query.groupable_columns.collect {|c| [c.caption, c.name.to_s]}, @query.group_by)) %></td>
</tr> </tr>
<tr>
<td><%= l(:label_project_plural) %></td>
<td>
<label>
<%= radio_button_tag('display_subprojects', '0', !@query.display_subprojects?) %>
<%= l(:text_current_project) %>
</label>
<br />
<label>
<%= radio_button_tag('display_subprojects', '1', @query.display_subprojects?) %>
<%= l(:label_and_its_subprojects, :value => l(:text_current_project)) %>
</label>
</td>
</tr>
</table> </table>
</div> </div>
</fieldset> </fieldset>
@ -75,9 +55,6 @@
</p> </p>
<% end %> <% end %>
</div><!-- .title-bar-extras -->
</div><!-- .title-bar -->
<%= error_messages_for 'query' %> <%= error_messages_for 'query' %>
<% if @query.valid? %> <% if @query.valid? %>
<% if @issues.empty? %> <% if @issues.empty? %>

View File

@ -1,65 +1,53 @@
<div class="title-bar" id="upper-title-bar"> <%= render :partial => 'action_menu' %>
<div class="title-bar-actions">
<%= render :partial => 'action_menu' %> <h2><%= h(@issue.tracker.name) %> #<%= h(@issue.id) %><%= call_hook(:view_issues_show_identifier, :issue => @issue) %></h2>
</div>
</div>
<div class="<%= @issue.css_classes %> details"> <div class="<%= @issue.css_classes %> details">
<%= avatar(@issue.author, :size => "50") %>
<h1 class="subject"> <div class="subject">
<%=h(@issue.subject) %> (<%= h(@issue.tracker.name) + ' #' +@issue.id.to_s %>) <%= render_issue_subject_with_tree(@issue) %>
</h1> </div>
<hr />
<p class="author"> <p class="author">
<%= avatar(@issue.author, :size => "14") %>
<%= authoring @issue.created_on, @issue.author %>. <%= authoring @issue.created_on, @issue.author %>.
<% if @issue.created_on != @issue.updated_on %> <% if @issue.created_on != @issue.updated_on %>
<%= l(:label_updated_time, time_tag(@issue.updated_on)) %>. <%= l(:label_updated_time, time_tag(@issue.updated_on)) %>.
<% end %> <% end %>
</p> </p>
<hr />
<div class="meta"> <table class="attributes">
<table class="attributes"> <tr>
<tr>
<th class="status"><%=l(:field_status)%>:</th><td class="status"><%= h(@issue.status.name) %></td> <th class="status"><%=l(:field_status)%>:</th><td class="status"><%= h(@issue.status.name) %></td>
<th class="start-date"><%=l(:field_start_date)%>:</th><td class="start-date"><%= format_date(@issue.start_date) %></td> <th class="start-date"><%=l(:field_start_date)%>:</th><td class="start-date"><%= format_date(@issue.start_date) %></td>
</tr> </tr>
<tr> <tr>
<th class="priority"><%=l(:field_priority)%>:</th><td class="priority"><%= h(@issue.priority.name) %></td> <th class="priority"><%=l(:field_priority)%>:</th><td class="priority"><%= h(@issue.priority.name) %></td>
<th class="due-date"><%=l(:field_due_date)%>:</th><td class="due-date"><%= format_date(@issue.due_date) %></td> <th class="due-date"><%=l(:field_due_date)%>:</th><td class="due-date"><%= format_date(@issue.due_date) %></td>
</tr> </tr>
<tr> <tr>
<th class="assigned-to"><%=l(:field_assigned_to)%>:</th><td class="assigned-to"><%= avatar(@issue.assigned_to, :size => "14") %><%= @issue.assigned_to ? link_to_user(@issue.assigned_to) : "-" %></td> <th class="assigned-to"><%=l(:field_assigned_to)%>:</th><td class="assigned-to"><%= avatar(@issue.assigned_to, :size => "14") %><%= @issue.assigned_to ? link_to_user(@issue.assigned_to) : "-" %></td>
<th class="progress"><%=l(:field_done_ratio)%>:</th><td class="progress"><%= progress_bar @issue.done_ratio, :width => '80px', :legend => "#{@issue.done_ratio}%" %></td> <th class="progress"><%=l(:field_done_ratio)%>:</th><td class="progress"><%= progress_bar @issue.done_ratio, :width => '80px', :legend => "#{@issue.done_ratio}%" %></td>
</tr> </tr>
<tr> <tr>
<th class="category"><%=l(:field_category)%>:</th><td class="category"><%=h(@issue.category ? @issue.category.name : "-") %></td> <th class="category"><%=l(:field_category)%>:</th><td class="category"><%=h(@issue.category ? @issue.category.name : "-") %></td>
<% if User.current.allowed_to?(:view_time_entries, @project) %> <% if User.current.allowed_to?(:view_time_entries, @project) %>
<th class="spent-time"><%=l(:label_spent_time)%>:</th> <th class="spent-time"><%=l(:label_spent_time)%>:</th>
<td class="spent-time"><%= @issue.spent_hours > 0 ? (link_to l_hours(@issue.spent_hours), {:controller => 'timelog', :action => 'index', :project_id => @project, :issue_id => @issue}) : "-" %></td> <td class="spent-time"><%= @issue.spent_hours > 0 ? (link_to l_hours(@issue.spent_hours), {:controller => 'timelog', :action => 'index', :project_id => @project, :issue_id => @issue}) : "-" %></td>
<% else %>
<th></th><td></td>
<% end %> <% end %>
</tr> </tr>
<tr> <tr>
<th class="fixed-version"><%=l(:field_fixed_version)%>:</th><td class="fixed-version"><%= @issue.fixed_version ? link_to_version(@issue.fixed_version) : "-" %></td> <th class="fixed-version"><%=l(:field_fixed_version)%>:</th><td class="fixed-version"><%= @issue.fixed_version ? link_to_version(@issue.fixed_version) : "-" %></td>
<% if @issue.estimated_hours %> <% if @issue.estimated_hours %>
<th class="estimated-hours"><%=l(:field_estimated_hours)%>:</th><td class="estimated-hours"><%= l_hours(@issue.estimated_hours) %></td> <th class="estimated-hours"><%=l(:field_estimated_hours)%>:</th><td class="estimated-hours"><%= l_hours(@issue.estimated_hours) %></td>
<% else %>
<th></th><td></td>
<% end %> <% end %>
</tr> </tr>
<%= render_custom_fields_rows(@issue) %> <%= render_custom_fields_rows(@issue) %>
<%= call_hook(:view_issues_show_details_bottom, :issue => @issue) %> <%= call_hook(:view_issues_show_details_bottom, :issue => @issue) %>
</table> </table>
</div><!-- .meta -->
<% if @issue.description? || @issue.attachments.any? -%>
<hr />
<% if @issue.description? %> <% if @issue.description? %>
<hr />
<div class="description">
<div class="contextual"> <div class="contextual">
<%= link_to_remote_if_authorized(l(:button_quote), { :url => {:controller => 'journals', :action => 'new', :id => @issue} }, :class => 'icon icon-comment') %> <%= link_to_remote_if_authorized(l(:button_quote), { :url => {:controller => 'journals', :action => 'new', :id => @issue} }, :class => 'icon icon-comment') %>
</div> </div>
@ -68,17 +56,22 @@
<div class="wiki"> <div class="wiki">
<%= textilizable @issue, :description, :attachments => @issue.attachments %> <%= textilizable @issue, :description, :attachments => @issue.attachments %>
</div> </div>
</div>
<% end %> <% end %>
<%= link_to_attachments @issue %>
<% if @issue.attachments.any? -%>
<hr />
<%= link_to_attachments @issue %>
<% end -%> <% end -%>
<%= call_hook(:view_issues_show_description_bottom, :issue => @issue) %> <%= call_hook(:view_issues_show_description_bottom, :issue => @issue) %>
<%= render :partial => 'tree_simple' %> <% if !@issue.leaf? || User.current.allowed_to?(:manage_subtasks, @project) %>
<hr />
<div id="issue_tree">
<div class="contextual">
<%= link_to(l(:button_add), {:controller => 'issues', :action => 'new', :project_id => @project, :issue => {:parent_issue_id => @issue}}) if User.current.allowed_to?(:manage_subtasks, @project) %>
</div>
<p><strong><%=l(:label_subtask_plural)%></strong></p>
<%= render_descendants_tree(@issue) unless @issue.leaf? %>
</div>
<% end %>
<% if authorize_for('issue_relations', 'new') || @issue.relations.present? %> <% if authorize_for('issue_relations', 'new') || @issue.relations.present? %>
<hr /> <hr />
@ -87,7 +80,6 @@
</div> </div>
<% end %> <% end %>
<hr />
</div> </div>
<% if @changesets.present? %> <% if @changesets.present? %>
@ -99,19 +91,14 @@
<% if @journals.present? %> <% if @journals.present? %>
<div id="history"> <div id="history">
<h3 class="rounded-background"><%=l(:label_history)%></h3> <h3><%=l(:label_history)%></h3>
<%= render :partial => 'history', :locals => { :issue => @issue, :journals => @journals } %> <%= render :partial => 'history', :locals => { :issue => @issue, :journals => @journals } %>
</div> </div>
<% end %> <% end %>
<div style="clear: both;"></div> <div style="clear: both;"></div>
<%= render :partial => 'action_menu' %>
<div class="title-bar" id="lower-title-bar">
<div class="title-bar-actions">
<%= render :partial => 'action_menu', :locals => {:replace_watcher => 'watcher2' } %>
</div>
</div>
<div style="clear: both;"></div> <div style="clear: both;"></div>
<% if authorize_for('issues', 'edit') %> <% if authorize_for('issues', 'edit') %>
@ -142,8 +129,9 @@
<% content_for :header_tags do %> <% content_for :header_tags do %>
<%= auto_discovery_link_tag(:atom, {:format => 'atom', :key => User.current.rss_key}, :title => "#{@issue.project} - #{@issue.tracker} ##{@issue.id}: #{@issue.subject}") %> <%= auto_discovery_link_tag(:atom, {:format => 'atom', :key => User.current.rss_key}, :title => "#{@issue.project} - #{@issue.tracker} ##{@issue.id}: #{@issue.subject}") %>
<%= stylesheet_link_tag 'scm' %> <%= stylesheet_link_tag 'scm' %>
<%= javascript_include_tag 'context_menu.jquery' %> <%= javascript_include_tag 'context_menu' %>
<%= stylesheet_link_tag 'context_menu' %> <%= stylesheet_link_tag 'context_menu' %>
<%= stylesheet_link_tag 'context_menu_rtl' if l(:direction) == 'rtl' %> <%= stylesheet_link_tag 'context_menu_rtl' if l(:direction) == 'rtl' %>
<% end %> <% end %>
<%= javascript_tag "jQuery(document).ContextMenu('#{issues_context_menu_path}')" %> <div id="context-menu" style="display: none;"></div>
<%= javascript_tag "new ContextMenu('#{issues_context_menu_path}')" %>

View File

@ -1,9 +0,0 @@
<p><%= authoring @journal.created_at, @journal.user, :label => :label_updated_time_by %></p>
<div class="text-diff">
<%= simple_format_without_paragraph @diff.to_html %>
</div>
<p><%= link_to(l(:button_back), issue_path(@issue)) %></p>
<% html_title "#{@issue.tracker.name} ##{@issue.id}: #{@issue.subject}" %>

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