Compare commits

..

7 Commits
2.2.4 ... 2.1.0

Author SHA1 Message Date
Jean-Philippe Lang
2a4774627d tagged version 2.1.0
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/tags/2.1.0@10395 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-09-16 12:47:11 +00:00
Jean-Philippe Lang
5eab4af70d Merged r10391 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.1-stable@10392 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-09-16 12:43:43 +00:00
Toshi MARUYAMA
96b2e00015 Merged r10379 from trunk (#11836)
Japanese translation updated by Go MAEDA.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.1-stable@10380 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-09-15 12:02:36 +00:00
Jean-Philippe Lang
9939fbeef6 Merged r10375 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.1-stable@10376 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-09-12 19:16:31 +00:00
Jean-Philippe Lang
b369f82d15 Merged r10368 to r10373 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.1-stable@10374 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-09-11 19:40:59 +00:00
Jean-Philippe Lang
0ad5dfaba0 Set branch to stable.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.1-stable@10367 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-09-11 16:33:49 +00:00
Jean-Philippe Lang
d7d18689b7 Added 2.1-stable branch.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.1-stable@10366 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-09-11 16:30:57 +00:00
322 changed files with 5607 additions and 10859 deletions

2
.gitignore vendored
View File

@@ -19,11 +19,9 @@
/public/plugin_assets
/tmp/*
/tmp/cache/*
/tmp/pdf/*
/tmp/sessions/*
/tmp/sockets/*
/tmp/test/*
/tmp/thumbnails/*
/vendor/cache
/vendor/rails
*.rbc

View File

@@ -21,11 +21,9 @@ public/dispatch.*
public/plugin_assets
tmp/*
tmp/cache/*
tmp/pdf/*
tmp/sessions/*
tmp/sockets/*
tmp/test/*
tmp/thumbnails/*
vendor/cache
vendor/rails
*.rbc

13
Gemfile
View File

@@ -1,6 +1,6 @@
source 'http://rubygems.org'
gem "rails", "3.2.13"
gem 'rails', '3.2.8'
gem "jquery-rails", "~> 2.0.2"
gem "i18n", "~> 0.6.0"
gem "coderay", "~> 1.0.6"
@@ -41,7 +41,7 @@ end
platforms :mri_18, :mingw_18 do
group :mysql do
gem "mysql", "~> 2.8.1"
gem "mysql"
end
end
@@ -74,12 +74,9 @@ end
group :test do
gem "shoulda", "~> 2.11"
# Shoulda does not work nice on Ruby 1.9.3 and JRuby 1.7.
# It seems to need test-unit explicitely.
platforms = [:mri_19]
platforms << :jruby if defined?(JRUBY_VERSION) && JRUBY_VERSION >= "1.7"
gem "test-unit", :platforms => platforms
gem "mocha", "~> 0.13.3"
# Shoulda does not work nice on Ruby 1.9.3 and seems to need test-unit explicitely.
gem "test-unit", :platforms => [:mri_19]
gem "mocha", "0.12.3"
end
local_gemfile = File.join(File.dirname(__FILE__), "Gemfile.local")

View File

@@ -238,7 +238,7 @@ class AccountController < ApplicationController
def onthefly_creation_failed(user, auth_source_options = { })
@user = user
session[:auth_source_registration] = auth_source_options unless auth_source_options.empty?
render :action => 'register'
render register_path
end
def invalid_credentials

View File

@@ -22,7 +22,7 @@ class Unauthorized < Exception; end
class ApplicationController < ActionController::Base
include Redmine::I18n
class_attribute :accept_api_auth_actions
class_attribute :accept_rss_auth_actions
class_attribute :model_object
@@ -90,7 +90,7 @@ class ApplicationController < ActionController::Base
def find_current_user
user = nil
unless api_request?
if session[:user_id]
if session[:user_id]
# existing session
user = (User.active.find(session[:user_id]) rescue nil)
elsif autologin_user = try_to_autologin
@@ -110,16 +110,6 @@ class ApplicationController < ActionController::Base
user = User.try_to_login(username, password) || User.find_by_api_key(username)
end
end
# Switch user if requested by an admin user
if user && user.admin? && (username = api_switch_user_from_request)
su = User.find_by_login(username)
if su && su.active?
logger.info(" User switched by: #{user.login} (id=#{user.id})") if logger
user = su
else
render_error :message => 'Invalid X-Redmine-Switch-User header', :status => 412
end
end
end
user
end
@@ -276,24 +266,14 @@ class ApplicationController < ActionController::Base
self.model_object = model
end
# Find the issue whose id is the :id parameter
# Raises a Unauthorized exception if the issue is not visible
def find_issue
# Issue.visible.find(...) can not be used to redirect user to the login form
# if the issue actually exists but requires authentication
@issue = Issue.find(params[:id])
raise Unauthorized unless @issue.visible?
@project = @issue.project
rescue ActiveRecord::RecordNotFound
render_404
end
# Find issues with a single :id param or :ids array param
# Raises a Unauthorized exception if one of the issues is not visible
# Filter for bulk issue operations
def find_issues
@issues = Issue.find_all_by_id(params[:id] || params[:ids])
raise ActiveRecord::RecordNotFound if @issues.empty?
raise Unauthorized unless @issues.all?(&:visible?)
if @issues.detect {|issue| !issue.visible?}
deny_access
return
end
@projects = @issues.collect(&:project).compact.uniq
@project = @projects.first if @projects.size == 1
rescue ActiveRecord::RecordNotFound
@@ -422,7 +402,7 @@ class ApplicationController < ActionController::Base
@items.sort! {|x,y| y.event_datetime <=> x.event_datetime }
@items = @items.slice(0, Setting.feeds_limit.to_i)
@title = options[:title] || Setting.app_title
render :template => "common/feed", :formats => [:atom], :layout => false,
render :template => "common/feed.atom", :layout => false,
:content_type => 'application/atom+xml'
end
@@ -528,11 +508,6 @@ class ApplicationController < ActionController::Base
end
end
# Returns the API 'switch user' value if present
def api_switch_user_from_request
request.headers["X-Redmine-Switch-User"].to_s.presence
end
# Renders a warning flash if obj has unsaved attachments
def render_attachment_warning_if_needed(obj)
flash[:warning] = l(:warning_attachments_not_saved, obj.unsaved_attachments.size) if obj.unsaved_attachments.present?
@@ -563,13 +538,8 @@ class ApplicationController < ActionController::Base
# Renders a 200 response for successfull updates or deletions via the API
def render_api_ok
render_api_head :ok
end
# Renders a head API response
def render_api_head(status)
# #head would return a response body with one space
render :text => '', :status => status, :layout => nil
# head :ok would return a response body with one space
render :text => '', :status => :ok, :layout => nil
end
# Renders API response on validation failure

View File

@@ -84,7 +84,7 @@ class AttachmentsController < ApplicationController
@attachment = Attachment.new(:file => request.raw_post)
@attachment.author = User.current
@attachment.filename = params[:filename].presence || Redmine::Utils.random_hex(16)
@attachment.filename = Redmine::Utils.random_hex(16)
if @attachment.save
respond_to do |format|

View File

@@ -39,18 +39,14 @@ class BoardsController < ApplicationController
sort_init 'updated_on', 'desc'
sort_update 'created_on' => "#{Message.table_name}.created_on",
'replies' => "#{Message.table_name}.replies_count",
'updated_on' => "COALESCE(last_replies_messages.created_on, #{Message.table_name}.created_on)"
'updated_on' => "#{Message.table_name}.updated_on"
@topic_count = @board.topics.count
@topic_pages = Paginator.new self, @topic_count, per_page_option, params['page']
@topics = @board.topics.
reorder("#{Message.table_name}.sticky DESC").
includes(:last_reply).
limit(@topic_pages.items_per_page).
offset(@topic_pages.current.offset).
order(sort_clause).
preload(:author, {:last_reply => :author}).
all
@topics = @board.topics.reorder("#{Message.table_name}.sticky DESC").order(sort_clause).all(
:include => [:author, {:last_reply => :author}],
:limit => @topic_pages.items_per_page,
:offset => @topic_pages.current.offset)
@message = Message.new(:board => @board)
render :action => 'show', :layout => !request.xhr?
}

View File

@@ -18,26 +18,13 @@
class EnumerationsController < ApplicationController
layout 'admin'
before_filter :require_admin, :except => :index
before_filter :require_admin_or_api_request, :only => :index
before_filter :require_admin
before_filter :build_new_enumeration, :only => [:new, :create]
before_filter :find_enumeration, :only => [:edit, :update, :destroy]
accept_api_auth :index
helper :custom_fields
def index
respond_to do |format|
format.html
format.api {
@klass = Enumeration.get_subclass(params[:type])
if @klass
@enumerations = @klass.shared.sorted.all
else
render_404
end
}
end
end
def new
@@ -46,7 +33,7 @@ class EnumerationsController < ApplicationController
def create
if request.post? && @enumeration.save
flash[:notice] = l(:notice_successful_create)
redirect_to :action => 'index'
redirect_to :action => 'index', :type => @enumeration.type
else
render :action => 'new'
end
@@ -58,7 +45,7 @@ class EnumerationsController < ApplicationController
def update
if request.put? && @enumeration.update_attributes(params[:enumeration])
flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'index'
redirect_to :action => 'index', :type => @enumeration.type
else
render :action => 'edit'
end

View File

@@ -23,7 +23,7 @@ class IssueCategoriesController < ApplicationController
before_filter :find_project_by_project_id, :only => [:index, :new, :create]
before_filter :authorize
accept_api_auth :index, :show, :create, :update, :destroy
def index
respond_to do |format|
format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'categories', :id => @project }
@@ -92,7 +92,7 @@ class IssueCategoriesController < ApplicationController
def destroy
@issue_count = @category.issues.size
if @issue_count == 0 || params[:todo] || api_request?
if @issue_count == 0 || params[:todo] || api_request?
reassign_to = nil
if params[:reassign_to_id] && (params[:todo] == 'reassign' || params[:todo].blank?)
reassign_to = @project.issue_categories.find_by_id(params[:reassign_to_id])

View File

@@ -56,7 +56,6 @@ class IssuesController < ApplicationController
retrieve_query
sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
sort_update(@query.sortable_columns)
@query.sort_criteria = sort_criteria.to_a
if @query.valid?
case params[:format]
@@ -82,7 +81,7 @@ class IssuesController < ApplicationController
respond_to do |format|
format.html { render :template => 'issues/index', :layout => !request.xhr? }
format.api {
Issue.load_visible_relations(@issues) if include_in_api_response?('relations')
Issue.load_relations(@issues) if include_in_api_response?('relations')
}
format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
format.csv { send_data(issues_to_csv(@issues, @project, @query, params), :type => 'text/csv; header=present', :filename => 'export.csv') }
@@ -100,9 +99,8 @@ class IssuesController < ApplicationController
end
def show
@journals = @issue.journals.includes(:user, :details).reorder("#{Journal.table_name}.id ASC").all
@journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
@journals.each_with_index {|j,i| j.indice = i+1}
@journals.reject!(&:private_notes?) unless User.current.allowed_to?(:view_private_notes, @issue.project)
@journals.reverse! if User.current.wants_comments_in_reverse_order?
@changesets = @issue.changesets.visible.all
@@ -120,10 +118,7 @@ class IssuesController < ApplicationController
}
format.api
format.atom { render :template => 'journals/index', :layout => false, :content_type => 'application/atom+xml' }
format.pdf {
pdf = issue_to_pdf(@issue, :journals => @journals)
send_data(pdf, :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf")
}
format.pdf { send_data(issue_to_pdf(@issue), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") }
end
end
@@ -178,7 +173,6 @@ class IssuesController < ApplicationController
@conflict = true
if params[:last_journal_id]
@conflict_journals = @issue.journals_after(params[:last_journal_id]).all
@conflict_journals.reject!(&:private_notes?) unless User.current.allowed_to?(:view_private_notes, @issue.project)
end
end
@@ -313,7 +307,19 @@ class IssuesController < ApplicationController
end
end
private
private
def find_issue
# Issue.visible.find(...) can not be used to redirect user to the login form
# if the issue actually exists but requires authentication
@issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
unless @issue.visible?
deny_access
return
end
@project = @issue.project
rescue ActiveRecord::RecordNotFound
render_404
end
def find_project
project_id = params[:project_id] || (params[:issue] && params[:issue][:project_id])
@@ -348,7 +354,8 @@ class IssuesController < ApplicationController
@time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
@time_entry.attributes = params[:time_entry]
@issue.init_journal(User.current)
@notes = params[:notes] || (params[:issue].present? ? params[:issue][:notes] : nil)
@issue.init_journal(User.current, @notes)
issue_attributes = params[:issue]
if issue_attributes && params[:conflict_resolution]
@@ -357,7 +364,7 @@ class IssuesController < ApplicationController
issue_attributes = issue_attributes.dup
issue_attributes.delete(:lock_version)
when 'add_notes'
issue_attributes = issue_attributes.slice(:notes)
issue_attributes = {}
when 'cancel'
redirect_to issue_path(@issue)
return false
@@ -391,7 +398,7 @@ class IssuesController < ApplicationController
end
@issue.project = @project
@issue.author ||= User.current
@issue.author = User.current
# Tracker must be set before custom field values
@issue.tracker ||= @project.trackers.find((params[:issue] && params[:issue][:tracker_id]) || params[:tracker_id] || :first)
if @issue.tracker.nil?

View File

@@ -57,10 +57,10 @@ class JournalsController < ApplicationController
end
def new
@journal = Journal.visible.find(params[:journal_id]) if params[:journal_id]
if @journal
user = @journal.user
text = @journal.notes
journal = Journal.find(params[:journal_id]) if params[:journal_id]
if journal
user = journal.user
text = journal.notes
else
user = @issue.author
text = @issue.description
@@ -69,8 +69,6 @@ class JournalsController < ApplicationController
text = text.to_s.strip.gsub(%r{<pre>((.|\s)*?)</pre>}m, '[...]')
@content = "#{ll(Setting.default_language, :text_user_wrote, user)}\n> "
@content << text.gsub(/(\r?\n|\r\n?)/, "\n> ") + "\n\n"
rescue ActiveRecord::RecordNotFound
render_404
end
def edit
@@ -97,9 +95,17 @@ class JournalsController < ApplicationController
private
def find_journal
@journal = Journal.visible.find(params[:id])
@journal = Journal.find(params[:id])
@project = @journal.journalized.project
rescue ActiveRecord::RecordNotFound
render_404
end
# TODO: duplicated in IssuesController
def find_issue
@issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
@project = @issue.project
rescue ActiveRecord::RecordNotFound
render_404
end
end

View File

@@ -123,7 +123,7 @@ class MessagesController < ApplicationController
private
def find_message
return unless find_board
find_board
@message = @board.messages.find(params[:id], :include => :parent)
@topic = @message.root
rescue ActiveRecord::RecordNotFound
@@ -135,6 +135,5 @@ private
@project = @board.project
rescue ActiveRecord::RecordNotFound
render_404
nil
end
end

View File

@@ -147,16 +147,15 @@ class MyController < ApplicationController
# params[:block] : id of the block to add
def add_block
block = params[:block].to_s.underscore
if block.present? && BLOCKS.key?(block)
@user = User.current
layout = @user.pref[:my_page_layout] || {}
# remove if already present in a group
%w(top left right).each {|f| (layout[f] ||= []).delete block }
# add it on top
layout['top'].unshift block
@user.pref[:my_page_layout] = layout
@user.pref.save
end
(render :nothing => true; return) unless block && (BLOCKS.keys.include? block)
@user = User.current
layout = @user.pref[:my_page_layout] || {}
# remove if already present in a group
%w(top left right).each {|f| (layout[f] ||= []).delete block }
# add it on top
layout['top'].unshift block
@user.pref[:my_page_layout] = layout
@user.pref.save
redirect_to :action => 'page_layout'
end

View File

@@ -26,8 +26,7 @@ class PreviewsController < ApplicationController
if @description && @description.gsub(/(\r?\n|\n\r?)/, "\n") == @issue.description.to_s.gsub(/(\r?\n|\n\r?)/, "\n")
@description = nil
end
# params[:notes] is useful for preview of notes in issue history
@notes = params[:notes] || (params[:issue] ? params[:issue][:notes] : nil)
@notes = params[:notes]
else
@description = (params[:issue] ? params[:issue][:description] : nil)
end
@@ -35,10 +34,6 @@ class PreviewsController < ApplicationController
end
def news
if params[:id].present? && news = News.visible.find_by_id(params[:id])
@previewed = news
@attachments = news.attachments
end
@text = (params[:news] ? params[:news][:description] : nil)
render :partial => 'common/preview'
end

View File

@@ -116,7 +116,11 @@ class ProjectsController < ApplicationController
@source_project = Project.find(params[:id])
if request.get?
@project = Project.copy_from(@source_project)
@project.identifier = Project.next_identifier if Setting.sequential_project_identifiers?
if @project
@project.identifier = Project.next_identifier if Setting.sequential_project_identifiers?
else
redirect_to :controller => 'admin', :action => 'projects'
end
else
Mailer.with_deliveries(params[:notifications] == '1') do
@project = Project.new
@@ -135,8 +139,7 @@ class ProjectsController < ApplicationController
end
end
rescue ActiveRecord::RecordNotFound
# source_project not found
render_404
redirect_to :controller => 'admin', :action => 'projects'
end
# Show @project

View File

@@ -242,7 +242,7 @@ class RepositoriesController < ApplicationController
# DELETE /projects/:project_id/repository/(:repository_id/)revisions/:rev/issues/:issue_id
def remove_related_issue
@issue = Issue.visible.find_by_id(params[:issue_id])
if @issue
if @issue
@changeset.issues.delete(@issue)
end
end

View File

@@ -18,10 +18,10 @@
class RolesController < ApplicationController
layout 'admin'
before_filter :require_admin, :except => [:index, :show]
before_filter :require_admin_or_api_request, :only => [:index, :show]
before_filter :find_role, :only => [:show, :edit, :update, :destroy]
accept_api_auth :index, :show
before_filter :require_admin, :except => :index
before_filter :require_admin_or_api_request, :only => :index
before_filter :find_role, :only => [:edit, :update, :destroy]
accept_api_auth :index
def index
respond_to do |format|
@@ -35,12 +35,6 @@ class RolesController < ApplicationController
end
end
def show
respond_to do |format|
format.api
end
end
def new
# Prefills the form with 'Non member' role permissions by default
@role = Role.new(params[:role] || {:permissions => Role.non_member.permissions})

View File

@@ -39,8 +39,7 @@ class SettingsController < ApplicationController
redirect_to :action => 'edit', :tab => params[:tab]
else
@options = {}
user_format = User::USER_FORMATS.collect{|key, value| [key, value[:setting_order]]}.sort{|a, b| a[1] <=> b[1]}
@options[:user_format] = user_format.collect{|f| [User.current.name(f[0]), f[0].to_s]}
@options[:user_format] = User::USER_FORMATS.keys.collect {|f| [User.current.name(f), f.to_s] }
@deliveries = ActionMailer::Base.perform_deliveries
@guessed_host_and_path = request.host_with_port.dup

View File

@@ -138,7 +138,7 @@ class TimelogController < ApplicationController
:time_entry => {:issue_id => @time_entry.issue_id, :activity_id => @time_entry.activity_id},
:back_url => params[:back_url]
else
redirect_to :action => 'new',
redirect_to :action => 'new',
:time_entry => {:project_id => @time_entry.project_id, :issue_id => @time_entry.issue_id, :activity_id => @time_entry.activity_id},
:back_url => params[:back_url]
end
@@ -308,9 +308,6 @@ private
when 'last_week'
@from = Date.today - 7 - (Date.today.cwday - 1)%7
@to = @from + 6
when 'last_2_weeks'
@from = Date.today - 14 - (Date.today.cwday - 1)%7
@to = @from + 13
when '7_days'
@from = Date.today - 7
@to = Date.today

View File

@@ -59,7 +59,7 @@ class TrackersController < ApplicationController
@tracker ||= Tracker.find(params[:id])
@projects = Project.find(:all)
end
def update
@tracker = Tracker.find(params[:id])
if request.put? and @tracker.update_attributes(params[:tracker])

View File

@@ -20,7 +20,7 @@ class VersionsController < ApplicationController
model_object Version
before_filter :find_model_object, :except => [:index, :new, :create, :close_completed]
before_filter :find_project_from_association, :except => [:index, :new, :create, :close_completed]
before_filter :find_project_by_project_id, :only => [:index, :new, :create, :close_completed]
before_filter :find_project, :only => [:index, :new, :create, :close_completed]
before_filter :authorize
accept_api_auth :index, :show, :create, :update, :destroy
@@ -169,7 +169,12 @@ class VersionsController < ApplicationController
end
end
private
private
def find_project
@project = Project.find(params[:project_id])
rescue ActiveRecord::RecordNotFound
render_404
end
def retrieve_selected_tracker_ids(selectable_trackers, default_trackers=nil)
if ids = params[:tracker_ids]
@@ -178,4 +183,5 @@ class VersionsController < ApplicationController
@selected_tracker_ids = (default_trackers || selectable_trackers).collect {|t| t.id.to_s }
end
end
end

View File

@@ -35,8 +35,7 @@ class WikiController < ApplicationController
default_search_scope :wiki_pages
before_filter :find_wiki, :authorize
before_filter :find_existing_or_new_page, :only => [:show, :edit, :update]
before_filter :find_existing_page, :only => [:rename, :protect, :history, :diff, :annotate, :add_attachment, :destroy, :destroy_version]
accept_api_auth :index, :show, :update, :destroy
before_filter :find_existing_page, :only => [:rename, :protect, :history, :diff, :annotate, :add_attachment, :destroy]
helper :attachments
include AttachmentsHelper
@@ -46,13 +45,7 @@ class WikiController < ApplicationController
# List of pages, sorted alphabetically and by parent (hierarchy)
def index
load_pages_for_index
respond_to do |format|
format.html {
@pages_by_parent_id = @pages.group_by(&:parent_id)
}
format.api
end
@pages_by_parent_id = @pages.group_by(&:parent_id)
end
# List of page, by last update
@@ -64,7 +57,7 @@ class WikiController < ApplicationController
# display a page (in editing mode if it doesn't exist)
def show
if @page.new_record?
if User.current.allowed_to?(:edit_wiki_pages, @project) && editable? && !api_request?
if User.current.allowed_to?(:edit_wiki_pages, @project) && editable?
edit
render :action => 'edit'
else
@@ -73,7 +66,8 @@ class WikiController < ApplicationController
return
end
if params[:version] && !User.current.allowed_to?(:view_wiki_edits, @project)
deny_access
# Redirects user to the current version if he's not allowed to view previous versions
redirect_to :version => nil
return
end
@content = @page.content_for_version(params[:version])
@@ -95,10 +89,7 @@ class WikiController < ApplicationController
@content.current_version? &&
Redmine::WikiFormatting.supports_section_edit?
respond_to do |format|
format.html
format.api
end
render :action => 'show'
end
# edit an existing page or a new one
@@ -130,65 +121,50 @@ class WikiController < ApplicationController
# Creates a new page or updates an existing one
def update
return render_403 unless editable?
was_new_page = @page.new_record?
@page.content = WikiContent.new(:page => @page) if @page.new_record?
@page.safe_attributes = params[:wiki_page]
@content = @page.content
content_params = params[:content]
if content_params.nil? && params[:wiki_page].is_a?(Hash)
content_params = params[:wiki_page].slice(:text, :comments, :version)
end
content_params ||= {}
@content = @page.content_for_version(params[:version])
@content.text = initial_page_content(@page) if @content.text.blank?
# don't keep previous comment
@content.comments = nil
@content.comments = content_params[:comments]
@text = content_params[:text]
if !@page.new_record? && params[:content].present? && @content.text == params[:content][:text]
attachments = Attachment.attach_files(@page, params[:attachments])
render_attachment_warning_if_needed(@page)
# don't save content if text wasn't changed
@page.save
redirect_to :action => 'show', :project_id => @project, :id => @page.title
return
end
@content.comments = params[:content][:comments]
@text = params[:content][:text]
if params[:section].present? && Redmine::WikiFormatting.supports_section_edit?
@section = params[:section].to_i
@section_hash = params[:section_hash]
@content.text = Redmine::WikiFormatting.formatter.new(@content.text).update_section(params[:section].to_i, @text, @section_hash)
else
@content.version = content_params[:version] if content_params[:version]
@content.version = params[:content][:version]
@content.text = @text
end
@content.author = User.current
if @page.save_with_content
@page.content = @content
if @page.save
attachments = Attachment.attach_files(@page, params[:attachments])
render_attachment_warning_if_needed(@page)
call_hook(:controller_wiki_edit_after_save, { :params => params, :page => @page})
respond_to do |format|
format.html { redirect_to :action => 'show', :project_id => @project, :id => @page.title }
format.api {
if was_new_page
render :action => 'show', :status => :created, :location => url_for(:controller => 'wiki', :action => 'show', :project_id => @project, :id => @page.title)
else
render_api_ok
end
}
end
redirect_to :action => 'show', :project_id => @project, :id => @page.title
else
respond_to do |format|
format.html { render :action => 'edit' }
format.api { render_validation_errors(@content) }
end
render :action => 'edit'
end
rescue ActiveRecord::StaleObjectError, Redmine::WikiFormatting::StaleSectionError
# Optimistic locking exception
respond_to do |format|
format.html {
flash.now[:error] = l(:notice_locking_conflict)
render :action => 'edit'
}
format.api { render_api_head :conflict }
end
flash.now[:error] = l(:notice_locking_conflict)
render :action => 'edit'
rescue ActiveRecord::RecordNotSaved
respond_to do |format|
format.html { render :action => 'edit' }
format.api { render_validation_errors(@content) }
end
render :action => 'edit'
end
# rename a page
@@ -211,7 +187,7 @@ class WikiController < ApplicationController
# show page history
def history
@version_count = @page.content.versions.count
@version_pages = Paginator.new self, @version_count, per_page_option, params['page']
@version_pages = Paginator.new self, @version_count, per_page_option, params['p']
# don't load text
@versions = @page.content.versions.find :all,
:select => "id, author_id, comments, updated_on, version",
@@ -254,28 +230,16 @@ class WikiController < ApplicationController
end
else
@reassignable_to = @wiki.pages - @page.self_and_descendants
# display the destroy form if it's a user request
return unless api_request?
return
end
end
@page.destroy
respond_to do |format|
format.html { redirect_to :action => 'index', :project_id => @project }
format.api { render_api_ok }
end
end
def destroy_version
return render_403 unless editable?
@content = @page.content_for_version(params[:version])
@content.destroy
redirect_to_referer_or :action => 'history', :id => @page.title, :project_id => @project
redirect_to :action => 'index', :project_id => @project
end
# Export wiki to a single pdf or html file
def export
@pages = @wiki.pages.all(:order => 'title', :include => [:content, {:attachments => :author}])
@pages = @wiki.pages.all(:order => 'title', :include => [:content, :attachments], :limit => 75)
respond_to do |format|
format.html {
export = render_to_string :action => 'export_multiple', :layout => false
@@ -349,6 +313,6 @@ private
end
def load_pages_for_index
@pages = @wiki.pages.with_updated_on.order("#{WikiPage.table_name}.title").includes(:wiki => :project).includes(:parent).all
@pages = @wiki.pages.with_updated_on.all(:order => 'title', :include => {:wiki => :project})
end
end

View File

@@ -47,8 +47,8 @@ module ApplicationHelper
def link_to_user(user, options={})
if user.is_a?(User)
name = h(user.name(options[:format]))
if user.active? || (User.current.admin? && user.logged?)
link_to name, user_path(user), :class => user.css_classes
if user.active?
link_to name, :controller => 'users', :action => 'show', :id => user
else
name
end
@@ -64,12 +64,10 @@ module ApplicationHelper
# link_to_issue(issue, :truncate => 6) # => Defect #6: This i...
# link_to_issue(issue, :subject => false) # => Defect #6
# link_to_issue(issue, :project => true) # => Foo - Defect #6
# link_to_issue(issue, :subject => false, :tracker => false) # => #6
#
def link_to_issue(issue, options={})
title = nil
subject = nil
text = options[:tracker] == false ? "##{issue.id}" : "#{issue.tracker} ##{issue.id}"
if options[:subject] == false
title = truncate(issue.subject, :length => 60)
else
@@ -78,7 +76,9 @@ module ApplicationHelper
subject = truncate(subject, :length => options[:truncate])
end
end
s = link_to text, issue_path(issue), :class => issue.css_classes, :title => title
s = link_to "#{h(issue.tracker)} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue},
:class => issue.css_classes,
:title => title
s << h(": #{subject}") if subject
s = h("#{issue.project} - ") + s if options[:project]
s
@@ -147,10 +147,6 @@ module ApplicationHelper
end
end
def wiki_page_path(page, options={})
url_for({:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title}.merge(options))
end
def thumbnail_tag(attachment)
link_to image_tag(url_for(:controller => 'attachments', :action => 'thumbnail', :id => attachment)),
{:controller => 'attachments', :action => 'show', :id => attachment, :filename => attachment.filename},
@@ -238,7 +234,7 @@ module ApplicationHelper
content << "<ul class=\"pages-hierarchy\">\n"
pages[node].each do |page|
content << "<li>"
content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title, :version => nil},
content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title},
:title => (options[:timestamp] && page.updated_on ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
content << "\n" + render_page_hierarchy(pages, page.id, options) if pages[page.id]
content << "</li>\n"
@@ -331,15 +327,6 @@ module ApplicationHelper
s.html_safe
end
# Options for the new membership projects combo-box
def options_for_membership_project_select(principal, projects)
options = content_tag('option', "--- #{l(:actionview_instancetag_blank_option)} ---")
options << project_tree_options_for_select(projects) do |p|
{:disabled => principal.projects.include?(p)}
end
options
end
# Truncates and returns the string as a single line
def truncate_single_line(string, *args)
truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
@@ -597,9 +584,8 @@ module ApplicationHelper
def parse_inline_attachments(text, project, obj, attr, only_path, options)
# when using an image link, try to use an attachment, if possible
attachments = options[:attachments] || []
attachments += obj.attachments if obj.respond_to?(:attachments)
if attachments.present?
if options[:attachments] || (obj && obj.respond_to?(:attachments))
attachments = options[:attachments] || obj.attachments
text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpe|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
filename, ext, alt, alttext = $1.downcase, $2, $3, $4
# search for the picture in attachments
@@ -658,7 +644,7 @@ module ApplicationHelper
wiki_page_id = page.present? ? Wiki.titleize(page) : nil
parent = wiki_page.nil? && obj.is_a?(WikiContent) && obj.page && project == link_project ? obj.page.title : nil
url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project,
:id => wiki_page_id, :version => nil, :anchor => anchor, :parent => parent)
:id => wiki_page_id, :anchor => anchor, :parent => parent)
end
end
link_to(title.present? ? title.html_safe : h(page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
@@ -704,11 +690,10 @@ module ApplicationHelper
# identifier:document:"Some document"
# identifier:version:1.0.0
# identifier:source:some/file
def parse_redmine_links(text, default_project, obj, attr, only_path, options)
text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-_]+):)?(attachment|document|version|forum|news|message|project|commit|source|export)?(((#)|((([a-z0-9\-_]+)\|)?(r)))((\d+)((#note)?-(\d+))?)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]][^A-Za-z0-9_/])|,|\s|\]|<|$)}) do |m|
def parse_redmine_links(text, project, obj, attr, only_path, options)
text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-_]+):)?(attachment|document|version|forum|news|message|project|commit|source|export)?(((#)|((([a-z0-9\-]+)\|)?(r)))((\d+)((#note)?-(\d+))?)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]][^A-Za-z0-9_/])|,|\s|\]|<|$)}) do |m|
leading, esc, project_prefix, project_identifier, prefix, repo_prefix, repo_identifier, sep, identifier, comment_suffix, comment_id = $1, $2, $3, $4, $5, $10, $11, $8 || $12 || $18, $14 || $19, $15, $17
link = nil
project = default_project
if project_identifier
project = Project.visible.find_by_identifier(project_identifier)
end
@@ -794,7 +779,7 @@ module ApplicationHelper
when 'commit', 'source', 'export'
if project
repository = nil
if name =~ %r{^(([a-z0-9\-_]+)\|)(.+)$}
if name =~ %r{^(([a-z0-9\-]+)\|)(.+)$}
repo_prefix, repo_identifier, name = $1, $2, $3
repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
else
@@ -810,10 +795,11 @@ module ApplicationHelper
if repository && User.current.allowed_to?(:browse_repository, project)
name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
path, rev, anchor = $1, $3, $5
link = link_to h("#{project_prefix}#{prefix}:#{repo_prefix}#{name}"), {:controller => 'repositories', :action => (prefix == 'export' ? 'raw' : 'entry'), :id => project, :repository_id => repository.identifier_param,
link = link_to h("#{project_prefix}#{prefix}:#{repo_prefix}#{name}"), {:controller => 'repositories', :action => 'entry', :id => project, :repository_id => repository.identifier_param,
:path => to_path_param(path),
:rev => rev,
:anchor => anchor},
:anchor => anchor,
:format => (prefix == 'export' ? 'raw' : nil)},
:class => (prefix == 'export' ? 'source download' : 'source')
end
end
@@ -821,7 +807,7 @@ module ApplicationHelper
end
when 'attachment'
attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
if attachments && attachment = Attachment.latest_attach(attachments, name)
if attachments && attachment = attachments.detect {|a| a.filename == name }
link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
:class => 'attachment'
end
@@ -973,7 +959,8 @@ module ApplicationHelper
end
def lang_options_for_select(blank=true)
(blank ? [["(auto)", ""]] : []) + languages_options
(blank ? [["(auto)", ""]] : []) +
valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
end
def label_tag_for(name, option_tags = nil, options = {})
@@ -1042,11 +1029,6 @@ module ApplicationHelper
content_tag(:a, name, {:href => '#', :onclick => "#{function}; return false;"}.merge(html_options))
end
# Helper to render JSON in views
def raw_json(arg)
arg.to_json.to_s.gsub('/', '\/').html_safe
end
def back_url
url = params[:back_url]
if url.nil? && referer = request.env['HTTP_REFERER']

View File

@@ -26,8 +26,8 @@ module ContextMenusHelper
end
if options.delete(:disabled)
options.delete(:method)
options.delete(:data)
options[:onclick] = 'return false;'
options.delete(:confirm)
options.delete(:onclick)
options[:class] << ' disabled'
url = '#'
end

View File

@@ -20,7 +20,16 @@
module CustomFieldsHelper
def custom_fields_tabs
CustomField::CUSTOM_FIELDS_TABS
tabs = [{:name => 'IssueCustomField', :partial => 'custom_fields/index', :label => :label_issue_plural},
{:name => 'TimeEntryCustomField', :partial => 'custom_fields/index', :label => :label_spent_time},
{:name => 'ProjectCustomField', :partial => 'custom_fields/index', :label => :label_project_plural},
{:name => 'VersionCustomField', :partial => 'custom_fields/index', :label => :label_version_plural},
{:name => 'UserCustomField', :partial => 'custom_fields/index', :label => :label_user_plural},
{:name => 'GroupCustomField', :partial => 'custom_fields/index', :label => :label_group_plural},
{:name => 'TimeEntryActivityCustomField', :partial => 'custom_fields/index', :label => TimeEntryActivity::OptionName},
{:name => 'IssuePriorityCustomField', :partial => 'custom_fields/index', :label => IssuePriority::OptionName},
{:name => 'DocumentCategoryCustomField', :partial => 'custom_fields/index', :label => DocumentCategory::OptionName}
]
end
# Return custom field html tag corresponding to its format

View File

@@ -18,6 +18,15 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module GroupsHelper
# Options for the new membership projects combo-box
def options_for_membership_project_select(user, projects)
options = content_tag('option', "--- #{l(:actionview_instancetag_blank_option)} ---")
options << project_tree_options_for_select(projects) do |p|
{:disabled => (user.projects.include?(p))}
end
options
end
def group_settings_tabs
tabs = [{:name => 'general', :partial => 'groups/general', :label => :label_general},
{:name => 'users', :partial => 'groups/users', :label => :label_user_plural},

View File

@@ -65,7 +65,7 @@ module IssuesHelper
s = ''
ancestors = issue.root? ? [] : issue.ancestors.visible.all
ancestors.each do |ancestor|
s << '<div>' + content_tag('p', link_to_issue(ancestor, :project => (issue.project_id != ancestor.project_id)))
s << '<div>' + content_tag('p', link_to_issue(ancestor))
end
s << '<div>'
subject = h(issue.subject)
@@ -80,29 +80,18 @@ module IssuesHelper
def render_descendants_tree(issue)
s = '<form><table class="list issues">'
issue_list(issue.descendants.visible.sort_by(&:lft)) do |child, level|
css = "issue issue-#{child.id} hascontextmenu"
css << " idnt idnt-#{level}" if level > 0
s << content_tag('tr',
content_tag('td', check_box_tag("ids[]", child.id, false, :id => nil), :class => 'checkbox') +
content_tag('td', link_to_issue(child, :truncate => 60, :project => (issue.project_id != child.project_id)), :class => 'subject') +
content_tag('td', link_to_issue(child, :truncate => 60), :class => 'subject') +
content_tag('td', h(child.status)) +
content_tag('td', link_to_user(child.assigned_to)) +
content_tag('td', progress_bar(child.done_ratio, :width => '80px')),
:class => css)
:class => "issue issue-#{child.id} hascontextmenu #{level > 0 ? "idnt idnt-#{level}" : nil}")
end
s << '</table></form>'
s.html_safe
end
# Returns a link for adding a new subtask to the given issue
def link_to_new_subtask(issue)
attrs = {
:tracker_id => issue.tracker,
:parent_issue_id => issue
}
link_to(l(:button_add), new_project_issue_path(issue.project, :issue => attrs))
end
class IssueFieldsRows
include ActionView::Helpers::TagHelper
@@ -350,10 +339,7 @@ module IssuesHelper
association = Issue.reflect_on_association(field.to_sym)
if association
record = association.class_name.constantize.find_by_id(id)
if record
record.name.force_encoding('UTF-8') if record.name.respond_to?(:force_encoding)
return record.name
end
return record.name if record
end
end
@@ -374,16 +360,12 @@ module IssuesHelper
def issues_to_csv(issues, project, query, options={})
decimal_separator = l(:general_csv_decimal_separator)
encoding = l(:general_csv_encoding)
columns = (options[:columns] == 'all' ? query.available_inline_columns : query.inline_columns)
if options[:description]
if description = query.available_columns.detect {|q| q.name == :description}
columns << description
end
end
columns = (options[:columns] == 'all' ? query.available_columns : query.columns)
export = FCSV.generate(:col_sep => l(:general_csv_separator)) do |csv|
# csv header fields
csv << [ "#" ] + columns.collect {|c| Redmine::CodesetUtil.from_utf8(c.caption.to_s, encoding) }
csv << [ "#" ] + columns.collect {|c| Redmine::CodesetUtil.from_utf8(c.caption.to_s, encoding) } +
(options[:description] ? [Redmine::CodesetUtil.from_utf8(l(:field_description), encoding)] : [])
# csv lines
issues.each do |issue|
@@ -405,7 +387,8 @@ module IssuesHelper
end
s.to_s
end
csv << [ issue.id.to_s ] + col_values.collect {|c| Redmine::CodesetUtil.from_utf8(c.to_s, encoding) }
csv << [ issue.id.to_s ] + col_values.collect {|c| Redmine::CodesetUtil.from_utf8(c.to_s, encoding) } +
(options[:description] ? [Redmine::CodesetUtil.from_utf8(issue.description, encoding)] : [])
end
end
export

View File

@@ -19,43 +19,11 @@
module QueriesHelper
def filters_options_for_select(query)
options_for_select(filters_options(query))
end
def filters_options(query)
options = [[]]
sorted_options = query.available_filters.sort do |a, b|
ord = 0
if !(a[1][:order] == 20 && b[1][:order] == 20)
ord = a[1][:order] <=> b[1][:order]
else
cn = (CustomField::CUSTOM_FIELDS_NAMES.index(a[1][:field].class.name) <=>
CustomField::CUSTOM_FIELDS_NAMES.index(b[1][:field].class.name))
if cn != 0
ord = cn
else
f = (a[1][:field] <=> b[1][:field])
if f != 0
ord = f
else
# assigned_to or author
ord = (a[0] <=> b[0])
end
end
end
ord
end
options += sorted_options.map do |field, field_options|
options += query.available_filters.sort {|a,b| a[1][:order] <=> b[1][:order]}.map do |field, field_options|
[field_options[:name], field]
end
end
def available_block_columns_tags(query)
tags = ''.html_safe
query.available_block_columns.each do |column|
tags << content_tag('label', check_box_tag('c[]', column.name.to_s, query.has_column?(column)) + " #{column.caption}", :class => 'inline')
end
tags
options_for_select(options)
end
def column_header(column)
@@ -67,7 +35,7 @@ module QueriesHelper
def column_content(column, issue)
value = column.value(issue)
if value.is_a?(Array)
value.collect {|v| column_value(column, issue, v)}.compact.join(', ').html_safe
value.collect {|v| column_value(column, issue, v)}.compact.sort.join(', ').html_safe
else
column_value(column, issue, value)
end
@@ -78,8 +46,6 @@ module QueriesHelper
when 'String'
if column.name == :subject
link_to(h(value), :controller => 'issues', :action => 'show', :id => issue)
elsif column.name == :description
issue.description? ? content_tag('div', textilizable(issue, :description), :class => "wiki") : ''
else
h(value)
end
@@ -87,14 +53,14 @@ module QueriesHelper
format_time(value)
when 'Date'
format_date(value)
when 'Fixnum'
when 'Fixnum', 'Float'
if column.name == :done_ratio
progress_bar(value, :width => '80px')
elsif column.name == :spent_hours
sprintf "%.2f", value
else
value.to_s
h(value.to_s)
end
when 'Float'
sprintf "%.2f", value
when 'User'
link_to_user value
when 'Project'
@@ -107,11 +73,6 @@ module QueriesHelper
l(:general_text_No)
when 'Issue'
link_to_issue(value, :subject => false)
when 'IssueRelation'
other = value.other_issue(issue)
content_tag('span',
(l(value.label_for(issue)) + " " + link_to_issue(other, :subject => false, :tracker => false)).html_safe,
:class => value.css_classes_for(issue))
else
h(value)
end

View File

@@ -17,6 +17,9 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
require 'iconv'
require 'redmine/codeset_util'
module RepositoriesHelper
def format_revision(revision)
if revision.respond_to? :format_identifier
@@ -250,13 +253,16 @@ module RepositoriesHelper
def index_commits(commits, heads)
return nil if commits.nil? or commits.first.parents.nil?
refs_map = {}
heads.each do |head|
refs_map[head.scmid] ||= []
refs_map[head.scmid] << head
end
commits_by_scmid = {}
commits.reverse.each_with_index do |commit, commit_index|
commits_by_scmid[commit.scmid] = {
:parent_scmids => commit.parents.collect { |parent| parent.scmid },
:rdmid => commit_index,
@@ -265,28 +271,38 @@ module RepositoriesHelper
:href => block_given? ? yield(commit.scmid) : commit.scmid
}
end
heads.sort! { |head1, head2| head1.to_s <=> head2.to_s }
space = nil
heads.each do |head|
if commits_by_scmid.include? head.scmid
space = index_head((space || -1) + 1, head, commits_by_scmid)
end
end
# when no head matched anything use first commit
space ||= index_head(0, commits.first, commits_by_scmid)
return commits_by_scmid, space
end
def index_head(space, commit, commits_by_scmid)
stack = [[space, commits_by_scmid[commit.scmid]]]
max_space = space
until stack.empty?
space, commit = stack.pop
commit[:space] = space if commit[:space].nil?
space -= 1
commit[:parent_scmids].each_with_index do |parent_scmid, parent_index|
parent_commit = commits_by_scmid[parent_scmid]
if parent_commit and parent_commit[:space].nil?
stack.unshift [space += 1, parent_commit]
end
end

View File

@@ -56,7 +56,7 @@ module SettingsHelper
Setting.send(setting).include?(value),
:id => nil
) + text.to_s,
:class => (options[:inline] ? 'inline' : 'block')
:class => 'block'
)
end.join.html_safe
end
@@ -91,16 +91,4 @@ module SettingsHelper
l_or_humanize(notifiable.name, :prefix => 'label_').html_safe,
:class => notifiable.parent.present? ? "parent" : '').html_safe
end
def cross_project_subtasks_options
options = [
[:label_disabled, ''],
[:label_cross_project_system, 'system'],
[:label_cross_project_tree, 'tree'],
[:label_cross_project_hierarchy, 'hierarchy'],
[:label_cross_project_descendants, 'descendants']
]
options.map {|label, value| [l(label), value.to_s]}
end
end

View File

@@ -89,10 +89,6 @@ module SortHelper
sql.blank? ? nil : sql
end
def to_a
@criteria.dup
end
def add!(key, asc)
@criteria.delete_if {|k,o| k == key}
@criteria = [[key, asc]] + @criteria
@@ -186,10 +182,6 @@ module SortHelper
@sort_criteria.to_sql
end
def sort_criteria
@sort_criteria
end
# Returns a link which sorts by the named column.
#
# - column is the name of an attribute in the sorted record collection.

View File

@@ -77,7 +77,6 @@ module TimelogHelper
[l(:label_yesterday), 'yesterday'],
[l(:label_this_week), 'current_week'],
[l(:label_last_week), 'last_week'],
[l(:label_last_n_weeks, 2), 'last_2_weeks'],
[l(:label_last_n_days, 7), '7_days'],
[l(:label_this_month), 'current_month'],
[l(:label_last_month), 'last_month'],

View File

@@ -26,6 +26,15 @@ module UsersHelper
["#{l(:status_locked)} (#{user_count_by_status[3].to_i})", '3']], selected.to_s)
end
# Options for the new membership projects combo-box
def options_for_membership_project_select(user, projects)
options = content_tag('option', "--- #{l(:actionview_instancetag_blank_option)} ---")
options << project_tree_options_for_select(projects) do |p|
{:disabled => (user.projects.include?(p))}
end
options
end
def user_mail_notification_options(user)
user.valid_notification_options.collect {|o| [l(o.last), o.first]}
end

View File

@@ -44,8 +44,7 @@ module VersionsHelper
rescue ActiveRecord::RecordNotFound
# When grouping by an association, Rails throws this exception if there's no result (bug)
end
# Sort with nil keys in last position
counts = h.keys.sort {|a,b| a.nil? ? 1 : (b.nil? ? -1 : a <=> b)}.collect {|k| {:group => k, :total => h[k][0], :open => h[k][1], :closed => (h[k][0] - h[k][1])}}
counts = h.keys.compact.sort.collect {|k| {:group => k, :total => h[k][0], :open => h[k][1], :closed => (h[k][0] - h[k][1])}}
max = counts.collect {|c| c[:total]}.max
render :partial => 'issue_counts', :locals => {:version => version, :criteria => criteria, :counts => counts, :max => max}

View File

@@ -37,7 +37,7 @@ module WikiHelper
def wiki_page_breadcrumb(page)
breadcrumb(page.ancestors.reverse.collect {|parent|
link_to(h(parent.pretty_title), {:controller => 'wiki', :action => 'show', :id => parent.title, :project_id => parent.project, :version => nil})
link_to(h(parent.pretty_title), {:controller => 'wiki', :action => 'show', :id => parent.title, :project_id => parent.project})
})
end
end

View File

@@ -30,34 +30,6 @@ class CustomField < ActiveRecord::Base
validate :validate_custom_field
before_validation :set_searchable
CUSTOM_FIELDS_TABS = [
{:name => 'IssueCustomField', :partial => 'custom_fields/index',
:label => :label_issue_plural},
{:name => 'TimeEntryCustomField', :partial => 'custom_fields/index',
:label => :label_spent_time},
{:name => 'ProjectCustomField', :partial => 'custom_fields/index',
:label => :label_project_plural},
{:name => 'VersionCustomField', :partial => 'custom_fields/index',
:label => :label_version_plural},
{:name => 'UserCustomField', :partial => 'custom_fields/index',
:label => :label_user_plural},
{:name => 'GroupCustomField', :partial => 'custom_fields/index',
:label => :label_group_plural},
{:name => 'TimeEntryActivityCustomField', :partial => 'custom_fields/index',
:label => TimeEntryActivity::OptionName},
{:name => 'IssuePriorityCustomField', :partial => 'custom_fields/index',
:label => IssuePriority::OptionName},
{:name => 'DocumentCategoryCustomField', :partial => 'custom_fields/index',
:label => DocumentCategory::OptionName}
]
CUSTOM_FIELDS_NAMES = CUSTOM_FIELDS_TABS.collect{|v| v[:name]}
def field_format=(arg)
# cannot change format of a saved custom field
super if new_record?
end
def set_searchable
# make sure these fields are not searchable
self.searchable = false if %w(int float date bool).include?(field_format)
@@ -158,13 +130,7 @@ class CustomField < ActiveRecord::Base
possible_values_options = possible_values_options(customized)
if possible_values_options.present?
keyword = keyword.to_s.downcase
if v = possible_values_options.detect {|text, id| text.downcase == keyword}
if v.is_a?(Array)
v.last
else
v
end
end
possible_values_options.detect {|text, id| text.downcase == keyword}.try(:last)
else
keyword
end

View File

@@ -36,7 +36,6 @@ class Enumeration < ActiveRecord::Base
validates_length_of :name, :maximum => 30
scope :shared, where(:project_id => nil)
scope :sorted, order("#{table_name}.position ASC")
scope :active, where(:active => true)
scope :named, lambda {|arg| where("LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip)}

View File

@@ -17,7 +17,6 @@
class Issue < ActiveRecord::Base
include Redmine::SafeAttributes
include Redmine::Utils::DateCalculation
belongs_to :project
belongs_to :tracker
@@ -29,14 +28,6 @@ class Issue < ActiveRecord::Base
belongs_to :category, :class_name => 'IssueCategory', :foreign_key => 'category_id'
has_many :journals, :as => :journalized, :dependent => :destroy
has_many :visible_journals,
:class_name => 'Journal',
:as => :journalized,
:conditions => Proc.new {
["(#{Journal.table_name}.private_notes = ? OR (#{Project.allowed_to_condition(User.current, :view_private_notes)}))", false]
},
:readonly => true
has_many :time_entries, :dependent => :delete_all
has_and_belongs_to_many :changesets, :order => "#{Changeset.table_name}.committed_on ASC, #{Changeset.table_name}.id ASC"
@@ -48,7 +39,7 @@ class Issue < ActiveRecord::Base
acts_as_customizable
acts_as_watchable
acts_as_searchable :columns => ['subject', "#{table_name}.description", "#{Journal.table_name}.notes"],
:include => [:project, :visible_journals],
:include => [:project, :journals],
# sort by id so that limited eager loading doesn't break with postgresql
:order_column => "#{table_name}.id"
acts_as_event :title => Proc.new {|o| "#{o.tracker.name} ##{o.id} (#{o.status}): #{o.subject}"},
@@ -61,7 +52,6 @@ class Issue < ActiveRecord::Base
DONE_RATIO_OPTIONS = %w(issue_field issue_status)
attr_reader :current_journal
delegate :notes, :notes=, :private_notes, :private_notes=, :to => :current_journal, :allow_nil => true
validates_presence_of :subject, :priority, :project, :tracker, :author, :status
@@ -94,21 +84,17 @@ class Issue < ActiveRecord::Base
# Returns a SQL conditions string used to find all issues visible by the specified user
def self.visible_condition(user, options={})
Project.allowed_to_condition(user, :view_issues, options) do |role, user|
if user.logged?
case role.issues_visibility
when 'all'
nil
when 'default'
user_ids = [user.id] + user.groups.map(&:id)
"(#{table_name}.is_private = #{connection.quoted_false} OR #{table_name}.author_id = #{user.id} OR #{table_name}.assigned_to_id IN (#{user_ids.join(',')}))"
when 'own'
user_ids = [user.id] + user.groups.map(&:id)
"(#{table_name}.author_id = #{user.id} OR #{table_name}.assigned_to_id IN (#{user_ids.join(',')}))"
else
'1=0'
end
case role.issues_visibility
when 'all'
nil
when 'default'
user_ids = [user.id] + user.groups.map(&:id)
"(#{table_name}.is_private = #{connection.quoted_false} OR #{table_name}.author_id = #{user.id} OR #{table_name}.assigned_to_id IN (#{user_ids.join(',')}))"
when 'own'
user_ids = [user.id] + user.groups.map(&:id)
"(#{table_name}.author_id = #{user.id} OR #{table_name}.assigned_to_id IN (#{user_ids.join(',')}))"
else
"(#{table_name}.is_private = #{connection.quoted_false})"
'1=0'
end
end
end
@@ -116,19 +102,15 @@ class Issue < ActiveRecord::Base
# Returns true if usr or current user is allowed to view the issue
def visible?(usr=nil)
(usr || User.current).allowed_to?(:view_issues, self.project) do |role, user|
if user.logged?
case role.issues_visibility
when 'all'
true
when 'default'
!self.is_private? || (self.author == user || user.is_or_belongs_to?(assigned_to))
when 'own'
self.author == user || user.is_or_belongs_to?(assigned_to)
else
false
end
case role.issues_visibility
when 'all'
true
when 'default'
!self.is_private? || self.author == user || user.is_or_belongs_to?(assigned_to)
when 'own'
self.author == user || user.is_or_belongs_to?(assigned_to)
else
!self.is_private?
false
end
end
end
@@ -286,8 +268,7 @@ class Issue < ActiveRecord::Base
if fixed_version && fixed_version.project != project && !project.shared_versions.include?(fixed_version)
self.fixed_version = nil
end
# Clear the parent task if it's no longer valid
unless valid_parent_project?
if parent && parent.project_id != project_id
self.parent_issue_id = nil
end
@custom_field_values = nil
@@ -346,7 +327,6 @@ class Issue < ActiveRecord::Base
'custom_field_values',
'custom_fields',
'lock_version',
'notes',
:if => lambda {|issue, user| issue.new_record? || user.allowed_to?(:edit_issues, issue.project) }
safe_attributes 'status_id',
@@ -354,15 +334,8 @@ class Issue < ActiveRecord::Base
'fixed_version_id',
'done_ratio',
'lock_version',
'notes',
:if => lambda {|issue, user| issue.new_statuses_allowed_to(user).any? }
safe_attributes 'notes',
:if => lambda {|issue, user| user.allowed_to?(:add_issue_notes, issue.project)}
safe_attributes 'private_notes',
:if => lambda {|issue, user| !issue.new_record? && user.allowed_to?(:set_notes_private, issue.project)}
safe_attributes 'watcher_user_ids',
:if => lambda {|issue, user| issue.new_record? && user.allowed_to?(:add_issue_watchers, issue.project)}
@@ -417,10 +390,7 @@ class Issue < ActiveRecord::Base
end
if attrs['parent_issue_id'].present?
s = attrs['parent_issue_id'].to_s
unless (m = s.match(%r{\A#?(\d+)\z})) && (m[1] == parent_id.to_s || Issue.visible(user).exists?(m[1]))
@invalid_parent_issue_id = attrs.delete('parent_issue_id')
end
attrs.delete('parent_issue_id') unless Issue.visible(user).exists?(attrs['parent_issue_id'].to_i)
end
if attrs['custom_field_values'].present?
@@ -526,15 +496,11 @@ class Issue < ActiveRecord::Base
end
def validate_issue
if due_date.nil? && @attributes['due_date'].present?
if self.due_date.nil? && @attributes['due_date'] && !@attributes['due_date'].empty?
errors.add :due_date, :not_a_date
end
if start_date.nil? && @attributes['start_date'].present?
errors.add :start_date, :not_a_date
end
if due_date && start_date && due_date < start_date
if self.due_date and self.start_date and self.due_date < self.start_date
errors.add :due_date, :greater_than_start_date
end
@@ -558,11 +524,9 @@ class Issue < ActiveRecord::Base
end
# Checks parent issue assignment
if @invalid_parent_issue_id.present?
errors.add :parent_issue_id, :invalid
elsif @parent_issue
if !valid_parent_project?(@parent_issue)
errors.add :parent_issue_id, :invalid
if @parent_issue
if @parent_issue.project_id != project_id
errors.add :parent_issue_id, :not_same_project
elsif !new_record?
# moving an existing issue
if @parent_issue.root_id != root_id
@@ -570,7 +534,7 @@ class Issue < ActiveRecord::Base
elsif move_possible?(@parent_issue)
# move accepted inside tree
else
errors.add :parent_issue_id, :invalid
errors.add :parent_issue_id, :not_a_valid_parent
end
end
end
@@ -743,8 +707,8 @@ class Issue < ActiveRecord::Base
end
end
# Returns the users that should be notified
def notified_users
# Returns the mail adresses of users that should be notified
def recipients
notified = []
# Author and assignee are always notified unless they have been
# locked or don't want to be notified
@@ -761,12 +725,7 @@ class Issue < ActiveRecord::Base
notified.uniq!
# Remove users that can not view the issue
notified.reject! {|user| !visible?(user)}
notified
end
# Returns the email addresses that should be notified
def recipients
notified_users.collect(&:mail)
notified.collect(&:mail)
end
# Returns the number of hours spent on this issue
@@ -785,7 +744,7 @@ class Issue < ActiveRecord::Base
end
def relations
@relations ||= IssueRelations.new(self, (relations_from + relations_to).sort)
@relations ||= (relations_from + relations_to).sort
end
# Preloads relations for a collection of issues
@@ -808,25 +767,6 @@ class Issue < ActiveRecord::Base
end
end
# Preloads visible relations for a collection of issues
def self.load_visible_relations(issues, user=User.current)
if issues.any?
issue_ids = issues.map(&:id)
# Relations with issue_from in given issues and visible issue_to
relations_from = IssueRelation.includes(:issue_to => [:status, :project]).where(visible_condition(user)).where(:issue_from_id => issue_ids).all
# Relations with issue_to in given issues and visible issue_from
relations_to = IssueRelation.includes(:issue_from => [:status, :project]).where(visible_condition(user)).where(:issue_to_id => issue_ids).all
issues.each do |issue|
relations =
relations_from.select {|relation| relation.issue_from_id == issue.id} +
relations_to.select {|relation| relation.issue_to_id == issue.id}
issue.instance_variable_set "@relations", IssueRelations.new(issue, relations.sort)
end
end
end
# Finds an issue relation given its id.
def find_relation(relation_id)
IssueRelation.find(relation_id, :conditions => ["issue_to_id = ? OR issue_from_id = ?", id, id])
@@ -864,58 +804,29 @@ class Issue < ActiveRecord::Base
(start_date && due_date) ? due_date - start_date : 0
end
# Returns the duration in working days
def working_duration
(start_date && due_date) ? working_days(start_date, due_date) : 0
end
def soonest_start(reload=false)
@soonest_start = nil if reload
def soonest_start
@soonest_start ||= (
relations_to(reload).collect{|relation| relation.successor_soonest_start} +
relations_to.collect{|relation| relation.successor_soonest_start} +
ancestors.collect(&:soonest_start)
).compact.max
end
# Sets start_date on the given date or the next working day
# and changes due_date to keep the same working duration.
def reschedule_on(date)
wd = working_duration
date = next_working_date(date)
self.start_date = date
self.due_date = add_working_days(date, wd)
end
# Reschedules the issue on the given date or the next working day and saves the record.
# If the issue is a parent task, this is done by rescheduling its subtasks.
def reschedule_on!(date)
def reschedule_after(date)
return if date.nil?
if leaf?
if start_date.nil? || start_date != date
if start_date && start_date > date
# Issue can not be moved earlier than its soonest start date
date = [soonest_start(true), date].compact.max
end
reschedule_on(date)
if start_date.nil? || start_date < date
self.start_date, self.due_date = date, date + duration
begin
save
rescue ActiveRecord::StaleObjectError
reload
reschedule_on(date)
self.start_date, self.due_date = date, date + duration
save
end
end
else
leaves.each do |leaf|
if leaf.start_date
# Only move subtask if it starts at the same date as the parent
# or if it starts before the given date
if start_date == leaf.start_date || date > leaf.start_date
leaf.reschedule_on!(date)
end
else
leaf.reschedule_on!(date)
end
leaf.reschedule_after(date)
end
end
end
@@ -936,7 +847,7 @@ class Issue < ActiveRecord::Base
# Returns a string of css classes that apply to the issue
def css_classes
s = "issue status-#{status_id} #{priority.try(:css_classes)}"
s = "issue status-#{status_id} priority-#{priority_id}"
s << ' closed' if closed?
s << ' overdue' if overdue?
s << ' child' if child?
@@ -986,44 +897,23 @@ class Issue < ActiveRecord::Base
end
def parent_issue_id=(arg)
s = arg.to_s.strip.presence
if s && (m = s.match(%r{\A#?(\d+)\z})) && (@parent_issue = Issue.find_by_id(m[1]))
parent_issue_id = arg.blank? ? nil : arg.to_i
if parent_issue_id && @parent_issue = Issue.find_by_id(parent_issue_id)
@parent_issue.id
else
@parent_issue = nil
@invalid_parent_issue_id = arg
nil
end
end
def parent_issue_id
if @invalid_parent_issue_id
@invalid_parent_issue_id
elsif instance_variable_defined? :@parent_issue
if instance_variable_defined? :@parent_issue
@parent_issue.nil? ? nil : @parent_issue.id
else
parent_id
end
end
# Returns true if issue's project is a valid
# parent issue project
def valid_parent_project?(issue=parent)
return true if issue.nil? || issue.project_id == project_id
case Setting.cross_project_subtasks
when 'system'
true
when 'tree'
issue.project.root == project.root
when 'hierarchy'
issue.project.is_or_is_ancestor_of?(project) || issue.project.is_descendant_of?(project)
when 'descendants'
issue.project.is_or_is_ancestor_of?(project)
else
false
end
end
# Extracted from the ReportsController.
def self.by_tracker(project)
count_and_group_by(:project => project,
@@ -1103,9 +993,8 @@ class Issue < ActiveRecord::Base
relations_to.clear
end
# Move subtasks that were in the same project
# Move subtasks
children.each do |child|
next unless child.project_id == project_id_was
# Change project and keep project
child.send :project=, project, true
unless child.save
@@ -1114,20 +1003,11 @@ class Issue < ActiveRecord::Base
end
end
# Callback for after the creation of an issue by copy
# * adds a "copied to" relation with the copied issue
# * copies subtasks from the copied issue
# Copies subtasks from the copied issue
def after_create_from_copy
return unless copy? && !@after_create_from_copy_handled
return unless copy?
if (@copied_from.project_id == project_id || Setting.cross_project_issue_relations?) && @copy_options[:link] != false
relation = IssueRelation.new(:issue_from => @copied_from, :issue_to => self, :relation_type => IssueRelation::TYPE_COPIED_TO)
unless relation.save
logger.error "Could not create relation while copying ##{@copied_from.id} to ##{id} due to validation errors: #{relation.errors.full_messages.join(', ')}" if logger
end
end
unless @copied_from.leaf? || @copy_options[:subtasks] == false
unless @copied_from.leaf? || @copy_options[:subtasks] == false || @subtasks_copied
@copied_from.children.each do |child|
unless child.visible?
# Do not copy subtasks that are not visible to avoid potential disclosure of private data
@@ -1143,8 +1023,8 @@ class Issue < ActiveRecord::Base
logger.error "Could not copy subtask ##{child.id} while copying ##{@copied_from.id} to ##{id} due to validation errors: #{copy.errors.full_messages.join(', ')}" if logger
end
end
@subtasks_copied = true
end
@after_create_from_copy_handled = true
end
def update_nested_set_attributes
@@ -1255,7 +1135,7 @@ class Issue < ActiveRecord::Base
end
end
# Callback on file attachment
# Callback on attachment deletion
def attachment_added(obj)
if @current_journal && !obj.new_record?
@current_journal.details << JournalDetail.new(:property => 'attachment', :prop_key => obj.id, :value => obj.filename)

View File

@@ -27,7 +27,7 @@ class IssueCategory < ActiveRecord::Base
safe_attributes 'name', 'assigned_to_id'
scope :named, lambda {|arg| where("LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip)}
scope :named, lambda {|arg| { :conditions => ["LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip]}}
alias :destroy_without_reassign :destroy

View File

@@ -18,9 +18,6 @@
class IssuePriority < Enumeration
has_many :issues, :foreign_key => 'priority_id'
after_destroy {|priority| priority.class.compute_position_names}
after_save {|priority| priority.class.compute_position_names if priority.position_changed? && priority.position}
OptionName = :enumeration_issue_priorities
def option_name
@@ -34,35 +31,4 @@ class IssuePriority < Enumeration
def transfer_relations(to)
issues.update_all("priority_id = #{to.id}")
end
def css_classes
"priority-#{id} priority-#{position_name}"
end
# Clears position_name for all priorities
# Called from migration 20121026003537_populate_enumerations_position_name
def self.clear_position_names
update_all :position_name => nil
end
# Updates position_name for active priorities
# Called from migration 20121026003537_populate_enumerations_position_name
def self.compute_position_names
priorities = where(:active => true).all.sort_by(&:position)
if priorities.any?
default = priorities.detect(&:is_default?) || priorities[(priorities.size - 1) / 2]
priorities.each_with_index do |priority, index|
name = case
when priority.position == default.position
"default"
when priority.position < default.position
index == 0 ? "lowest" : "low#{index+1}"
else
index == (priorities.size - 1) ? "highest" : "high#{priorities.size - index}"
end
update_all({:position_name => name}, :id => priority.id)
end
end
end
end

View File

@@ -15,20 +15,6 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
# Class used to represent the relations of an issue
class IssueRelations < Array
include Redmine::I18n
def initialize(issue, *args)
@issue = issue
super(*args)
end
def to_s(*args)
map {|relation| "#{l(relation.label_for(@issue))} ##{relation.other_issue(@issue).id}"}.join(', ')
end
end
class IssueRelation < ActiveRecord::Base
belongs_to :issue_from, :class_name => 'Issue', :foreign_key => 'issue_from_id'
belongs_to :issue_to, :class_name => 'Issue', :foreign_key => 'issue_to_id'
@@ -40,37 +26,25 @@ class IssueRelation < ActiveRecord::Base
TYPE_BLOCKED = "blocked"
TYPE_PRECEDES = "precedes"
TYPE_FOLLOWS = "follows"
TYPE_COPIED_TO = "copied_to"
TYPE_COPIED_FROM = "copied_from"
TYPES = {
TYPE_RELATES => { :name => :label_relates_to, :sym_name => :label_relates_to,
:order => 1, :sym => TYPE_RELATES },
TYPE_DUPLICATES => { :name => :label_duplicates, :sym_name => :label_duplicated_by,
:order => 2, :sym => TYPE_DUPLICATED },
TYPE_DUPLICATED => { :name => :label_duplicated_by, :sym_name => :label_duplicates,
:order => 3, :sym => TYPE_DUPLICATES, :reverse => TYPE_DUPLICATES },
TYPE_BLOCKS => { :name => :label_blocks, :sym_name => :label_blocked_by,
:order => 4, :sym => TYPE_BLOCKED },
TYPE_BLOCKED => { :name => :label_blocked_by, :sym_name => :label_blocks,
:order => 5, :sym => TYPE_BLOCKS, :reverse => TYPE_BLOCKS },
TYPE_PRECEDES => { :name => :label_precedes, :sym_name => :label_follows,
:order => 6, :sym => TYPE_FOLLOWS },
TYPE_FOLLOWS => { :name => :label_follows, :sym_name => :label_precedes,
:order => 7, :sym => TYPE_PRECEDES, :reverse => TYPE_PRECEDES },
TYPE_COPIED_TO => { :name => :label_copied_to, :sym_name => :label_copied_from,
:order => 8, :sym => TYPE_COPIED_FROM },
TYPE_COPIED_FROM => { :name => :label_copied_from, :sym_name => :label_copied_to,
:order => 9, :sym => TYPE_COPIED_TO, :reverse => TYPE_COPIED_TO }
}.freeze
TYPES = { TYPE_RELATES => { :name => :label_relates_to, :sym_name => :label_relates_to, :order => 1, :sym => TYPE_RELATES },
TYPE_DUPLICATES => { :name => :label_duplicates, :sym_name => :label_duplicated_by, :order => 2, :sym => TYPE_DUPLICATED },
TYPE_DUPLICATED => { :name => :label_duplicated_by, :sym_name => :label_duplicates, :order => 3, :sym => TYPE_DUPLICATES, :reverse => TYPE_DUPLICATES },
TYPE_BLOCKS => { :name => :label_blocks, :sym_name => :label_blocked_by, :order => 4, :sym => TYPE_BLOCKED },
TYPE_BLOCKED => { :name => :label_blocked_by, :sym_name => :label_blocks, :order => 5, :sym => TYPE_BLOCKS, :reverse => TYPE_BLOCKS },
TYPE_PRECEDES => { :name => :label_precedes, :sym_name => :label_follows, :order => 6, :sym => TYPE_FOLLOWS },
TYPE_FOLLOWS => { :name => :label_follows, :sym_name => :label_precedes, :order => 7, :sym => TYPE_PRECEDES, :reverse => TYPE_PRECEDES }
}.freeze
validates_presence_of :issue_from, :issue_to, :relation_type
validates_inclusion_of :relation_type, :in => TYPES.keys
validates_numericality_of :delay, :allow_nil => true
validates_uniqueness_of :issue_to_id, :scope => :issue_from_id
validate :validate_issue_relation
attr_protected :issue_from_id, :issue_to_id
before_save :handle_issue_order
def visible?(user=User.current)
@@ -95,19 +69,14 @@ class IssueRelation < ActiveRecord::Base
def validate_issue_relation
if issue_from && issue_to
errors.add :issue_to_id, :invalid if issue_from_id == issue_to_id
unless issue_from.project_id == issue_to.project_id ||
Setting.cross_project_issue_relations?
errors.add :issue_to_id, :not_same_project
end
# detect circular dependencies depending wether the relation should be reversed
errors.add :issue_to_id, :not_same_project unless issue_from.project_id == issue_to.project_id || Setting.cross_project_issue_relations?
#detect circular dependencies depending wether the relation should be reversed
if TYPES.has_key?(relation_type) && TYPES[relation_type][:reverse]
errors.add :base, :circular_dependency if issue_from.all_dependent_issues.include? issue_to
else
errors.add :base, :circular_dependency if issue_to.all_dependent_issues.include? issue_from
end
if issue_from.is_descendant_of?(issue_to) || issue_from.is_ancestor_of?(issue_to)
errors.add :base, :cant_link_an_issue_with_a_descendant
end
errors.add :base, :cant_link_an_issue_with_a_descendant if issue_from.is_descendant_of?(issue_to) || issue_from.is_ancestor_of?(issue_to)
end
end
@@ -127,13 +96,7 @@ class IssueRelation < ActiveRecord::Base
end
def label_for(issue)
TYPES[relation_type] ?
TYPES[relation_type][(self.issue_from_id == issue.id) ? :name : :sym_name] :
:unknow
end
def css_classes_for(issue)
"rel-#{relation_type_for(issue)}"
TYPES[relation_type] ? TYPES[relation_type][(self.issue_from_id == issue.id) ? :name : :sym_name] : :unknow
end
def handle_issue_order
@@ -150,20 +113,18 @@ class IssueRelation < ActiveRecord::Base
def set_issue_to_dates
soonest_start = self.successor_soonest_start
if soonest_start && issue_to
issue_to.reschedule_on!(soonest_start)
issue_to.reschedule_after(soonest_start)
end
end
def successor_soonest_start
if (TYPE_PRECEDES == self.relation_type) && delay && issue_from &&
(issue_from.start_date || issue_from.due_date)
if (TYPE_PRECEDES == self.relation_type) && delay && issue_from && (issue_from.start_date || issue_from.due_date)
(issue_from.due_date || issue_from.start_date) + 1 + delay
end
end
def <=>(relation)
r = TYPES[self.relation_type][:order] <=> TYPES[relation.relation_type][:order]
r == 0 ? id <=> relation.id : r
TYPES[self.relation_type][:order] <=> TYPES[relation.relation_type][:order]
end
private

View File

@@ -29,7 +29,7 @@ class IssueStatus < ActiveRecord::Base
validates_inclusion_of :default_done_ratio, :in => 0..100, :allow_nil => true
scope :sorted, order("#{table_name}.position ASC")
scope :named, lambda {|arg| where("LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip)}
scope :named, lambda {|arg| where(["LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip])}
def update_default
IssueStatus.update_all({:is_default => false}, ['id <> ?', id]) if self.is_default?

View File

@@ -37,15 +37,10 @@ class Journal < ActiveRecord::Base
:conditions => "#{Journal.table_name}.journalized_type = 'Issue' AND" +
" (#{JournalDetail.table_name}.prop_key = 'status_id' OR #{Journal.table_name}.notes <> '')"}
before_create :split_private_notes
scope :visible, lambda {|*args|
user = args.shift || User.current
includes(:issue => :project).
where(Issue.visible_condition(user, *args)).
where("(#{Journal.table_name}.private_notes = ? OR (#{Project.allowed_to_condition(user, :view_private_notes, *args)}))", false)
}
scope :visible, lambda {|*args| {
:include => {:issue => :project},
:conditions => Issue.visible_condition(args.shift || User.current, *args)
}}
def save(*args)
# Do not save an empty journal
@@ -80,7 +75,6 @@ class Journal < ActiveRecord::Base
s = 'journal'
s << ' has-notes' unless notes.blank?
s << ' has-details' unless details.blank?
s << ' private-notes' if private_notes?
s
end
@@ -91,41 +85,4 @@ class Journal < ActiveRecord::Base
def notify=(arg)
@notify = arg
end
def recipients
notified = journalized.notified_users
if private_notes?
notified = notified.select {|user| user.allowed_to?(:view_private_notes, journalized.project)}
end
notified.map(&:mail)
end
def watcher_recipients
notified = journalized.notified_watchers
if private_notes?
notified = notified.select {|user| user.allowed_to?(:view_private_notes, journalized.project)}
end
notified.map(&:mail)
end
private
def split_private_notes
if private_notes?
if notes.present?
if details.any?
# Split the journal (notes/changes) so we don't have half-private journals
journal = Journal.new(:journalized => journalized, :user => user, :notes => nil, :private_notes => false)
journal.details = details
journal.save
self.details = []
self.created_on = journal.created_on
end
else
# Blank notes should not be private
self.private_notes = false
end
end
true
end
end

View File

@@ -124,7 +124,6 @@ class MailHandler < ActionMailer::Base
def dispatch
headers = [email.in_reply_to, email.references].flatten.compact
subject = email.subject.to_s
if headers.detect {|h| h.to_s =~ MESSAGE_ID_RE}
klass, object_id = $1, $2.to_i
method_name = "receive_#{klass}_reply"
@@ -133,9 +132,9 @@ class MailHandler < ActionMailer::Base
else
# ignoring it
end
elsif m = subject.match(ISSUE_REPLY_SUBJECT_RE)
elsif m = email.subject.match(ISSUE_REPLY_SUBJECT_RE)
receive_issue_reply(m[1].to_i)
elsif m = subject.match(MESSAGE_REPLY_SUBJECT_RE)
elsif m = email.subject.match(MESSAGE_REPLY_SUBJECT_RE)
receive_message_reply(m[1].to_i)
else
dispatch_to_default
@@ -182,7 +181,7 @@ class MailHandler < ActionMailer::Base
end
# Adds a note to an existing issue
def receive_issue_reply(issue_id, from_journal=nil)
def receive_issue_reply(issue_id)
issue = Issue.find_by_id(issue_id)
return unless issue
# check permission
@@ -197,10 +196,6 @@ class MailHandler < ActionMailer::Base
@@handler_options[:issue].clear
journal = issue.init_journal(user)
if from_journal && from_journal.private_notes?
# If the received email was a reply to a private note, make the added note private
issue.private_notes = true
end
issue.safe_attributes = issue_attributes_from_keywords(issue)
issue.safe_attributes = {'custom_field_values' => custom_field_values_from_keywords(issue)}
journal.notes = cleaned_up_text_body
@@ -216,7 +211,7 @@ class MailHandler < ActionMailer::Base
def receive_journal_reply(journal_id)
journal = Journal.find_by_id(journal_id)
if journal && journal.journalized_type == 'Issue'
receive_issue_reply(journal.journalized_id, journal)
receive_issue_reply(journal.journalized_id)
end
end
@@ -374,6 +369,18 @@ class MailHandler < ActionMailer::Base
def cleaned_up_subject
subject = email.subject.to_s
unless subject.respond_to?(:encoding)
# try to reencode to utf8 manually with ruby1.8
begin
if h = email.header[:subject]
if m = h.value.match(/^=\?([^\?]+)\?/)
subject = Redmine::CodesetUtil.to_utf8(subject, m[1])
end
end
rescue
# nop
end
end
subject.strip[0,255]
end

View File

@@ -62,9 +62,9 @@ class Mailer < ActionMailer::Base
message_id journal
references issue
@author = journal.user
recipients = journal.recipients
recipients = issue.recipients
# Watchers in cc
cc = journal.watcher_recipients - recipients
cc = issue.watcher_recipients - recipients
s = "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] "
s << "(#{issue.status.name}) " if journal.new_value_for('status_id')
s << issue.subject

View File

@@ -27,20 +27,19 @@ class Principal < ActiveRecord::Base
scope :active, :conditions => "#{Principal.table_name}.status = 1"
scope :like, lambda {|q|
q = q.to_s
if q.blank?
where({})
{}
else
q = q.to_s.downcase
pattern = "%#{q}%"
sql = %w(login firstname lastname mail).map {|column| "LOWER(#{table_name}.#{column}) LIKE LOWER(:p)"}.join(" OR ")
sql = "LOWER(login) LIKE :p OR LOWER(firstname) LIKE :p OR LOWER(lastname) LIKE :p OR LOWER(mail) LIKE :p"
params = {:p => pattern}
if q =~ /^(.+)\s+(.+)$/
a, b = "#{$1}%", "#{$2}%"
sql << " OR (LOWER(#{table_name}.firstname) LIKE LOWER(:a) AND LOWER(#{table_name}.lastname) LIKE LOWER(:b))"
sql << " OR (LOWER(#{table_name}.firstname) LIKE LOWER(:b) AND LOWER(#{table_name}.lastname) LIKE LOWER(:a))"
sql << " OR (LOWER(firstname) LIKE :a AND LOWER(lastname) LIKE :b) OR (LOWER(firstname) LIKE :b AND LOWER(lastname) LIKE :a)"
params.merge!(:a => a, :b => b)
end
where(sql, params)
{:conditions => [sql, params]}
end
}
@@ -48,20 +47,20 @@ class Principal < ActiveRecord::Base
scope :member_of, lambda {|projects|
projects = [projects] unless projects.is_a?(Array)
if projects.empty?
where("1=0")
{:conditions => "1=0"}
else
ids = projects.map(&:id)
where("#{Principal.table_name}.status = 1 AND #{Principal.table_name}.id IN (SELECT DISTINCT user_id FROM #{Member.table_name} WHERE project_id IN (?))", ids)
{:conditions => ["#{Principal.table_name}.status = 1 AND #{Principal.table_name}.id IN (SELECT DISTINCT user_id FROM #{Member.table_name} WHERE project_id IN (?))", ids]}
end
}
# Principals that are not members of projects
scope :not_member_of, lambda {|projects|
projects = [projects] unless projects.is_a?(Array)
if projects.empty?
where("1=0")
{:conditions => "1=0"}
else
ids = projects.map(&:id)
where("#{Principal.table_name}.id NOT IN (SELECT DISTINCT user_id FROM #{Member.table_name} WHERE project_id IN (?))", ids)
{:conditions => ["#{Principal.table_name}.id NOT IN (SELECT DISTINCT user_id FROM #{Member.table_name} WHERE project_id IN (?))", ids]}
end
}

View File

@@ -28,7 +28,7 @@ class Project < ActiveRecord::Base
# Specific overidden Activities
has_many :time_entry_activities
has_many :members, :include => [:principal, :roles], :conditions => "#{User.table_name}.type='User' AND #{User.table_name}.status=#{User::STATUS_ACTIVE}"
has_many :members, :include => [:user, :roles], :conditions => "#{User.table_name}.type='User' AND #{User.table_name}.status=#{User::STATUS_ACTIVE}"
has_many :memberships, :class_name => 'Member'
has_many :member_principals, :class_name => 'Member',
:include => :principal,
@@ -393,16 +393,6 @@ class Project < ActiveRecord::Base
end
end
# Recalculates all lft and rgt values based on project names
# Unlike Project.rebuild!, these values are recalculated even if the tree "looks" valid
# Used in BuildProjectsTree migration
def self.rebuild_tree!
transaction do
update_all "lft = NULL, rgt = NULL"
rebuild!(false)
end
end
# Returns an array of the trackers used by the project and its active sub projects
def rolled_up_trackers
@rolled_up_trackers ||=
@@ -482,7 +472,7 @@ class Project < ActiveRecord::Base
# Returns the users that should be notified on project events
def notified_users
# TODO: User part should be extracted to User#notify_about?
members.select {|m| m.principal.present? && (m.mail_notification? || m.principal.mail_notification == 'all')}.collect {|m| m.principal}
members.select {|m| m.mail_notification? || m.user.mail_notification == 'all'}.collect {|m| m.user}
end
# Returns an array of all custom fields enabled for project issues
@@ -731,7 +721,7 @@ class Project < ActiveRecord::Base
def copy_wiki(project)
# Check that the source project has a wiki first
unless project.wiki.nil?
wiki = self.wiki || Wiki.new
self.wiki ||= Wiki.new
wiki.attributes = project.wiki.attributes.dup.except("id", "project_id")
wiki_pages_map = {}
project.wiki.pages.each do |page|
@@ -743,8 +733,6 @@ class Project < ActiveRecord::Base
wiki.pages << new_wiki_page
wiki_pages_map[page.id] = new_wiki_page
end
self.wiki = wiki
wiki.save
# Reproduce page hierarchy
project.wiki.pages.each do |page|
@@ -790,7 +778,7 @@ class Project < ActiveRecord::Base
# get copied before their children
project.issues.find(:all, :order => 'root_id, lft').each do |issue|
new_issue = Issue.new
new_issue.copy_from(issue, :subtasks => false, :link => false)
new_issue.copy_from(issue, :subtasks => false)
new_issue.project = self
# Reassign fixed_versions by name, since names are unique per project
if issue.fixed_version && issue.fixed_version.project == project

View File

@@ -27,7 +27,6 @@ class QueryColumn
self.groupable = name.to_s
end
self.default_order = options[:default_order]
@inline = options.key?(:inline) ? options[:inline] : true
@caption_key = options[:caption] || "field_#{name}"
end
@@ -39,15 +38,11 @@ class QueryColumn
def sortable?
!@sortable.nil?
end
def sortable
@sortable.is_a?(Proc) ? @sortable.call : @sortable
end
def inline?
@inline
end
def value(issue)
issue.send name
end
@@ -63,7 +58,6 @@ class QueryCustomFieldColumn < QueryColumn
self.name = "cf_#{custom_field.id}".to_sym
self.sortable = custom_field.order_statement || false
self.groupable = custom_field.group_statement || false
@inline = true
@cf = custom_field
end
@@ -77,7 +71,7 @@ class QueryCustomFieldColumn < QueryColumn
def value(issue)
cv = issue.custom_values.select {|v| v.custom_field_id == @cf.id}.collect {|v| @cf.cast_value(v.value)}
cv.size > 1 ? cv.sort {|a,b| a.to_s <=> b.to_s} : cv.first
cv.size > 1 ? cv : cv.first
end
def css_classes
@@ -106,25 +100,20 @@ class Query < ActiveRecord::Base
"o" => :label_open_issues,
"c" => :label_closed_issues,
"!*" => :label_none,
"*" => :label_any,
"*" => :label_all,
">=" => :label_greater_or_equal,
"<=" => :label_less_or_equal,
"><" => :label_between,
"<t+" => :label_in_less_than,
">t+" => :label_in_more_than,
"><t+"=> :label_in_the_next_days,
"t+" => :label_in,
"t" => :label_today,
"w" => :label_this_week,
">t-" => :label_less_than_ago,
"<t-" => :label_more_than_ago,
"><t-"=> :label_in_the_past_days,
"t-" => :label_ago,
"~" => :label_contains,
"!~" => :label_not_contains,
"=p" => :label_any_issues_in_project,
"=!p" => :label_any_issues_not_in_project,
"!p" => :label_no_issues_in_project}
"!~" => :label_not_contains }
cattr_reader :operators
@@ -132,13 +121,12 @@ class Query < ActiveRecord::Base
:list_status => [ "o", "=", "!", "c", "*" ],
:list_optional => [ "=", "!", "!*", "*" ],
:list_subprojects => [ "*", "!*", "=" ],
:date => [ "=", ">=", "<=", "><", "<t+", ">t+", "><t+", "t+", "t", "w", ">t-", "<t-", "><t-", "t-", "!*", "*" ],
:date_past => [ "=", ">=", "<=", "><", ">t-", "<t-", "><t-", "t-", "t", "w", "!*", "*" ],
:date => [ "=", ">=", "<=", "><", "<t+", ">t+", "t+", "t", "w", ">t-", "<t-", "t-", "!*", "*" ],
:date_past => [ "=", ">=", "<=", "><", ">t-", "<t-", "t-", "t", "w", "!*", "*" ],
:string => [ "=", "~", "!", "!~", "!*", "*" ],
:text => [ "~", "!~", "!*", "*" ],
:integer => [ "=", ">=", "<=", "><", "!*", "*" ],
:float => [ "=", ">=", "<=", "><", "!*", "*" ],
:relation => ["=", "=p", "=!p", "!p", "!*", "*"]}
:float => [ "=", ">=", "<=", "><", "!*", "*" ] }
cattr_reader :operators_by_filter_type
@@ -159,8 +147,6 @@ class Query < ActiveRecord::Base
QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours"),
QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio", :groupable => true),
QueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on", :default_order => 'desc'),
QueryColumn.new(:relations, :caption => :label_related_issues),
QueryColumn.new(:description, :inline => false)
]
cattr_reader :available_columns
@@ -192,7 +178,7 @@ class Query < ActiveRecord::Base
case operator_for(field)
when "=", ">=", "<=", "><"
add_filter_error(field, :invalid) if values_for(field).detect {|v| v.present? && (!v.match(/^\d{4}-\d{2}-\d{2}$/) || (Date.parse(v) rescue nil).nil?) }
when ">t-", "<t-", "t-", ">t+", "<t+", "t+", "><t+", "><t-"
when ">t-", "<t-", "t-"
add_filter_error(field, :invalid) if values_for(field).detect {|v| v.present? && !v.match(/^\d+$/) }
end
end
@@ -235,57 +221,44 @@ class Query < ActiveRecord::Base
def available_filters
return @available_filters if @available_filters
@available_filters = {
"status_id" => {
:type => :list_status, :order => 0,
:values => IssueStatus.find(:all, :order => 'position').collect{|s| [s.name, s.id.to_s] }
},
"tracker_id" => {
:type => :list, :order => 2, :values => trackers.collect{|s| [s.name, s.id.to_s] }
},
"priority_id" => {
:type => :list, :order => 3, :values => IssuePriority.all.collect{|s| [s.name, s.id.to_s] }
},
"subject" => { :type => :text, :order => 8 },
"created_on" => { :type => :date_past, :order => 9 },
"updated_on" => { :type => :date_past, :order => 10 },
"start_date" => { :type => :date, :order => 11 },
"due_date" => { :type => :date, :order => 12 },
"estimated_hours" => { :type => :float, :order => 13 },
"done_ratio" => { :type => :integer, :order => 14 }
}
IssueRelation::TYPES.each do |relation_type, options|
@available_filters[relation_type] = {
:type => :relation, :order => @available_filters.size + 100,
:label => options[:name]
}
end
@available_filters = { "status_id" => { :type => :list_status, :order => 1, :values => IssueStatus.find(:all, :order => 'position').collect{|s| [s.name, s.id.to_s] } },
"tracker_id" => { :type => :list, :order => 2, :values => trackers.collect{|s| [s.name, s.id.to_s] } },
"priority_id" => { :type => :list, :order => 3, :values => IssuePriority.all.collect{|s| [s.name, s.id.to_s] } },
"subject" => { :type => :text, :order => 8 },
"created_on" => { :type => :date_past, :order => 9 },
"updated_on" => { :type => :date_past, :order => 10 },
"start_date" => { :type => :date, :order => 11 },
"due_date" => { :type => :date, :order => 12 },
"estimated_hours" => { :type => :float, :order => 13 },
"done_ratio" => { :type => :integer, :order => 14 }}
principals = []
if project
principals += project.principals.sort
unless project.leaf?
subprojects = project.descendants.visible.all
if subprojects.any?
@available_filters["subproject_id"] = {
:type => :list_subprojects, :order => 13,
:values => subprojects.collect{|s| [s.name, s.id.to_s] }
}
@available_filters["subproject_id"] = { :type => :list_subprojects, :order => 13, :values => subprojects.collect{|s| [s.name, s.id.to_s] } }
principals += Principal.member_of(subprojects)
end
end
else
all_projects = Project.visible.all
if all_projects.any?
# members of visible projects
principals += Principal.member_of(all_projects)
# project filter
project_values = []
if User.current.logged? && User.current.memberships.any?
project_values << ["<< #{l(:label_my_projects).downcase} >>", "mine"]
end
project_values += all_projects_values
@available_filters["project_id"] = {
:type => :list, :order => 1, :values => project_values
} unless project_values.empty?
Project.project_tree(all_projects) do |p, level|
prefix = (level > 0 ? ('--' * level + ' ') : '')
project_values << ["#{prefix}#{p.name}", p.id.to_s]
end
@available_filters["project_id"] = { :type => :list, :order => 1, :values => project_values} unless project_values.empty?
end
end
principals.uniq!
@@ -294,88 +267,63 @@ class Query < ActiveRecord::Base
assigned_to_values = []
assigned_to_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
assigned_to_values += (Setting.issue_group_assignment? ?
principals : users).collect{|s| [s.name, s.id.to_s] }
@available_filters["assigned_to_id"] = {
:type => :list_optional, :order => 4, :values => assigned_to_values
} unless assigned_to_values.empty?
assigned_to_values += (Setting.issue_group_assignment? ? principals : users).collect{|s| [s.name, s.id.to_s] }
@available_filters["assigned_to_id"] = { :type => :list_optional, :order => 4, :values => assigned_to_values } unless assigned_to_values.empty?
author_values = []
author_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
author_values += users.collect{|s| [s.name, s.id.to_s] }
@available_filters["author_id"] = {
:type => :list, :order => 5, :values => author_values
} unless author_values.empty?
@available_filters["author_id"] = { :type => :list, :order => 5, :values => author_values } unless author_values.empty?
group_values = Group.all.collect {|g| [g.name, g.id.to_s] }
@available_filters["member_of_group"] = {
:type => :list_optional, :order => 6, :values => group_values
} unless group_values.empty?
@available_filters["member_of_group"] = { :type => :list_optional, :order => 6, :values => group_values } unless group_values.empty?
role_values = Role.givable.collect {|r| [r.name, r.id.to_s] }
@available_filters["assigned_to_role"] = {
:type => :list_optional, :order => 7, :values => role_values
} unless role_values.empty?
@available_filters["assigned_to_role"] = { :type => :list_optional, :order => 7, :values => role_values } unless role_values.empty?
if User.current.logged?
@available_filters["watcher_id"] = {
:type => :list, :order => 15, :values => [["<< #{l(:label_me)} >>", "me"]]
}
@available_filters["watcher_id"] = { :type => :list, :order => 15, :values => [["<< #{l(:label_me)} >>", "me"]] }
end
if project
# project specific filters
categories = project.issue_categories.all
unless categories.empty?
@available_filters["category_id"] = {
:type => :list_optional, :order => 6,
:values => categories.collect{|s| [s.name, s.id.to_s] }
}
@available_filters["category_id"] = { :type => :list_optional, :order => 6, :values => categories.collect{|s| [s.name, s.id.to_s] } }
end
versions = project.shared_versions.all
unless versions.empty?
@available_filters["fixed_version_id"] = {
:type => :list_optional, :order => 7,
:values => versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] }
}
@available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] } }
end
add_custom_fields_filters(project.all_issue_custom_fields)
else
# global filters for cross project issue list
system_shared_versions = Version.visible.find_all_by_sharing('system')
unless system_shared_versions.empty?
@available_filters["fixed_version_id"] = {
:type => :list_optional, :order => 7,
:values => system_shared_versions.sort.collect{|s|
["#{s.project.name} - #{s.name}", s.id.to_s]
}
}
@available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => system_shared_versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] } }
end
add_custom_fields_filters(
IssueCustomField.find(:all,
:conditions => {
:is_filter => true,
:is_for_all => true
}))
add_custom_fields_filters(IssueCustomField.find(:all, :conditions => {:is_filter => true, :is_for_all => true}))
end
add_associations_custom_fields_filters :project, :author, :assigned_to, :fixed_version
if User.current.allowed_to?(:set_issues_private, nil, :global => true) ||
User.current.allowed_to?(:set_own_issues_private, nil, :global => true)
@available_filters["is_private"] = {
:type => :list, :order => 16,
:values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]]
}
@available_filters["is_private"] = { :type => :list, :order => 15, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]] }
end
Tracker.disabled_core_fields(trackers).each {|field|
@available_filters.delete field
}
@available_filters.each do |field, options|
options[:name] ||= l(options[:label] || "field_#{field}".gsub(/_id$/, ''))
options[:name] ||= l("field_#{field}".gsub(/_id$/, ''))
end
@available_filters
end
# Returns a representation of the available filters for JSON serialization
# Returns a representation of the available filters for JSON serialization
def available_filters_as_json
json = {}
available_filters.each do |field, options|
@@ -384,21 +332,6 @@ class Query < ActiveRecord::Base
json
end
def all_projects
@all_projects ||= Project.visible.all
end
def all_projects_values
return @all_projects_values if @all_projects_values
values = []
Project.project_tree(all_projects) do |p, level|
prefix = (level > 0 ? ('--' * level + ' ') : '')
values << ["#{prefix}#{p.name}", p.id.to_s]
end
@all_projects_values = values
end
def add_filter(field, operator, values)
# values must be an array
return unless values.nil? || values.is_a?(Array)
@@ -518,22 +451,6 @@ class Query < ActiveRecord::Base
end.compact
end
def inline_columns
columns.select(&:inline?)
end
def block_columns
columns.reject(&:inline?)
end
def available_inline_columns
available_columns.select(&:inline?)
end
def available_block_columns
available_columns.reject(&:inline?)
end
def default_columns_names
@default_columns_names ||= begin
default_columns = Setting.issue_list_default_columns.map(&:to_sym)
@@ -567,7 +484,7 @@ class Query < ActiveRecord::Base
if arg.is_a?(Hash)
arg = arg.keys.sort.collect {|k| arg[k]}
end
c = arg.select {|k,o| !k.to_s.blank?}.slice(0,3).collect {|k,o| [k.to_s, (o == 'desc' || o == false) ? 'desc' : 'asc']}
c = arg.select {|k,o| !k.to_s.blank?}.slice(0,3).collect {|k,o| [k.to_s, o == 'desc' ? o : 'asc']}
write_attribute(:sort_criteria, c)
end
@@ -583,17 +500,12 @@ class Query < ActiveRecord::Base
sort_criteria && sort_criteria[arg] && sort_criteria[arg].last
end
def sort_criteria_order_for(key)
sort_criteria.detect {|k, order| key.to_s == k}.try(:last)
end
# Returns the SQL sort order that should be prepended for grouping
def group_by_sort_order
if grouped? && (column = group_by_column)
order = sort_criteria_order_for(column.name) || column.default_order
column.sortable.is_a?(Array) ?
column.sortable.collect {|s| "#{s} #{order}"}.join(',') :
"#{column.sortable} #{order}"
column.sortable.collect {|s| "#{s} #{column.default_order}"}.join(',') :
"#{column.sortable} #{column.default_order}"
end
end
@@ -723,9 +635,6 @@ class Query < ActiveRecord::Base
if has_column?(:spent_hours)
Issue.load_visible_spent_hours(issues)
end
if has_column?(:relations)
Issue.load_visible_relations(issues)
end
issues
rescue ::ActiveRecord::StatementInvalid => e
raise StatementInvalid.new(e.message)
@@ -802,10 +711,10 @@ class Query < ActiveRecord::Base
"(#{nl} #{Issue.table_name}.assigned_to_id #{sw} IN (SELECT DISTINCT #{Member.table_name}.user_id FROM #{Member.table_name}" +
" WHERE #{Member.table_name}.project_id = #{Issue.table_name}.project_id))"
when "=", "!"
role_cond = value.any? ?
role_cond = value.any? ?
"#{MemberRole.table_name}.role_id IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")" :
"1=0"
sw = operator == "!" ? 'NOT' : ''
nl = operator == "!" ? "#{Issue.table_name}.assigned_to_id IS NULL OR" : ''
"(#{nl} #{Issue.table_name}.assigned_to_id #{sw} IN (SELECT DISTINCT #{Member.table_name}.user_id FROM #{Member.table_name}, #{MemberRole.table_name}" +
@@ -820,42 +729,6 @@ class Query < ActiveRecord::Base
"#{Issue.table_name}.is_private #{op} (#{va})"
end
def sql_for_relations(field, operator, value, options={})
relation_options = IssueRelation::TYPES[field]
return relation_options unless relation_options
relation_type = field
join_column, target_join_column = "issue_from_id", "issue_to_id"
if relation_options[:reverse] || options[:reverse]
relation_type = relation_options[:reverse] || relation_type
join_column, target_join_column = target_join_column, join_column
end
sql = case operator
when "*", "!*"
op = (operator == "*" ? 'IN' : 'NOT IN')
"#{Issue.table_name}.id #{op} (SELECT DISTINCT #{IssueRelation.table_name}.#{join_column} FROM #{IssueRelation.table_name} WHERE #{IssueRelation.table_name}.relation_type = '#{connection.quote_string(relation_type)}')"
when "=", "!"
op = (operator == "=" ? 'IN' : 'NOT IN')
"#{Issue.table_name}.id #{op} (SELECT DISTINCT #{IssueRelation.table_name}.#{join_column} FROM #{IssueRelation.table_name} WHERE #{IssueRelation.table_name}.relation_type = '#{connection.quote_string(relation_type)}' AND #{IssueRelation.table_name}.#{target_join_column} = #{value.first.to_i})"
when "=p", "=!p", "!p"
op = (operator == "!p" ? 'NOT IN' : 'IN')
comp = (operator == "=!p" ? '<>' : '=')
"#{Issue.table_name}.id #{op} (SELECT DISTINCT #{IssueRelation.table_name}.#{join_column} FROM #{IssueRelation.table_name}, #{Issue.table_name} relissues WHERE #{IssueRelation.table_name}.relation_type = '#{connection.quote_string(relation_type)}' AND #{IssueRelation.table_name}.#{target_join_column} = relissues.id AND relissues.project_id #{comp} #{value.first.to_i})"
end
if relation_options[:sym] == field && !options[:reverse]
sqls = [sql, sql_for_relations(field, operator, value, :reverse => true)]
sqls.join(["!", "!*", "!p"].include?(operator) ? " AND " : " OR ")
else
sql
end
end
IssueRelation::TYPES.keys.each do |relation_type|
alias_method "sql_for_#{relation_type}_field".to_sym, :sql_for_relations
end
private
def sql_for_custom_field(field, operator, value, custom_field_id)
@@ -961,35 +834,21 @@ class Query < ActiveRecord::Base
sql = "#{Issue.table_name}.status_id IN (SELECT id FROM #{IssueStatus.table_name} WHERE is_closed=#{connection.quoted_false})" if field == "status_id"
when "c"
sql = "#{Issue.table_name}.status_id IN (SELECT id FROM #{IssueStatus.table_name} WHERE is_closed=#{connection.quoted_true})" if field == "status_id"
when "><t-"
# between today - n days and today
sql = relative_date_clause(db_table, db_field, - value.first.to_i, 0)
when ">t-"
# >= today - n days
sql = relative_date_clause(db_table, db_field, - value.first.to_i, nil)
sql = relative_date_clause(db_table, db_field, - value.first.to_i, 0)
when "<t-"
# <= today - n days
sql = relative_date_clause(db_table, db_field, nil, - value.first.to_i)
when "t-"
# = n days in past
sql = relative_date_clause(db_table, db_field, - value.first.to_i, - value.first.to_i)
when "><t+"
# between today and today + n days
sql = relative_date_clause(db_table, db_field, 0, value.first.to_i)
when ">t+"
# >= today + n days
sql = relative_date_clause(db_table, db_field, value.first.to_i, nil)
when "<t+"
# <= today + n days
sql = relative_date_clause(db_table, db_field, nil, value.first.to_i)
sql = relative_date_clause(db_table, db_field, 0, value.first.to_i)
when "t+"
# = today + n days
sql = relative_date_clause(db_table, db_field, value.first.to_i, value.first.to_i)
when "t"
# = today
sql = relative_date_clause(db_table, db_field, 0, 0)
when "w"
# = this week
first_day_of_week = l(:general_first_day_of_week).to_i
day_of_week = Date.today.cwday
days_ago = (day_of_week >= first_day_of_week ? day_of_week - first_day_of_week : day_of_week + 7 - first_day_of_week)
@@ -1039,11 +898,7 @@ class Query < ActiveRecord::Base
filter_id = "#{assoc}.#{filter_id}"
filter_name = l("label_attribute_of_#{assoc}", :name => filter_name)
end
@available_filters[filter_id] = options.merge({
:name => filter_name,
:format => field.field_format,
:field => field
})
@available_filters[filter_id] = options.merge({ :name => filter_name, :format => field.field_format })
end
end

View File

@@ -20,7 +20,7 @@ class ScmFetchError < Exception; end
class Repository < ActiveRecord::Base
include Redmine::Ciphering
include Redmine::SafeAttributes
# Maximum length for repository identifiers
IDENTIFIER_MAX_LENGTH = 255
@@ -418,7 +418,7 @@ class Repository < ActiveRecord::Base
# Deletes repository data
def clear_changesets
cs = Changeset.table_name
cs = Changeset.table_name
ch = Change.table_name
ci = "#{table_name_prefix}changesets_issues#{table_name_suffix}"
cp = "#{table_name_prefix}changeset_parents#{table_name_suffix}"
@@ -427,9 +427,5 @@ class Repository < ActiveRecord::Base
connection.delete("DELETE FROM #{ci} WHERE #{ci}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})")
connection.delete("DELETE FROM #{cp} WHERE #{cp}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})")
connection.delete("DELETE FROM #{cs} WHERE #{cs}.repository_id = #{id}")
clear_extra_info_of_changesets
end
def clear_extra_info_of_changesets
end
end

View File

@@ -37,28 +37,7 @@ class Repository::Bazaar < Repository
'Bazaar'
end
def entry(path=nil, identifier=nil)
scm.bzr_path_encodig = log_encoding
scm.entry(path, identifier)
end
def cat(path, identifier=nil)
scm.bzr_path_encodig = log_encoding
scm.cat(path, identifier)
end
def annotate(path, identifier=nil)
scm.bzr_path_encodig = log_encoding
scm.annotate(path, identifier)
end
def diff(path, rev, rev_to)
scm.bzr_path_encodig = log_encoding
scm.diff(path, rev, rev_to)
end
def entries(path=nil, identifier=nil)
scm.bzr_path_encodig = log_encoding
entries = scm.entries(path, identifier)
if entries
entries.each do |e|
@@ -89,7 +68,6 @@ class Repository::Bazaar < Repository
end
def fetch_changesets
scm.bzr_path_encodig = log_encoding
scm_info = scm.info
if scm_info
# latest revision found in database
@@ -102,7 +80,7 @@ class Repository::Bazaar < Repository
while (identifier_from <= scm_revision)
# loads changesets by batches of 200
identifier_to = [identifier_from + 199, scm_revision].min
revisions = scm.revisions('', identifier_to, identifier_from)
revisions = scm.revisions('', identifier_to, identifier_from, :with_paths => true)
transaction do
revisions.reverse_each do |revision|
changeset = Changeset.create(:repository => self,

View File

@@ -255,15 +255,4 @@ class Repository::Git < Repository
:order => 'committed_on DESC'
)
end
def clear_extra_info_of_changesets
return if extra_info.nil?
v = extra_info["extra_report_last_commit"]
write_attribute(:extra_info, nil)
h = {}
h["extra_report_last_commit"] = v
merge_extra_info(h)
self.save
end
private :clear_extra_info_of_changesets
end

View File

@@ -133,11 +133,6 @@ class Role < ActiveRecord::Base
self.builtin != 0
end
# Return true if the role is the anonymous role
def anonymous?
builtin == 2
end
# Return true if the role is a project member role
def member?
!self.builtin?

View File

@@ -28,41 +28,11 @@ class User < Principal
# Different ways of displaying/sorting users
USER_FORMATS = {
:firstname_lastname => {
:string => '#{firstname} #{lastname}',
:order => %w(firstname lastname id),
:setting_order => 1
},
:firstname_lastinitial => {
:string => '#{firstname} #{lastname.to_s.chars.first}.',
:order => %w(firstname lastname id),
:setting_order => 2
},
:firstname => {
:string => '#{firstname}',
:order => %w(firstname id),
:setting_order => 3
},
:lastname_firstname => {
:string => '#{lastname} #{firstname}',
:order => %w(lastname firstname id),
:setting_order => 4
},
:lastname_coma_firstname => {
:string => '#{lastname}, #{firstname}',
:order => %w(lastname firstname id),
:setting_order => 5
},
:lastname => {
:string => '#{lastname}',
:order => %w(lastname id),
:setting_order => 6
},
:username => {
:string => '#{login}',
:order => %w(login id),
:setting_order => 7
},
:firstname_lastname => {:string => '#{firstname} #{lastname}', :order => %w(firstname lastname id)},
:firstname => {:string => '#{firstname}', :order => %w(firstname id)},
:lastname_firstname => {:string => '#{lastname} #{firstname}', :order => %w(lastname firstname id)},
:lastname_coma_firstname => {:string => '#{lastname}, #{firstname}', :order => %w(lastname firstname id)},
:username => {:string => '#{login}', :order => %w(login id)},
}
MAIL_NOTIFICATION_OPTIONS = [
@@ -97,7 +67,7 @@ class User < Principal
validates_presence_of :login, :firstname, :lastname, :mail, :if => Proc.new { |user| !user.is_a?(AnonymousUser) }
validates_uniqueness_of :login, :if => Proc.new { |user| user.login_changed? && user.login.present? }, :case_sensitive => false
validates_uniqueness_of :mail, :if => Proc.new { |user| user.mail_changed? && user.mail.present? }, :case_sensitive => false
validates_uniqueness_of :mail, :if => Proc.new { |user| !user.mail.blank? }, :case_sensitive => false
# Login must contain lettres, numbers, underscores only
validates_format_of :login, :with => /^[a-z0-9_\-@\.]*$/i
validates_length_of :login, :maximum => LOGIN_LENGTH_LIMIT
@@ -114,11 +84,11 @@ class User < Principal
scope :in_group, lambda {|group|
group_id = group.is_a?(Group) ? group.id : group.to_i
where("#{User.table_name}.id IN (SELECT gu.user_id FROM #{table_name_prefix}groups_users#{table_name_suffix} gu WHERE gu.group_id = ?)", group_id)
{ :conditions => ["#{User.table_name}.id IN (SELECT gu.user_id FROM #{table_name_prefix}groups_users#{table_name_suffix} gu WHERE gu.group_id = ?)", group_id] }
}
scope :not_in_group, lambda {|group|
group_id = group.is_a?(Group) ? group.id : group.to_i
where("#{User.table_name}.id NOT IN (SELECT gu.user_id FROM #{table_name_prefix}groups_users#{table_name_suffix} gu WHERE gu.group_id = ?)", group_id)
{ :conditions => ["#{User.table_name}.id NOT IN (SELECT gu.user_id FROM #{table_name_prefix}groups_users#{table_name_suffix} gu WHERE gu.group_id = ?)", group_id] }
}
def set_mail_notification
@@ -360,10 +330,10 @@ class User < Principal
# version. Exact matches will be given priority.
def self.find_by_login(login)
# First look for an exact match
user = where(:login => login).all.detect {|u| u.login == login}
user = all(:conditions => {:login => login}).detect {|u| u.login == login}
unless user
# Fail over to case-insensitive if none was found
user = where("LOWER(login) = ?", login.to_s.downcase).first
user = first(:conditions => ["LOWER(login) = ?", login.to_s.downcase])
end
user
end
@@ -380,7 +350,7 @@ class User < Principal
# Makes find_by_mail case-insensitive
def self.find_by_mail(mail)
where("LOWER(mail) = ?", mail.to_s.downcase).first
find(:first, :conditions => ["LOWER(mail) = ?", mail.to_s.downcase])
end
# Returns true if the default admin account can no longer be used
@@ -392,17 +362,6 @@ class User < Principal
name
end
CSS_CLASS_BY_STATUS = {
STATUS_ANONYMOUS => 'anon',
STATUS_ACTIVE => 'active',
STATUS_REGISTERED => 'registered',
STATUS_LOCKED => 'locked'
}
def css_classes
"user #{CSS_CLASS_BY_STATUS[status]}"
end
# Returns the current day according to user's time zone
def today
if time_zone.nil?
@@ -503,17 +462,17 @@ class User < Principal
roles = roles_for_project(context)
return false unless roles
roles.any? {|role|
roles.detect {|role|
(context.is_public? || role.member?) &&
role.allowed_to?(action) &&
(block_given? ? yield(role, self) : true)
}
elsif context && context.is_a?(Array)
if context.empty?
false
else
# Authorize if user is authorized on every element of the array
context.map {|project| allowed_to?(action, project, options, &block)}.reduce(:&)
# Authorize if user is authorized on every element of the array
context.map do |project|
allowed_to?(action, project, options, &block)
end.inject do |memo,allowed|
memo && allowed
end
elsif options[:global]
# Admin users are always authorized
@@ -522,7 +481,7 @@ class User < Principal
# authorize if user has at least one role that has this permission
roles = memberships.collect {|m| m.roles}.flatten.uniq
roles << (self.logged? ? Role.non_member : Role.anonymous)
roles.any? {|role|
roles.detect {|role|
role.allowed_to?(action) &&
(block_given? ? yield(role, self) : true)
}
@@ -540,7 +499,7 @@ class User < Principal
# Returns true if the user is allowed to delete his own account
def own_account_deletable?
Setting.unsubscribe? &&
(!admin? || User.active.where("admin = ? AND id <> ?", true, id).exists?)
(!admin? || User.active.first(:conditions => ["admin = ? AND id <> ?", true, id]).present?)
end
safe_attributes 'login',
@@ -611,7 +570,7 @@ class User < Principal
# Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only
# one anonymous user per database.
def self.anonymous
anonymous_user = AnonymousUser.first
anonymous_user = AnonymousUser.find(:first)
if anonymous_user.nil?
anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :mail => '', :login => '', :status => 0)
raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
@@ -624,11 +583,11 @@ class User < Principal
# This method is used in the SaltPasswords migration and is to be kept as is
def self.salt_unsalted_passwords!
transaction do
User.where("salt IS NULL OR salt = ''").find_each do |user|
User.find_each(:conditions => "salt IS NULL OR salt = ''") do |user|
next if user.hashed_password.blank?
salt = User.generate_salt
hashed_password = User.hash_password("#{salt}#{user.hashed_password}")
User.where(:id => user.id).update_all(:salt => salt, :hashed_password => hashed_password)
User.update_all("salt = '#{salt}', hashed_password = '#{hashed_password}'", ["id = ?", user.id] )
end
end
end

View File

@@ -44,7 +44,7 @@ class UserPreference < ActiveRecord::Base
if attribute_present? attr_name
super
else
h = (read_attribute(:others) || {}).dup
h = read_attribute(:others).dup || {}
h.update(attr_name => value)
write_attribute(:others, h)
value

View File

@@ -33,13 +33,11 @@ class Version < ActiveRecord::Base
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 :sharing, :in => VERSION_SHARINGS
validate :validate_version
scope :named, lambda {|arg| where("LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip)}
scope :open, where(:status => 'open')
scope :visible, lambda {|*args|
includes(:project).where(Project.allowed_to_condition(args.first || User.current, :view_issues))
}
scope :named, lambda {|arg| { :conditions => ["LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip]}}
scope :open, :conditions => {:status => 'open'}
scope :visible, lambda {|*args| { :include => :project,
:conditions => Project.allowed_to_condition(args.first || User.current, :view_issues) } }
safe_attributes 'name',
'description',
@@ -80,7 +78,7 @@ class Version < ActiveRecord::Base
# Returns the total reported time for this version
def spent_hours
@spent_hours ||= TimeEntry.joins(:issue).where("#{Issue.table_name}.fixed_version_id = ?", id).sum(:hours).to_f
@spent_hours ||= TimeEntry.sum(:hours, :joins => :issue, :conditions => ["#{Issue.table_name}.fixed_version_id = ?", id]).to_f
end
def closed?
@@ -93,7 +91,7 @@ class Version < ActiveRecord::Base
# Returns true if the version is completed: due date reached and no open issues
def completed?
effective_date && (effective_date < Date.today) && (open_issues_count == 0)
effective_date && (effective_date <= Date.today) && (open_issues_count == 0)
end
def behind_schedule?
@@ -269,16 +267,12 @@ class Version < ActiveRecord::Base
if issues_count > 0
ratio = open ? 'done_ratio' : 100
done = fixed_issues.open(open).sum("COALESCE(estimated_hours, #{estimated_average}) * #{ratio}").to_f
done = fixed_issues.sum("COALESCE(estimated_hours, #{estimated_average}) * #{ratio}",
:joins => :status,
:conditions => ["#{IssueStatus.table_name}.is_closed = ?", !open]).to_f
progress = done / (estimated_average * issues_count)
end
progress
end
end
def validate_version
if effective_date.nil? && @attributes['effective_date'].present?
errors.add :effective_date, :not_a_date
end
end
end

View File

@@ -73,8 +73,6 @@ class WikiContent < ActiveRecord::Base
"LEFT JOIN #{Wiki.table_name} ON #{Wiki.table_name}.id = #{WikiPage.table_name}.wiki_id " +
"LEFT JOIN #{Project.table_name} ON #{Project.table_name}.id = #{Wiki.table_name}.project_id"}
after_destroy :page_update_after_destroy
def text=(plain)
case Setting.wiki_compression
when 'gzip'
@@ -117,31 +115,10 @@ class WikiContent < ActiveRecord::Base
# Returns the previous version or nil
def previous
@previous ||= WikiContent::Version.
reorder('version DESC').
includes(:author).
where("wiki_content_id = ? AND version < ?", wiki_content_id, version).first
end
# Returns the next version or nil
def next
@next ||= WikiContent::Version.
reorder('version ASC').
includes(:author).
where("wiki_content_id = ? AND version > ?", wiki_content_id, version).first
end
private
# Updates page's content if the latest version is removed
# or destroys the page if it was the only version
def page_update_after_destroy
latest = page.content.versions.reorder("#{self.class.table_name}.version DESC").first
if latest && page.content.version != latest.version
raise ActiveRecord::Rollback unless page.content.revert_to!(latest)
elsif latest.nil?
raise ActiveRecord::Rollback unless page.destroy
end
@previous ||= WikiContent::Version.find(:first,
:order => 'version DESC',
:include => :author,
:conditions => ["wiki_content_id = ? AND version < ?", wiki_content_id, version])
end
end
end

View File

@@ -50,14 +50,14 @@ class WikiPage < ActiveRecord::Base
# eager load information about last updates, without loading text
scope :with_updated_on, {
:select => "#{WikiPage.table_name}.*, #{WikiContent.table_name}.updated_on, #{WikiContent.table_name}.version",
:select => "#{WikiPage.table_name}.*, #{WikiContent.table_name}.updated_on",
:joins => "LEFT JOIN #{WikiContent.table_name} ON #{WikiContent.table_name}.page_id = #{WikiPage.table_name}.id"
}
# Wiki pages that are protected by default
DEFAULT_PROTECTED_PAGES = %w(sidebar)
safe_attributes 'parent_id', 'parent_title',
safe_attributes 'parent_id',
:if => lambda {|page, user| page.new_record? || user.allowed_to?(:rename_wiki_pages, page.project)}
def initialize(attributes=nil, *args)
@@ -111,13 +111,11 @@ class WikiPage < ActiveRecord::Base
def diff(version_to=nil, version_from=nil)
version_to = version_to ? version_to.to_i : self.content.version
content_to = content.versions.find_by_version(version_to)
content_from = version_from ? content.versions.find_by_version(version_from.to_i) : content_to.try(:previous)
return nil unless content_to && content_from
version_from = version_from ? version_from.to_i : version_to - 1
version_to, version_from = version_from, version_to unless version_from < version_to
if content_from.version > content_to.version
content_to, content_from = content_from, content_to
end
content_to = content.versions.find_by_version(version_to)
content_from = content.versions.find_by_version(version_from)
(content_to && content_from) ? WikiDiff.new(content_to, content_from) : nil
end
@@ -174,21 +172,6 @@ class WikiPage < ActiveRecord::Base
self.parent = parent_page
end
# Saves the page and its content if text was changed
def save_with_content
ret = nil
transaction do
if new_record?
# Rails automatically saves associated content
ret = save
else
ret = save && (content.text_changed? ? content.save : true)
end
raise ActiveRecord::Rollback unless ret
end
ret
end
protected
def validate_parent_title

View File

@@ -5,7 +5,7 @@
<table>
<tr>
<td align="right"><label for="username"><%=l(:field_login)%>:</label></td>
<td align="left"><%= text_field_tag 'username', params[:username], :tabindex => '1' %></td>
<td align="left"><%= text_field_tag 'username', nil, :tabindex => '1' %></td>
</tr>
<tr>
<td align="right"><label for="password"><%=l(:field_password)%>:</label></td>
@@ -36,12 +36,7 @@
</td>
</tr>
</table>
<%= javascript_tag "$('#username').focus();" %>
<% end %>
</div>
<%= call_hook :view_account_login_bottom %>
<% if params[:username].present? %>
<%= javascript_tag "$('#password').focus();" %>
<% else %>
<%= javascript_tag "$('#username').focus();" %>
<% end %>

View File

@@ -6,14 +6,16 @@
<p><%= link_to_attachment @attachment, :text => l(:button_download), :download => true -%>
<span class="size">(<%= number_to_human_size @attachment.filesize %>)</span></p>
</div>
<p>
<%= form_tag({}, :method => 'get') do %>
<p>
<%= l(:label_view_diff) %>:
<label><%= radio_button_tag 'type', 'inline', @diff_type != 'sbs', :onchange => "this.form.submit()" %> <%= l(:label_diff_inline) %></label>
<label><%= radio_button_tag 'type', 'sbs', @diff_type == 'sbs', :onchange => "this.form.submit()" %> <%= l(:label_diff_side_by_side) %></label>
</p>
<label><%= l(:label_view_diff) %></label>
<%= select_tag 'type',
options_for_select(
[[l(:label_diff_inline), "inline"], [l(:label_diff_side_by_side), "sbs"]], @diff_type),
:onchange => "if (this.value != '') {this.form.submit()}" %>
<% end %>
<%= render :partial => 'common/diff', :locals => {:diff => @diff, :diff_type => @diff_type, :diff_style => nil} %>
</p>
<%= render :partial => 'common/diff', :locals => {:diff => @diff, :diff_type => @diff_type} %>
<% html_title @attachment.filename %>

View File

@@ -1,7 +1,6 @@
<% diff = Redmine::UnifiedDiff.new(
diff, :type => diff_type,
:max_lines => Setting.diff_max_lines_displayed.to_i,
:style => diff_style) -%>
:max_lines => Setting.diff_max_lines_displayed.to_i) -%>
<% diff.each do |table_file| -%>
<div class="autoscroll">

View File

@@ -3,6 +3,6 @@
<% if @message.present? %>
<p id="errorExplanation"><%=h @message %></p>
<% end %>
<p><a href="javascript:history.back()"><%= l(:button_back) %></a></p>
<p><a href="javascript:history.back()">Back</a></p>
<% html_title @status %>

View File

@@ -111,7 +111,7 @@
<li><%= bulk_update_custom_field_context_menu_link(field, text, value || text) %></li>
<% end %>
<% unless field.is_required? %>
<li><%= bulk_update_custom_field_context_menu_link(field, l(:label_none), '__none__') %></li>
<li><%= bulk_update_custom_field_context_menu_link(field, l(:label_none), '') %></li>
<% end %>
</ul>
</li>

View File

@@ -1,9 +0,0 @@
api.array @klass.name.underscore.pluralize do
@enumerations.each do |enumeration|
api.__send__ @klass.name.underscore do
api.id enumeration.id
api.name enumeration.name
api.is_default enumeration.is_default
end
end
end

View File

@@ -201,10 +201,9 @@
style += "width: #{width}px;"
style += "height: #{height}px;"
style += "font-size:0.7em;"
clss = "gantt_hdr"
clss << " nwday" if @gantt.non_working_week_days.include?(wday)
style += 'background:#f1f1f1;' if wday > 5
%>
<%= content_tag(:div, :style => style, :class => clss) do %>
<%= content_tag(:div, :style => style, :class => "gantt_hdr") do %>
<%= day_letter(wday) %>
<% end %>
<%

View File

@@ -6,7 +6,7 @@
<%= l(:field_delay) %>: <%= f.text_field :delay, :size => 3 %> <%= l(:label_day_plural) %>
</span>
<%= submit_tag l(:button_add) %>
<%= link_to_function l(:button_cancel), '$("#new-relation-form").hide();'%>
<%= toggle_link l(:button_cancel), 'new-relation-form'%>
</p>
<%= javascript_tag "observeAutocompleteField('relation_issue_to_id', '#{escape_javascript auto_complete_issues_path(:project_id => @project, :scope => (Setting.cross_project_issue_relations? ? 'all' : nil))}')" %>

View File

@@ -1,5 +1,5 @@
<div class="contextual">
<%= link_to_if_authorized(l(:button_update), {:controller => 'issues', :action => 'edit', :id => @issue }, :onclick => 'showAndScrollTo("update", "issue_notes"); return false;', :class => 'icon icon-edit', :accesskey => accesskey(:edit)) %>
<%= link_to_if_authorized(l(:button_update), {:controller => 'issues', :action => 'edit', :id => @issue }, :onclick => 'showAndScrollTo("update", "notes"); return false;', :class => 'icon icon-edit', :accesskey => accesskey(:edit)) %>
<%= link_to l(:button_log_time), new_issue_time_entry_path(@issue), :class => 'icon icon-time-add' if User.current.allowed_to?(:log_time, @project) %>
<%= watcher_tag(@issue, User.current) %>
<%= link_to_if_authorized l(:button_copy), {:controller => 'issues', :action => 'new', :project_id => @project, :copy_from => @issue}, :class => 'icon icon-copy' %>

View File

@@ -2,7 +2,7 @@
<div class="splitcontent">
<div class="splitcontentleft">
<% if @issue.safe_attribute?('status_id') && @allowed_statuses.present? %>
<% if @issue.safe_attribute? 'status_id' %>
<p><%= f.select :status_id, (@allowed_statuses.collect {|p| [p.name, p.id]}), {:required => true},
:onchange => "updateIssueFrom('#{escape_javascript project_issue_form_path(@project, :id => @issue, :format => 'js')}')" %></p>
@@ -43,7 +43,7 @@
<div class="splitcontentright">
<% if @issue.safe_attribute? 'parent_issue_id' %>
<p id="parent_issue"><%= f.text_field :parent_issue_id, :size => 10, :required => @issue.required_attribute?('parent_issue_id') %></p>
<%= javascript_tag "observeAutocompleteField('issue_parent_issue_id', '#{escape_javascript auto_complete_issues_path}')" %>
<%= javascript_tag "observeAutocompleteField('issue_parent_issue_id', '#{escape_javascript auto_complete_issues_path(:project_id => @issue.project)}')" %>
<% end %>
<% if @issue.safe_attribute? 'start_date' %>

View File

@@ -18,7 +18,7 @@
</div>
<p>
<label><%= radio_button_tag 'conflict_resolution', 'overwrite' %> <%= l(:text_issue_conflict_resolution_overwrite) %></label><br />
<% if @issue.notes.present? %>
<% if @notes.present? %>
<label><%= radio_button_tag 'conflict_resolution', 'add_notes' %> <%= l(:text_issue_conflict_resolution_add_notes) %></label><br />
<% end %>
<label><%= radio_button_tag 'conflict_resolution', 'cancel' %> <%= l(:text_issue_conflict_resolution_cancel, :link => link_to_issue(@issue, :subject => false)).html_safe %></label>

View File

@@ -27,18 +27,11 @@
<% end %>
<fieldset><legend><%= l(:field_notes) %></legend>
<%= f.text_area :notes, :cols => 60, :rows => 10, :class => 'wiki-edit', :no_label => true %>
<%= wikitoolbar_for 'issue_notes' %>
<% if @issue.safe_attribute? 'private_notes' %>
<label for="issue_private_notes"><%= f.check_box :private_notes, :no_label => true %> <%= l(:field_private_notes) %></label>
<% end %>
<%= text_area_tag 'notes', @notes, :cols => 60, :rows => 10, :class => 'wiki-edit' %>
<%= wikitoolbar_for 'notes' %>
<%= call_hook(:view_issues_edit_notes_bottom, { :issue => @issue, :notes => @notes, :form => f }) %>
</fieldset>
<fieldset><legend><%= l(:label_attachment_plural) %></legend>
<p><%= render :partial => 'attachments/form', :locals => {:container => @issue} %></p>
<p><%=l(:label_attachment_plural)%><br /><%= render :partial => 'attachments/form', :locals => {:container => @issue} %></p>
</fieldset>
</div>

View File

@@ -18,7 +18,7 @@
<% end %>
<% if @issue.safe_attribute? 'subject' %>
<p><%= f.text_field :subject, :size => 80, :maxlength => 255, :required => true %></p>
<p><%= f.text_field :subject, :size => 80, :required => true %></p>
<% end %>
<% if @issue.safe_attribute? 'description' %>

View File

@@ -2,30 +2,25 @@
<%= hidden_field_tag 'back_url', url_for(params), :id => nil %>
<div class="autoscroll">
<table class="list issues">
<thead>
<tr>
<th class="checkbox hide-when-print">
<%= link_to image_tag('toggle_check.png'), {},
:onclick => 'toggleIssuesSelection(this); return false;',
:title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}" %>
</th>
<%= sort_header_tag('id', :caption => '#', :default_order => 'desc') %>
<% query.inline_columns.each do |column| %>
<%= column_header(column) %>
<% end %>
</tr>
</thead>
<thead><tr>
<th class="checkbox hide-when-print"><%= link_to image_tag('toggle_check.png'), {}, :onclick => 'toggleIssuesSelection(this); return false;',
:title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}" %>
</th>
<%= sort_header_tag('id', :caption => '#', :default_order => 'desc') %>
<% query.columns.each do |column| %>
<%= column_header(column) %>
<% end %>
</tr></thead>
<% previous_group = false %>
<tbody>
<% issue_list(issues) do |issue, level| -%>
<% if @query.grouped? && (group = @query.group_by_column.value(issue)) != previous_group %>
<% reset_cycle %>
<tr class="group open">
<td colspan="<%= query.inline_columns.size + 2 %>">
<td colspan="<%= query.columns.size + 2 %>">
<span class="expander" onclick="toggleRowGroup(this);">&nbsp;</span>
<%= group.blank? ? l(:label_none) : column_content(@query.group_by_column, issue) %> <span class="count"><%= @issue_count_by_group[group] %></span>
<%= link_to_function("#{l(:button_collapse_all)}/#{l(:button_expand_all)}",
"toggleAllRowGroups(this)", :class => 'toggle-all') %>
<%= group.blank? ? 'None' : column_content(@query.group_by_column, issue) %> <span class="count">(<%= @issue_count_by_group[group] %>)</span>
<%= link_to_function("#{l(:button_collapse_all)}/#{l(:button_expand_all)}", "toggleAllRowGroups(this)", :class => 'toggle-all') %>
</td>
</tr>
<% previous_group = group %>
@@ -33,15 +28,8 @@
<tr id="issue-<%= issue.id %>" class="hascontextmenu <%= cycle('odd', 'even') %> <%= issue.css_classes %> <%= level > 0 ? "idnt idnt-#{level}" : nil %>">
<td class="checkbox hide-when-print"><%= check_box_tag("ids[]", issue.id, false, :id => nil) %></td>
<td class="id"><%= link_to issue.id, issue_path(issue) %></td>
<%= raw query.inline_columns.map {|column| "<td class=\"#{column.css_classes}\">#{column_content(column, issue)}</td>"}.join %>
<%= raw query.columns.map {|column| "<td class=\"#{column.css_classes}\">#{column_content(column, issue)}</td>"}.join %>
</tr>
<% @query.block_columns.each do |column|
if (text = column_content(column, issue)) && text.present? -%>
<tr class="<%= current_cycle %>">
<td colspan="<%= @query.inline_columns.size + 2 %>" class="<%= column.css_classes %>"><%= text %></td>
</tr>
<% end -%>
<% end -%>
<% end -%>
</tbody>
</table>

View File

@@ -1 +1 @@
$('#content').html('<%= escape_javascript(render :template => 'issues/bulk_edit', :formats => [:html]) %>');
$('#content').html('<%= escape_javascript(render :template => 'issues/bulk_edit.html') %>');

View File

@@ -34,10 +34,6 @@
@query.group_by)
) %></td>
</tr>
<tr>
<td><%= l(:button_show) %></td>
<td><%= available_block_columns_tags(@query) %></td>
</tr>
</table>
</div>
</fieldset>
@@ -77,7 +73,7 @@
<label><%= radio_button_tag 'columns', 'all' %> <%= l(:description_all_columns) %></label>
</p>
<p>
<label><%= check_box_tag 'description', '1', @query.has_column?(:description) %> <%= l(:field_description) %></label>
<label><%= check_box_tag 'description', '1' %> <%= l(:field_description) %></label>
</p>
<p class="buttons">
<%= submit_tag l(:button_export), :name => nil, :onclick => "hideModal(this);" %>

View File

@@ -48,7 +48,7 @@ api.issue do
end if include_in_api_response?('changesets') && User.current.allowed_to?(:view_changesets, @project)
api.array :journals do
@journals.each do |journal|
@issue.journals.each do |journal|
api.journal :id => journal.id do
api.user(:id => journal.user_id, :name => journal.user.name) unless journal.user.nil?
api.notes journal.notes

View File

@@ -71,7 +71,6 @@ end %>
<% if @issue.description? || @issue.attachments.any? -%>
<hr />
<% if @issue.description? %>
<div class="description">
<div class="contextual">
<%= link_to l(:button_quote),
{:controller => 'journals', :action => 'new', :id => @issue},
@@ -84,7 +83,6 @@ end %>
<div class="wiki">
<%= textilizable @issue, :description, :attachments => @issue.attachments %>
</div>
</div>
<% end %>
<%= link_to_attachments @issue, :thumbnails => true %>
<% end -%>
@@ -95,7 +93,7 @@ end %>
<hr />
<div id="issue_tree">
<div class="contextual">
<%= link_to_new_subtask(@issue) if User.current.allowed_to?(:manage_subtasks, @project) %>
<%= 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? %>

View File

@@ -1,8 +1,2 @@
$("#journal-<%= @journal.id %>-notes").hide();
if ($("form#journal-<%= @journal.id %>-form").length > 0) {
// journal edit form already loaded
$("#journal-<%= @journal.id %>-form").show();
} else {
$("#journal-<%= @journal.id %>-notes").after('<%= escape_javascript(render :partial => 'notes_form') %>');
}
$("#journal-<%= @journal.id %>-notes").after('<%= escape_javascript(render :partial => 'notes_form') %>');

View File

@@ -1,10 +1,3 @@
$('#issue_notes').val("<%= raw escape_javascript(@content) %>");
<%
# when quoting a private journal, check the private checkbox
if @journal && @journal.private_notes?
%>
$('#issue_private_notes').attr('checked', true);
<% end %>
$('#notes').val("<%= raw escape_javascript(@content) %>");
showAndScrollTo("update", "notes");
$('#notes').scrollTop = $('#notes').scrollHeight - $('#notes').clientHeight;

View File

@@ -18,7 +18,6 @@
<body class="<%=h body_css_classes %>">
<div id="wrapper">
<div id="wrapper2">
<div id="wrapper3">
<div id="top-menu">
<div id="account">
<%= render_menu :account_menu -%>
@@ -63,7 +62,6 @@
<div style="clear:both;"></div>
</div>
</div>
</div>
<div id="ajax-indicator" style="display:none;"><span><%= l(:label_loading) %></span></div>
<div id="ajax-modal" style="display:none;"></div>

View File

@@ -3,7 +3,7 @@
<%= labelled_form_for @news, :html => { :id => 'news-form', :multipart => true, :method => :put } do |f| %>
<%= render :partial => 'form', :locals => { :f => f } %>
<%= submit_tag l(:button_save) %>
<%= preview_link preview_news_path(:project_id => @project, :id => @news), 'news-form' %>
<%= preview_link preview_news_path(:project_id => @project), 'news-form' %>
<% end %>
<div id="preview" class="wiki"></div>

View File

@@ -16,7 +16,7 @@
:html => { :id => 'news-form', :multipart => true, :method => :put } do |f| %>
<%= render :partial => 'form', :locals => { :f => f } %>
<%= submit_tag l(:button_save) %>
<%= preview_link preview_news_path(:project_id => @project, :id => @news), 'news-form' %> |
<%= preview_link preview_news_path(:project_id => @project), 'news-form' %> |
<%= link_to l(:button_cancel), "#", :onclick => '$("#edit-news").hide(); return false;' %>
<% end %>
<div id="preview" class="wiki"></div>

View File

@@ -14,7 +14,7 @@
<tr class="<%= cycle 'odd', 'even' %>">
<td>
<%= link_to repository.identifier,
{:controller => 'repositories', :action => 'show',:id => @project, :repository_id => repository.identifier_param} if repository.identifier.present? %>
{:controller => 'repositories', :action => 'show',:id => @project, :repository_id => repository.identifier_param} if repository.identifier_param.present? %>
</td>
<td align="center"><%= checked_image repository.is_default? %></td>
<td><%=h repository.scm_name %></td>

View File

@@ -4,7 +4,7 @@
<%= label_tag "available_columns", l(:description_available_columns) %>
<br />
<%= select_tag 'available_columns',
options_for_select((query.available_inline_columns - query.columns).collect {|column| [column.caption, column.name]}),
options_for_select((query.available_columns - query.columns).collect {|column| [column.caption, column.name]}),
:multiple => true, :size => 10, :style => "width:150px",
:ondblclick => "moveOptions(this.form.available_columns, this.form.selected_columns);" %>
</td>
@@ -18,7 +18,7 @@
<%= label_tag "selected_columns", l(:description_selected_columns) %>
<br />
<%= select_tag((defined?(tag_name) ? tag_name : 'c[]'),
options_for_select(query.inline_columns.collect {|column| [column.caption, column.name]}),
options_for_select(query.columns.collect {|column| [column.caption, column.name]}),
:id => 'selected_columns', :multiple => true, :size => 10, :style => "width:150px",
:ondblclick => "moveOptions(this.form.selected_columns, this.form.available_columns);") %>
</td>

View File

@@ -1,13 +1,12 @@
<%= javascript_tag do %>
var operatorLabels = <%= raw_json Query.operators_labels %>;
var operatorByType = <%= raw_json Query.operators_by_filter_type %>;
var availableFilters = <%= raw_json query.available_filters_as_json %>;
var labelDayPlural = <%= raw_json l(:label_day_plural) %>;
var allProjects = <%= raw query.all_projects_values.to_json %>;
var operatorLabels = <%= raw Query.operators_labels.to_json %>;
var operatorByType = <%= raw Query.operators_by_filter_type.to_json %>;
var availableFilters = <%= raw query.available_filters_as_json.to_json %>;
var labelDayPlural = "<%= raw escape_javascript(l(:label_day_plural)) %>";
$(document).ready(function(){
initFilters();
<% query.filters.each do |field, options| %>
addFilter("<%= field %>", <%= raw_json query.operator_for(field) %>, <%= raw_json query.values_for(field) %>);
addFilter("<%= field %>", <%= raw query.operator_for(field).to_json %>, <%= raw query.values_for(field).to_json %>);
<% end %>
});
<% end %>

View File

@@ -21,9 +21,6 @@
<p><label for="query_group_by"><%= l(:field_group_by) %></label>
<%= select 'query', 'group_by', @query.groupable_columns.collect {|c| [c.caption, c.name.to_s]}, :include_blank => true %></p>
<p><label><%= l(:button_show) %></label>
<%= available_block_columns_tags(@query) %></p>
</div>
<fieldset id="filters"><legend><%= l(:label_filter_plural) %></legend>

View File

@@ -1,19 +1,20 @@
<h2><%= l(:label_revision) %> <%= @diff_format_revisions %> <%=h @path %></h2>
<!-- Choose view type -->
<%= form_tag({:action => 'diff', :id => @project,
:repository_id => @repository.identifier_param,
:path => to_path_param(@path), :rev=> @rev}, :method => 'get') do %>
<%= form_tag({:path => to_path_param(@path)}, :method => 'get') do %>
<%= hidden_field_tag('rev', params[:rev]) if params[:rev] %>
<%= hidden_field_tag('rev_to', params[:rev_to]) if params[:rev_to] %>
<p>
<%= l(:label_view_diff) %>:
<label><%= radio_button_tag 'type', 'inline', @diff_type != 'sbs', :onchange => "this.form.submit()" %> <%= l(:label_diff_inline) %></label>
<label><%= radio_button_tag 'type', 'sbs', @diff_type == 'sbs', :onchange => "this.form.submit()" %> <%= l(:label_diff_side_by_side) %></label>
<label><%= l(:label_view_diff) %></label>
<%= select_tag 'type',
options_for_select(
[[l(:label_diff_inline), "inline"], [l(:label_diff_side_by_side), "sbs"]], @diff_type),
:onchange => "if (this.value != '') {this.form.submit()}" %>
</p>
<% end %>
<% cache(@cache_key) do -%>
<%= render :partial => 'common/diff', :locals => {:diff => @diff, :diff_type => @diff_type, :diff_style => @repository.class.scm_name} %>
<%= render :partial => 'common/diff', :locals => {:diff => @diff, :diff_type => @diff_type} %>
<% end -%>
<% other_formats_links do |f| %>

View File

@@ -1 +1 @@
$('#content').html('<%= escape_javascript(render :template => 'repositories/new', :formats => [:html]) %>');
$('#content').html('<%= escape_javascript(render :template => 'repositories/new.html') %>');

View File

@@ -1,10 +1,9 @@
<div class="contextual">
<%= form_tag(
{:controller => 'repositories', :action => 'revision', :id => @project,
:repository_id => @repository.identifier_param},
:method => :get
:repository_id => @repository.identifier_param}
) do %>
<%= l(:label_revision) %>: <%= text_field_tag 'rev', nil, :size => 8 %>
<%= l(:label_revision) %>: <%= text_field_tag 'rev', @rev, :size => 8 %>
<%= submit_tag 'OK' %>
<% end %>
</div>

View File

@@ -1,6 +1,5 @@
<%= error_messages_for 'role' %>
<% unless @role.anonymous? %>
<div class="box tabular">
<% unless @role.builtin? %>
<p><%= f.text_field :name, :required => true %></p>
@@ -12,7 +11,6 @@
<%= select_tag(:copy_workflow_from, content_tag("option") + options_from_collection_for_select(@roles, :id, :name, params[:copy_workflow_from] || @copy_from.try(:id))) %></p>
<% end %>
</div>
<% end %>
<h3><%= l(:label_permissions) %></h3>
<div class="box tabular" id="permissions">

View File

@@ -1,9 +0,0 @@
api.role do
api.id @role.id
api.name @role.name
api.array :permissions do
@role.permissions.each do |perm|
api.permission(perm.to_s)
end
end
end

View File

@@ -3,8 +3,6 @@
<div class="box tabular settings">
<p><%= setting_check_box :cross_project_issue_relations %></p>
<p><%= setting_select :cross_project_subtasks, cross_project_subtasks_options %></p>
<p><%= setting_check_box :issue_group_assignment %></p>
<p><%= setting_check_box :default_issue_start_date_to_creation_date %></p>
@@ -13,8 +11,6 @@
<p><%= setting_select :issue_done_ratio, Issue::DONE_RATIO_OPTIONS.collect {|i| [l("setting_issue_done_ratio_#{i}"), i]} %></p>
<p><%= setting_multiselect :non_working_week_days, (1..7).map {|d| [day_name(d), d.to_s]}, :inline => true %></p>
<p><%= setting_text_field :issues_export_limit, :size => 6 %></p>
<p><%= setting_text_field :gantt_items_limit, :size => 6 %></p>

View File

@@ -12,7 +12,7 @@
<p><%= f.text_field :issue_id, :size => 6 %> <em><%= h("#{@time_entry.issue.tracker.name} ##{@time_entry.issue.id}: #{@time_entry.issue.subject}") if @time_entry.issue %></em></p>
<p><%= f.text_field :spent_on, :size => 10, :required => true %><%= calendar_for('time_entry_spent_on') %></p>
<p><%= f.text_field :hours, :size => 6, :required => true %></p>
<p><%= f.text_field :comments, :size => 100, :maxlength => 255 %></p>
<p><%= f.text_field :comments, :size => 100 %></p>
<p><%= f.select :activity_id, activity_collection_for_select_options(@time_entry), :required => true %></p>
<% @time_entry.custom_field_values.each do |value| %>
<p><%= custom_field_tag_with_label :time_entry, value %></p>

View File

@@ -36,7 +36,7 @@
</tr></thead>
<tbody>
<% for user in @users -%>
<tr class="<%= user.css_classes %> <%= cycle("odd", "even") %>">
<tr class="user <%= cycle("odd", "even") %> <%= %w(anon active registered locked)[user.status] %>">
<td class="username"><%= avatar(user, :size => "14") %><%= link_to h(user.login), edit_user_path(user) %></td>
<td class="firstname"><%= h(user.firstname) %></td>
<td class="lastname"><%= h(user.lastname) %></td>

View File

@@ -14,11 +14,12 @@
<% counts.each do |count| %>
<tr>
<td width="130px" align="right" >
<% if count[:group] -%>
<%= link_to(h(count[:group]), project_issues_path(version.project, :set_filter => 1, :status_id => '*', :fixed_version_id => version, "#{criteria}_id" => count[:group])) %>
<% else -%>
<%= link_to(l(:label_none), project_issues_path(version.project, :set_filter => 1, :status_id => '*', :fixed_version_id => version, "#{criteria}_id" => "!*")) %>
<% end %>
<%= link_to h(count[:group]), {:controller => 'issues',
:action => 'index',
:project_id => version.project,
:set_filter => 1,
:status_id => '*',
:fixed_version_id => version}.merge("#{criteria}_id".to_sym => count[:group]) %>
</td>
<td width="240px">
<%= progress_bar((count[:closed].to_f / count[:total])*100,

View File

@@ -28,15 +28,12 @@
<td class="updated_on"><%= format_time(ver.updated_on) %></td>
<td class="author"><%= link_to_user ver.author %></td>
<td class="comments"><%=h ver.comments %></td>
<td class="buttons">
<%= link_to l(:button_annotate), :action => 'annotate', :id => @page.title, :version => ver.version %>
<%= delete_link wiki_page_path(@page, :version => ver.version) if User.current.allowed_to?(:delete_wiki_pages, @page.project) && @version_count > 1 %>
</td>
<td class="buttons"><%= link_to l(:button_annotate), :action => 'annotate', :id => @page.title, :version => ver.version %></td>
</tr>
<% line_num += 1 %>
<% end %>
</tbody>
</table>
<%= submit_tag l(:label_view_diff), :class => 'small' if show_diff %>
<span class="pagination"><%= pagination_links_full @version_pages, @version_count %></span>
<span class="pagination"><%= pagination_links_full @version_pages, @version_count, :page_param => :p %></span>
<% end %>

View File

@@ -1,13 +0,0 @@
api.array :wiki_pages do
@pages.each do |page|
api.wiki_page do
api.title page.title
if page.parent
api.parent :title => page.parent.title
end
api.version page.version
api.created_on page.created_on
api.updated_on page.updated_on
end
end
end

View File

@@ -1,18 +0,0 @@
api.wiki_page do
api.title @page.title
if @page.parent
api.parent :title => @page.parent.title
end
api.text @content.text
api.version @content.version
api.author(:id => @content.author_id, :name => @content.author.name)
api.comments @page.content.comments
api.created_on @page.created_on
api.updated_on @content.updated_on
api.array :attachments do
@page.attachments.each do |attachment|
render_api_attachment(attachment, api)
end
end if include_in_api_response?('attachments')
end

View File

@@ -1,15 +1,12 @@
<div class="contextual">
<% if @editable %>
<% if @content.current_version? %>
<%= link_to_if_authorized(l(:button_edit), {:action => 'edit', :id => @page.title}, :class => 'icon icon-edit', :accesskey => accesskey(:edit)) %>
<%= watcher_tag(@page, User.current) %>
<%= link_to_if_authorized(l(:button_lock), {:action => 'protect', :id => @page.title, :protected => 1}, :method => :post, :class => 'icon icon-lock') if !@page.protected? %>
<%= link_to_if_authorized(l(:button_unlock), {:action => 'protect', :id => @page.title, :protected => 0}, :method => :post, :class => 'icon icon-unlock') if @page.protected? %>
<%= link_to_if_authorized(l(:button_rename), {:action => 'rename', :id => @page.title}, :class => 'icon icon-move') %>
<%= link_to_if_authorized(l(:button_delete), {:action => 'destroy', :id => @page.title}, :method => :delete, :data => {:confirm => l(:text_are_you_sure)}, :class => 'icon icon-del') %>
<% else %>
<%= link_to_if_authorized(l(:button_rollback), {:action => 'edit', :id => @page.title, :version => @content.version }, :class => 'icon icon-cancel') %>
<% end %>
<%= link_to_if_authorized(l(:button_edit), {:action => 'edit', :id => @page.title}, :class => 'icon icon-edit', :accesskey => accesskey(:edit)) if @content.current_version? %>
<%= watcher_tag(@page, User.current) %>
<%= link_to_if_authorized(l(:button_lock), {:action => 'protect', :id => @page.title, :protected => 1}, :method => :post, :class => 'icon icon-lock') if !@page.protected? %>
<%= link_to_if_authorized(l(:button_unlock), {:action => 'protect', :id => @page.title, :protected => 0}, :method => :post, :class => 'icon icon-unlock') if @page.protected? %>
<%= link_to_if_authorized(l(:button_rename), {:action => 'rename', :id => @page.title}, :class => 'icon icon-move') if @content.current_version? %>
<%= link_to_if_authorized(l(:button_delete), {:action => 'destroy', :id => @page.title}, :method => :delete, :data => {:confirm => l(:text_are_you_sure)}, :class => 'icon icon-del') %>
<%= link_to_if_authorized(l(:button_rollback), {:action => 'edit', :id => @page.title, :version => @content.version }, :class => 'icon icon-cancel') unless @content.current_version? %>
<% end %>
<%= link_to_if_authorized(l(:label_history), {:action => 'history', :id => @page.title}, :class => 'icon icon-history') %>
</div>
@@ -20,15 +17,15 @@
<p>
<%= link_to(("\xc2\xab " + l(:label_previous)),
:action => 'show', :id => @page.title, :project_id => @page.project,
:version => @content.previous.version) + " - " if @content.previous %>
:version => (@content.version - 1)) + " - " if @content.version > 1 %>
<%= "#{l(:label_version)} #{@content.version}/#{@page.content.version}" %>
<%= '('.html_safe + link_to(l(:label_diff), :controller => 'wiki', :action => 'diff',
:id => @page.title, :project_id => @page.project,
:version => @content.version) + ')'.html_safe if @content.previous %> -
:version => @content.version) + ')'.html_safe if @content.version > 1 %> -
<%= link_to((l(:label_next) + " \xc2\xbb"), :action => 'show',
:id => @page.title, :project_id => @page.project,
:version => @content.next.version) + " - " if @content.next %>
<%= link_to(l(:label_current_version), :action => 'show', :id => @page.title, :project_id => @page.project, :version => nil) %>
:version => (@content.version + 1)) + " - " if @content.version < @page.content.version %>
<%= link_to(l(:label_current_version), :action => 'show', :id => @page.title, :project_id => @page.project) %>
<br />
<em><%= @content.author ? link_to_user(@content.author) : l(:label_user_anonymous)
%>, <%= format_time(@content.updated_on) %> </em><br />

View File

@@ -50,9 +50,6 @@ module RedmineApp
config.action_mailer.perform_deliveries = false
# Do not include all helpers
config.action_controller.include_all_helpers = false
config.session_store :cookie_store, :key => '_redmine_session'
if File.exists?(File.join(File.dirname(__FILE__), 'additional_environment.rb'))

View File

@@ -166,28 +166,6 @@ default:
# the ImageMagick's `convert` binary. Used to generate attachment thumbnails.
#imagemagick_convert_command:
# Configuration of RMagcik font.
#
# Redmine uses RMagcik in order to export gantt png.
# You don't need this setting if you don't install RMagcik.
#
# In CJK (Chinese, Japanese and Korean),
# in order to show CJK characters correctly,
# you need to set this configuration.
#
# Because there is no standard font across platforms in CJK,
# you need to set a font installed in your server.
#
# This setting is not necessary in non CJK.
#
# Examples for Japanese:
# Windows:
# rmagick_font_path: C:\windows\fonts\msgothic.ttc
# Linux:
# rmagick_font_path: /usr/share/fonts/ipa-mincho/ipam.ttf
#
rmagick_font_path:
# specific configuration options for production environment
# that overrides the default ones
production:

View File

@@ -7,7 +7,7 @@ production:
database: redmine
host: localhost
username: root
password: ""
password:
encoding: utf8
development:
@@ -15,7 +15,7 @@ development:
database: redmine_development
host: localhost
username: root
password: ""
password:
encoding: utf8
# Warning: The database defined as "test" will be erased and
@@ -26,7 +26,7 @@ test:
database: redmine_test
host: localhost
username: root
password: ""
password:
encoding: utf8
test_pgsql:

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