Merge branch 'stable' into unstable
This commit is contained in:
commit
74cfd7b5ca
|
@ -26,4 +26,5 @@
|
||||||
doc/app
|
doc/app
|
||||||
/.bundle
|
/.bundle
|
||||||
/Gemfile.lock
|
/Gemfile.lock
|
||||||
|
/Gemfile.local
|
||||||
/.rvmrc*
|
/.rvmrc*
|
||||||
|
|
27
Gemfile
27
Gemfile
|
@ -11,6 +11,9 @@ group :test do
|
||||||
gem 'shoulda', '~> 2.10.3'
|
gem 'shoulda', '~> 2.10.3'
|
||||||
gem 'edavis10-object_daddy', :require => 'object_daddy'
|
gem 'edavis10-object_daddy', :require => 'object_daddy'
|
||||||
gem 'mocha'
|
gem 'mocha'
|
||||||
|
|
||||||
|
platforms :mri_18 do gem 'ruby-debug' end
|
||||||
|
platforms :mri_19 do gem 'ruby-debug19', :require => 'ruby-debug' end
|
||||||
end
|
end
|
||||||
|
|
||||||
group :openid do
|
group :openid do
|
||||||
|
@ -36,15 +39,22 @@ platforms :mri do
|
||||||
group :mysql2 do
|
group :mysql2 do
|
||||||
gem "mysql2", "~> 0.2.7"
|
gem "mysql2", "~> 0.2.7"
|
||||||
end
|
end
|
||||||
|
|
||||||
group :postgres do
|
group :postgres do
|
||||||
gem "pg", "~> 0.9.0"
|
gem "pg", "~> 0.9.0"
|
||||||
# gem "postgres-pr"
|
# gem "postgres-pr"
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
platforms :mri_18 do
|
||||||
group :sqlite do
|
group :sqlite do
|
||||||
gem "sqlite3-ruby", "< 1.3", :require => "sqlite3"
|
gem "sqlite3-ruby", "< 1.3", :require => "sqlite3"
|
||||||
# please tell me, if you are fond of a pure ruby sqlite3 binding
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
platforms :mri_19 do
|
||||||
|
group :sqlite do
|
||||||
|
gem "sqlite3"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -54,16 +64,23 @@ platforms :jruby do
|
||||||
group :mysql do
|
group :mysql do
|
||||||
gem "activerecord-jdbcmysql-adapter"
|
gem "activerecord-jdbcmysql-adapter"
|
||||||
end
|
end
|
||||||
|
|
||||||
group :postgres do
|
group :postgres do
|
||||||
gem "activerecord-jdbcpostgresql-adapter"
|
gem "activerecord-jdbcpostgresql-adapter"
|
||||||
end
|
end
|
||||||
|
|
||||||
group :sqlite do
|
group :sqlite do
|
||||||
gem "activerecord-jdbcsqlite3-adapter"
|
gem "activerecord-jdbcsqlite3-adapter"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Load a "local" Gemfile
|
||||||
|
gemfile_local = File.join(File.dirname(__FILE__), "Gemfile.local")
|
||||||
|
if File.readable?(gemfile_local)
|
||||||
|
puts "Loading #{gemfile_local} ..." if $DEBUG
|
||||||
|
instance_eval(File.read(gemfile_local))
|
||||||
|
end
|
||||||
|
|
||||||
# Load plugins' Gemfiles
|
# Load plugins' Gemfiles
|
||||||
Dir.glob File.expand_path("../vendor/plugins/*/Gemfile", __FILE__) do |file|
|
Dir.glob File.expand_path("../vendor/plugins/*/Gemfile", __FILE__) do |file|
|
||||||
puts "Loading #{file} ..." if $DEBUG # `ruby -d` or `bundle -v`
|
puts "Loading #{file} ..." if $DEBUG # `ruby -d` or `bundle -v`
|
||||||
|
|
|
@ -24,6 +24,12 @@ class ApplicationController < ActionController::Base
|
||||||
layout 'base'
|
layout 'base'
|
||||||
exempt_from_layout 'builder', 'rsb'
|
exempt_from_layout 'builder', 'rsb'
|
||||||
|
|
||||||
|
protect_from_forgery
|
||||||
|
def handle_unverified_request
|
||||||
|
super
|
||||||
|
cookies.delete(:autologin)
|
||||||
|
end
|
||||||
|
|
||||||
# Remove broken cookie after upgrade from 0.8.x (#4292)
|
# Remove broken cookie after upgrade from 0.8.x (#4292)
|
||||||
# See https://rails.lighthouseapp.com/projects/8994/tickets/3360
|
# See https://rails.lighthouseapp.com/projects/8994/tickets/3360
|
||||||
# TODO: remove it when Rails is fixed
|
# TODO: remove it when Rails is fixed
|
||||||
|
@ -38,7 +44,6 @@ class ApplicationController < ActionController::Base
|
||||||
|
|
||||||
before_filter :user_setup, :check_if_login_required, :set_localization
|
before_filter :user_setup, :check_if_login_required, :set_localization
|
||||||
filter_parameter_logging :password
|
filter_parameter_logging :password
|
||||||
protect_from_forgery
|
|
||||||
|
|
||||||
rescue_from ActionController::InvalidAuthenticityToken, :with => :invalid_authenticity_token
|
rescue_from ActionController::InvalidAuthenticityToken, :with => :invalid_authenticity_token
|
||||||
|
|
||||||
|
|
|
@ -286,7 +286,7 @@ private
|
||||||
render_error l(:error_no_tracker_in_project)
|
render_error l(:error_no_tracker_in_project)
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
@issue.start_date ||= Date.today
|
@issue.start_date ||= User.current.today
|
||||||
if params[:issue].is_a?(Hash)
|
if params[:issue].is_a?(Hash)
|
||||||
@issue.safe_attributes = params[:issue]
|
@issue.safe_attributes = params[:issue]
|
||||||
if User.current.allowed_to?(:add_issue_watchers, @project) && @issue.new_record?
|
if User.current.allowed_to?(:add_issue_watchers, @project) && @issue.new_record?
|
||||||
|
|
|
@ -16,10 +16,12 @@ class ProjectsController < ApplicationController
|
||||||
menu_item :roadmap, :only => :roadmap
|
menu_item :roadmap, :only => :roadmap
|
||||||
menu_item :settings, :only => :settings
|
menu_item :settings, :only => :settings
|
||||||
|
|
||||||
before_filter :find_project, :except => [ :index, :list, :new, :create, :copy ]
|
before_filter :find_project, :except => [ :index, :new, :create, :copy ]
|
||||||
before_filter :authorize, :except => [ :index, :list, :new, :create, :copy, :archive, :unarchive, :destroy]
|
before_filter :authorize, :only => [ :show, :settings, :edit, :update, :modules ]
|
||||||
before_filter :authorize_global, :only => [:new, :create]
|
before_filter :authorize_global, :only => [:new, :create]
|
||||||
before_filter :require_admin, :only => [ :copy, :archive, :unarchive, :destroy ]
|
before_filter :require_admin, :only => [ :copy, :archive, :unarchive, :destroy ]
|
||||||
|
before_filter :jump_to_project_menu_item, :only => :show
|
||||||
|
before_filter :load_project_settings, :only => :settings
|
||||||
accept_key_auth :index, :show, :create, :update, :destroy
|
accept_key_auth :index, :show, :create, :update, :destroy
|
||||||
|
|
||||||
after_filter :only => [:create, :edit, :update, :archive, :unarchive, :destroy] do |controller|
|
after_filter :only => [:create, :edit, :update, :archive, :unarchive, :destroy] do |controller|
|
||||||
|
@ -68,12 +70,7 @@ class ProjectsController < ApplicationController
|
||||||
|
|
||||||
if validate_parent_id && @project.save
|
if validate_parent_id && @project.save
|
||||||
@project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
|
@project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
|
||||||
# Add current user as a project member if he is not admin
|
add_current_user_to_project_if_not_admin(@project)
|
||||||
unless User.current.admin?
|
|
||||||
r = Role.givable.find_by_id(Setting.new_project_user_role_id.to_i) || Role.givable.first
|
|
||||||
m = Member.new(:user => User.current, :roles => [r])
|
|
||||||
@project.members << m
|
|
||||||
end
|
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.html {
|
format.html {
|
||||||
flash[:notice] = l(:notice_successful_create)
|
flash[:notice] = l(:notice_successful_create)
|
||||||
|
@ -128,11 +125,6 @@ class ProjectsController < ApplicationController
|
||||||
|
|
||||||
# Show @project
|
# Show @project
|
||||||
def show
|
def show
|
||||||
if params[:jump]
|
|
||||||
# try to redirect to the requested menu item
|
|
||||||
redirect_to_project_menu_item(@project, params[:jump]) && return
|
|
||||||
end
|
|
||||||
|
|
||||||
@users_by_role = @project.users_by_role
|
@users_by_role = @project.users_by_role
|
||||||
@subprojects = @project.children.visible.all
|
@subprojects = @project.children.visible.all
|
||||||
@news = @project.news.find(:all, :limit => 5, :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC")
|
@news = @project.news.find(:all, :limit => 5, :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC")
|
||||||
|
@ -151,8 +143,6 @@ class ProjectsController < ApplicationController
|
||||||
@total_hours = TimeEntry.visible.sum(:hours, :include => :project, :conditions => cond).to_f
|
@total_hours = TimeEntry.visible.sum(:hours, :include => :project, :conditions => cond).to_f
|
||||||
end
|
end
|
||||||
|
|
||||||
@key = User.current.rss_key
|
|
||||||
|
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.html
|
format.html
|
||||||
format.api
|
format.api
|
||||||
|
@ -160,12 +150,6 @@ class ProjectsController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def settings
|
def settings
|
||||||
@issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
|
|
||||||
@issue_category ||= IssueCategory.new
|
|
||||||
@member ||= @project.members.new
|
|
||||||
@trackers = Tracker.all
|
|
||||||
@repository ||= @project.repository
|
|
||||||
@wiki ||= @project.wiki
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def edit
|
def edit
|
||||||
|
@ -187,7 +171,7 @@ class ProjectsController < ApplicationController
|
||||||
else
|
else
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.html {
|
format.html {
|
||||||
settings
|
load_project_settings
|
||||||
render :action => 'settings'
|
render :action => 'settings'
|
||||||
}
|
}
|
||||||
format.api { render_validation_errors(@project) }
|
format.api { render_validation_errors(@project) }
|
||||||
|
@ -230,8 +214,7 @@ class ProjectsController < ApplicationController
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
# hide project in layout
|
hide_project_in_layout
|
||||||
@project = nil
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
@ -257,4 +240,33 @@ private
|
||||||
end
|
end
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def jump_to_project_menu_item
|
||||||
|
if params[:jump]
|
||||||
|
# try to redirect to the requested menu item
|
||||||
|
redirect_to_project_menu_item(@project, params[:jump]) && return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def load_project_settings
|
||||||
|
@issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
|
||||||
|
@issue_category ||= IssueCategory.new
|
||||||
|
@member ||= @project.members.new
|
||||||
|
@trackers = Tracker.all
|
||||||
|
@repository ||= @project.repository
|
||||||
|
@wiki ||= @project.wiki
|
||||||
|
end
|
||||||
|
|
||||||
|
def hide_project_in_layout
|
||||||
|
@project = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def add_current_user_to_project_if_not_admin(project)
|
||||||
|
unless User.current.admin?
|
||||||
|
r = Role.givable.find_by_id(Setting.new_project_user_role_id.to_i) || Role.givable.first
|
||||||
|
m = Member.new(:user => User.current, :roles => [r])
|
||||||
|
project.members << m
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -143,7 +143,12 @@ class RepositoriesController < ApplicationController
|
||||||
return true if Redmine::MimeType.is_type?('text', path)
|
return true if Redmine::MimeType.is_type?('text', path)
|
||||||
# Ruby 1.8.6 has a bug of integer divisions.
|
# Ruby 1.8.6 has a bug of integer divisions.
|
||||||
# http://apidock.com/ruby/v1_8_6_287/String/is_binary_data%3F
|
# http://apidock.com/ruby/v1_8_6_287/String/is_binary_data%3F
|
||||||
return false if ent.is_binary_data?
|
if ent.respond_to?("is_binary_data?") && ent.is_binary_data? # Ruby 1.8.x and <1.9.2
|
||||||
|
return false
|
||||||
|
elsif ent.respond_to?(:force_encoding) && (ent.dup.force_encoding("UTF-8") != ent.dup.force_encoding("BINARY") ) # Ruby 1.9.2
|
||||||
|
# TODO: need to handle edge cases of non-binary content that isn't UTF-8
|
||||||
|
return false
|
||||||
|
end
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
private :is_entry_text_data?
|
private :is_entry_text_data?
|
||||||
|
|
|
@ -81,7 +81,7 @@ module ApplicationHelper
|
||||||
subject = truncate(subject, :length => options[:truncate])
|
subject = truncate(subject, :length => options[:truncate])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
s = link_to "#{issue.tracker} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue},
|
s = link_to "#{h(issue.tracker)} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue},
|
||||||
:class => issue.css_classes,
|
:class => issue.css_classes,
|
||||||
:title => title
|
:title => title
|
||||||
s << ": #{h subject}" if subject
|
s << ": #{h subject}" if subject
|
||||||
|
|
|
@ -129,82 +129,6 @@ module IssuesHelper
|
||||||
out
|
out
|
||||||
end
|
end
|
||||||
|
|
||||||
def show_detail(detail, no_html=false)
|
|
||||||
case detail.property
|
|
||||||
when 'attr'
|
|
||||||
field = detail.prop_key.to_s.gsub(/\_id$/, "")
|
|
||||||
label = l(("field_" + field).to_sym)
|
|
||||||
case
|
|
||||||
when ['due_date', 'start_date'].include?(detail.prop_key)
|
|
||||||
value = format_date(detail.value.to_date) if detail.value
|
|
||||||
old_value = format_date(detail.old_value.to_date) if detail.old_value
|
|
||||||
|
|
||||||
when ['project_id', 'status_id', 'tracker_id', 'assigned_to_id', 'priority_id', 'category_id', 'fixed_version_id'].include?(detail.prop_key)
|
|
||||||
value = find_name_by_reflection(field, detail.value)
|
|
||||||
old_value = find_name_by_reflection(field, detail.old_value)
|
|
||||||
|
|
||||||
when detail.prop_key == 'estimated_hours'
|
|
||||||
value = "%0.02f" % detail.value.to_f unless detail.value.blank?
|
|
||||||
old_value = "%0.02f" % detail.old_value.to_f unless detail.old_value.blank?
|
|
||||||
|
|
||||||
when detail.prop_key == 'parent_id'
|
|
||||||
label = l(:field_parent_issue)
|
|
||||||
value = "##{detail.value}" unless detail.value.blank?
|
|
||||||
old_value = "##{detail.old_value}" unless detail.old_value.blank?
|
|
||||||
end
|
|
||||||
when 'cf'
|
|
||||||
custom_field = CustomField.find_by_id(detail.prop_key)
|
|
||||||
if custom_field
|
|
||||||
label = custom_field.name
|
|
||||||
value = format_value(detail.value, custom_field.field_format) if detail.value
|
|
||||||
old_value = format_value(detail.old_value, custom_field.field_format) if detail.old_value
|
|
||||||
end
|
|
||||||
when 'attachment'
|
|
||||||
label = l(:label_attachment)
|
|
||||||
end
|
|
||||||
call_hook(:helper_issues_show_detail_after_setting, {:detail => detail, :label => label, :value => value, :old_value => old_value })
|
|
||||||
|
|
||||||
label ||= detail.prop_key
|
|
||||||
value ||= detail.value
|
|
||||||
old_value ||= detail.old_value
|
|
||||||
|
|
||||||
unless no_html
|
|
||||||
label = content_tag('strong', label)
|
|
||||||
old_value = content_tag("i", h(old_value)) if detail.old_value
|
|
||||||
old_value = content_tag("strike", old_value) if detail.old_value and (!detail.value or detail.value.empty?)
|
|
||||||
if detail.property == 'attachment' && !value.blank? && a = Attachment.find_by_id(detail.prop_key)
|
|
||||||
# Link to the attachment if it has not been removed
|
|
||||||
value = link_to_attachment(a)
|
|
||||||
else
|
|
||||||
value = content_tag("i", h(value)) if value
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if detail.property == 'attr' && detail.prop_key == 'description'
|
|
||||||
s = l(:text_journal_changed_no_detail, :label => label)
|
|
||||||
unless no_html
|
|
||||||
diff_link = link_to 'diff',
|
|
||||||
{:controller => 'journals', :action => 'diff', :id => detail.journal_id, :detail_id => detail.id},
|
|
||||||
:title => l(:label_view_diff)
|
|
||||||
s << " (#{ diff_link })"
|
|
||||||
end
|
|
||||||
s
|
|
||||||
elsif !detail.value.blank?
|
|
||||||
case detail.property
|
|
||||||
when 'attr', 'cf'
|
|
||||||
if !detail.old_value.blank?
|
|
||||||
l(:text_journal_changed, :label => label, :old => old_value, :new => value)
|
|
||||||
else
|
|
||||||
l(:text_journal_set_to, :label => label, :value => value)
|
|
||||||
end
|
|
||||||
when 'attachment'
|
|
||||||
l(:text_journal_added, :label => label, :value => value)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
l(:text_journal_deleted, :label => label, :old => old_value)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Find the name of an associated record stored in the field attribute
|
# Find the name of an associated record stored in the field attribute
|
||||||
def find_name_by_reflection(field, id)
|
def find_name_by_reflection(field, id)
|
||||||
association = Issue.reflect_on_association(field.to_sym)
|
association = Issue.reflect_on_association(field.to_sym)
|
||||||
|
|
|
@ -48,7 +48,7 @@ module JournalsHelper
|
||||||
if d = journal.render_detail(detail)
|
if d = journal.render_detail(detail)
|
||||||
content_tag("li", d)
|
content_tag("li", d)
|
||||||
end
|
end
|
||||||
end.compact
|
end.compact.join(' ')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -57,4 +57,14 @@ module SearchHelper
|
||||||
end
|
end
|
||||||
('<ul>' + links.map {|link| content_tag('li', link)}.join(' ') + '</ul>') unless links.empty?
|
('<ul>' + links.map {|link| content_tag('li', link)}.join(' ') + '</ul>') unless links.empty?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def link_to_previous_search_page(pagination_previous_date)
|
||||||
|
link_to_content_update('« ' + l(:label_previous),
|
||||||
|
params.merge(:previous => 1, :offset => pagination_previous_date.strftime("%Y%m%d%H%M%S")))
|
||||||
|
end
|
||||||
|
|
||||||
|
def link_to_next_search_page(pagination_next_date)
|
||||||
|
link_to_content_update(l(:label_next) + ' »',
|
||||||
|
params.merge(:previous => nil, :offset => pagination_next_date.strftime("%Y%m%d%H%M%S")))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -17,10 +17,22 @@ class Change < ActiveRecord::Base
|
||||||
validates_presence_of :changeset_id, :action, :path
|
validates_presence_of :changeset_id, :action, :path
|
||||||
before_save :init_path
|
before_save :init_path
|
||||||
|
|
||||||
|
delegate :repository_encoding, :to => :changeset, :allow_nil => true, :prefix => true
|
||||||
|
|
||||||
def relative_path
|
def relative_path
|
||||||
changeset.repository.relative_path(path)
|
changeset.repository.relative_path(path)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def path
|
||||||
|
# TODO: shouldn't access Changeset#to_utf8 directly
|
||||||
|
self.path = Changeset.to_utf8(read_attribute(:path), changeset_repository_encoding)
|
||||||
|
end
|
||||||
|
|
||||||
|
def from_path
|
||||||
|
# TODO: shouldn't access Changeset#to_utf8 directly
|
||||||
|
self.path = Changeset.to_utf8(read_attribute(:from_path), changeset_repository_encoding)
|
||||||
|
end
|
||||||
|
|
||||||
def init_path
|
def init_path
|
||||||
self.path ||= ""
|
self.path ||= ""
|
||||||
end
|
end
|
||||||
|
|
|
@ -74,6 +74,23 @@ class Changeset < ActiveRecord::Base
|
||||||
user || committer.to_s.split('<').first
|
user || committer.to_s.split('<').first
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Delegate to a Repository's log encoding
|
||||||
|
def repository_encoding
|
||||||
|
if repository.present?
|
||||||
|
repository.repo_log_encoding
|
||||||
|
else
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Committer of the Changeset
|
||||||
|
#
|
||||||
|
# Attribute reader for committer that encodes the committer string to
|
||||||
|
# the repository log encoding (e.g. UTF-8)
|
||||||
|
def committer
|
||||||
|
self.class.to_utf8(read_attribute(:committer), repository.repo_log_encoding)
|
||||||
|
end
|
||||||
|
|
||||||
def before_create
|
def before_create
|
||||||
self.committer = self.class.to_utf8(self.committer, repository.repo_log_encoding)
|
self.committer = self.class.to_utf8(self.committer, repository.repo_log_encoding)
|
||||||
self.comments = self.class.normalize_comments(self.comments, repository.repo_log_encoding)
|
self.comments = self.class.normalize_comments(self.comments, repository.repo_log_encoding)
|
||||||
|
@ -239,6 +256,7 @@ class Changeset < ActiveRecord::Base
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
# TODO: refactor to a standard helper method
|
||||||
def self.to_utf8(str, encoding)
|
def self.to_utf8(str, encoding)
|
||||||
return str if str.nil?
|
return str if str.nil?
|
||||||
str.force_encoding("ASCII-8BIT") if str.respond_to?(:force_encoding)
|
str.force_encoding("ASCII-8BIT") if str.respond_to?(:force_encoding)
|
||||||
|
@ -273,12 +291,6 @@ class Changeset < ActiveRecord::Base
|
||||||
end
|
end
|
||||||
str = txtar
|
str = txtar
|
||||||
end
|
end
|
||||||
# removes invalid UTF8 sequences
|
str
|
||||||
begin
|
|
||||||
Iconv.conv('UTF-8//IGNORE', 'UTF-8', str + ' ')[0..-3]
|
|
||||||
rescue Iconv::InvalidEncoding
|
|
||||||
# "UTF-8//IGNORE" is not supported on some OS
|
|
||||||
str
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -23,9 +23,17 @@ class Journal < ActiveRecord::Base
|
||||||
|
|
||||||
# Make sure each journaled model instance only has unique version ids
|
# Make sure each journaled model instance only has unique version ids
|
||||||
validates_uniqueness_of :version, :scope => [:journaled_id, :type]
|
validates_uniqueness_of :version, :scope => [:journaled_id, :type]
|
||||||
belongs_to :journaled, :touch => true
|
|
||||||
|
# Define a default class_name to prevent `uninitialized constant Journal::Journaled`
|
||||||
|
# subclasses will be given an actual class name when they are created by aaj
|
||||||
|
#
|
||||||
|
# e.g. IssueJournal will get :class_name => 'Issue'
|
||||||
|
belongs_to :journaled, :class_name => 'Journal'
|
||||||
belongs_to :user
|
belongs_to :user
|
||||||
|
|
||||||
|
# "touch" the journaled object on creation
|
||||||
|
after_create :touch_journaled_after_creation
|
||||||
|
|
||||||
# ActiveRecord::Base#changes is an existing method, so before serializing the +changes+ column,
|
# ActiveRecord::Base#changes is an existing method, so before serializing the +changes+ column,
|
||||||
# the existing +changes+ method is undefined. The overridden +changes+ method pertained to
|
# the existing +changes+ method is undefined. The overridden +changes+ method pertained to
|
||||||
# dirty attributes, but will not affect the partial updates functionality as that's based on
|
# dirty attributes, but will not affect the partial updates functionality as that's based on
|
||||||
|
@ -33,6 +41,10 @@ class Journal < ActiveRecord::Base
|
||||||
# undef_method :changes
|
# undef_method :changes
|
||||||
serialize :changes, Hash
|
serialize :changes, Hash
|
||||||
|
|
||||||
|
def touch_journaled_after_creation
|
||||||
|
journaled.touch
|
||||||
|
end
|
||||||
|
|
||||||
# In conjunction with the included Comparable module, allows comparison of journal records
|
# In conjunction with the included Comparable module, allows comparison of journal records
|
||||||
# based on their corresponding version numbers, creation timestamps and IDs.
|
# based on their corresponding version numbers, creation timestamps and IDs.
|
||||||
def <=>(other)
|
def <=>(other)
|
||||||
|
|
|
@ -185,7 +185,7 @@ class Mailer < ActionMailer::Base
|
||||||
cc((message.root.watcher_recipients + message.board.watcher_recipients).uniq - @recipients)
|
cc((message.root.watcher_recipients + message.board.watcher_recipients).uniq - @recipients)
|
||||||
subject "[#{message.board.project.name} - #{message.board.name} - msg#{message.root.id}] #{message.subject}"
|
subject "[#{message.board.project.name} - #{message.board.name} - msg#{message.root.id}] #{message.subject}"
|
||||||
body :message => message,
|
body :message => message,
|
||||||
:message_url => url_for(message.last_journal.event_url)
|
:message_url => url_for({ :controller => 'messages', :action => 'show', :board_id => message.board, :id => message.root, :r => message, :anchor => "message-#{message.id}" })
|
||||||
render_multipart('message_posted', body)
|
render_multipart('message_posted', body)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -12,10 +12,7 @@
|
||||||
#++
|
#++
|
||||||
|
|
||||||
class MessageObserver < ActiveRecord::Observer
|
class MessageObserver < ActiveRecord::Observer
|
||||||
def after_save(message)
|
def after_create(message)
|
||||||
if message.last_journal.version == 1
|
Mailer.deliver_message_posted(message) if Setting.notified_events.include?('message_posted')
|
||||||
# Only deliver mails for the first journal
|
|
||||||
Mailer.deliver_message_posted(message) if Setting.notified_events.include?('message_posted')
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -126,8 +126,9 @@ class Query < ActiveRecord::Base
|
||||||
QueryColumn.new(:updated_on, :sortable => "#{Issue.table_name}.updated_on", :default_order => 'desc'),
|
QueryColumn.new(:updated_on, :sortable => "#{Issue.table_name}.updated_on", :default_order => 'desc'),
|
||||||
QueryColumn.new(:category, :sortable => "#{IssueCategory.table_name}.name", :groupable => true),
|
QueryColumn.new(:category, :sortable => "#{IssueCategory.table_name}.name", :groupable => true),
|
||||||
QueryColumn.new(:fixed_version, :sortable => ["#{Version.table_name}.effective_date", "#{Version.table_name}.name"], :default_order => 'desc', :groupable => true),
|
QueryColumn.new(:fixed_version, :sortable => ["#{Version.table_name}.effective_date", "#{Version.table_name}.name"], :default_order => 'desc', :groupable => true),
|
||||||
QueryColumn.new(:start_date, :sortable => "#{Issue.table_name}.start_date"),
|
# Put empty start_dates and due_dates in the far future rather than in the far past
|
||||||
QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date"),
|
QueryColumn.new(:start_date, :sortable => ["CASE WHEN #{Issue.table_name}.start_date IS NULL THEN 1 ELSE 0 END", "#{Issue.table_name}.start_date"]),
|
||||||
|
QueryColumn.new(:due_date, :sortable => ["CASE WHEN #{Issue.table_name}.due_date IS NULL THEN 1 ELSE 0 END", "#{Issue.table_name}.due_date"]),
|
||||||
QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours"),
|
QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours"),
|
||||||
QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio", :groupable => true),
|
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(:created_on, :sortable => "#{Issue.table_name}.created_on", :default_order => 'desc'),
|
||||||
|
@ -587,9 +588,17 @@ class Query < ActiveRecord::Base
|
||||||
sql = "#{db_table}.#{db_field} IS NOT NULL"
|
sql = "#{db_table}.#{db_field} IS NOT NULL"
|
||||||
sql << " AND #{db_table}.#{db_field} <> ''" if is_custom_filter
|
sql << " AND #{db_table}.#{db_field} <> ''" if is_custom_filter
|
||||||
when ">="
|
when ">="
|
||||||
sql = "#{db_table}.#{db_field} >= #{value.first.to_i}"
|
if is_custom_filter
|
||||||
|
sql = "#{db_table}.#{db_field} != '' AND CAST(#{db_table}.#{db_field} AS decimal(60,4)) >= #{value.first.to_f}"
|
||||||
|
else
|
||||||
|
sql = "#{db_table}.#{db_field} >= #{value.first.to_f}"
|
||||||
|
end
|
||||||
when "<="
|
when "<="
|
||||||
sql = "#{db_table}.#{db_field} <= #{value.first.to_i}"
|
if is_custom_filter
|
||||||
|
sql = "#{db_table}.#{db_field} != '' AND CAST(#{db_table}.#{db_field} AS decimal(60,4)) <= #{value.first.to_f}"
|
||||||
|
else
|
||||||
|
sql = "#{db_table}.#{db_field} <= #{value.first.to_f}"
|
||||||
|
end
|
||||||
when "o"
|
when "o"
|
||||||
sql = "#{IssueStatus.table_name}.is_closed=#{connection.quoted_false}" if field == "status_id"
|
sql = "#{IssueStatus.table_name}.is_closed=#{connection.quoted_false}" if field == "status_id"
|
||||||
when "c"
|
when "c"
|
||||||
|
@ -629,6 +638,8 @@ class Query < ActiveRecord::Base
|
||||||
|
|
||||||
custom_fields.select(&:is_filter?).each do |field|
|
custom_fields.select(&:is_filter?).each do |field|
|
||||||
case field.field_format
|
case field.field_format
|
||||||
|
when "int", "float"
|
||||||
|
options = { :type => :integer, :order => 20 }
|
||||||
when "text"
|
when "text"
|
||||||
options = { :type => :text, :order => 20 }
|
options = { :type => :text, :order => 20 }
|
||||||
when "list"
|
when "list"
|
||||||
|
|
|
@ -59,12 +59,7 @@ class WikiContent < ActiveRecord::Base
|
||||||
end
|
end
|
||||||
|
|
||||||
def version
|
def version
|
||||||
unless last_journal
|
new_record? ? 0 : last_journal.version
|
||||||
# FIXME: This is code that caters for a case that should never happen in the normal code paths!!
|
|
||||||
create_journal
|
|
||||||
last_journal.update_attribute(:created_at, updated_on)
|
|
||||||
end
|
|
||||||
last_journal.version
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
@ -106,9 +101,9 @@ class WikiContent < ActiveRecord::Base
|
||||||
end
|
end
|
||||||
|
|
||||||
def text
|
def text
|
||||||
@text ||= case changes[:compression]
|
@text ||= case changes["compression"]
|
||||||
when 'gzip'
|
when "gzip"
|
||||||
Zlib::Inflate.inflate(data)
|
Zlib::Inflate.inflate(changes["data"])
|
||||||
else
|
else
|
||||||
# uncompressed data
|
# uncompressed data
|
||||||
changes["data"]
|
changes["data"]
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<%= render :partial => 'action_menu' %>
|
<%= render :partial => 'action_menu' %>
|
||||||
|
|
||||||
<h2><%= @issue.tracker.name %> #<%= @issue.id %><%= call_hook(:view_issues_show_identifier, :issue => @issue) %></h2>
|
<h2><%= h(@issue.tracker.name) %> #<%= h(@issue.id) %><%= call_hook(:view_issues_show_identifier, :issue => @issue) %></h2>
|
||||||
|
|
||||||
<div class="<%= @issue.css_classes %> details">
|
<div class="<%= @issue.css_classes %> details">
|
||||||
<%= avatar(@issue.author, :size => "50") %>
|
<%= avatar(@issue.author, :size => "50") %>
|
||||||
|
@ -17,11 +17,11 @@
|
||||||
|
|
||||||
<table class="attributes">
|
<table class="attributes">
|
||||||
<tr>
|
<tr>
|
||||||
<th class="status"><%=l(:field_status)%>:</th><td class="status"><%= @issue.status.name %></td>
|
<th class="status"><%=l(:field_status)%>:</th><td class="status"><%= h(@issue.status.name) %></td>
|
||||||
<th class="start-date"><%=l(:field_start_date)%>:</th><td class="start-date"><%= format_date(@issue.start_date) %></td>
|
<th class="start-date"><%=l(:field_start_date)%>:</th><td class="start-date"><%= format_date(@issue.start_date) %></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="priority"><%=l(:field_priority)%>:</th><td class="priority"><%= @issue.priority.name %></td>
|
<th class="priority"><%=l(:field_priority)%>:</th><td class="priority"><%= h(@issue.priority.name) %></td>
|
||||||
<th class="due-date"><%=l(:field_due_date)%>:</th><td class="due-date"><%= format_date(@issue.due_date) %></td>
|
<th class="due-date"><%=l(:field_due_date)%>:</th><td class="due-date"><%= format_date(@issue.due_date) %></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -29,7 +29,7 @@
|
||||||
<th class="progress"><%=l(:field_done_ratio)%>:</th><td class="progress"><%= progress_bar @issue.done_ratio, :width => '80px', :legend => "#{@issue.done_ratio}%" %></td>
|
<th class="progress"><%=l(:field_done_ratio)%>:</th><td class="progress"><%= progress_bar @issue.done_ratio, :width => '80px', :legend => "#{@issue.done_ratio}%" %></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="category"><%=l(:field_category)%>:</th><td class="category"><%=h @issue.category ? @issue.category.name : "-" %></td>
|
<th class="category"><%=l(:field_category)%>:</th><td class="category"><%=h(@issue.category ? @issue.category.name : "-") %></td>
|
||||||
<% if User.current.allowed_to?(:view_time_entries, @project) %>
|
<% if User.current.allowed_to?(:view_time_entries, @project) %>
|
||||||
<th class="spent-time"><%=l(:label_spent_time)%>:</th>
|
<th class="spent-time"><%=l(:label_spent_time)%>:</th>
|
||||||
<td class="spent-time"><%= @issue.spent_hours > 0 ? (link_to l_hours(@issue.spent_hours), {:controller => 'timelog', :action => 'index', :project_id => @project, :issue_id => @issue}) : "-" %></td>
|
<td class="spent-time"><%= @issue.spent_hours > 0 ? (link_to l_hours(@issue.spent_hours), {:controller => 'timelog', :action => 'index', :project_id => @project, :issue_id => @issue}) : "-" %></td>
|
||||||
|
|
|
@ -22,6 +22,6 @@
|
||||||
<td class="revision"><%= link_to_revision(changeset, @project) if changeset %></td>
|
<td class="revision"><%= link_to_revision(changeset, @project) if changeset %></td>
|
||||||
<td class="age"><%= distance_of_time_in_words(entry.lastrev.time, Time.now) if entry.lastrev && entry.lastrev.time %></td>
|
<td class="age"><%= distance_of_time_in_words(entry.lastrev.time, Time.now) if entry.lastrev && entry.lastrev.time %></td>
|
||||||
<td class="author"><%= changeset.nil? ? h(replace_invalid_utf8(entry.lastrev.author.to_s.split('<').first)) : changeset.author if entry.lastrev %></td>
|
<td class="author"><%= changeset.nil? ? h(replace_invalid_utf8(entry.lastrev.author.to_s.split('<').first)) : changeset.author if entry.lastrev %></td>
|
||||||
<td class="comments"><%=h truncate(changeset.comments, :length => 50) unless changeset.nil? %></td>
|
<td class="comments"><%=h truncate(Changeset.to_utf8(changeset.comments, changeset.repository.repo_log_encoding), :length => 50) unless changeset.nil? %></td>
|
||||||
</tr>
|
</tr>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
<td class="checkbox"><%= radio_button_tag('rev_to', changeset.identifier, (line_num==2), :id => "cbto-#{line_num}", :onclick => "if ($('cb-#{line_num}').checked==true) {$('cb-#{line_num-1}').checked=true;}") if show_diff && (line_num > 1) %></td>
|
<td class="checkbox"><%= radio_button_tag('rev_to', changeset.identifier, (line_num==2), :id => "cbto-#{line_num}", :onclick => "if ($('cb-#{line_num}').checked==true) {$('cb-#{line_num-1}').checked=true;}") if show_diff && (line_num > 1) %></td>
|
||||||
<td class="committed_on"><%= format_time(changeset.committed_on) %></td>
|
<td class="committed_on"><%= format_time(changeset.committed_on) %></td>
|
||||||
<td class="author"><%=h changeset.author %></td>
|
<td class="author"><%=h changeset.author %></td>
|
||||||
<td class="comments"><%= textilizable(truncate_at_line_break(changeset.comments)) %></td>
|
<td class="comments"><%= textilizable(truncate_at_line_break(Changeset.to_utf8(changeset.comments, changeset.repository.repo_log_encoding))) %></td>
|
||||||
</tr>
|
</tr>
|
||||||
<% line_num += 1 %>
|
<% line_num += 1 %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
<div class="search-pagination">
|
||||||
|
<p>
|
||||||
|
<% if pagination_previous_date %>
|
||||||
|
<%= link_to_previous_search_page(pagination_previous_date) %>
|
||||||
|
<% end %>
|
||||||
|
<% if pagination_next_date %>
|
||||||
|
<%= link_to_next_search_page(pagination_next_date) %>
|
||||||
|
<% end %>
|
||||||
|
</p>
|
||||||
|
</div>
|
|
@ -24,6 +24,9 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3><%= l(:label_result_plural) %> (<%= @results_by_type.values.sum %>)</h3>
|
<h3><%= l(:label_result_plural) %> (<%= @results_by_type.values.sum %>)</h3>
|
||||||
|
|
||||||
|
<%= render :partial => 'pagination', :locals => {:pagination_previous_date => @pagination_previous_date, :pagination_next_date => @pagination_next_date } %>
|
||||||
|
|
||||||
<dl id="search-results">
|
<dl id="search-results">
|
||||||
<% @results.each do |e| %>
|
<% @results.each do |e| %>
|
||||||
<dt class="<%= e.event_type %>">
|
<dt class="<%= e.event_type %>">
|
||||||
|
@ -36,15 +39,6 @@
|
||||||
</dl>
|
</dl>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<p><center>
|
<%= render :partial => 'pagination', :locals => {:pagination_previous_date => @pagination_previous_date, :pagination_next_date => @pagination_next_date } %>
|
||||||
<% if @pagination_previous_date %>
|
|
||||||
<%= link_to_content_update('« ' + l(:label_previous),
|
|
||||||
params.merge(:previous => 1, :offset => @pagination_previous_date.strftime("%Y%m%d%H%M%S"))) %>
|
|
||||||
<% end %>
|
|
||||||
<% if @pagination_next_date %>
|
|
||||||
<%= link_to_content_update(l(:label_next) + ' »',
|
|
||||||
params.merge(:previous => nil, :offset => @pagination_next_date.strftime("%Y%m%d%H%M%S"))) %>
|
|
||||||
<% end %>
|
|
||||||
</center></p>
|
|
||||||
|
|
||||||
<% html_title(l(:label_search)) -%>
|
<% html_title(l(:label_search)) -%>
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
<head>
|
<head>
|
||||||
<title><%=h @page.pretty_title %></title>
|
<title><%=h @page.pretty_title %></title>
|
||||||
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
||||||
|
<base href="<%= "#{h Setting.protocol}://#{h Setting.host_name}" %>" />
|
||||||
<style>
|
<style>
|
||||||
body { font:80% Verdana,Tahoma,Arial,sans-serif; }
|
body { font:80% Verdana,Tahoma,Arial,sans-serif; }
|
||||||
h1, h2, h3, h4 { font-family: "Trebuchet MS",Georgia,"Times New Roman",serif; }
|
h1, h2, h3, h4 { font-family: "Trebuchet MS",Georgia,"Times New Roman",serif; }
|
||||||
|
@ -16,6 +17,6 @@ h1:hover a.wiki-anchor, h2:hover a.wiki-anchor, h3:hover a.wiki-anchor { display
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<%= textilizable @content, :text, :wiki_links => :local %>
|
<%= textilizable @content, :text, :wiki_links => :local, :only_path => false %>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -945,21 +945,21 @@ bg:
|
||||||
enumeration_activities: Дейности (time tracking)
|
enumeration_activities: Дейности (time tracking)
|
||||||
enumeration_system_activity: Системна активност
|
enumeration_system_activity: Системна активност
|
||||||
|
|
||||||
text_powered_by: Powered by %{link}
|
text_powered_by: Този сайт е задвижван от %{link}
|
||||||
label_cvs_module: Module
|
label_cvs_module: Модул
|
||||||
label_filesystem_path: Root directory
|
label_filesystem_path: Коренна директория
|
||||||
label_darcs_path: Root directory
|
label_darcs_path: Коренна директория
|
||||||
label_bazaar_path: Root directory
|
label_bazaar_path: Коренна директория
|
||||||
label_cvs_path: CVSROOT
|
label_cvs_path: CVSROOT
|
||||||
label_git_path: Path to .git directory
|
label_git_path: Път до директория .git
|
||||||
label_mercurial_path: Root directory
|
label_mercurial_path: Коренна директория
|
||||||
label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee
|
label_additional_workflow_transitions_for_assignee: Позволени са допълнителни преходи, когато потребителят е назначеният към задачата
|
||||||
button_expand_all: Expand all
|
button_expand_all: Разгъване всички
|
||||||
button_collapse_all: Collapse all
|
button_collapse_all: Скриване всички
|
||||||
label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author
|
label_additional_workflow_transitions_for_author: Позволени са допълнителни преходи, когато потребителят е авторът
|
||||||
field_effective_date: Due date
|
field_effective_date: Дата
|
||||||
text_default_encoding: "Default: UTF-8"
|
text_default_encoding: "По подразбиране: UTF-8"
|
||||||
text_git_repo_example: a bare and local repository (e.g. /gitrepo, c:\gitrepo)
|
text_git_repo_example: a bare and local repository (e.g. /gitrepo, c:\gitrepo)
|
||||||
label_notify_member_plural: Email issue updates
|
label_notify_member_plural: Email issue updates
|
||||||
label_path_encoding: Path encoding
|
label_path_encoding: Кодиране на пътищата
|
||||||
text_mercurial_repo_example: local repository (e.g. /hgrepo, c:\hgrepo)
|
text_mercurial_repo_example: локално хранилище (например /hgrepo, c:\hgrepo)
|
||||||
|
|
|
@ -1,3 +1,16 @@
|
||||||
|
#-- copyright
|
||||||
|
# ChiliProject is a project management system.
|
||||||
|
#
|
||||||
|
# Copyright (C) 2010-2011 the ChiliProject Team
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU General Public License
|
||||||
|
# as published by the Free Software Foundation; either version 2
|
||||||
|
# of the License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# See doc/COPYRIGHT.rdoc for more details.
|
||||||
|
#++
|
||||||
|
|
||||||
Redmine::Activity.providers.values.flatten.uniq.collect(&:underscore).each {|klass| require_dependency klass }
|
Redmine::Activity.providers.values.flatten.uniq.collect(&:underscore).each {|klass| require_dependency klass }
|
||||||
|
|
||||||
class UpdateJournalsForActsAsJournalized < ActiveRecord::Migration
|
class UpdateJournalsForActsAsJournalized < ActiveRecord::Migration
|
||||||
|
|
|
@ -1,3 +1,16 @@
|
||||||
|
#-- copyright
|
||||||
|
# ChiliProject is a project management system.
|
||||||
|
#
|
||||||
|
# Copyright (C) 2010-2011 the ChiliProject Team
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU General Public License
|
||||||
|
# as published by the Free Software Foundation; either version 2
|
||||||
|
# of the License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# See doc/COPYRIGHT.rdoc for more details.
|
||||||
|
#++
|
||||||
|
|
||||||
class BuildInitialJournalsForActsAsJournalized < ActiveRecord::Migration
|
class BuildInitialJournalsForActsAsJournalized < ActiveRecord::Migration
|
||||||
def self.up
|
def self.up
|
||||||
# This is provided here for migrating up after the JournalDetails has been removed
|
# This is provided here for migrating up after the JournalDetails has been removed
|
||||||
|
@ -11,53 +24,24 @@ class BuildInitialJournalsForActsAsJournalized < ActiveRecord::Migration
|
||||||
klass.reset_column_information if klass.respond_to?(:reset_column_information)
|
klass.reset_column_information if klass.respond_to?(:reset_column_information)
|
||||||
end
|
end
|
||||||
|
|
||||||
providers = Redmine::Activity.providers.collect {|k, v| v.collect(&:constantize) }.flatten.compact.uniq
|
|
||||||
providers.each do |p|
|
|
||||||
next unless p.table_exists? # Objects not in the DB yet need creation journal entries
|
|
||||||
|
|
||||||
|
[Message, Attachment, Document, Changeset, Issue, TimeEntry, News].each do |p|
|
||||||
say_with_time("Building initial journals for #{p.class_name}") do
|
say_with_time("Building initial journals for #{p.class_name}") do
|
||||||
|
|
||||||
|
# avoid touching the journaled object on journal creation
|
||||||
|
p.journal_class.class_exec {
|
||||||
|
def touch_journaled_after_creation
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
activity_type = p.activity_provider_options.keys.first
|
activity_type = p.activity_provider_options.keys.first
|
||||||
|
|
||||||
|
# Create initial journals
|
||||||
p.find(:all).each do |o|
|
p.find(:all).each do |o|
|
||||||
# Create initial journals
|
|
||||||
new_journal = o.journals.build
|
|
||||||
# Mock up a list of changes for the creation journal based on Class defaults
|
|
||||||
new_attributes = o.class.new.attributes.except(o.class.primary_key,
|
|
||||||
o.class.inheritance_column,
|
|
||||||
:updated_on,
|
|
||||||
:updated_at,
|
|
||||||
:lock_version,
|
|
||||||
:lft,
|
|
||||||
:rgt)
|
|
||||||
creation_changes = {}
|
|
||||||
new_attributes.each do |name, default_value|
|
|
||||||
# Set changes based on the initial value to current. Can't get creation value without
|
|
||||||
# rebuiling the object history
|
|
||||||
creation_changes[name] = [default_value, o.send(name)] # [initial_value, creation_value]
|
|
||||||
end
|
|
||||||
new_journal.changes = creation_changes
|
|
||||||
new_journal.version = 1
|
|
||||||
new_journal.activity_type = activity_type
|
|
||||||
|
|
||||||
if o.respond_to?(:author)
|
|
||||||
new_journal.user = o.author
|
|
||||||
elsif o.respond_to?(:user)
|
|
||||||
new_journal.user = o.user
|
|
||||||
end
|
|
||||||
# Using rescue and save! here because either the Journal or the
|
# Using rescue and save! here because either the Journal or the
|
||||||
# touched record could fail. This will catch either error and continue
|
# touched record could fail. This will catch either error and continue
|
||||||
begin
|
begin
|
||||||
new_journal.save!
|
new_journal = o.recreate_initial_journal!
|
||||||
|
|
||||||
new_journal.reload
|
|
||||||
|
|
||||||
# Backdate journal
|
|
||||||
if o.respond_to?(:created_at)
|
|
||||||
new_journal.update_attribute(:created_at, o.created_at)
|
|
||||||
elsif o.respond_to?(:created_on)
|
|
||||||
new_journal.update_attribute(:created_at, o.created_on)
|
|
||||||
end
|
|
||||||
rescue ActiveRecord::RecordInvalid => ex
|
rescue ActiveRecord::RecordInvalid => ex
|
||||||
if new_journal.errors.count == 1 && new_journal.errors.first[0] == "version"
|
if new_journal.errors.count == 1 && new_journal.errors.first[0] == "version"
|
||||||
# Skip, only error was from creating the initial journal for a record that already had one.
|
# Skip, only error was from creating the initial journal for a record that already had one.
|
||||||
|
|
|
@ -1,3 +1,16 @@
|
||||||
|
#-- copyright
|
||||||
|
# ChiliProject is a project management system.
|
||||||
|
#
|
||||||
|
# Copyright (C) 2010-2011 the ChiliProject Team
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU General Public License
|
||||||
|
# as published by the Free Software Foundation; either version 2
|
||||||
|
# of the License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# See doc/COPYRIGHT.rdoc for more details.
|
||||||
|
#++
|
||||||
|
|
||||||
class AddChangesFromJournalDetailsForActsAsJournalized < ActiveRecord::Migration
|
class AddChangesFromJournalDetailsForActsAsJournalized < ActiveRecord::Migration
|
||||||
def self.up
|
def self.up
|
||||||
# This is provided here for migrating up after the JournalDetails has been removed
|
# This is provided here for migrating up after the JournalDetails has been removed
|
||||||
|
|
|
@ -18,6 +18,12 @@ class MergeWikiVersionsWithJournals < ActiveRecord::Migration
|
||||||
WikiContent.const_set("Version", Class.new(ActiveRecord::Base))
|
WikiContent.const_set("Version", Class.new(ActiveRecord::Base))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# avoid touching WikiContent on journal creation
|
||||||
|
WikiContentJournal.class_exec {
|
||||||
|
def touch_journaled_after_creation
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
WikiContent::Version.find_by_sql("SELECT * FROM wiki_content_versions").each do |wv|
|
WikiContent::Version.find_by_sql("SELECT * FROM wiki_content_versions").each do |wv|
|
||||||
journal = WikiContentJournal.create!(:journaled_id => wv.wiki_content_id, :user_id => wv.author_id,
|
journal = WikiContentJournal.create!(:journaled_id => wv.wiki_content_id, :user_id => wv.author_id,
|
||||||
:notes => wv.comments, :created_at => wv.updated_on, :activity_type => "wiki_edits")
|
:notes => wv.comments, :created_at => wv.updated_on, :activity_type => "wiki_edits")
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
class RemoveDoubleInitialWikiContentJournals < ActiveRecord::Migration
|
||||||
|
def self.up
|
||||||
|
# Remove the newest initial WikiContentJournal (the one erroneously created by a former migration) if there are more than one
|
||||||
|
WikiContentJournal.find(:all, :conditions => {:version => 1}).group_by(&:journaled_id).select {|k,v| v.size > 1}.each {|k,v| v.max_by(&:created_at).delete}
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.down
|
||||||
|
# noop
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,6 +1,29 @@
|
||||||
= ChiliProject changelog
|
= ChiliProject changelog
|
||||||
|
|
||||||
== TBD v2.0.0
|
== 2011-07-29 v2.1.0
|
||||||
|
|
||||||
|
* Bug #191: Add Next/Previous links to the top of search results
|
||||||
|
* Bug #467: uninitialized constant Journal::Journaled
|
||||||
|
* Bug #498: Wrong filters for int and float custom fields
|
||||||
|
* Bug #511: Encoding of strings coming out of SQLite
|
||||||
|
* Bug #512: reposman.rb do not work properly in Gentoo Linux.
|
||||||
|
* Bug #513: Attached files in "comment" no longer link to file
|
||||||
|
* Bug #514: Multiple emails for each forum post
|
||||||
|
* Bug #523: Gzipped history of wiki pages is garbeled during an update of an older version to 2.0
|
||||||
|
* Bug #530: Start date default should consider timezone
|
||||||
|
* Bug #536: CSRF Protection
|
||||||
|
* Bug #537: Accessing version of newly created WikiContent results in NoMethodError
|
||||||
|
* Bug #540: Hook helper_issues_show_detail_after_setting gets different parameters in Chili 1.x and 2.0
|
||||||
|
* Bug #542: Double initial journal for migrated wiki history
|
||||||
|
* Bug #543: Journalized touch on journal update causes StaleObjectErrors
|
||||||
|
* Bug #544: XSS in app/views/issues/show.rhtml
|
||||||
|
* Feature #499: Due date sort order should sort issues with no due date to the end of the list
|
||||||
|
* Feature #506: Support for "local" Gemfile - Gemfile.local
|
||||||
|
* Feature #526: Bulgarian translation
|
||||||
|
* Feature #539: Remove dead code in IssueHelper
|
||||||
|
* Task #518: Document how to create a Journal using acts_as_journalized
|
||||||
|
|
||||||
|
== 2011-07-01 v2.0.0
|
||||||
|
|
||||||
* Bug #262: Fix line endings
|
* Bug #262: Fix line endings
|
||||||
* Bug #341: Remove English strings from RepositoriesHelper
|
* Bug #341: Remove English strings from RepositoriesHelper
|
||||||
|
|
|
@ -7,7 +7,7 @@ This is a sample plugin for Redmine
|
||||||
1. Copy the plugin directory into the vendor/plugins directory
|
1. Copy the plugin directory into the vendor/plugins directory
|
||||||
|
|
||||||
2. Migrate plugin:
|
2. Migrate plugin:
|
||||||
rake db:migrate_plugins
|
rake db:migrate:plugins
|
||||||
|
|
||||||
3. Start Redmine
|
3. Start Redmine
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
#++
|
#++
|
||||||
|
|
||||||
# Sample plugin migration
|
# Sample plugin migration
|
||||||
# Use rake db:migrate_plugins to migrate installed plugins
|
# Use rake db:migrate:plugins to migrate installed plugins
|
||||||
class CreateMeetings < ActiveRecord::Migration
|
class CreateMeetings < ActiveRecord::Migration
|
||||||
def self.up
|
def self.up
|
||||||
create_table :meetings do |t|
|
create_table :meetings do |t|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
#!/usr/bin/env ruby
|
||||||
#-- copyright
|
#-- copyright
|
||||||
# ChiliProject is a project management system.
|
# ChiliProject is a project management system.
|
||||||
#
|
#
|
||||||
|
@ -11,8 +12,6 @@
|
||||||
# See doc/COPYRIGHT.rdoc for more details.
|
# See doc/COPYRIGHT.rdoc for more details.
|
||||||
#++
|
#++
|
||||||
|
|
||||||
#!/usr/bin/env ruby
|
|
||||||
|
|
||||||
# == Synopsis
|
# == Synopsis
|
||||||
#
|
#
|
||||||
# reposman: manages your repositories with Redmine
|
# reposman: manages your repositories with Redmine
|
||||||
|
|
|
@ -11,18 +11,14 @@
|
||||||
# See doc/COPYRIGHT.rdoc for more details.
|
# See doc/COPYRIGHT.rdoc for more details.
|
||||||
#++
|
#++
|
||||||
|
|
||||||
namespace :db do
|
module ChiliProject
|
||||||
desc 'Migrates installed plugins.'
|
class Compatibility
|
||||||
task :migrate_plugins => :environment do
|
# Is acts_as_journalized included?
|
||||||
if Rails.respond_to?('plugins')
|
#
|
||||||
Rails.plugins.each do |plugin|
|
# Released: ChiliProject 2.0.0
|
||||||
next unless plugin.respond_to?('migrate')
|
def self.using_acts_as_journalized?
|
||||||
puts "Migrating #{plugin.name}..."
|
Journal.included_modules.include?(Redmine::Acts::Journalized)
|
||||||
plugin.migrate
|
|
||||||
end
|
|
||||||
else
|
|
||||||
puts "Undefined method plugins for Rails!"
|
|
||||||
puts "Make sure engines plugin is installed."
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
|
@ -30,7 +30,7 @@ module ChiliProject
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
# Get the raw namme of the currently used database adapter.
|
# Get the raw name of the currently used database adapter.
|
||||||
# This string is set by the used adapter gem.
|
# This string is set by the used adapter gem.
|
||||||
def self.adapter_name
|
def self.adapter_name
|
||||||
ActiveRecord::Base.connection.adapter_name
|
ActiveRecord::Base.connection.adapter_name
|
||||||
|
|
|
@ -288,7 +288,12 @@ module Redmine
|
||||||
content = nil
|
content = nil
|
||||||
scm_cmd(*cmd_args) { |io| io.binmode; content = io.read }
|
scm_cmd(*cmd_args) { |io| io.binmode; content = io.read }
|
||||||
# git annotates binary files
|
# git annotates binary files
|
||||||
return nil if content.is_binary_data?
|
if content.respond_to?("is_binary_data?") && content.is_binary_data? # Ruby 1.8.x and <1.9.2
|
||||||
|
return nil
|
||||||
|
elsif content.respond_to?(:force_encoding) && (content.dup.force_encoding("UTF-8") != content.dup.force_encoding("BINARY")) # Ruby 1.9.2
|
||||||
|
# TODO: need to handle edge cases of non-binary content that isn't UTF-8
|
||||||
|
return nil
|
||||||
|
end
|
||||||
identifier = ''
|
identifier = ''
|
||||||
# git shows commit author on the first occurrence only
|
# git shows commit author on the first occurrence only
|
||||||
authors_by_commit = {}
|
authors_by_commit = {}
|
||||||
|
|
|
@ -16,7 +16,7 @@ require 'rexml/document'
|
||||||
module Redmine
|
module Redmine
|
||||||
module VERSION #:nodoc:
|
module VERSION #:nodoc:
|
||||||
MAJOR = 2
|
MAJOR = 2
|
||||||
MINOR = 0
|
MINOR = 1
|
||||||
PATCH = 0
|
PATCH = 0
|
||||||
TINY = PATCH # Redmine compat
|
TINY = PATCH # Redmine compat
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,7 @@ namespace :ci do
|
||||||
Rake::Task["db:drop"].invoke
|
Rake::Task["db:drop"].invoke
|
||||||
Rake::Task["db:create"].invoke
|
Rake::Task["db:create"].invoke
|
||||||
Rake::Task["db:migrate"].invoke
|
Rake::Task["db:migrate"].invoke
|
||||||
Rake::Task["db:migrate_plugins"].invoke
|
Rake::Task["db:migrate:plugins"].invoke
|
||||||
Rake::Task["db:schema:dump"].invoke
|
Rake::Task["db:schema:dump"].invoke
|
||||||
Rake::Task["test:scm:update"].invoke
|
Rake::Task["test:scm:update"].invoke
|
||||||
end
|
end
|
||||||
|
|
|
@ -20,3 +20,4 @@ end
|
||||||
deprecated_task :load_default_data, "redmine:load_default_data"
|
deprecated_task :load_default_data, "redmine:load_default_data"
|
||||||
deprecated_task :migrate_from_mantis, "redmine:migrate_from_mantis"
|
deprecated_task :migrate_from_mantis, "redmine:migrate_from_mantis"
|
||||||
deprecated_task :migrate_from_trac, "redmine:migrate_from_trac"
|
deprecated_task :migrate_from_trac, "redmine:migrate_from_trac"
|
||||||
|
deprecated_task "db:migrate_plugins", "db:migrate:plugins"
|
|
@ -19,11 +19,12 @@ begin
|
||||||
files << Dir['vendor/plugins/**/*.rb'].reject {|f| f.match(/test/) } # Exclude test files
|
files << Dir['vendor/plugins/**/*.rb'].reject {|f| f.match(/test/) } # Exclude test files
|
||||||
t.files = files
|
t.files = files
|
||||||
|
|
||||||
static_files = ['doc/CHANGELOG',
|
static_files = ['doc/CHANGELOG.rdoc',
|
||||||
'doc/COPYING',
|
'doc/COPYING.rdoc',
|
||||||
'doc/INSTALL',
|
'doc/COPYRIGHT.rdoc',
|
||||||
'doc/RUNNING_TESTS',
|
'doc/INSTALL.rdoc',
|
||||||
'doc/UPGRADING'].join(',')
|
'doc/RUNNING_TESTS.rdoc',
|
||||||
|
'doc/UPGRADING.rdoc'].join(',')
|
||||||
|
|
||||||
t.options += ['--output-dir', './doc/app', '--files', static_files]
|
t.options += ['--output-dir', './doc/app', '--files', static_files]
|
||||||
end
|
end
|
||||||
|
|
|
@ -331,6 +331,9 @@ dt.time-entry { background-image: url(../images/time.png); }
|
||||||
|
|
||||||
#search-results dt.issue.closed { background-image: url(../images/ticket_checked.png); }
|
#search-results dt.issue.closed { background-image: url(../images/ticket_checked.png); }
|
||||||
|
|
||||||
|
.search-pagination { text-align: center; }
|
||||||
|
.search-pagination a {padding: 0 5px; }
|
||||||
|
|
||||||
div#roadmap .related-issues { margin-bottom: 1em; }
|
div#roadmap .related-issues { margin-bottom: 1em; }
|
||||||
div#roadmap .related-issues td.checkbox { display: none; }
|
div#roadmap .related-issues td.checkbox { display: none; }
|
||||||
div#roadmap .wiki h1:first-child { display: none; }
|
div#roadmap .wiki h1:first-child { display: none; }
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
#++
|
#++
|
||||||
|
|
||||||
class Journal < ActiveRecord::Base
|
class Journal < ActiveRecord::Base
|
||||||
generator_for :journalized, :method => :generate_issue
|
generator_for :journaled, :method => :generate_issue
|
||||||
generator_for :user, :method => :generate_user
|
generator_for :user, :method => :generate_user
|
||||||
|
|
||||||
def self.generate_issue
|
def self.generate_issue
|
||||||
|
|
|
@ -61,6 +61,7 @@ class IssuesTest < ActionController::IntegrationTest
|
||||||
|
|
||||||
# add then remove 2 attachments to an issue
|
# add then remove 2 attachments to an issue
|
||||||
def test_issue_attachments
|
def test_issue_attachments
|
||||||
|
Issue.find(1).recreate_initial_journal!
|
||||||
log_user('jsmith', 'jsmith')
|
log_user('jsmith', 'jsmith')
|
||||||
set_tmp_attachments_directory
|
set_tmp_attachments_directory
|
||||||
|
|
||||||
|
@ -68,6 +69,7 @@ class IssuesTest < ActionController::IntegrationTest
|
||||||
:notes => 'Some notes',
|
:notes => 'Some notes',
|
||||||
:attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'This is an attachment'}}
|
:attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'This is an attachment'}}
|
||||||
assert_redirected_to "/issues/1"
|
assert_redirected_to "/issues/1"
|
||||||
|
follow_redirect!
|
||||||
|
|
||||||
# make sure attachment was saved
|
# make sure attachment was saved
|
||||||
attachment = Issue.find(1).attachments.find_by_filename("testfile.txt")
|
attachment = Issue.find(1).attachments.find_by_filename("testfile.txt")
|
||||||
|
@ -79,6 +81,12 @@ class IssuesTest < ActionController::IntegrationTest
|
||||||
# verify that the attachment was written to disk
|
# verify that the attachment was written to disk
|
||||||
assert File.exist?(attachment.diskfile)
|
assert File.exist?(attachment.diskfile)
|
||||||
|
|
||||||
|
assert_select "#history" do
|
||||||
|
assert_select ".journal .details" do
|
||||||
|
assert_select "a", :text => /testfile.txt/
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# remove the attachments
|
# remove the attachments
|
||||||
Issue.find(1).attachments.each(&:destroy)
|
Issue.find(1).attachments.each(&:destroy)
|
||||||
assert_equal 0, Issue.find(1).attachments.length
|
assert_equal 0, Issue.find(1).attachments.length
|
||||||
|
|
|
@ -39,33 +39,30 @@ class IssuesHelperTest < HelperTestCase
|
||||||
@request ||= ActionController::TestRequest.new
|
@request ||= ActionController::TestRequest.new
|
||||||
end
|
end
|
||||||
|
|
||||||
# This is probably needed in this test only anymore
|
|
||||||
def show_detail(journal, detail, html = true)
|
|
||||||
journal.render_detail(detail, html)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
# TODO: Move test code to Journal class
|
||||||
context "IssuesHelper#show_detail" do
|
context "IssuesHelper#show_detail" do
|
||||||
context "with no_html" do
|
context "with no_html" do
|
||||||
should 'show a changing attribute' do
|
should 'show a changing attribute' do
|
||||||
@journal = IssueJournal.generate!(:changes => {"done_ratio" => [40, 100]}, :journaled => Issue.last)
|
@journal = IssueJournal.generate!(:changes => {"done_ratio" => [40, 100]}, :journaled => Issue.last)
|
||||||
assert_equal "% Done changed from 40 to 100", show_detail(@journal, @journal.details.to_a.first, true)
|
assert_equal "% Done changed from 40 to 100", @journal.render_detail(@journal.details.to_a.first, true)
|
||||||
end
|
end
|
||||||
|
|
||||||
should 'show a new attribute' do
|
should 'show a new attribute' do
|
||||||
@journal = IssueJournal.generate!(:changes => {"done_ratio" => [nil, 100]}, :journaled => Issue.last)
|
@journal = IssueJournal.generate!(:changes => {"done_ratio" => [nil, 100]}, :journaled => Issue.last)
|
||||||
assert_equal "% Done set to 100", show_detail(@journal, @journal.details.to_a.first, true)
|
assert_equal "% Done set to 100", @journal.render_detail(@journal.details.to_a.first, true)
|
||||||
end
|
end
|
||||||
|
|
||||||
should 'show a deleted attribute' do
|
should 'show a deleted attribute' do
|
||||||
@journal = IssueJournal.generate!(:changes => {"done_ratio" => [50, nil]}, :journaled => Issue.last)
|
@journal = IssueJournal.generate!(:changes => {"done_ratio" => [50, nil]}, :journaled => Issue.last)
|
||||||
assert_equal "% Done deleted (50)", show_detail(@journal, @journal.details.to_a.first, true)
|
assert_equal "% Done deleted (50)", @journal.render_detail(@journal.details.to_a.first, true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "with html" do
|
context "with html" do
|
||||||
should 'show a changing attribute with HTML highlights' do
|
should 'show a changing attribute with HTML highlights' do
|
||||||
@journal = IssueJournal.generate!(:changes => {"done_ratio" => [40, 100]}, :journaled => Issue.last)
|
@journal = IssueJournal.generate!(:changes => {"done_ratio" => [40, 100]}, :journaled => Issue.last)
|
||||||
@response.body = show_detail(@journal, @journal.details.to_a.first, false)
|
@response.body = @journal.render_detail(@journal.details.to_a.first, false)
|
||||||
|
|
||||||
assert_select 'strong', :text => '% Done'
|
assert_select 'strong', :text => '% Done'
|
||||||
assert_select 'i', :text => '40'
|
assert_select 'i', :text => '40'
|
||||||
|
@ -74,7 +71,7 @@ class IssuesHelperTest < HelperTestCase
|
||||||
|
|
||||||
should 'show a new attribute with HTML highlights' do
|
should 'show a new attribute with HTML highlights' do
|
||||||
@journal = IssueJournal.generate!(:changes => {"done_ratio" => [nil, 100]}, :journaled => Issue.last)
|
@journal = IssueJournal.generate!(:changes => {"done_ratio" => [nil, 100]}, :journaled => Issue.last)
|
||||||
@response.body = show_detail(@journal, @journal.details.to_a.first, false)
|
@response.body = @journal.render_detail(@journal.details.to_a.first, false)
|
||||||
|
|
||||||
assert_select 'strong', :text => '% Done'
|
assert_select 'strong', :text => '% Done'
|
||||||
assert_select 'i', :text => '100'
|
assert_select 'i', :text => '100'
|
||||||
|
@ -82,7 +79,7 @@ class IssuesHelperTest < HelperTestCase
|
||||||
|
|
||||||
should 'show a deleted attribute with HTML highlights' do
|
should 'show a deleted attribute with HTML highlights' do
|
||||||
@journal = IssueJournal.generate!(:changes => {"done_ratio" => [50, nil]}, :journaled => Issue.last)
|
@journal = IssueJournal.generate!(:changes => {"done_ratio" => [50, nil]}, :journaled => Issue.last)
|
||||||
@response.body = show_detail(@journal, @journal.details.to_a.first, false)
|
@response.body = @journal.render_detail(@journal.details.to_a.first, false)
|
||||||
|
|
||||||
assert_select 'strong', :text => '% Done'
|
assert_select 'strong', :text => '% Done'
|
||||||
assert_select 'strike' do
|
assert_select 'strike' do
|
||||||
|
@ -94,24 +91,24 @@ class IssuesHelperTest < HelperTestCase
|
||||||
context "with a start_date attribute" do
|
context "with a start_date attribute" do
|
||||||
should "format the current date" do
|
should "format the current date" do
|
||||||
@journal = IssueJournal.generate!(:changes => {"start_date" => ['2010-01-01', '2010-01-31']}, :journaled => Issue.last)
|
@journal = IssueJournal.generate!(:changes => {"start_date" => ['2010-01-01', '2010-01-31']}, :journaled => Issue.last)
|
||||||
assert_match "01/31/2010", show_detail(@journal, @journal.details.to_a.first, true)
|
assert_match "01/31/2010", @journal.render_detail(@journal.details.to_a.first, true)
|
||||||
end
|
end
|
||||||
|
|
||||||
should "format the old date" do
|
should "format the old date" do
|
||||||
@journal = IssueJournal.generate!(:changes => {"start_date" => ['2010-01-01', '2010-01-31']}, :journaled => Issue.last)
|
@journal = IssueJournal.generate!(:changes => {"start_date" => ['2010-01-01', '2010-01-31']}, :journaled => Issue.last)
|
||||||
assert_match "01/01/2010", show_detail(@journal, @journal.details.to_a.first, true)
|
assert_match "01/01/2010", @journal.render_detail(@journal.details.to_a.first, true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "with a due_date attribute" do
|
context "with a due_date attribute" do
|
||||||
should "format the current date" do
|
should "format the current date" do
|
||||||
@journal = IssueJournal.generate!(:changes => {"due_date" => ['2010-01-01', '2010-01-31']}, :journaled => Issue.last)
|
@journal = IssueJournal.generate!(:changes => {"due_date" => ['2010-01-01', '2010-01-31']}, :journaled => Issue.last)
|
||||||
assert_match "01/31/2010", show_detail(@journal, @journal.details.to_a.first, true)
|
assert_match "01/31/2010", @journal.render_detail(@journal.details.to_a.first, true)
|
||||||
end
|
end
|
||||||
|
|
||||||
should "format the old date" do
|
should "format the old date" do
|
||||||
@journal = IssueJournal.generate!(:changes => {"due_date" => ['2010-01-01', '2010-01-31']}, :journaled => Issue.last)
|
@journal = IssueJournal.generate!(:changes => {"due_date" => ['2010-01-01', '2010-01-31']}, :journaled => Issue.last)
|
||||||
assert_match "01/01/2010", show_detail(@journal, @journal.details.to_a.first, true)
|
assert_match "01/01/2010", @journal.render_detail(@journal.details.to_a.first, true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
require File.expand_path('../../test_helper', __FILE__)
|
require File.expand_path('../../test_helper', __FILE__)
|
||||||
|
|
||||||
class JournalTest < ActiveSupport::TestCase
|
class JournalTest < ActiveSupport::TestCase
|
||||||
fixtures :issues, :issue_statuses, :journals
|
fixtures :issues, :issue_statuses, :journals, :enumerations
|
||||||
|
|
||||||
def setup
|
def setup
|
||||||
@journal = IssueJournal.find(1)
|
@journal = IssueJournal.find(1)
|
||||||
|
@ -94,4 +94,24 @@ class JournalTest < ActiveSupport::TestCase
|
||||||
|
|
||||||
assert_not_equal start, @issue.reload.updated_on
|
assert_not_equal start, @issue.reload.updated_on
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "accessing #journaled on a Journal should not error (parent class)" do
|
||||||
|
journal = Journal.new
|
||||||
|
assert_nothing_raised do
|
||||||
|
assert_equal nil, journal.journaled
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "setting journal fields through the journaled object for creation" do
|
||||||
|
@issue = Issue.generate_for_project!(Project.generate!)
|
||||||
|
|
||||||
|
@issue.journal_user = @issue.author
|
||||||
|
@issue.journal_notes = 'Test setting fields on Journal from Issue'
|
||||||
|
assert_difference('Journal.count') do
|
||||||
|
assert @issue.save
|
||||||
|
end
|
||||||
|
|
||||||
|
assert_equal "Test setting fields on Journal from Issue", @issue.last_journal.notes
|
||||||
|
assert_equal @issue.author, @issue.last_journal.user
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -155,7 +155,8 @@ begin
|
||||||
assert_equal "2010-09-18 19:59:46".to_time, last_rev.time
|
assert_equal "2010-09-18 19:59:46".to_time, last_rev.time
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_latin_1_path
|
# TODO: need to handle edge cases of non-binary content that isn't UTF-8
|
||||||
|
should_eventually "test_latin_1_path" do
|
||||||
if WINDOWS_PASS
|
if WINDOWS_PASS
|
||||||
#
|
#
|
||||||
else
|
else
|
||||||
|
@ -163,7 +164,9 @@ begin
|
||||||
['4fc55c43bf3d3dc2efb66145365ddc17639ce81e', '4fc55c43bf3'].each do |r1|
|
['4fc55c43bf3d3dc2efb66145365ddc17639ce81e', '4fc55c43bf3'].each do |r1|
|
||||||
assert @adapter.diff(p2, r1)
|
assert @adapter.diff(p2, r1)
|
||||||
assert @adapter.cat(p2, r1)
|
assert @adapter.cat(p2, r1)
|
||||||
assert_equal 1, @adapter.annotate(p2, r1).lines.length
|
annotation = @adapter.annotate(p2, r1)
|
||||||
|
assert annotation.present?, "No annotation returned"
|
||||||
|
assert_equal 1, annotation.lines.length
|
||||||
['64f1f3e89ad1cb57976ff0ad99a107012ba3481d', '64f1f3e89ad1cb5797'].each do |r2|
|
['64f1f3e89ad1cb57976ff0ad99a107012ba3481d', '64f1f3e89ad1cb5797'].each do |r2|
|
||||||
assert @adapter.diff(p2, r1, r2)
|
assert @adapter.diff(p2, r1, r2)
|
||||||
end
|
end
|
||||||
|
|
|
@ -179,7 +179,7 @@ class MailerTest < ActiveSupport::TestCase
|
||||||
assert_nil mail.references
|
assert_nil mail.references
|
||||||
assert_select_email do
|
assert_select_email do
|
||||||
# link to the message
|
# link to the message
|
||||||
assert_select "a[href=?]", "http://mydomain.foo/boards/#{message.board.id}/topics/#{message.id}", :text => message.subject
|
assert_select "a[href*=?]", "http://mydomain.foo/boards/#{message.board.id}/topics/#{message.id}", :text => message.subject
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -316,7 +316,7 @@ class MailerTest < ActiveSupport::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_wiki_content_added
|
def test_wiki_content_added
|
||||||
content = WikiContent.find(:first)
|
content = WikiContent.find(1)
|
||||||
valid_languages.each do |lang|
|
valid_languages.each do |lang|
|
||||||
Setting.default_language = lang.to_s
|
Setting.default_language = lang.to_s
|
||||||
assert_difference 'ActionMailer::Base.deliveries.size' do
|
assert_difference 'ActionMailer::Base.deliveries.size' do
|
||||||
|
@ -326,7 +326,7 @@ class MailerTest < ActiveSupport::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_wiki_content_updated
|
def test_wiki_content_updated
|
||||||
content = WikiContent.find(:first)
|
content = WikiContent.find(1)
|
||||||
valid_languages.each do |lang|
|
valid_languages.each do |lang|
|
||||||
Setting.default_language = lang.to_s
|
Setting.default_language = lang.to_s
|
||||||
assert_difference 'ActionMailer::Base.deliveries.size' do
|
assert_difference 'ActionMailer::Base.deliveries.size' do
|
||||||
|
|
|
@ -16,6 +16,7 @@ class MessageTest < ActiveSupport::TestCase
|
||||||
fixtures :projects, :roles, :members, :member_roles, :boards, :messages, :users, :watchers
|
fixtures :projects, :roles, :members, :member_roles, :boards, :messages, :users, :watchers
|
||||||
|
|
||||||
def setup
|
def setup
|
||||||
|
Setting.notified_events = ['message_posted']
|
||||||
@board = Board.find(1)
|
@board = Board.find(1)
|
||||||
@user = User.find(1)
|
@user = User.find(1)
|
||||||
end
|
end
|
||||||
|
@ -138,4 +139,12 @@ class MessageTest < ActiveSupport::TestCase
|
||||||
message.sticky = '1'
|
message.sticky = '1'
|
||||||
assert_equal 1, message.sticky
|
assert_equal 1, message.sticky
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "email notifications for creating a message" do
|
||||||
|
assert_difference("ActionMailer::Base.deliveries.count") do
|
||||||
|
message = Message.new(:board => @board, :subject => 'Test message', :content => 'Test message content', :author => @user)
|
||||||
|
assert message.save
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -80,4 +80,11 @@ class WikiContentTest < ActiveSupport::TestCase
|
||||||
page.reload
|
page.reload
|
||||||
assert_equal 500.kilobyte, page.content.text.size
|
assert_equal 500.kilobyte, page.content.text.size
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "new WikiContent is version 0" do
|
||||||
|
page = WikiPage.new(:wiki => @wiki, :title => "Page")
|
||||||
|
page.content = WikiContent.new(:text => "Content text", :author => User.find(1), :comments => "My comment")
|
||||||
|
|
||||||
|
assert_equal 0, page.content.version
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
|
|
||||||
|
|
||||||
Dir[File.expand_path("../redmine/acts/journalized/*.rb", __FILE__)].each{|f| require f }
|
Dir[File.expand_path("../redmine/acts/journalized/*.rb", __FILE__)].each{|f| require f }
|
||||||
require_dependency File.expand_path('lib/ar_condition', Rails.root)
|
require "ar_condition"
|
||||||
|
|
||||||
module Redmine
|
module Redmine
|
||||||
module Acts
|
module Acts
|
||||||
|
@ -140,7 +140,7 @@ module Redmine
|
||||||
|
|
||||||
h[:find_options] ||= {} # in case it is nil
|
h[:find_options] ||= {} # in case it is nil
|
||||||
h[:find_options] = {}.tap do |opts|
|
h[:find_options] = {}.tap do |opts|
|
||||||
cond = ARCondition.new
|
cond = ::ARCondition.new
|
||||||
cond.add(["#{journal_class.table_name}.activity_type = ?", h[:type]])
|
cond.add(["#{journal_class.table_name}.activity_type = ?", h[:type]])
|
||||||
cond.add(h[:find_options][:conditions]) if h[:find_options][:conditions]
|
cond.add(h[:find_options][:conditions]) if h[:find_options][:conditions]
|
||||||
opts[:conditions] = cond.conditions
|
opts[:conditions] = cond.conditions
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
class JournalDetail
|
||||||
|
attr_reader :prop_key, :value, :old_value
|
||||||
|
|
||||||
|
def initialize(prop_key, old_value, value)
|
||||||
|
@prop_key = prop_key
|
||||||
|
@old_value = old_value
|
||||||
|
@value = value
|
||||||
|
end
|
||||||
|
end
|
|
@ -27,8 +27,18 @@ module JournalFormatter
|
||||||
include CustomFieldsHelper
|
include CustomFieldsHelper
|
||||||
include ActionView::Helpers::TagHelper
|
include ActionView::Helpers::TagHelper
|
||||||
include ActionView::Helpers::UrlHelper
|
include ActionView::Helpers::UrlHelper
|
||||||
|
include ActionController::UrlWriter
|
||||||
extend Redmine::I18n
|
extend Redmine::I18n
|
||||||
|
|
||||||
|
def self.included(base)
|
||||||
|
base.class_eval do
|
||||||
|
# Required to use any link_to in the formatters
|
||||||
|
def self.default_url_options
|
||||||
|
{:only_path => true }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def self.register(hash)
|
def self.register(hash)
|
||||||
if hash[:class]
|
if hash[:class]
|
||||||
klazz = hash.delete(:class)
|
klazz = hash.delete(:class)
|
||||||
|
@ -90,9 +100,7 @@ module JournalFormatter
|
||||||
|
|
||||||
def format_html_attachment_detail(key, value)
|
def format_html_attachment_detail(key, value)
|
||||||
if !value.blank? && a = Attachment.find_by_id(key.to_i)
|
if !value.blank? && a = Attachment.find_by_id(key.to_i)
|
||||||
# Link to the attachment if it has not been removed
|
link_to_attachment(a)
|
||||||
# FIXME: this is broken => link_to_attachment(a)
|
|
||||||
a.filename
|
|
||||||
else
|
else
|
||||||
content_tag("i", h(value)) if value.present?
|
content_tag("i", h(value)) if value.present?
|
||||||
end
|
end
|
||||||
|
@ -163,7 +171,7 @@ module JournalFormatter
|
||||||
end
|
end
|
||||||
|
|
||||||
label, old_value, value = attr_detail || cv_detail || attachment_detail
|
label, old_value, value = attr_detail || cv_detail || attachment_detail
|
||||||
Redmine::Hook.call_hook :helper_issues_show_detail_after_setting, {:detail => detail,
|
Redmine::Hook.call_hook :helper_issues_show_detail_after_setting, {:detail => JournalDetail.new(label, old_value, value),
|
||||||
:label => label, :value => value, :old_value => old_value }
|
:label => label, :value => value, :old_value => old_value }
|
||||||
return nil unless label || old_value || value # print nothing if there are no values
|
return nil unless label || old_value || value # print nothing if there are no values
|
||||||
label, old_value, value = [label, old_value, value].collect(&:to_s)
|
label, old_value, value = [label, old_value, value].collect(&:to_s)
|
||||||
|
|
|
@ -68,6 +68,48 @@ module Redmine::Acts::Journalized
|
||||||
|
|
||||||
# Instance methods that determine whether to save a journal and actually perform the save.
|
# Instance methods that determine whether to save a journal and actually perform the save.
|
||||||
module InstanceMethods
|
module InstanceMethods
|
||||||
|
# Recreates the initial journal used to track the beginning state
|
||||||
|
# of the object. Useful for objects that didn't have an initial journal
|
||||||
|
# created (e.g. legacy data)
|
||||||
|
def recreate_initial_journal!
|
||||||
|
new_journal = journals.find_by_version(1)
|
||||||
|
new_journal ||= journals.build
|
||||||
|
# Mock up a list of changes for the creation journal based on Class defaults
|
||||||
|
new_attributes = self.class.new.attributes.except(self.class.primary_key,
|
||||||
|
self.class.inheritance_column,
|
||||||
|
:updated_on,
|
||||||
|
:updated_at,
|
||||||
|
:lock_version,
|
||||||
|
:lft,
|
||||||
|
:rgt)
|
||||||
|
creation_changes = {}
|
||||||
|
new_attributes.each do |name, default_value|
|
||||||
|
# Set changes based on the initial value to current. Can't get creation value without
|
||||||
|
# rebuiling the object history
|
||||||
|
creation_changes[name] = [default_value, self.send(name)] # [initial_value, creation_value]
|
||||||
|
end
|
||||||
|
new_journal.changes = creation_changes
|
||||||
|
new_journal.version = 1
|
||||||
|
new_journal.activity_type = self.class.send(:journalized_activity_hash, {})[:type]
|
||||||
|
|
||||||
|
if respond_to?(:author)
|
||||||
|
new_journal.user = author
|
||||||
|
elsif respond_to?(:user)
|
||||||
|
new_journal.user = user
|
||||||
|
end
|
||||||
|
|
||||||
|
new_journal.save!
|
||||||
|
new_journal.reload
|
||||||
|
|
||||||
|
# Backdate journal
|
||||||
|
if respond_to?(:created_at)
|
||||||
|
new_journal.update_attribute(:created_at, created_at)
|
||||||
|
elsif respond_to?(:created_on)
|
||||||
|
new_journal.update_attribute(:created_at, created_on)
|
||||||
|
end
|
||||||
|
new_journal
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
# Returns whether a new journal should be created upon updating the parent record.
|
# Returns whether a new journal should be created upon updating the parent record.
|
||||||
# A new journal will be created if
|
# A new journal will be created if
|
||||||
|
@ -120,7 +162,8 @@ module Redmine::Acts::Journalized
|
||||||
def journal_attributes
|
def journal_attributes
|
||||||
attributes = { :journaled_id => self.id, :activity_type => activity_type,
|
attributes = { :journaled_id => self.id, :activity_type => activity_type,
|
||||||
:changes => journal_changes, :version => last_version + 1,
|
:changes => journal_changes, :version => last_version + 1,
|
||||||
:notes => journal_notes, :user_id => (journal_user.try(:id) || User.current.try(:id)) }
|
:notes => journal_notes, :user_id => (journal_user.try(:id) || User.current.try(:id))
|
||||||
|
}.merge(extra_journal_attributes || {})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -28,7 +28,7 @@ module Redmine::Acts::Journalized
|
||||||
before_save :init_journal
|
before_save :init_journal
|
||||||
after_save :reset_instance_variables
|
after_save :reset_instance_variables
|
||||||
|
|
||||||
attr_reader :journal_notes, :journal_user
|
attr_accessor :journal_notes, :journal_user, :extra_journal_attributes
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue