Commit 30285ce6 authored by Eric Davis's avatar Eric Davis

Merge branch 'release-v2.1.0' into stable

parents effcd726 2d67c2f2
......@@ -26,4 +26,5 @@
doc/app
/.bundle
/Gemfile.lock
/Gemfile.local
/.rvmrc*
......@@ -11,6 +11,9 @@ group :test do
gem 'shoulda', '~> 2.10.3'
gem 'edavis10-object_daddy', :require => 'object_daddy'
gem 'mocha'
platforms :mri_18 do gem 'ruby-debug' end
platforms :mri_19 do gem 'ruby-debug19', :require => 'ruby-debug' end
end
group :openid do
......@@ -36,15 +39,22 @@ platforms :mri do
group :mysql2 do
gem "mysql2", "~> 0.2.7"
end
group :postgres do
gem "pg", "~> 0.9.0"
# gem "postgres-pr"
end
end
platforms :mri_18 do
group :sqlite do
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
......@@ -54,16 +64,23 @@ platforms :jruby do
group :mysql do
gem "activerecord-jdbcmysql-adapter"
end
group :postgres do
gem "activerecord-jdbcpostgresql-adapter"
end
group :sqlite do
gem "activerecord-jdbcsqlite3-adapter"
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
Dir.glob File.expand_path("../vendor/plugins/*/Gemfile", __FILE__) do |file|
puts "Loading #{file} ..." if $DEBUG # `ruby -d` or `bundle -v`
......
......@@ -24,6 +24,12 @@ class ApplicationController < ActionController::Base
layout 'base'
exempt_from_layout 'builder', 'rsb'
protect_from_forgery
def handle_unverified_request
super
cookies.delete(:autologin)
end
# Remove broken cookie after upgrade from 0.8.x (#4292)
# See https://rails.lighthouseapp.com/projects/8994/tickets/3360
# TODO: remove it when Rails is fixed
......@@ -38,7 +44,6 @@ class ApplicationController < ActionController::Base
before_filter :user_setup, :check_if_login_required, :set_localization
filter_parameter_logging :password
protect_from_forgery
rescue_from ActionController::InvalidAuthenticityToken, :with => :invalid_authenticity_token
......
......@@ -286,7 +286,7 @@ private
render_error l(:error_no_tracker_in_project)
return false
end
@issue.start_date ||= Date.today
@issue.start_date ||= User.current.today
if params[:issue].is_a?(Hash)
@issue.safe_attributes = params[:issue]
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 :settings, :only => :settings
before_filter :find_project, :except => [ :index, :list, :new, :create, :copy ]
before_filter :authorize, :except => [ :index, :list, :new, :create, :copy, :archive, :unarchive, :destroy]
before_filter :find_project, :except => [ :index, :new, :create, :copy ]
before_filter :authorize, :only => [ :show, :settings, :edit, :update, :modules ]
before_filter :authorize_global, :only => [:new, :create]
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
after_filter :only => [:create, :edit, :update, :archive, :unarchive, :destroy] do |controller|
......@@ -68,12 +70,7 @@ class ProjectsController < ApplicationController
if validate_parent_id && @project.save
@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
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
add_current_user_to_project_if_not_admin(@project)
respond_to do |format|
format.html {
flash[:notice] = l(:notice_successful_create)
......@@ -128,11 +125,6 @@ class ProjectsController < ApplicationController
# Show @project
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
@subprojects = @project.children.visible.all
@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
end
@key = User.current.rss_key
respond_to do |format|
format.html
format.api
......@@ -160,12 +150,6 @@ class ProjectsController < ApplicationController
end
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
def edit
......@@ -187,7 +171,7 @@ class ProjectsController < ApplicationController
else
respond_to do |format|
format.html {
settings
load_project_settings
render :action => 'settings'
}
format.api { render_validation_errors(@project) }
......@@ -230,8 +214,7 @@ class ProjectsController < ApplicationController
end
end
end
# hide project in layout
@project = nil
hide_project_in_layout
end
private
......@@ -257,4 +240,33 @@ private
end
true
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
......@@ -143,7 +143,12 @@ class RepositoriesController < ApplicationController
return true if Redmine::MimeType.is_type?('text', path)
# Ruby 1.8.6 has a bug of integer divisions.
# 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
end
private :is_entry_text_data?
......
......@@ -81,7 +81,7 @@ module ApplicationHelper
subject = truncate(subject, :length => options[:truncate])
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,
:title => title
s << ": #{h subject}" if subject
......
......@@ -129,82 +129,6 @@ module IssuesHelper
out
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
def find_name_by_reflection(field, id)
association = Issue.reflect_on_association(field.to_sym)
......
......@@ -48,7 +48,7 @@ module JournalsHelper
if d = journal.render_detail(detail)
content_tag("li", d)
end
end.compact
end.compact.join(' ')
end
end
......
......@@ -57,4 +57,14 @@ module SearchHelper
end
('<ul>' + links.map {|link| content_tag('li', link)}.join(' ') + '</ul>') unless links.empty?
end
def link_to_previous_search_page(pagination_previous_date)
link_to_content_update('&#171; ' + 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) + ' &#187;',
params.merge(:previous => nil, :offset => pagination_next_date.strftime("%Y%m%d%H%M%S")))
end
end
......@@ -17,10 +17,22 @@ class Change < ActiveRecord::Base
validates_presence_of :changeset_id, :action, :path
before_save :init_path
delegate :repository_encoding, :to => :changeset, :allow_nil => true, :prefix => true
def relative_path
changeset.repository.relative_path(path)
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
self.path ||= ""
end
......
......@@ -74,6 +74,23 @@ class Changeset < ActiveRecord::Base
user || committer.to_s.split('<').first
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
self.committer = self.class.to_utf8(self.committer, 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
# TODO: refactor to a standard helper method
def self.to_utf8(str, encoding)
return str if str.nil?
str.force_encoding("ASCII-8BIT") if str.respond_to?(:force_encoding)
......@@ -273,12 +291,6 @@ class Changeset < ActiveRecord::Base
end
str = txtar
end
# removes invalid UTF8 sequences
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
str
end
end
......@@ -23,9 +23,17 @@ class Journal < ActiveRecord::Base
# Make sure each journaled model instance only has unique version ids
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
# "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,
# 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
......@@ -33,6 +41,10 @@ class Journal < ActiveRecord::Base
# undef_method :changes
serialize :changes, Hash
def touch_journaled_after_creation
journaled.touch
end
# In conjunction with the included Comparable module, allows comparison of journal records
# based on their corresponding version numbers, creation timestamps and IDs.
def <=>(other)
......
......@@ -185,7 +185,7 @@ class Mailer < ActionMailer::Base
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}"
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)
end
......
......@@ -12,10 +12,7 @@
#++
class MessageObserver < ActiveRecord::Observer
def after_save(message)
if message.last_journal.version == 1
# Only deliver mails for the first journal
Mailer.deliver_message_posted(message) if Setting.notified_events.include?('message_posted')
end
def after_create(message)
Mailer.deliver_message_posted(message) if Setting.notified_events.include?('message_posted')
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(: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(:start_date, :sortable => "#{Issue.table_name}.start_date"),
QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date"),
# Put empty start_dates and due_dates in the far future rather than in the far past
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(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio", :groupable => true),
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 << " AND #{db_table}.#{db_field} <> ''" if is_custom_filter
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 "<="
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"
sql = "#{IssueStatus.table_name}.is_closed=#{connection.quoted_false}" if field == "status_id"
when "c"
......@@ -629,6 +638,8 @@ class Query < ActiveRecord::Base
custom_fields.select(&:is_filter?).each do |field|
case field.field_format
when "int", "float"
options = { :type => :integer, :order => 20 }
when "text"
options = { :type => :text, :order => 20 }
when "list"
......
......@@ -59,12 +59,7 @@ class WikiContent < ActiveRecord::Base
end
def version
unless last_journal
# 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
new_record? ? 0 : last_journal.version
end
private
......@@ -106,9 +101,9 @@ class WikiContent < ActiveRecord::Base
end
def text
@text ||= case changes[:compression]
when 'gzip'
Zlib::Inflate.inflate(data)
@text ||= case changes["compression"]
when "gzip"
Zlib::Inflate.inflate(changes["data"])
else
# uncompressed data
changes["data"]
......
<%= 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">
<%= avatar(@issue.author, :size => "50") %>
......@@ -17,11 +17,11 @@
<table class="attributes">
<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>
</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>
</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>
</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) %>
<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>
......
......@@ -22,6 +22,6 @@
<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="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>
<% 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="committed_on"><%= format_time(changeset.committed_on) %></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>
<% line_num += 1 %>
<% end %>
......
<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>
<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">
<% @results.each do |e| %>
<dt class="<%= e.event_type %>">
......@@ -36,15 +39,6 @@
</dl>
<% end %>
<p><center>
<% if @pagination_previous_date %>
<%= link_to_content_update('&#171; ' + l(:label_previous),
params.merge(:previous => 1, :offset => @pagination_previous_date.strftime("%Y%m%d%H%M%S"))) %>&nbsp;
<% end %>
<% if @pagination_next_date %>
<%= link_to_content_update(l(:label_next) + ' &#187;',
params.merge(:previous => nil, :offset => @pagination_next_date.strftime("%Y%m%d%H%M%S"))) %>
<% end %>
</center></p>
<%= render :partial => 'pagination', :locals => {:pagination_previous_date => @pagination_previous_date, :pagination_next_date => @pagination_next_date } %>
<% html_title(l(:label_search)) -%>
......@@ -3,6 +3,7 @@
<head>
<title><%=h @page.pretty_title %></title>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<base href="<%= "#{h Setting.protocol}://#{h Setting.host_name}" %>" />
<style>
body { font:80% Verdana,Tahoma,Arial,sans-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>
</head>
<body>
<%= textilizable @content, :text, :wiki_links => :local %>
<%= textilizable @content, :text, :wiki_links => :local, :only_path => false %>
</body>
</html>
......@@ -945,21 +945,21 @@ bg:
enumeration_activities: Дейности (time tracking)
enumeration_system_activity: Системна активност
text_powered_by: Powered by %{link}
label_cvs_module: Module
label_filesystem_path: Root directory
label_darcs_path: Root directory
label_bazaar_path: Root directory
text_powered_by: Този сайт е задвижван от %{link}
label_cvs_module: Модул
label_filesystem_path: Коренна директория
label_darcs_path: Коренна директория
label_bazaar_path: Коренна директория
label_cvs_path: CVSROOT
label_git_path: Path to .git directory
label_mercurial_path: Root directory
label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee
button_expand_all: Expand all
button_collapse_all: Collapse all
label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author
field_effective_date: Due date
text_default_encoding: "Default: UTF-8"
label_git_path: Път до директория .git
label_mercurial_path: Коренна директория
label_additional_workflow_transitions_for_assignee: Позволени са допълнителни преходи, когато потребителят е назначеният към задачата
button_expand_all: Разгъване всички
button_collapse_all: Скриване всички
label_additional_workflow_transitions_for_author: Позволени са допълнителни преходи, когато потребителят е авторът
field_effective_date: Дата
text_default_encoding: "По подразбиране: UTF-8"
text_git_repo_example: a bare and local repository (e.g. /gitrepo, c:\gitrepo)
label_notify_member_plural: Email issue updates
label_path_encoding: Path encoding
text_mercurial_repo_example: local repository (e.g. /hgrepo, c:\hgrepo)
label_path_encoding: Кодиране на пътищата
text_mercurial_repo_example: локално хранилище (например /hgrepo, c:\hgrepo)
#-- 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 }
class UpdateJournalsForActsAsJournalized < ActiveRecord::Migration
......
#-- 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
def self.up
# 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)
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
# 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
# Create initial journals
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
# touched record could fail. This will catch either error and continue
begin
new_journal.save!
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
new_journal = o.recreate_initial_journal!
rescue ActiveRecord::RecordInvalid => ex
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.
......
#-- 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
def self.up
# 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))
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|
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")
......
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
= 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 #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
2. Migrate plugin:
rake db:migrate_plugins
rake db:migrate:plugins
3. Start Redmine
......
......@@ -12,7 +12,7 @@
#++
# 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
def self.up
create_table :meetings do |t|
......
#!/usr/bin/env ruby
#-- copyright
# ChiliProject is a project management system.
#
......@@ -11,8 +12,6 @@
# See doc/COPYRIGHT.rdoc for more details.
#++
#!/usr/bin/env ruby
# == Synopsis
#
# reposman: manages your repositories with Redmine
......
......@@ -11,18 +11,14 @@
# See doc/COPYRIGHT.rdoc for more details.
#++
namespace :db do
desc 'Migrates installed plugins.'
task :migrate_plugins => :environment do
if Rails.respond_to?('plugins')
Rails.plugins.each do |plugin|
next unless plugin.respond_to?('migrate')
puts "Migrating #{plugin.name}..."
plugin.migrate
end
else
puts "Undefined method plugins for Rails!"
puts "Make sure engines plugin is installed."
module ChiliProject
class Compatibility
# Is acts_as_journalized included?
#
# Released: ChiliProject 2.0.0
def self.using_acts_as_journalized?
Journal.included_modules.include?(Redmine::Acts::Journalized)
end
end
end
......@@ -30,7 +30,7 @@ module ChiliProject
})
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.
def self.adapter_name
ActiveRecord::Base.connection.adapter_name
......
......@@ -288,7 +288,12 @@ module Redmine
content = nil
scm_cmd(*cmd_args) { |io| io.binmode; content = io.read }
# 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 = ''
# git shows commit author on the first occurrence only
authors_by_commit = {}
......
......@@ -16,7 +16,7 @@ require 'rexml/document'
module Redmine
module VERSION #:nodoc:
MAJOR = 2
MINOR = 0
MINOR = 1
PATCH = 0
TINY = PATCH # Redmine compat
......
......@@ -29,7 +29,7 @@ namespace :ci do
Rake::Task["db:drop"].invoke
Rake::Task["db:create"].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["test:scm:update"].invoke
end
......
......@@ -20,3 +20,4 @@ end
deprecated_task :load_default_data, "redmine:load_default_data"
deprecated_task :migrate_from_mantis, "redmine:migrate_from_mantis"
deprecated_task :migrate_from_trac, "redmine:migrate_from_trac"
deprecated_task "db:migrate_plugins", "db:migrate:plugins"
\ No newline at end of file
......@@ -19,11 +19,12 @@ begin
files << Dir['vendor/plugins/**/*.rb'].reject {|f| f.match(/test/) } # Exclude test files
t.files = files
static_files = ['doc/CHANGELOG',
'doc/COPYING',
'doc/INSTALL',
'doc/RUNNING_TESTS',
'doc/UPGRADING'].join(',')
static_files = ['doc/CHANGELOG.rdoc',
'doc/COPYING.rdoc',
'doc/COPYRIGHT.rdoc',
'doc/INSTALL.rdoc',
'doc/RUNNING_TESTS.rdoc',
'doc/UPGRADING.rdoc'].join(',')
t.options += ['--output-dir', './doc/app', '--files', static_files]
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-pagination { text-align: center; }
.search-pagination a {padding: 0 5px; }
div#roadmap .related-issues { margin-bottom: 1em; }
div#roadmap .related-issues td.checkbox { display: none; }
div#roadmap .wiki h1:first-child { display: none; }
......
......@@ -12,7 +12,7 @@
#++
class Journal < ActiveRecord::Base
generator_for :journalized, :method => :generate_issue
generator_for :journaled, :method => :generate_issue
generator_for :user, :method => :generate_user
def self.generate_issue
......
......@@ -61,6 +61,7 @@ class IssuesTest < ActionController::IntegrationTest
# add then remove 2 attachments to an issue
def test_issue_attachments
Issue.find(1).recreate_initial_journal!
log_user('jsmith', 'jsmith')
set_tmp_attachments_directory
......@@ -68,6 +69,7 @@ class IssuesTest < ActionController::IntegrationTest
:notes => 'Some notes',
:attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'This is an attachment'}}
assert_redirected_to "/issues/1"
follow_redirect!
# make sure attachment was saved
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
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
Issue.find(1).attachments.each(&:destroy)
assert_equal 0, Issue.find(1).attachments.length
......
......@@ -39,33 +39,30 @@ class IssuesHelperTest < HelperTestCase
@request ||= ActionController::TestRequest.new
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 "with no_html" do
should 'show a changing attribute' do
@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
should 'show a new attribute' do
@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
should 'show a deleted attribute' do
@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
context "with html" do
should 'show a changing attribute with HTML highlights' do
@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 'i', :text => '40'
......@@ -74,7 +71,7 @@ class IssuesHelperTest < HelperTestCase
should 'show a new attribute with HTML highlights' do
@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 'i', :text => '100'
......@@ -82,7 +79,7 @@ class IssuesHelperTest < HelperTestCase
should 'show a deleted attribute with HTML highlights' do
@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 'strike' do
......@@ -94,24 +91,24 @@ class IssuesHelperTest < HelperTestCase
context "with a start_date attribute" do
should "format the current date" do
@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
should "format the old date" do
@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
context "with a due_date attribute" do
should "format the current date" do
@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
should "format the old date" do
@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
......
......@@ -13,7 +13,7 @@
require File.expand_path('../../test_helper', __FILE__)
class JournalTest < ActiveSupport::TestCase
fixtures :issues, :issue_statuses, :journals
fixtures :issues, :issue_statuses, :journals, :enumerations
def setup
@journal = IssueJournal.find(1)
......@@ -94,4 +94,24 @@ class JournalTest < ActiveSupport::TestCase
assert_not_equal start, @issue.reload.updated_on
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
......@@ -155,7 +155,8 @@ begin
assert_equal "2010-09-18 19:59:46".to_time, last_rev.time
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
#
else
......@@ -163,7 +164,9 @@ begin
['4fc55c43bf3d3dc2efb66145365ddc17639ce81e', '4fc55c43bf3'].each do |r1|
assert @adapter.diff(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|
assert @adapter.diff(p2, r1, r2)
end
......
......@@ -179,7 +179,7 @@ class MailerTest < ActiveSupport::TestCase
assert_nil mail.references
assert_select_email do
# 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
......@@ -316,7 +316,7 @@ class MailerTest < ActiveSupport::TestCase
end
def test_wiki_content_added
content = WikiContent.find(:first)
content = WikiContent.find(1)
valid_languages.each do |lang|
Setting.default_language = lang.to_s
assert_difference 'ActionMailer::Base.deliveries.size' do
......@@ -326,7 +326,7 @@ class MailerTest < ActiveSupport::TestCase
end
def test_wiki_content_updated
content = WikiContent.find(:first)
content = WikiContent.find(1)
valid_languages.each do |lang|
Setting.default_language = lang.to_s
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
def setup
Setting.notified_events = ['message_posted']
@board = Board.find(1)
@user = User.find(1)
end
......@@ -138,4 +139,12 @@ class MessageTest < ActiveSupport::TestCase
message.sticky = '1'
assert_equal 1, message.sticky
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
......@@ -80,4 +80,11 @@ class WikiContentTest < ActiveSupport::TestCase
page.reload
assert_equal 500.kilobyte, page.content.text.size
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
......@@ -19,7 +19,7 @@
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 Acts
......@@ -140,7 +140,7 @@ module Redmine
h[:find_options] ||= {} # in case it is nil
h[:find_options] = {}.tap do |opts|
cond = ARCondition.new
cond = ::ARCondition.new
cond.add(["#{journal_class.table_name}.activity_type = ?", h[:type]])
cond.add(h[:find_options][:conditions]) if h[:find_options][:conditions]
opts[:conditions] = cond.conditions
......
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 ActionView::Helpers::TagHelper
include ActionView::Helpers::UrlHelper
include ActionController::UrlWriter
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)
if hash[:class]
klazz = hash.delete(:class)
......@@ -90,9 +100,7 @@ module JournalFormatter
def format_html_attachment_detail(key, value)
if !value.blank? && a = Attachment.find_by_id(key.to_i)
# Link to the attachment if it has not been removed
# FIXME: this is broken => link_to_attachment(a)
a.filename
link_to_attachment(a)
else
content_tag("i", h(value)) if value.present?
end
......@@ -163,7 +171,7 @@ module JournalFormatter
end
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 }
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)
......
......@@ -68,6 +68,48 @@ module Redmine::Acts::Journalized
# Instance methods that determine whether to save a journal and actually perform the save.
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
# Returns whether a new journal should be created upon updating the parent record.
# A new journal will be created if
......@@ -120,7 +162,8 @@ module Redmine::Acts::Journalized
def journal_attributes
attributes = { :journaled_id => self.id, :activity_type => activity_type,
: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
......
......@@ -28,7 +28,7 @@ module Redmine::Acts::Journalized
before_save :init_journal
after_save :reset_instance_variables
attr_reader :journal_notes, :journal_user
attr_accessor :journal_notes, :journal_user, :extra_journal_attributes
end
end
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment