commit
dabe5caa1a
@ -1,115 +0,0 @@ |
||||
# Redmine - project management software |
||||
# Copyright (C) 2006-2011 Jean-Philippe Lang |
||||
# |
||||
# 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. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
|
||||
class JournalsController < ApplicationController |
||||
before_filter :find_journal, :only => [:edit, :diff] |
||||
before_filter :find_issue, :only => [:new] |
||||
before_filter :find_optional_project, :only => [:index] |
||||
before_filter :authorize, :only => [:new, :edit, :diff] |
||||
accept_key_auth :index |
||||
menu_item :issues |
||||
|
||||
include QueriesHelper |
||||
include SortHelper |
||||
|
||||
def index |
||||
retrieve_query |
||||
sort_init 'id', 'desc' |
||||
sort_update(@query.sortable_columns) |
||||
|
||||
if @query.valid? |
||||
@journals = @query.journals(:order => "#{Journal.table_name}.created_on DESC", |
||||
:limit => 25) |
||||
end |
||||
@title = (@project ? @project.name : Setting.app_title) + ": " + (@query.new_record? ? l(:label_changes_details) : @query.name) |
||||
render :layout => false, :content_type => 'application/atom+xml' |
||||
rescue ActiveRecord::RecordNotFound |
||||
render_404 |
||||
end |
||||
|
||||
def diff |
||||
@issue = @journal.issue |
||||
if params[:detail_id].present? |
||||
@detail = @journal.details.find_by_id(params[:detail_id]) |
||||
else |
||||
@detail = @journal.details.detect {|d| d.prop_key == 'description'} |
||||
end |
||||
(render_404; return false) unless @issue && @detail |
||||
@diff = Redmine::Helpers::Diff.new(@detail.value, @detail.old_value) |
||||
end |
||||
|
||||
def new |
||||
journal = Journal.find(params[:journal_id]) if params[:journal_id] |
||||
if journal |
||||
user = journal.user |
||||
text = journal.notes |
||||
else |
||||
user = @issue.author |
||||
text = @issue.description |
||||
end |
||||
# Replaces pre blocks with [...] |
||||
text = text.to_s.strip.gsub(%r{<pre>((.|\s)*?)</pre>}m, '[...]') |
||||
content = "#{ll(Setting.default_language, :text_user_wrote, user)}\n> " |
||||
content << text.gsub(/(\r?\n|\r\n?)/, "\n> ") + "\n\n" |
||||
|
||||
render(:update) { |page| |
||||
page.<< "$('notes').value = \"#{escape_javascript content}\";" |
||||
page.show 'update' |
||||
page << "Form.Element.focus('notes');" |
||||
page << "Element.scrollTo('update');" |
||||
page << "$('notes').scrollTop = $('notes').scrollHeight - $('notes').clientHeight;" |
||||
} |
||||
end |
||||
|
||||
def edit |
||||
(render_403; return false) unless @journal.editable_by?(User.current) |
||||
if request.post? |
||||
@journal.update_attributes(:notes => params[:notes]) if params[:notes] |
||||
@journal.destroy if @journal.details.empty? && @journal.notes.blank? |
||||
call_hook(:controller_journals_edit_post, { :journal => @journal, :params => params}) |
||||
respond_to do |format| |
||||
format.html { redirect_to :controller => 'issues', :action => 'show', :id => @journal.journalized_id } |
||||
format.js { render :action => 'update' } |
||||
end |
||||
else |
||||
respond_to do |format| |
||||
format.html { |
||||
# TODO: implement non-JS journal update |
||||
render :nothing => true |
||||
} |
||||
format.js |
||||
end |
||||
end |
||||
end |
||||
|
||||
private |
||||
|
||||
def find_journal |
||||
@journal = Journal.find(params[:id]) |
||||
@project = @journal.journalized.project |
||||
rescue ActiveRecord::RecordNotFound |
||||
render_404 |
||||
end |
||||
|
||||
# TODO: duplicated in IssuesController |
||||
def find_issue |
||||
@issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category]) |
||||
@project = @issue.project |
||||
rescue ActiveRecord::RecordNotFound |
||||
render_404 |
||||
end |
||||
end |
@ -1,42 +0,0 @@ |
||||
# redMine - project management software |
||||
# Copyright (C) 2006-2008 Jean-Philippe Lang |
||||
# |
||||
# 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. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
|
||||
module JournalsHelper |
||||
def render_notes(issue, journal, options={}) |
||||
content = '' |
||||
editable = User.current.logged? && (User.current.allowed_to?(:edit_issue_notes, issue.project) || (journal.user == User.current && User.current.allowed_to?(:edit_own_issue_notes, issue.project))) |
||||
links = [] |
||||
if !journal.notes.blank? |
||||
links << link_to_remote(image_tag('comment.png'), |
||||
{ :url => {:controller => 'journals', :action => 'new', :id => issue, :journal_id => journal} }, |
||||
:title => l(:button_quote)) if options[:reply_links] |
||||
links << link_to_in_place_notes_editor(image_tag('edit.png'), "journal-#{journal.id}-notes", |
||||
{ :controller => 'journals', :action => 'edit', :id => journal }, |
||||
:title => l(:button_edit)) if editable |
||||
end |
||||
content << content_tag('div', links.join(' '), :class => 'contextual') unless links.empty? |
||||
content << textilizable(journal, :notes) |
||||
css_classes = "wiki" |
||||
css_classes << " editable" if editable |
||||
content_tag('div', content, :id => "journal-#{journal.id}-notes", :class => css_classes) |
||||
end |
||||
|
||||
def link_to_in_place_notes_editor(text, field_id, url, options={}) |
||||
onclick = "new Ajax.Request('#{url_for(url)}', {asynchronous:true, evalScripts:true, method:'get'}); return false;" |
||||
link_to text, '#', options.merge(:onclick => onclick) |
||||
end |
||||
end |
@ -1,81 +0,0 @@ |
||||
# Redmine - project management software |
||||
# Copyright (C) 2006-2011 Jean-Philippe Lang |
||||
# |
||||
# 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. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
|
||||
class Journal < ActiveRecord::Base |
||||
belongs_to :journalized, :polymorphic => true |
||||
# added as a quick fix to allow eager loading of the polymorphic association |
||||
# since always associated to an issue, for now |
||||
belongs_to :issue, :foreign_key => :journalized_id |
||||
|
||||
belongs_to :user |
||||
has_many :details, :class_name => "JournalDetail", :dependent => :delete_all |
||||
attr_accessor :indice |
||||
|
||||
acts_as_event :title => Proc.new {|o| status = ((s = o.new_status) ? " (#{s})" : nil); "#{o.issue.tracker} ##{o.issue.id}#{status}: #{o.issue.subject}" }, |
||||
:description => :notes, |
||||
:author => :user, |
||||
:type => Proc.new {|o| (s = o.new_status) ? (s.is_closed? ? 'issue-closed' : 'issue-edit') : 'issue-note' }, |
||||
:url => Proc.new {|o| {:controller => 'issues', :action => 'show', :id => o.issue.id, :anchor => "change-#{o.id}"}} |
||||
|
||||
acts_as_activity_provider :type => 'issues', |
||||
:permission => :view_issues, |
||||
:author_key => :user_id, |
||||
:find_options => {:include => [{:issue => :project}, :details, :user], |
||||
:conditions => "#{Journal.table_name}.journalized_type = 'Issue' AND" + |
||||
" (#{JournalDetail.table_name}.prop_key = 'status_id' OR #{Journal.table_name}.notes <> '')"} |
||||
|
||||
named_scope :visible, lambda {|*args| { |
||||
:include => {:issue => :project}, |
||||
:conditions => Issue.visible_condition(args.first || User.current) |
||||
}} |
||||
|
||||
def save(*args) |
||||
# Do not save an empty journal |
||||
(details.empty? && notes.blank?) ? false : super |
||||
end |
||||
|
||||
# Returns the new status if the journal contains a status change, otherwise nil |
||||
def new_status |
||||
c = details.detect {|detail| detail.prop_key == 'status_id'} |
||||
(c && c.value) ? IssueStatus.find_by_id(c.value.to_i) : nil |
||||
end |
||||
|
||||
def new_value_for(prop) |
||||
c = details.detect {|detail| detail.prop_key == prop} |
||||
c ? c.value : nil |
||||
end |
||||
|
||||
def editable_by?(usr) |
||||
usr && usr.logged? && (usr.allowed_to?(:edit_issue_notes, project) || (self.user == usr && usr.allowed_to?(:edit_own_issue_notes, project))) |
||||
end |
||||
|
||||
def project |
||||
journalized.respond_to?(:project) ? journalized.project : nil |
||||
end |
||||
|
||||
def attachments |
||||
journalized.respond_to?(:attachments) ? journalized.attachments : nil |
||||
end |
||||
|
||||
# Returns a string of css classes |
||||
def css_classes |
||||
s = 'journal' |
||||
s << ' has-notes' unless notes.blank? |
||||
s << ' has-details' unless details.blank? |
||||
s |
||||
end |
||||
end |
@ -1,20 +0,0 @@ |
||||
# Redmine - project management software |
||||
# Copyright (C) 2006-2011 Jean-Philippe Lang |
||||
# |
||||
# 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. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
|
||||
class JournalDetail < ActiveRecord::Base |
||||
belongs_to :journal |
||||
end |
@ -1,21 +0,0 @@ |
||||
<% form_remote_tag(:url => {}, :html => { :id => "journal-#{@journal.id}-form" }) do %> |
||||
<%= text_area_tag :notes, @journal.notes, |
||||
:id => "journal_#{@journal.id}_notes", |
||||
:class => 'wiki-edit', |
||||
:rows => (@journal.notes.blank? ? 10 : [[10, @journal.notes.length / 50].max, 100].min) %> |
||||
<%= call_hook(:view_journals_notes_form_after_notes, { :journal => @journal}) %> |
||||
<p><%= submit_tag l(:button_save) %> |
||||
<%= link_to_remote l(:label_preview), |
||||
{ :url => preview_issue_path(:project_id => @project, :id => @journal.issue), |
||||
:method => 'post', |
||||
:update => "journal_#{@journal.id}_preview", |
||||
:with => "Form.serialize('journal-#{@journal.id}-form')", |
||||
:complete => "Element.scrollTo('journal_#{@journal.id}_preview')" |
||||
}, :accesskey => accesskey(:preview) %> |
||||
| |
||||
<%= link_to l(:button_cancel), '#', :onclick => "Element.remove('journal-#{@journal.id}-form'); " + |
||||
"Element.show('journal-#{@journal.id}-notes'); return false;" %></p> |
||||
|
||||
<div id="journal_<%= @journal.id %>_preview" class="wiki"></div> |
||||
<% end %> |
||||
<%= wikitoolbar_for "journal_#{@journal.id}_notes" %> |
@ -0,0 +1,109 @@ |
||||
class GeneralizeJournals < ActiveRecord::Migration |
||||
def self.up |
||||
# This is provided here for migrating up after the JournalDetails has been removed |
||||
unless Object.const_defined?("JournalDetails") |
||||
Object.const_set("JournalDetails", Class.new(ActiveRecord::Base)) |
||||
end |
||||
|
||||
change_table :journals do |t| |
||||
t.rename :journalized_id, :journaled_id |
||||
t.rename :created_on, :created_at |
||||
|
||||
t.integer :version, :default => 0, :null => false |
||||
t.string :activity_type |
||||
t.text :changes |
||||
t.string :type |
||||
|
||||
t.index :journaled_id |
||||
t.index :activity_type |
||||
t.index :created_at |
||||
t.index :type |
||||
end |
||||
|
||||
Journal.all.group_by(&:journaled_id).each_pair do |id, journals| |
||||
journals.sort_by(&:created_at).each_with_index do |j, idx| |
||||
j.update_attribute(:type, "#{j.journalized_type}Journal") |
||||
j.update_attribute(:version, idx + 1) |
||||
# FIXME: Find some way to choose the right activity here |
||||
j.update_attribute(:activity_type, j.journalized_type.constantize.activity_provider_options.keys.first) |
||||
end |
||||
end |
||||
|
||||
change_table :journals do |t| |
||||
t.remove :journalized_type |
||||
end |
||||
|
||||
JournalDetails.all.each do |detail| |
||||
journal = Journal.find(detail.journal_id) |
||||
changes = journal.changes || {} |
||||
if detail.property == 'attr' # Standard attributes |
||||
changes[detail.prop_key.to_s] = [detail.old_value, detail.value] |
||||
elsif detail.property == 'cf' # Custom fields |
||||
changes["custom_values_" + detail.prop_key.to_s] = [detail.old_value, detail.value] |
||||
elsif detail.property == 'attachment' # Attachment |
||||
changes["attachments_" + detail.prop_key.to_s] = [detail.old_value, detail.value] |
||||
end |
||||
journal.update_attribute(:changes, changes.to_yaml) |
||||
end |
||||
|
||||
# Create creation journals for all activity providers |
||||
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 |
||||
p.find(:all).each do |o| |
||||
unless o.last_journal |
||||
o.send(:update_journal) |
||||
created_at = nil |
||||
[:created_at, :created_on, :updated_at, :updated_on].each do |m| |
||||
if o.respond_to? m |
||||
created_at = o.send(m) |
||||
break |
||||
end |
||||
end |
||||
p "Updating #{o}" |
||||
o.last_journal.update_attribute(:created_at, created_at) if created_at and o.last_journal |
||||
end |
||||
end |
||||
end |
||||
|
||||
# drop_table :journal_details |
||||
end |
||||
|
||||
def self.down |
||||
# create_table "journal_details", :force => true do |t| |
||||
# t.integer "journal_id", :default => 0, :null => false |
||||
# t.string "property", :limit => 30, :default => "", :null => false |
||||
# t.string "prop_key", :limit => 30, :default => "", :null => false |
||||
# t.string "old_value" |
||||
# t.string "value" |
||||
# end |
||||
|
||||
change_table "journals" do |t| |
||||
t.rename :journaled_id, :journalized_id |
||||
t.rename :created_at, :created_on |
||||
|
||||
t.string :journalized_type, :limit => 30, :default => "", :null => false |
||||
end |
||||
|
||||
custom_field_names = CustomField.all.group_by(&:type)[IssueCustomField].collect(&:name) |
||||
Journal.all.each do |j| |
||||
# Can't used j.journalized.class.name because the model changes make it nil |
||||
j.update_attribute(:journalized_type, j.type.to_s.sub("Journal","")) if j.type.present? |
||||
end |
||||
|
||||
change_table "journals" do |t| |
||||
t.remove_index :journaled_id |
||||
t.remove_index :activity_type |
||||
t.remove_index :created_at |
||||
t.remove_index :type |
||||
|
||||
t.remove :type |
||||
t.remove :version |
||||
t.remove :activity_type |
||||
t.remove :changes |
||||
end |
||||
|
||||
# add_index "journal_details", ["journal_id"], :name => "journal_details_journal_id" |
||||
# add_index "journals", ["journalized_id", "journalized_type"], :name => "journals_journalized_id" |
||||
end |
||||
end |
@ -0,0 +1,49 @@ |
||||
class MergeWikiVersionsWithJournals < ActiveRecord::Migration |
||||
def self.up |
||||
# This is provided here for migrating up after the WikiContent::Version class has been removed |
||||
unless WikiContent.const_defined?("Version") |
||||
WikiContent.const_set("Version", Class.new(ActiveRecord::Base)) |
||||
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, :activity_type => "wiki_edits") |
||||
changes = {} |
||||
changes["compression"] = wv.compression |
||||
changes["data"] = wv.data |
||||
journal.update_attribute(:changes, changes.to_yaml) |
||||
journal.update_attribute(:version, wv.version) |
||||
end |
||||
# drop_table :wiki_content_versions |
||||
|
||||
change_table :wiki_contents do |t| |
||||
t.rename :version, :lock_version |
||||
end |
||||
end |
||||
|
||||
def self.down |
||||
change_table :wiki_contents do |t| |
||||
t.rename :lock_version, :version |
||||
end |
||||
|
||||
# create_table :wiki_content_versions do |t| |
||||
# t.column :wiki_content_id, :integer, :null => false |
||||
# t.column :page_id, :integer, :null => false |
||||
# t.column :author_id, :integer |
||||
# t.column :data, :binary |
||||
# t.column :compression, :string, :limit => 6, :default => "" |
||||
# t.column :comments, :string, :limit => 255, :default => "" |
||||
# t.column :updated_on, :datetime, :null => false |
||||
# t.column :version, :integer, :null => false |
||||
# end |
||||
# add_index :wiki_content_versions, :wiki_content_id, :name => :wiki_content_versions_wcid |
||||
# |
||||
# WikiContentJournal.all.each do |j| |
||||
# WikiContent::Version.create(:wiki_content_id => j.journaled_id, :page_id => j.journaled.page_id, |
||||
# :author_id => j.user_id, :data => j.changes["data"], :compression => j.changes["compression"], |
||||
# :comments => j.notes, :updated_on => j.created_at, :version => j.version) |
||||
# end |
||||
|
||||
WikiContentJournal.destroy_all |
||||
end |
||||
end |
@ -1,11 +1,9 @@ |
||||
class Meeting < ActiveRecord::Base |
||||
belongs_to :project |
||||
|
||||
acts_as_event :title => Proc.new {|o| "#{o.scheduled_on} Meeting"}, |
||||
:datetime => :scheduled_on, |
||||
:author => nil, |
||||
:url => Proc.new {|o| {:controller => 'meetings', :action => 'show', :id => o.id}} |
||||
|
||||
acts_as_activity_provider :timestamp => 'scheduled_on', |
||||
:find_options => { :include => :project } |
||||
acts_as_journalized :event_title => Proc.new {|o| "#{o.scheduled_on} Meeting"}, |
||||
:event_datetime => :scheduled_on, |
||||
:event_author => nil, |
||||
:event_url => Proc.new {|o| {:controller => 'meetings', :action => 'show', :id => o.id}} |
||||
:activity_timestamp => 'scheduled_on' |
||||
end |
||||
|
@ -1,36 +0,0 @@ |
||||
--- |
||||
journal_details_001: |
||||
old_value: "1" |
||||
property: attr |
||||
id: 1 |
||||
value: "2" |
||||
prop_key: status_id |
||||
journal_id: 1 |
||||
journal_details_002: |
||||
old_value: "40" |
||||
property: attr |
||||
id: 2 |
||||
value: "30" |
||||
prop_key: done_ratio |
||||
journal_id: 1 |
||||
journal_details_003: |
||||
old_value: nil |
||||
property: attr |
||||
id: 3 |
||||
value: "6" |
||||
prop_key: fixed_version_id |
||||
journal_id: 4 |
||||
journal_details_004: |
||||
old_value: "This word was removed and an other was" |
||||
property: attr |
||||
id: 4 |
||||
value: "This word was and an other was added" |
||||
prop_key: description |
||||
journal_id: 3 |
||||
journal_details_005: |
||||
old_value: Old value |
||||
property: cf |
||||
id: 5 |
||||
value: New value |
||||
prop_key: 2 |
||||
journal_id: 3 |
@ -1,29 +1,217 @@ |
||||
--- |
||||
journals_001: |
||||
created_on: <%= 2.days.ago.to_date.to_s(:db) %> |
||||
notes: "Journal notes" |
||||
--- |
||||
journals_001: |
||||
id: 1 |
||||
journalized_type: Issue |
||||
type: "IssueJournal" |
||||
activity_type: "issues" |
||||
created_at: <%= 3.days.ago.to_date.to_s(:db) %> |
||||
version: 1 |
||||
user_id: 1 |
||||
journalized_id: 1 |
||||
journals_002: |
||||
created_on: <%= 1.days.ago.to_date.to_s(:db) %> |
||||
notes: "Some notes with Redmine links: #2, r2." |
||||
notes: "Journal notes" |
||||
journaled_id: 1 |
||||
changes: | |
||||
--- |
||||
status_id: |
||||
- 1 |
||||
- 2 |
||||
done_ratio: |
||||
- 40 |
||||
- 30 |
||||
journals_002: |
||||
id: 2 |
||||
journalized_type: Issue |
||||
type: "IssueJournal" |
||||
activity_type: "issues" |
||||
created_at: <%= 1.days.ago.to_date.to_s(:db) %> |
||||
version: 2 |
||||
user_id: 2 |
||||
journalized_id: 1 |
||||
journals_003: |
||||
created_on: <%= 1.days.ago.to_date.to_s(:db) %> |
||||
notes: "A comment with inline image: !picture.jpg!" |
||||
notes: "Some notes with Redmine links: #2, r2." |
||||
journaled_id: 1 |
||||
changes: "--- {}" |
||||
journals_003: |
||||
id: 3 |
||||
journalized_type: Issue |
||||
type: "IssueJournal" |
||||
activity_type: "issues" |
||||
created_at: <%= 2.days.ago.to_date.to_s(:db) %> |
||||
version: 1 |
||||
user_id: 2 |
||||
journalized_id: 2 |
||||
journals_004: |
||||
created_on: <%= 1.days.ago.to_date.to_s(:db) %> |
||||
notes: "A comment with a private version." |
||||
notes: "A comment with inline image: !picture.jpg!" |
||||
journaled_id: 2 |
||||
changes: "--- {}" |
||||
journals_004: |
||||
id: 4 |
||||
journalized_type: Issue |
||||
type: "IssueJournal" |
||||
activity_type: "issues" |
||||
created_at: <%= 1.days.ago.to_date.to_s(:db) %> |
||||
version: 1 |
||||
user_id: 2 |
||||
notes: "A comment with a private version." |
||||
journaled_id: 6 |
||||
changes: | |
||||
--- |
||||
fixed_version_id: |
||||
- |
||||
- 6 |
||||
journals_005: |
||||
id: 5 |
||||
type: "IssueJournal" |
||||
activity_type: "issues" |
||||
created_at: <%= 2.days.ago.to_date.to_s(:db) %> |
||||
version: 0 |
||||
user_id: 1 |
||||
notes: |
||||
journaled_id: 5 |
||||
changes: "--- {}" |
||||
journals_006: |
||||
id: 6 |
||||
type: "WikiContentJournal" |
||||
activity_type: "wiki_edits" |
||||
created_at: 2007-03-07 00:08:07 +01:00 |
||||
version: 1 |
||||
user_id: 2 |
||||
notes: Page creation |
||||
journaled_id: 1 |
||||
changes: | |
||||
--- |
||||
compression: "" |
||||
data: |- |
||||
h1. CookBook documentation |
||||
|
||||
|
||||
|
||||
Some [[documentation]] here... |
||||
journals_007: |
||||
id: 7 |
||||
type: "WikiContentJournal" |
||||
activity_type: "wiki_edits" |
||||
created_at: 2007-03-07 00:08:34 +01:00 |
||||
version: 2 |
||||
user_id: 1 |
||||
journalized_id: 6 |
||||
notes: Small update |
||||
journaled_id: 1 |
||||
changes: | |
||||
--- |
||||
compression: "" |
||||
data: |- |
||||
h1. CookBook documentation |
||||
|
||||
|
||||
|
||||
Some updated [[documentation]] here... |
||||
journals_008: |
||||
id: 8 |
||||
type: "WikiContentJournal" |
||||
activity_type: "wiki_edits" |
||||
created_at: 2007-03-07 00:10:51 +01:00 |
||||
version: 3 |
||||
user_id: 1 |
||||
notes: "" |
||||
journaled_id: 1 |
||||
changes: | |
||||
--- |
||||
compression: "" |
||||
data: |- |
||||
h1. CookBook documentation |
||||
Some updated [[documentation]] here... |
||||
journals_009: |
||||
id: 9 |
||||
type: "WikiContentJournal" |
||||
activity_type: "wiki_edits" |
||||
created_at: 2007-03-08 00:18:07 +01:00 |
||||
version: 1 |
||||
user_id: 1 |
||||
notes: |
||||
journaled_id: 2 |
||||
changes: | |
||||
--- |
||||
data: |- |
||||
h1. Another page |
||||
|
||||
This is a link to a ticket: #2 |
||||
|
||||
|
||||
journals_010: |
||||
id: 10 |
||||
type: "MessageJournal" |
||||
activity_type: "messages" |
||||
created_at: <%= 2.days.ago.to_date.to_s(:db) %> |
||||
version: 1 |
||||
user_id: 1 |
||||
notes: |
||||
journaled_id: 5 |
||||
changes: --- {} |
||||
journals_011: |
||||
id: 11 |
||||
type: "AttachmentJournal" |
||||
activity_type: "files" |
||||
created_at: <%= 2.days.ago.to_date.to_s(:db) %> |
||||
version: 1 |
||||
user_id: 2 |
||||
notes: "An attachment on a version" |
||||
journaled_id: 9 |
||||
changes: --- {} |
||||
journals_012: |
||||
id: 12 |
||||
type: "AttachmentJournal" |
||||
activity_type: "files" |
||||
created_at: <%= 2.days.ago.to_date.to_s(:db) %> |
||||
version: 1 |
||||
user_id: 2 |
||||
notes: "An attachment on a project" |
||||
journaled_id: 8 |
||||
changes: --- {} |
||||
journals_013: |
||||
id: 13 |
||||
type: "AttachmentJournal" |
||||
activity_type: "files" |
||||
created_at: <%= 2.days.ago.to_date.to_s(:db) %> |
||||
version: 1 |
||||
user_id: 2 |
||||
notes: "An attachment on an issue" |
||||
journaled_id: 7 |
||||
changes: --- {} |
||||
journals_014: |
||||
id: 14 |
||||
type: "AttachmentJournal" |
||||
activity_type: "documents" |
||||
created_at: <%= 2.days.ago.to_date.to_s(:db) %> |
||||
version: 1 |
||||
user_id: 2 |
||||
notes: "An attachment on a document" |
||||
journaled_id: 2 |
||||
changes: --- {} |
||||
journals_015: |
||||
id: 15 |
||||
type: "MessageJournal" |
||||
activity_type: "messages" |
||||
created_at: <%= 2.days.ago.to_date.to_s(:db) %> |
||||
version: 1 |
||||
user_id: 2 |
||||
notes: "A message journal" |
||||
journaled_id: 1 |
||||
journals_016: |
||||
id: 16 |
||||
type: "MessageJournal" |
||||
activity_type: "messages" |
||||
created_at: <%= 2.days.ago.to_date.to_s(:db) %> |
||||
version: 1 |
||||
user_id: 2 |
||||
notes: "A message journal" |
||||
journaled_id: 2 |
||||
journals_017: |
||||
id: 17 |
||||
type: "MessageJournal" |
||||
activity_type: "messages" |
||||
created_at: <%= 2.days.ago.to_date.to_s(:db) %> |
||||
version: 1 |
||||
user_id: 2 |
||||
notes: "A message journal" |
||||
journaled_id: 3 |
||||
journals_018: |
||||
id: 18 |
||||
type: "IssueJournal" |
||||
activity_type: "issues" |
||||
created_at: <%= 3.days.ago.to_date.to_s(:db) %> |
||||
version: 0 |
||||
user_id: 2 |
||||
notes: |
||||
journaled_id: 11 |
||||
changes: "--- {}" |
@ -1,56 +0,0 @@ |
||||
--- |
||||
wiki_content_versions_001: |
||||
updated_on: 2007-03-07 00:08:07 +01:00 |
||||
page_id: 1 |
||||
id: 1 |
||||
version: 1 |
||||
author_id: 2 |
||||
comments: Page creation |
||||
wiki_content_id: 1 |
||||
compression: "" |
||||
data: |- |
||||
h1. CookBook documentation |
||||
|
||||
|
||||
|
||||
Some [[documentation]] here... |
||||
wiki_content_versions_002: |
||||
updated_on: 2007-03-07 00:08:34 +01:00 |
||||
page_id: 1 |
||||
id: 2 |
||||
version: 2 |
||||
author_id: 1 |
||||
comments: Small update |
||||
wiki_content_id: 1 |
||||
compression: "" |
||||
data: |- |
||||
h1. CookBook documentation |
||||
|
||||
|
||||
|
||||
Some updated [[documentation]] here... |
||||
wiki_content_versions_003: |
||||
updated_on: 2007-03-07 00:10:51 +01:00 |
||||
page_id: 1 |
||||
id: 3 |
||||
version: 3 |
||||
author_id: 1 |
||||
comments: "" |
||||
wiki_content_id: 1 |
||||
compression: "" |
||||
data: |- |
||||
h1. CookBook documentation |
||||
Some updated [[documentation]] here... |
||||
wiki_content_versions_004: |
||||
data: |- |
||||
h1. Another page |
||||
|
||||
This is a link to a ticket: #2 |
||||
updated_on: 2007-03-08 00:18:07 +01:00 |
||||
page_id: 2 |
||||
wiki_content_id: 2 |
||||
id: 4 |
||||
version: 1 |
||||
author_id: 1 |
||||
comments: |
||||
|
@ -0,0 +1,22 @@ |
||||
## MAC OS |
||||
.DS_Store |
||||
|
||||
## TEXTMATE |
||||
*.tmproj |
||||
tmtags |
||||
|
||||
## EMACS |
||||
*~ |
||||
\#* |
||||
.\#* |
||||
|
||||
## VIM |
||||
*.swp |
||||
|
||||
## PROJECT::GENERAL |
||||
coverage |
||||
rdoc |
||||
pkg |
||||
|
||||
## PROJECT::SPECIFIC |
||||
*.db |
@ -0,0 +1,339 @@ |
||||
GNU GENERAL PUBLIC LICENSE |
||||
Version 2, June 1991 |
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc., |
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
||||
Everyone is permitted to copy and distribute verbatim copies |
||||
of this license document, but changing it is not allowed. |
||||
|
||||
Preamble |
||||
|
||||
The licenses for most software are designed to take away your |
||||
freedom to share and change it. By contrast, the GNU General Public |
||||
License is intended to guarantee your freedom to share and change free |
||||
software--to make sure the software is free for all its users. This |
||||
General Public License applies to most of the Free Software |
||||
Foundation's software and to any other program whose authors commit to |
||||
using it. (Some other Free Software Foundation software is covered by |
||||
the GNU Lesser General Public License instead.) You can apply it to |
||||
your programs, too. |
||||
|
||||
When we speak of free software, we are referring to freedom, not |
||||
price. Our General Public Licenses are designed to make sure that you |
||||
have the freedom to distribute copies of free software (and charge for |
||||
this service if you wish), that you receive source code or can get it |
||||
if you want it, that you can change the software or use pieces of it |
||||
in new free programs; and that you know you can do these things. |
||||
|
||||
To protect your rights, we need to make restrictions that forbid |
||||
anyone to deny you these rights or to ask you to surrender the rights. |
||||
These restrictions translate to certain responsibilities for you if you |
||||
distribute copies of the software, or if you modify it. |
||||
|
||||
For example, if you distribute copies of such a program, whether |
||||
gratis or for a fee, you must give the recipients all the rights that |
||||
you have. You must make sure that they, too, receive or can get the |
||||
source code. And you must show them these terms so they know their |
||||
rights. |
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and |
||||
(2) offer you this license which gives you legal permission to copy, |
||||
distribute and/or modify the software. |
||||
|
||||
Also, for each author's protection and ours, we want to make certain |
||||
that everyone understands that there is no warranty for this free |
||||
software. If the software is modified by someone else and passed on, we |
||||
want its recipients to know that what they have is not the original, so |
||||
that any problems introduced by others will not reflect on the original |
||||
authors' reputations. |
||||
|
||||
Finally, any free program is threatened constantly by software |
||||
patents. We wish to avoid the danger that redistributors of a free |
||||
program will individually obtain patent licenses, in effect making the |
||||
program proprietary. To prevent this, we have made it clear that any |
||||
patent must be licensed for everyone's free use or not licensed at all. |
||||
|
||||
The precise terms and conditions for copying, distribution and |
||||
modification follow. |
||||
|
||||
GNU GENERAL PUBLIC LICENSE |
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION |
||||
|
||||
0. This License applies to any program or other work which contains |
||||
a notice placed by the copyright holder saying it may be distributed |
||||
under the terms of this General Public License. The "Program", below, |
||||
refers to any such program or work, and a "work based on the Program" |
||||
means either the Program or any derivative work under copyright law: |
||||
that is to say, a work containing the Program or a portion of it, |
||||
either verbatim or with modifications and/or translated into another |
||||
language. (Hereinafter, translation is included without limitation in |
||||
the term "modification".) Each licensee is addressed as "you". |
||||
|
||||
Activities other than copying, distribution and modification are not |
||||
covered by this License; they are outside its scope. The act of |
||||
running the Program is not restricted, and the output from the Program |
||||
is covered only if its contents constitute a work based on the |
||||
Program (independent of having been made by running the Program). |
||||
Whether that is true depends on what the Program does. |
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's |
||||
source code as you receive it, in any medium, provided that you |
||||
conspicuously and appropriately publish on each copy an appropriate |
||||
copyright notice and disclaimer of warranty; keep intact all the |
||||
notices that refer to this License and to the absence of any warranty; |
||||
and give any other recipients of the Program a copy of this License |
||||
along with the Program. |
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and |
||||
you may at your option offer warranty protection in exchange for a fee. |
||||
|
||||
2. You may modify your copy or copies of the Program or any portion |
||||
of it, thus forming a work based on the Program, and copy and |
||||
distribute such modifications or work under the terms of Section 1 |
||||
above, provided that you also meet all of these conditions: |
||||
|
||||
a) You must cause the modified files to carry prominent notices |
||||
stating that you changed the files and the date of any change. |
||||
|
||||
b) You must cause any work that you distribute or publish, that in |
||||
whole or in part contains or is derived from the Program or any |
||||
part thereof, to be licensed as a whole at no charge to all third |
||||
parties under the terms of this License. |
||||
|
||||
c) If the modified program normally reads commands interactively |
||||
when run, you must cause it, when started running for such |
||||
interactive use in the most ordinary way, to print or display an |
||||
announcement including an appropriate copyright notice and a |
||||
notice that there is no warranty (or else, saying that you provide |
||||
a warranty) and that users may redistribute the program under |
||||
these conditions, and telling the user how to view a copy of this |
||||
License. (Exception: if the Program itself is interactive but |
||||
does not normally print such an announcement, your work based on |
||||
the Program is not required to print an announcement.) |
||||
|
||||
These requirements apply to the modified work as a whole. If |
||||
identifiable sections of that work are not derived from the Program, |
||||
and can be reasonably considered independent and separate works in |
||||
themselves, then this License, and its terms, do not apply to those |
||||
sections when you distribute them as separate works. But when you |
||||
distribute the same sections as part of a whole which is a work based |
||||
on the Program, the distribution of the whole must be on the terms of |
||||
this License, whose permissions for other licensees extend to the |
||||
entire whole, and thus to each and every part regardless of who wrote it. |
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest |
||||
your rights to work written entirely by you; rather, the intent is to |
||||
exercise the right to control the distribution of derivative or |
||||
collective works based on the Program. |
||||
|
||||
In addition, mere aggregation of another work not based on the Program |
||||
with the Program (or with a work based on the Program) on a volume of |
||||
a storage or distribution medium does not bring the other work under |
||||
the scope of this License. |
||||
|
||||
3. You may copy and distribute the Program (or a work based on it, |
||||
under Section 2) in object code or executable form under the terms of |
||||
Sections 1 and 2 above provided that you also do one of the following: |
||||
|
||||
a) Accompany it with the complete corresponding machine-readable |
||||
source code, which must be distributed under the terms of Sections |
||||
1 and 2 above on a medium customarily used for software interchange; or, |
||||
|
||||
b) Accompany it with a written offer, valid for at least three |
||||
years, to give any third party, for a charge no more than your |
||||
cost of physically performing source distribution, a complete |
||||
machine-readable copy of the corresponding source code, to be |
||||
distributed under the terms of Sections 1 and 2 above on a medium |
||||
customarily used for software interchange; or, |
||||
|
||||
c) Accompany it with the information you received as to the offer |
||||
to distribute corresponding source code. (This alternative is |
||||
allowed only for noncommercial distribution and only if you |
||||
received the program in object code or executable form with such |
||||
an offer, in accord with Subsection b above.) |
||||
|
||||
The source code for a work means the preferred form of the work for |
||||
making modifications to it. For an executable work, complete source |
||||
code means all the source code for all modules it contains, plus any |
||||
associated interface definition files, plus the scripts used to |
||||
control compilation and installation of the executable. However, as a |
||||
special exception, the source code distributed need not include |
||||
anything that is normally distributed (in either source or binary |
||||
form) with the major components (compiler, kernel, and so on) of the |
||||
operating system on which the executable runs, unless that component |
||||
itself accompanies the executable. |
||||
|
||||
If distribution of executable or object code is made by offering |
||||
access to copy from a designated place, then offering equivalent |
||||
access to copy the source code from the same place counts as |
||||
distribution of the source code, even though third parties are not |
||||
compelled to copy the source along with the object code. |
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program |
||||
except as expressly provided under this License. Any attempt |
||||
otherwise to copy, modify, sublicense or distribute the Program is |
||||
void, and will automatically terminate your rights under this License. |
||||
However, parties who have received copies, or rights, from you under |
||||
this License will not have their licenses terminated so long as such |
||||
parties remain in full compliance. |
||||
|
||||
5. You are not required to accept this License, since you have not |
||||
signed it. However, nothing else grants you permission to modify or |
||||
distribute the Program or its derivative works. These actions are |
||||
prohibited by law if you do not accept this License. Therefore, by |
||||
modifying or distributing the Program (or any work based on the |
||||
Program), you indicate your acceptance of this License to do so, and |
||||
all its terms and conditions for copying, distributing or modifying |
||||
the Program or works based on it. |
||||
|
||||
6. Each time you redistribute the Program (or any work based on the |
||||
Program), the recipient automatically receives a license from the |
||||
original licensor to copy, distribute or modify the Program subject to |
||||
these terms and conditions. You may not impose any further |
||||
restrictions on the recipients' exercise of the rights granted herein. |
||||
You are not responsible for enforcing compliance by third parties to |
||||
this License. |
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent |
||||
infringement or for any other reason (not limited to patent issues), |
||||
conditions are imposed on you (whether by court order, agreement or |
||||
otherwise) that contradict the conditions of this License, they do not |
||||
excuse you from the conditions of this License. If you cannot |
||||
distribute so as to satisfy simultaneously your obligations under this |
||||
License and any other pertinent obligations, then as a consequence you |
||||
may not distribute the Program at all. For example, if a patent |
||||
license would not permit royalty-free redistribution of the Program by |
||||
all those who receive copies directly or indirectly through you, then |
||||
the only way you could satisfy both it and this License would be to |
||||
refrain entirely from distribution of the Program. |
||||
|
||||
If any portion of this section is held invalid or unenforceable under |
||||
any particular circumstance, the balance of the section is intended to |
||||
apply and the section as a whole is intended to apply in other |
||||
circumstances. |
||||
|
||||
It is not the purpose of this section to induce you to infringe any |
||||
patents or other property right claims or to contest validity of any |
||||
such claims; this section has the sole purpose of protecting the |
||||
integrity of the free software distribution system, which is |
||||
implemented by public license practices. Many people have made |
||||
generous contributions to the wide range of software distributed |
||||
through that system in reliance on consistent application of that |
||||
system; it is up to the author/donor to decide if he or she is willing |
||||
to distribute software through any other system and a licensee cannot |
||||
impose that choice. |
||||
|
||||
This section is intended to make thoroughly clear what is believed to |
||||
be a consequence of the rest of this License. |
||||
|
||||
8. If the distribution and/or use of the Program is restricted in |
||||
certain countries either by patents or by copyrighted interfaces, the |
||||
original copyright holder who places the Program under this License |
||||
may add an explicit geographical distribution limitation excluding |
||||
those countries, so that distribution is permitted only in or among |
||||
countries not thus excluded. In such case, this License incorporates |
||||
the limitation as if written in the body of this License. |
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions |
||||
of the General Public License from time to time. Such new versions will |
||||
be similar in spirit to the present version, but may differ in detail to |
||||
address new problems or concerns. |
||||
|
||||
Each version is given a distinguishing version number. If the Program |
||||
specifies a version number of this License which applies to it and "any |
||||
later version", you have the option of following the terms and conditions |
||||
either of that version or of any later version published by the Free |
||||
Software Foundation. If the Program does not specify a version number of |
||||
this License, you may choose any version ever published by the Free Software |
||||
Foundation. |
||||
|
||||
10. If you wish to incorporate parts of the Program into other free |
||||
programs whose distribution conditions are different, write to the author |
||||
to ask for permission. For software which is copyrighted by the Free |
||||
Software Foundation, write to the Free Software Foundation; we sometimes |
||||
make exceptions for this. Our decision will be guided by the two goals |
||||
of preserving the free status of all derivatives of our free software and |
||||
of promoting the sharing and reuse of software generally. |
||||
|
||||
NO WARRANTY |
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY |
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN |
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES |
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED |
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF |
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS |
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE |
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, |
||||
REPAIR OR CORRECTION. |
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING |
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR |
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, |
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING |
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED |
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY |
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER |
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE |
||||
POSSIBILITY OF SUCH DAMAGES. |
||||
|
||||
END OF TERMS AND CONDITIONS |
||||
|
||||
How to Apply These Terms to Your New Programs |
||||
|
||||
If you develop a new program, and you want it to be of the greatest |
||||
possible use to the public, the best way to achieve this is to make it |
||||
free software which everyone can redistribute and change under these terms. |
||||
|
||||
To do so, attach the following notices to the program. It is safest |
||||
to attach them to the start of each source file to most effectively |
||||
convey the exclusion of warranty; and each file should have at least |
||||
the "copyright" line and a pointer to where the full notice is found. |
||||
|
||||
<one line to give the program's name and a brief idea of what it does.> |
||||
Copyright (C) <year> <name of author> |
||||
|
||||
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. |
||||
|
||||
This program is distributed in the hope that it will be useful, |
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
GNU General Public License for more details. |
||||
|
||||
You should have received a copy of the GNU General Public License along |
||||
with this program; if not, write to the Free Software Foundation, Inc., |
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
||||
|
||||
Also add information on how to contact you by electronic and paper mail. |
||||
|
||||
If the program is interactive, make it output a short notice like this |
||||
when it starts in an interactive mode: |
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author |
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. |
||||
This is free software, and you are welcome to redistribute it |
||||
under certain conditions; type `show c' for details. |
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate |
||||
parts of the General Public License. Of course, the commands you use may |
||||
be called something other than `show w' and `show c'; they could even be |
||||
mouse-clicks or menu items--whatever suits your program. |
||||
|
||||
You should also get your employer (if you work as a programmer) or your |
||||
school, if any, to sign a "copyright disclaimer" for the program, if |
||||
necessary. Here is a sample; alter the names: |
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program |
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker. |
||||
|
||||
<signature of Ty Coon>, 1 April 1989 |
||||
Ty Coon, President of Vice |
||||
|
||||
This General Public License does not permit incorporating your program into |
||||
proprietary programs. If your program is a subroutine library, you may |
||||
consider it more useful to permit linking proprietary applications with the |
||||
library. If this is what you want to do, use the GNU Lesser General |
||||
Public License instead of this License. |
@ -0,0 +1,87 @@ |
||||
"Acts_as_journalized" is a Redmine core plugin derived from the vestal_versions |
||||
Ruby on Rails plugin. The parts are under different copyright and license conditions |
||||
noted below. |
||||
|
||||
The overall license terms applying to "Acts_as_journalized" as in |
||||
this distribution are 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. |
||||
|
||||
This program is distributed in the hope that it will be useful, |
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
GNU General Public License for more details. |
||||
|
||||
You should have received a copy of the GNU General Public License |
||||
along with this program; if not, write to the Free Software |
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
|
||||
|
||||
|
||||
For the individual files, the following copyrights and licenses apply: |
||||
|
||||
app/controllers/** |
||||
app/views/** |
||||
app/helpers/** |
||||
app/models/journal_observer.rb |
||||
Copyright (C) 2006-2008 Jean-Philippe Lang |
||||
|
||||
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. |
||||
|
||||
This program is distributed in the hope that it will be useful, |
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
GNU General Public License for more details. |
||||
|
||||
You should have received a copy of the GNU General Public License |
||||
along with this program; if not, write to the Free Software |
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
|
||||
|
||||
lib/acts_as_journalized.rb |
||||
lib/journal_formatter.rb |
||||
lib/redmine/acts/journalized/permissions.rb |
||||
lib/redmine/acts/journalized/save_hooks.rb |
||||
lib/redmine/acts/journalized/format_hooks.rb |
||||
lib/redmine/acts/journalized/deprecated.rb |
||||
Copyright (c) 2010 Finn GmbH |
||||
|
||||
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. |
||||
|
||||
This program is distributed in the hope that it will be useful, |
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
GNU General Public License for more details. |
||||
|
||||
You should have received a copy of the GNU General Public License |
||||
along with this program; if not, write to the Free Software |
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
|
||||
|
||||
All remaining files are: |
||||
Copyright (c) 2009 Steve Richert |
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining |
||||
a copy of this software and associated documentation files (the |
||||
"Software"), to deal in the Software without restriction, including |
||||
without limitation the rights to use, copy, modify, merge, publish, |
||||
distribute, sublicense, and/or sell copies of the Software, and to |
||||
permit persons to whom the Software is furnished to do so, subject to |
||||
the following conditions: |
||||
|
||||
The above copyright notice and this permission notice shall be |
||||
included in all copies or substantial portions of the Software. |
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE |
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION |
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
@ -0,0 +1,3 @@ |
||||
acts as journalized |
||||
|
||||
A redmine core plugin for unification of journals, events and activities in redmine |
@ -0,0 +1 @@ |
||||
67a8c4bee0a06420f1ba64eb9906a15d63bf5ac5 https://github.com/edavis10/acts_as_journalized |
@ -0,0 +1,45 @@ |
||||
# This file is part of the acts_as_journalized plugin for the redMine |
||||
# project management software |
||||
# |
||||
# Copyright (C) 2006-2008 Jean-Philippe Lang |
||||
# |
||||
# 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 journal 2 |
||||
# of the License, or (at your option) any later journal. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
|
||||
class JournalsController < ApplicationController |
||||
unloadable |
||||
before_filter :find_journal |
||||
|
||||
def edit |
||||
if request.post? |
||||
@journal.update_attribute(:notes, params[:notes]) if params[:notes] |
||||
@journal.destroy if @journal.details.empty? && @journal.notes.blank? |
||||
call_hook(:controller_journals_edit_post, { :journal => @journal, :params => params}) |
||||
respond_to do |format| |
||||
format.html { redirect_to :controller => @journal.journaled.class.name.pluralize.downcase, |
||||
:action => 'show', :id => @journal.journaled_id } |
||||
format.js { render :action => 'update' } |
||||
end |
||||
end |
||||
end |
||||
|
||||
private |
||||
def find_journal |
||||
@journal = Journal.find(params[:id]) |
||||
(render_403; return false) unless @journal.editable_by?(User.current) |
||||
@project = @journal.project |
||||
rescue ActiveRecord::RecordNotFound |
||||
render_404 |
||||
end |
||||
end |
@ -0,0 +1,128 @@ |
||||
# This file is part of the acts_as_journalized plugin for the redMine |
||||
# project management software |
||||
# |
||||
# Copyright (C) 2006-2008 Jean-Philippe Lang |
||||
# Copyright (C) 2010 Finn GmbH, http://finn.de |
||||
# |
||||
# 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 journal 2 |
||||
# of the License, or (at your option) any later journal. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
|
||||
module JournalsHelper |
||||
unloadable |
||||
include ApplicationHelper |
||||
include ActionView::Helpers::TagHelper |
||||
|
||||
def self.included(base) |
||||
base.class_eval do |
||||
if respond_to? :before_filter |
||||
before_filter :find_optional_journal, :only => [:edit] |
||||
end |
||||
end |
||||
end |
||||
|
||||
def render_journal(model, journal, options = {}) |
||||
return "" if journal.initial? |
||||
journal_content = render_journal_details(journal, :label_updated_time_by) |
||||
journal_content += render_notes(model, journal, options) unless journal.notes.blank? |
||||
content_tag "div", journal_content, { :id => "change-#{journal.id}", :class => journal.css_classes } |
||||
end |
||||
|
||||
# This renders a journal entry wiht a header and details |
||||
def render_journal_details(journal, header_label = :label_updated_time_by) |
||||
header = <<-HTML |
||||
<h4> |
||||
<div style="float:right;">#{link_to "##{journal.anchor}", :anchor => "note-#{journal.anchor}"}</div> |
||||
#{avatar(journal.user, :size => "24")} |
||||
#{content_tag('a', '', :name => "note-#{journal.anchor}")} |
||||
#{authoring journal.created_at, journal.user, :label => header_label} |
||||
</h4> |
||||
HTML |
||||
|
||||
if journal.details.any? |
||||
details = content_tag "ul", :class => "details" do |
||||
journal.details.collect do |detail| |
||||
if d = journal.render_detail(detail) |
||||
content_tag("li", d) |
||||
end |
||||
end.compact |
||||
end |
||||
end |
||||
|
||||
content_tag("div", "#{header}#{details}", :id => "change-#{journal.id}", :class => "journal") |
||||
end |
||||
|
||||
def render_notes(model, journal, options={}) |
||||
controller = model.class.name.downcase.pluralize |
||||
action = 'edit' |
||||
reply_links = authorize_for(controller, action) |
||||
|
||||
if User.current.logged? |
||||
editable = User.current.allowed_to?(options[:edit_permission], journal.project) if options[:edit_permission] |
||||
if journal.user == User.current && options[:edit_own_permission] |
||||
editable ||= User.current.allowed_to?(options[:edit_own_permission], journal.project) |
||||
end |
||||
end |
||||
|
||||
unless journal.notes.blank? |
||||
links = returning [] do |l| |
||||
if reply_links |
||||
l << link_to_remote(image_tag('comment.png'), :title => l(:button_quote), |
||||
:url => {:controller => controller, :action => action, :id => model, :journal_id => journal}) |
||||
end |
||||
if editable |
||||
l << link_to_in_place_notes_editor(image_tag('edit.png'), "journal-#{journal.id}-notes", |
||||
{ :controller => 'journals', :action => 'edit', :id => journal }, |
||||
:title => l(:button_edit)) |
||||
end |
||||
end |
||||
end |
||||
|
||||
content = '' |
||||
content << content_tag('div', links.join(' '), :class => 'contextual') unless links.empty? |
||||
content << textilizable(journal, :notes) |
||||
|
||||
css_classes = "wiki" |
||||
css_classes << " editable" if editable |
||||
|
||||
content_tag('div', content, :id => "journal-#{journal.id}-notes", :class => css_classes) |
||||
end |
||||
|
||||
def link_to_in_place_notes_editor(text, field_id, url, options={}) |
||||
onclick = "new Ajax.Request('#{url_for(url)}', {asynchronous:true, evalScripts:true, method:'get'}); return false;" |
||||
link_to text, '#', options.merge(:onclick => onclick) |
||||
end |
||||
|
||||
# This may conveniently be used by controllers to find journals referred to in the current request |
||||
def find_optional_journal |
||||
@journal = Journal.find_by_id(params[:journal_id]) |
||||
end |
||||
|
||||
def render_reply(journal) |
||||
user = journal.user |
||||
text = journal.notes |
||||
|
||||
# Replaces pre blocks with [...] |
||||
text = text.to_s.strip.gsub(%r{<pre>((.|\s)*?)</pre>}m, '[...]') |
||||
content = "#{ll(Setting.default_language, :text_user_wrote, user)}\n> " |
||||
content << text.gsub(/(\r?\n|\r\n?)/, "\n> ") + "\n\n" |
||||
|
||||
render(:update) do |page| |
||||
page << "$('notes').value = \"#{escape_javascript content}\";" |
||||
page.show 'update' |
||||
page << "Form.Element.focus('notes');" |
||||
page << "Element.scrollTo('update');" |
||||
page << "$('notes').scrollTop = $('notes').scrollHeight - $('notes').clientHeight;" |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,111 @@ |
||||
# This file is part of the acts_as_journalized plugin for the redMine |
||||
# project management software |
||||
# |
||||
# Copyright (c) 2009 Steve Richert |
||||
# Copyright (c) 2010 Finn GmbH, http://finn.de |
||||
# |
||||
# 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 journal 2 |
||||
# of the License, or (at your option) any later journal. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
|
||||
require_dependency 'journal_formatter' |
||||
|
||||
# The ActiveRecord model representing journals. |
||||
class Journal < ActiveRecord::Base |
||||
unloadable |
||||
|
||||
include Comparable |
||||
include JournalFormatter |
||||
include JournalDeprecated |
||||
|
||||
# Make sure each journaled model instance only has unique version ids |
||||
validates_uniqueness_of :version, :scope => [:journaled_id, :type] |
||||
belongs_to :journaled |
||||
belongs_to :user |
||||
|
||||
# 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 |
||||
# an underlying +changed_attributes+ method, not +changes+ itself. |
||||
# undef_method :changes |
||||
serialize :changes, Hash |
||||
|
||||
# In conjunction with the included Comparable module, allows comparison of journal records |
||||
# based on their corresponding version numbers, creation timestamps and IDs. |
||||
def <=>(other) |
||||
[version, created_at, id].map(&:to_i) <=> [other.version, other.created_at, other.id].map(&:to_i) |
||||
end |
||||
|
||||
# Returns whether the version has a version number of 1. Useful when deciding whether to ignore |
||||
# the version during reversion, as initial versions have no serialized changes attached. Helps |
||||
# maintain backwards compatibility. |
||||
def initial? |
||||
version < 2 |
||||
end |
||||
|
||||
# The anchor number for html output |
||||
def anchor |
||||
version - 1 |
||||
end |
||||
|
||||
# Possible shortcut to the associated project |
||||
def project |
||||
if journaled.respond_to?(:project) |
||||
journaled.project |
||||
elsif journaled.is_a? Project |
||||
journaled |
||||
else |
||||
nil |
||||
end |
||||
end |
||||
|
||||
def editable_by?(user) |
||||
journaled.journal_editable_by?(user) |
||||
end |
||||
|
||||
def details |
||||
attributes["changes"] || {} |
||||
end |
||||
|
||||
alias_method :changes, :details |
||||
|
||||
def new_value_for(prop) |
||||
details[prop.to_s].last if details.keys.include? prop.to_s |
||||
end |
||||
|
||||
def old_value_for(prop) |
||||
details[prop.to_s].first if details.keys.include? prop.to_s |
||||
end |
||||
|
||||
# Returns a string of css classes |
||||
def css_classes |
||||
s = 'journal' |
||||
s << ' has-notes' unless notes.blank? |
||||
s << ' has-details' unless details.empty? |
||||
s |
||||
end |
||||
|
||||
# This is here to allow people to disregard the difference between working with a |
||||
# Journal and the object it is attached to. |
||||
# The lookup is as follows: |
||||
## => Call super if the method corresponds to one of our attributes (will end up in AR::Base) |
||||
## => Try the journaled object with the same method and arguments |
||||
## => On error, call super |
||||
def method_missing(method, *args, &block) |
||||
return super if attributes[method.to_s] |
||||
journaled.send(method, *args, &block) |
||||
rescue NoMethodError => e |
||||
e.name == method ? super : raise(e) |
||||
end |
||||
|
||||
end |
@ -0,0 +1,8 @@ |
||||
<% form_remote_tag(:url => {}, :html => { :id => "journal-#{@journal.id}-form" }) do %> |
||||
<%= text_area_tag :notes, @journal.notes, :class => 'wiki-edit', |
||||
:rows => (@journal.notes.blank? ? 10 : [[10, @journal.notes.length / 50].max, 100].min) %> |
||||
<%= call_hook(:view_journals_notes_form_after_notes, { :journal => @journal}) %> |
||||
<p><%= submit_tag l(:button_save) %> |
||||
<%= link_to l(:button_cancel), '#', :onclick => "Element.remove('journal-#{@journal.id}-form'); " + |
||||
"Element.show('journal-#{@journal.id}-notes'); return false;" %></p> |
||||
<% end %> |
@ -0,0 +1,17 @@ |
||||
$LOAD_PATH.unshift File.expand_path("../lib/", __FILE__) |
||||
|
||||
require "acts_as_journalized" |
||||
ActiveRecord::Base.send(:include, Redmine::Acts::Journalized) |
||||
|
||||
require 'dispatcher' |
||||
Dispatcher.to_prepare do |
||||
# Model |
||||
require_dependency "journal" |
||||
|
||||
# this is for compatibility with current trunk |
||||
# once the plugin is part of the core, this will not be needed |
||||
# patches should then be ported onto the core |
||||
# require_dependency File.dirname(__FILE__) + '/lib/acts_as_journalized/journal_patch' |
||||
# require_dependency File.dirname(__FILE__) + '/lib/acts_as_journalized/journal_observer_patch' |
||||
# require_dependency File.dirname(__FILE__) + '/lib/acts_as_journalized/activity_fetcher_patch' |
||||
end |
@ -0,0 +1,181 @@ |
||||
# This file is part of the acts_as_journalized plugin for the redMine |
||||
# project management software |
||||
# |
||||
# Copyright (C) 2010 Finn GmbH, http://finn.de |
||||
# |
||||
# 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 journal 2 |
||||
# of the License, or (at your option) any later journal. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
|
||||
|
||||
Dir[File.expand_path("../redmine/acts/journalized/*.rb", __FILE__)].each{|f| require f } |
||||
require_dependency 'lib/ar_condition' |
||||
|
||||
module Redmine |
||||
module Acts |
||||
module Journalized |
||||
|
||||
def self.included(base) |
||||
base.extend ClassMethods |
||||
base.extend Versioned |
||||
end |
||||
|
||||
module ClassMethods |
||||
|
||||
def plural_name |
||||
self.name.underscore.pluralize |
||||
end |
||||
|
||||
# A model might provide as many activity_types as it wishes. |
||||
# Activities are just different search options for the event a model provides |
||||
def acts_as_activity(options = {}) |
||||
activity_hash = journalized_activity_hash(options) |
||||
type = activity_hash[:type] |
||||
acts_as_activity_provider activity_hash |
||||
unless Redmine::Activity.providers[type].include? self.name |
||||
Redmine::Activity.register type.to_sym, :class_name => self.name |
||||
end |
||||
end |
||||
|
||||
# This call will add an activity and, if neccessary, start the journaling and |
||||
# add an event callback on the model. |
||||
# Versioning and acting as an Event may only be applied once. |
||||
# To apply more than on activity, use acts_as_activity |
||||
def acts_as_journalized(options = {}, &block) |
||||
activity_hash, event_hash, journal_hash = split_option_hashes(options) |
||||
|
||||
acts_as_activity(activity_hash) |
||||
|
||||
return if journaled? |
||||
|
||||
include Options |
||||
include Changes |
||||
include Creation |
||||
include Users |
||||
include Reversion |
||||
include Reset |
||||
include Reload |
||||
include Permissions |
||||
include SaveHooks |
||||
include FormatHooks |
||||
|
||||
# FIXME: When the transition to the new API is complete, remove me |
||||
include Deprecated |
||||
|
||||
journal_class.acts_as_event journalized_event_hash(event_hash) |
||||
|
||||
(journal_hash[:except] ||= []) << self.primary_key << inheritance_column << |
||||
:updated_on << :updated_at << :lock_version << :lft << :rgt |
||||
prepare_journaled_options(journal_hash) |
||||
has_many :journals, journal_hash.merge({:class_name => journal_class.name, |
||||
:foreign_key => "journaled_id"}), &block |
||||
end |
||||
|
||||
def journal_class |
||||
journal_class_name = "#{name.gsub("::", "_")}Journal" |
||||
if Object.const_defined?(journal_class_name) |
||||
Object.const_get(journal_class_name) |
||||
else |
||||
Object.const_set(journal_class_name, Class.new(Journal)).tap do |c| |
||||
# Run after the inherited hook to associate with the parent record. |
||||
# This eager loads the associated project (for permissions) if possible |
||||
if project_assoc = reflect_on_association(:project).try(:name) |
||||
include_option = ", :include => :#{project_assoc.to_s}" |
||||
end |
||||
c.class_eval("belongs_to :journaled, :class_name => '#{name}' #{include_option}") |
||||
c.class_eval("belongs_to :#{name.gsub("::", "_").underscore}, |
||||
:foreign_key => 'journaled_id' #{include_option}") |
||||
end |
||||
end |
||||
end |
||||
|
||||
private |
||||
# Splits an option has into three hashes: |
||||
## => [{ options prefixed with "activity_" }, { options prefixed with "event_" }, { other options }] |
||||
def split_option_hashes(options) |
||||
activity_hash = {} |
||||
event_hash = {} |
||||
journal_hash = {} |
||||
|
||||
options.each_pair do |k, v| |
||||
case |
||||
when k.to_s =~ /^activity_(.+)$/ |
||||
activity_hash[$1.to_sym] = v |
||||
when k.to_s =~ /^event_(.+)$/ |
||||
event_hash[$1.to_sym] = v |
||||
else |
||||
journal_hash[k.to_sym] = v |
||||
end |
||||
end |
||||
[activity_hash, event_hash, journal_hash] |
||||
end |
||||
|
||||
# Merges the passed activity_hash with the options we require for |
||||
# acts_as_journalized to work, as follows: |
||||
# # type is the supplied or the pluralized class name |
||||
# # timestamp is supplied or the journal's created_at |
||||
# # author_key will always be the journal's author |
||||
# # |
||||
# # find_options are merged as follows: |
||||
# # # select statement is enriched with the journal fields |
||||
# # # journal association is added to the includes |
||||
# # # if a project is associated with the model, this is added to the includes |
||||
# # # the find conditions are extended to only choose journals which have the proper activity_type |
||||
# => a valid activity hash |
||||
def journalized_activity_hash(options) |
||||
options.tap do |h| |
||||
h[:type] ||= plural_name |
||||
h[:timestamp] = "#{journal_class.table_name}.created_at" |
||||
h[:author_key] = "#{journal_class.table_name}.user_id" |
||||
|
||||
h[:find_options] ||= {} # in case it is nil |
||||
h[:find_options] = {}.tap do |opts| |
||||
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 |
||||
|
||||
include_opts = [] |
||||
include_opts << :project if reflect_on_association(:project) |
||||
if h[:find_options][:include] |
||||
include_opts += case h[:find_options][:include] |
||||
when Array then h[:find_options][:include] |
||||
else [h[:find_options][:include]] |
||||
end |
||||
end |
||||
include_opts.uniq! |
||||
opts[:include] = [:journaled => include_opts] |
||||
|
||||
#opts[:joins] = h[:find_options][:joins] if h[:find_options][:joins] |
||||
end |
||||
end |
||||
end |
||||
|
||||
# Merges the event hashes defaults with the options provided by the user |
||||
# The defaults take their details from the journal |
||||
def journalized_event_hash(options) |
||||
unless options.has_key? :url |
||||
options[:url] = Proc.new do |journal| |
||||
{ :controller => plural_name, |
||||
:action => 'show', |
||||
:id => journal.journaled_id, |
||||
:anchor => ("note-#{journal.anchor}" unless journal.initial?) } |
||||
end |
||||
end |
||||
{ :description => :notes, :author => :user }.reverse_merge options |
||||
end |
||||
end |
||||
|
||||
end |
||||
end |
||||
end |
@ -0,0 +1,49 @@ |
||||
# This file is part of the acts_as_journalized plugin for the redMine |
||||
# project management software |
||||
# |
||||
# Copyright (C) 2010 Finn GmbH, http://finn.de |
||||
# |
||||
# 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 journal 2 |
||||
# of the License, or (at your option) any later journal. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
|
||||
# This module holds the formatting methods that each journal has. |
||||
# It provides the hooks to apply different formatting to the details |
||||
# of a specific journal. |
||||
module JournalDeprecated |
||||
unloadable |
||||
# Old timestamps. created_at is what t.timestamps creates in recent Rails journals |
||||
def created_on |
||||
created_at |
||||
end |
||||
|
||||
# Old naming |
||||
def journalized |
||||
journaled |
||||
end |
||||
|
||||
# Old naming |
||||
def journalized= obj |
||||
journaled = obj |
||||
end |
||||
|
||||
|
||||
# Shortcut from more issue-specific journals |
||||
def attachments |
||||
journalized.respond_to?(:attachments) ? journalized.attachments : nil |
||||
end |
||||
|
||||
# deprecate :created_on => "use #created_at" |
||||
# deprecate :journalized => "use journaled" |
||||
# deprecate :attachments => "implement it yourself" |
||||
end |
@ -0,0 +1,190 @@ |
||||
# This file is part of the acts_as_journalized plugin for the redMine |
||||
# project management software |
||||
# |
||||
# Copyright (C) 2010 Finn GmbH, http://finn.de |
||||
# |
||||
# 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 journal 2 |
||||
# of the License, or (at your option) any later journal. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
|
||||
# This module holds the formatting methods that each journal has. |
||||
# It provides the hooks to apply different formatting to the details |
||||
# of a specific journal. |
||||
module JournalFormatter |
||||
unloadable |
||||
mattr_accessor :formatters, :registered_fields |
||||
include ApplicationHelper |
||||
include CustomFieldsHelper |
||||
include ActionView::Helpers::TagHelper |
||||
include ActionView::Helpers::UrlHelper |
||||
extend Redmine::I18n |
||||
|
||||
def self.register(hash) |
||||
if hash[:class] |
||||
klazz = hash.delete(:class) |
||||
registered_fields[klazz] ||= {} |
||||
registered_fields[klazz].merge!(hash) |
||||
else |
||||
formatters.merge(hash) |
||||
end |
||||
end |
||||
|
||||
# TODO: Document Formatters (can take up to three params, value, journaled, field ...) |
||||
def self.default_formatters |
||||
{ :plaintext => (Proc.new {|v,*| v.try(:to_s) }), |
||||
:datetime => (Proc.new {|v,*| format_date(v.to_date) }), |
||||
:named_association => (Proc.new do |value, journaled, field| |
||||
association = journaled.class.reflect_on_association(field.to_sym) |
||||
if association |
||||
record = association.class_name.constantize.find_by_id(value.to_i) |
||||
record.name if record |
||||
end |
||||
end), |
||||
:fraction => (Proc.new {|v,*| "%0.02f" % v.to_f }), |
||||
:decimal => (Proc.new {|v,*| v.to_i.to_s }), |
||||
:id => (Proc.new {|v,*| "##{v}" }) } |
||||
end |
||||
|
||||
self.formatters = default_formatters |
||||
self.registered_fields = {} |
||||
|
||||
def format_attribute_detail(key, values, no_html=false) |
||||
field = key.to_s.gsub(/\_id$/, "") |
||||
label = l(("field_" + field).to_sym) |
||||
|
||||
if format = JournalFormatter.registered_fields[self.class.name.to_sym][key] |
||||
formatter = JournalFormatter.formatters[format] |
||||
old_value = formatter.call(values.first, journaled, field) if values.first |
||||
value = formatter.call(values.last, journaled, field) if values.last |
||||
[label, old_value, value] |
||||
else |
||||
return nil |
||||
end |
||||
end |
||||
|
||||
def format_custom_value_detail(custom_field, values, no_html) |
||||
label = custom_field.name |
||||
old_value = format_value(values.first, custom_field.field_format) if values.first |
||||
value = format_value(values.last, custom_field.field_format) if values.last |
||||
|
||||
[label, old_value, value] |
||||
end |
||||
|
||||
def format_attachment_detail(key, values, no_html) |
||||
label = l(:label_attachment) |
||||
old_value = values.first |
||||
value = values.last |
||||
|
||||
[label, old_value, value] |
||||
end |
||||
|
||||
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 |
||||
else |
||||
content_tag("i", h(value)) if value.present? |
||||
end |
||||
end |
||||
|
||||
def format_html_detail(label, old_value, value) |
||||
label = content_tag('strong', label) |
||||
old_value = content_tag("i", h(old_value)) if old_value && !old_value.blank? |
||||
old_value = content_tag("strike", old_value) if old_value and value.blank? |
||||
value = content_tag("i", h(value)) if value.present? |
||||
value ||= "" |
||||
[label, old_value, value] |
||||
end |
||||
|
||||
def property(detail) |
||||
key = prop_key(detail) |
||||
if key.start_with? "custom_values" |
||||
:custom_field |
||||
elsif key.start_with? "attachments" |
||||
:attachment |
||||
elsif journaled.class.columns.collect(&:name).include? key |
||||
:attribute |
||||
end |
||||
end |
||||
|
||||
def prop_key(detail) |
||||
if detail.respond_to? :to_ary |
||||
detail.first |
||||
else |
||||
detail |
||||
end |
||||
end |
||||
|
||||
def values(detail) |
||||
key = prop_key(detail) |
||||
if detail != key |
||||
detail.last |
||||
else |
||||
details[key.to_s] |
||||
end |
||||
end |
||||
|
||||
def old_value(detail) |
||||
values(detail).first |
||||
end |
||||
|
||||
def value(detail) |
||||
values(detail).last |
||||
end |
||||
|
||||
def render_detail(detail, no_html=false) |
||||
if detail.respond_to? :to_ary |
||||
key = detail.first |
||||
values = detail.last |
||||
else |
||||
key = detail |
||||
values = details[key.to_s] |
||||
end |
||||
|
||||
case property(detail) |
||||
when :attribute |
||||
attr_detail = format_attribute_detail(key, values, no_html) |
||||
when :custom_field |
||||
custom_field = CustomField.find_by_id(key.sub("custom_values", "").to_i) |
||||
cv_detail = format_custom_value_detail(custom_field, values, no_html) |
||||
when :attachment |
||||
attachment_detail = format_attachment_detail(key.sub("attachments", ""), values, no_html) |
||||
end |
||||
|
||||
label, old_value, value = attr_detail || cv_detail || attachment_detail |
||||
Redmine::Hook.call_hook :helper_issues_show_detail_after_setting, {:detail => detail, |
||||
: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) |
||||
|
||||
unless no_html |
||||
label, old_value, value = *format_html_detail(label, old_value, value) |
||||
value = format_html_attachment_detail(key.sub("attachments", ""), value) if attachment_detail |
||||
end |
||||
|
||||
unless value.blank? |
||||
if attr_detail || cv_detail |
||||
unless 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 |
||||
elsif attachment_detail |
||||
l(:text_journal_added, :label => label, :value => value) |
||||
end |
||||
else |
||||
l(:text_journal_deleted, :label => label, :old => old_value) |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,162 @@ |
||||
# This file included as part of the acts_as_journalized plugin for |
||||
# the redMine project management 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 journal 2 |
||||
# of the License, or (at your option) any later journal. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
# |
||||
# The original copyright and license conditions are: |
||||
# Copyright (c) 2009 Steve Richert |
||||
# |
||||
# Permission is hereby granted, free of charge, to any person obtaining |
||||
# a copy of this software and associated documentation files (the |
||||
# "Software"), to deal in the Software without restriction, including |
||||
# without limitation the rights to use, copy, modify, merge, publish, |
||||
# distribute, sublicense, and/or sell copies of the Software, and to |
||||
# permit persons to whom the Software is furnished to do so, subject to |
||||
# the following conditions: |
||||
# |
||||
# The above copyright notice and this permission notice shall be |
||||
# included in all copies or substantial portions of the Software. |
||||
# |
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE |
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION |
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
||||
|
||||
module Redmine::Acts::Journalized |
||||
# Provides the ability to manipulate hashes in the specific format that ActiveRecord gives to |
||||
# dirty attribute changes: string keys and unique, two-element array values. |
||||
module Changes |
||||
def self.included(base) # :nodoc: |
||||
Hash.send(:include, HashMethods) |
||||
|
||||
base.class_eval do |
||||
include InstanceMethods |
||||
|
||||
after_update :merge_journal_changes |
||||
end |
||||
end |
||||
|
||||
# Methods available to journaled ActiveRecord::Base instances in order to manage changes used |
||||
# for journal creation. |
||||
module InstanceMethods |
||||
# Collects an array of changes from a record's journals between the given range and compiles |
||||
# them into one summary hash of changes. The +from+ and +to+ arguments can each be either a |
||||
# version number, a symbol representing an association proxy method, a string representing a |
||||
# journal tag or a journal object itself. |
||||
def changes_between(from, to) |
||||
from_number, to_number = journals.journal_at(from), journals.journal_at(to) |
||||
return {} if from_number == to_number |
||||
chain = journals.between(from_number, to_number).reject(&:initial?) |
||||
return {} if chain.empty? |
||||
|
||||
backward = from_number > to_number |
||||
backward ? chain.pop : chain.shift unless from_number == 1 || to_number == 1 |
||||
|
||||
chain.inject({}) do |changes, journal| |
||||
changes.append_changes!(backward ? journal.changes.reverse_changes : journal.changes) |
||||
end |
||||
end |
||||
|
||||
private |
||||
# Before a new journal is created, the newly-changed attributes are appended onto a hash |
||||
# of previously-changed attributes. Typically the previous changes will be empty, except in |
||||
# the case that a control block is used where journals are to be merged. See |
||||
# VestalVersions::Control for more information. |
||||
def merge_journal_changes |
||||
journal_changes.append_changes!(incremental_journal_changes) |
||||
end |
||||
|
||||
# Stores the cumulative changes that are eventually used for journal creation. |
||||
def journal_changes |
||||
@journal_changes ||= {} |
||||
end |
||||
|
||||
# Stores the incremental changes that are appended to the cumulative changes before journal |
||||
# creation. Incremental changes are reset when the record is saved because they represent |
||||
# a subset of the dirty attribute changes, which are reset upon save. |
||||
def incremental_journal_changes |
||||
changes.slice(*journaled_columns) |
||||
end |
||||
|
||||
# Simply resets the cumulative changes after journal creation. |
||||
def reset_journal_changes |
||||
@journal_changes = nil |
||||
end |
||||
end |
||||
|
||||
# Instance methods included into Hash for dealing with manipulation of hashes in the specific |
||||
# format of ActiveRecord::Base#changes. |
||||
module HashMethods |
||||
# When called on a hash of changes and given a second hash of changes as an argument, |
||||
# +append_changes+ will run the second hash on top of the first, updating the last element |
||||
# of each array value with its own, or creating its own key/value pair for missing keys. |
||||
# Resulting non-unique array values are removed. |
||||
# |
||||
# == Example |
||||
# |
||||
# first = { |
||||
# "first_name" => ["Steve", "Stephen"], |
||||
# "age" => [25, 26] |
||||
# } |
||||
# second = { |
||||
# "first_name" => ["Stephen", "Steve"], |
||||
# "last_name" => ["Richert", "Jobs"], |
||||
# "age" => [26, 54] |
||||
# } |
||||
# first.append_changes(second) |
||||
# # => { |
||||
# "last_name" => ["Richert", "Jobs"], |
||||
# "age" => [25, 54] |
||||
# } |
||||
def append_changes(changes) |
||||
changes.inject(self) do |new_changes, (attribute, change)| |
||||
new_change = [new_changes.fetch(attribute, change).first, change.last] |
||||
new_changes.merge(attribute => new_change) |
||||
end.reject do |attribute, change| |
||||
change.first == change.last |
||||
end |
||||
end |
||||
|
||||
# Destructively appends a given hash of changes onto an existing hash of changes. |
||||
def append_changes!(changes) |
||||
replace(append_changes(changes)) |
||||
end |
||||
|
||||
# Appends the existing hash of changes onto a given hash of changes. Relates to the |
||||
# +append_changes+ method in the same way that Hash#reverse_merge relates to |
||||
# Hash#merge. |
||||
def prepend_changes(changes) |
||||
changes.append_changes(self) |
||||
end |
||||
|
||||
# Destructively prepends a given hash of changes onto an existing hash of changes. |
||||
def prepend_changes!(changes) |
||||
replace(prepend_changes(changes)) |
||||
end |
||||
|
||||
# Reverses the array values of a hash of changes. Useful for rejournal both backward and |
||||
# forward through a record's history of changes. |
||||
def reverse_changes |
||||
inject({}){|nc,(a,c)| nc.merge!(a => c.reverse) } |
||||
end |
||||
|
||||
# Destructively reverses the array values of a hash of changes. |
||||
def reverse_changes! |
||||
replace(reverse_changes) |
||||
end |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,77 @@ |
||||
# This file included as part of the acts_as_journalized plugin for |
||||
# the redMine project management 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. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
# |
||||
# The original copyright and license conditions are: |
||||
# Copyright (c) 2009 Steve Richert |
||||
# |
||||
# Permission is hereby granted, free of charge, to any person obtaining |
||||
# a copy of this software and associated documentation files (the |
||||
# "Software"), to deal in the Software without restriction, including |
||||
# without limitation the rights to use, copy, modify, merge, publish, |
||||
# distribute, sublicense, and/or sell copies of the Software, and to |
||||
# permit persons to whom the Software is furnished to do so, subject to |
||||
# the following conditions: |
||||
# |
||||
# The above copyright notice and this permission notice shall be |
||||
# included in all copies or substantial portions of the Software. |
||||
# |
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE |
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION |
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
||||
|
||||
module Redmine::Acts::Journalized |
||||
# Allows for easy application-wide configuration of options passed into the +journaled+ method. |
||||
module Configuration |
||||
# The VestalVersions module is extended by VestalVersions::Configuration, allowing the |
||||
# +configure method+ to be used as follows in a Rails initializer: |
||||
# |
||||
# VestalVersions.configure do |config| |
||||
# config.class_name = "MyCustomVersion" |
||||
# config.dependent = :destroy |
||||
# end |
||||
# |
||||
# Each variable assignment in the +configure+ block corresponds directly with the options |
||||
# available to the +journaled+ method. Assigning common options in an initializer can keep your |
||||
# models tidy. |
||||
# |
||||
# If an option is given in both an initializer and in the options passed to +journaled+, the |
||||
# value given in the model itself will take precedence. |
||||
def configure |
||||
yield Configuration |
||||
end |
||||
|
||||
class << self |
||||
# Simply stores a hash of options given to the +configure+ block. |
||||
def options |
||||
@options ||= {} |
||||
end |
||||
|
||||
# If given a setter method name, will assign the first argument to the +options+ hash with |
||||
# the method name (sans "=") as the key. If given a getter method name, will attempt to |
||||
# a value from the +options+ hash for that key. If the key doesn't exist, defers to +super+. |
||||
def method_missing(symbol, *args) |
||||
if (method = symbol.to_s).sub!(/\=$/, '') |
||||
options[method.to_sym] = args.first |
||||
else |
||||
options.fetch(method.to_sym, super) |
||||
end |
||||
end |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,127 @@ |
||||
# This file included as part of the acts_as_journalized plugin for |
||||
# the redMine project management 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. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
# |
||||
# The original copyright and license conditions are: |
||||
# Copyright (c) 2009 Steve Richert |
||||
# |
||||
# Permission is hereby granted, free of charge, to any person obtaining |
||||
# a copy of this software and associated documentation files (the |
||||
# "Software"), to deal in the Software without restriction, including |
||||
# without limitation the rights to use, copy, modify, merge, publish, |
||||
# distribute, sublicense, and/or sell copies of the Software, and to |
||||
# permit persons to whom the Software is furnished to do so, subject to |
||||
# the following conditions: |
||||
# |
||||
# The above copyright notice and this permission notice shall be |
||||
# included in all copies or substantial portions of the Software. |
||||
# |
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE |
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION |
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
||||
|
||||
module Redmine::Acts::Journalized |
||||
# Adds the functionality necessary to control journal creation on a journaled instance of |
||||
# ActiveRecord::Base. |
||||
module Creation |
||||
def self.included(base) # :nodoc: |
||||
base.class_eval do |
||||
extend ClassMethods |
||||
include InstanceMethods |
||||
|
||||
after_save :create_journal, :if => :create_journal? |
||||
|
||||
class << self |
||||
alias_method_chain :prepare_journaled_options, :creation |
||||
end |
||||
end |
||||
end |
||||
|
||||
# Class methods added to ActiveRecord::Base to facilitate the creation of new journals. |
||||
module ClassMethods |
||||
# Overrides the basal +prepare_journaled_options+ method defined in VestalVersions::Options |
||||
# to extract the <tt>:only</tt> and <tt>:except</tt> options into +vestal_journals_options+. |
||||
def prepare_journaled_options_with_creation(options) |
||||
result = prepare_journaled_options_without_creation(options) |
||||
|
||||
self.vestal_journals_options[:only] = Array(options.delete(:only)).map(&:to_s).uniq if options[:only] |
||||
self.vestal_journals_options[:except] = Array(options.delete(:except)).map(&:to_s).uniq if options[:except] |
||||
|
||||
result |
||||
end |
||||
end |
||||
|
||||
# Instance methods that determine whether to save a journal and actually perform the save. |
||||
module InstanceMethods |
||||
private |
||||
# Returns whether a new journal should be created upon updating the parent record. |
||||
# A new journal will be created if |
||||
# a) attributes have changed |
||||
# b) no previous journal exists |
||||
# c) journal notes were added |
||||
# d) the parent record is already saved |
||||
def create_journal? |
||||
update_journal |
||||
(journal_changes.present? or journal_notes.present? or journals.empty?) and !new_record? |
||||
end |
||||
|
||||
# Creates a new journal upon updating the parent record. |
||||
# "update_journal" has been called in "update_journal?" at this point (to get a hold on association changes) |
||||
# It must not be called again here. |
||||
def create_journal |
||||
journals << self.class.journal_class.create(journal_attributes) |
||||
reset_journal_changes |
||||
reset_journal |
||||
true |
||||
rescue Exception => e # FIXME: What to do? This likely means that the parent record is invalid! |
||||
p e |
||||
p e.message |
||||
p e.backtrace |
||||
false |
||||
end |
||||
|
||||
# Returns an array of column names that should be included in the changes of created |
||||
# journals. If <tt>vestal_journals_options[:only]</tt> is specified, only those columns |
||||
# will be journaled. Otherwise, if <tt>vestal_journals_options[:except]</tt> is specified, |
||||
# all columns will be journaled other than those specified. Without either option, the |
||||
# default is to journal all columns. At any rate, the four "automagic" timestamp columns |
||||
# maintained by Rails are never journaled. |
||||
def journaled_columns |
||||
case |
||||
when vestal_journals_options[:only] then self.class.column_names & vestal_journals_options[:only] |
||||
when vestal_journals_options[:except] then self.class.column_names - vestal_journals_options[:except] |
||||
else self.class.column_names |
||||
end - %w(created_at updated_at) |
||||
end |
||||
|
||||
# Returns the activity type. Should be overridden in the journalized class to offer |
||||
# multiple types |
||||
def activity_type |
||||
self.class.name.underscore.pluralize |
||||
end |
||||
|
||||
# Specifies the attributes used during journal creation. This is separated into its own |
||||
# method so that it can be overridden by the VestalVersions::Users feature. |
||||
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) } |
||||
end |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,68 @@ |
||||
# This file is part of the acts_as_journalized plugin for the redMine |
||||
# project management software |
||||
# |
||||
# Copyright (C) 2010 Finn GmbH, http://finn.de |
||||
# |
||||
# 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. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
|
||||
# These hooks make sure journals are properly created and updated with Redmine user detail, |
||||
# notes and associated custom fields |
||||
|
||||
module Redmine::Acts::Journalized |
||||
module Deprecated |
||||
# Old mailer API |
||||
def recipients |
||||
notified = project.notified_users |
||||
notified.reject! {|user| !visible?(user)} |
||||
notified.collect(&:mail) |
||||
end |
||||
|
||||
def current_journal |
||||
last_journal |
||||
end |
||||
|
||||
# FIXME: When the new API is settled, remove me |
||||
Redmine::Acts::Event::InstanceMethods.instance_methods(false).each do |m| |
||||
if m.start_with? "event_" |
||||
class_eval(<<-RUBY, __FILE__, __LINE__) |
||||
def #{m} |
||||
if last_journal.nil? |
||||
begin |
||||
journals << self.class.journal_class.create(journal_attributes) |
||||
reset_journal_changes |
||||
reset_journal |
||||
true |
||||
rescue Exception => e # FIXME: What to do? This likely means that the parent record is invalid! |
||||
p e |
||||
p e.message |
||||
p e.backtrace |
||||
false |
||||
end |
||||
journals.reload |
||||
end |
||||
return last_journal.#{m} |
||||
end |
||||
RUBY |
||||
end |
||||
end |
||||
|
||||
def event_url(options = {}) |
||||
last_journal.event_url(options) |
||||
end |
||||
|
||||
# deprecate :recipients => "use #last_journal.recipients" |
||||
# deprecate :current_journal => "use #last_journal" |
||||
end |
||||
end |
@ -0,0 +1,42 @@ |
||||
# This file is part of the acts_as_journalized plugin for the redMine |
||||
# project management software |
||||
# |
||||
# Copyright (C) 2010 Finn GmbH, http://finn.de |
||||
# |
||||
# 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. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
|
||||
module Redmine::Acts::Journalized |
||||
module FormatHooks |
||||
def self.included(base) |
||||
base.extend ClassMethods |
||||
end |
||||
|
||||
module ClassMethods |
||||
# Shortcut to register a formatter for a number of fields |
||||
def register_on_journal_formatter(formatter, *field_names) |
||||
formatter = formatter.to_sym |
||||
field_names.collect(&:to_s).each do |field| |
||||
JournalFormatter.register :class => self.journal_class.name.to_sym, field => formatter |
||||
end |
||||
end |
||||
|
||||
# Shortcut to register a new proc as a named formatter. Overwrites |
||||
# existing formatters with the same name |
||||
def register_journal_formatter(formatter) |
||||
JournalFormatter.register formatter.to_sym => Proc.new |
||||
end |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,81 @@ |
||||
# This file included as part of the acts_as_journalized plugin for |
||||
# the redMine project management 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. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
# |
||||
# The original copyright and license conditions are: |
||||
# Copyright (c) 2009 Steve Richert |
||||
# |
||||
# Permission is hereby granted, free of charge, to any person obtaining |
||||
# a copy of this software and associated documentation files (the |
||||
# "Software"), to deal in the Software without restriction, including |
||||
# without limitation the rights to use, copy, modify, merge, publish, |
||||
# distribute, sublicense, and/or sell copies of the Software, and to |
||||
# permit persons to whom the Software is furnished to do so, subject to |
||||
# the following conditions: |
||||
# |
||||
# The above copyright notice and this permission notice shall be |
||||
# included in all copies or substantial portions of the Software. |
||||
# |
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE |
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION |
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
||||
|
||||
module Redmine::Acts::Journalized |
||||
# Provides +journaled+ options conjournal and cleanup. |
||||
module Options |
||||
def self.included(base) # :nodoc: |
||||
base.class_eval do |
||||
extend ClassMethods |
||||
end |
||||
end |
||||
|
||||
# Class methods that provide preparation of options passed to the +journaled+ method. |
||||
module ClassMethods |
||||
# The +prepare_journaled_options+ method has three purposes: |
||||
# 1. Populate the provided options with default values where needed |
||||
# 2. Prepare options for use with the +has_many+ association |
||||
# 3. Save user-configurable options in a class-level variable |
||||
# |
||||
# Options are given priority in the following order: |
||||
# 1. Those passed directly to the +journaled+ method |
||||
# 2. Those specified in an initializer +configure+ block |
||||
# 3. Default values specified in +prepare_journaled_options+ |
||||
# |
||||
# The method is overridden in feature modules that require specific options outside the |
||||
# standard +has_many+ associations. |
||||
def prepare_journaled_options(options) |
||||
options.symbolize_keys! |
||||
options.reverse_merge!(Configuration.options) |
||||
options.reverse_merge!( |
||||
:class_name => 'Journal', |
||||
:dependent => :delete_all |
||||
) |
||||
options.reverse_merge!( |
||||
:order => "#{options[:class_name].constantize.table_name}.version ASC" |
||||
) |
||||
|
||||
class_inheritable_accessor :vestal_journals_options |
||||
self.vestal_journals_options = options.dup |
||||
|
||||
options.merge!( |
||||
:extend => Array(options[:extend]).unshift(Versions) |
||||
) |
||||
end |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,36 @@ |
||||
# This file is part of the acts_as_journalized plugin for the redMine |
||||
# project management software |
||||
# |
||||
# Copyright (C) 2010 Finn GmbH, http://finn.de |
||||
# |
||||
# 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. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
|
||||
module Redmine::Acts::Journalized |
||||
module Permissions |
||||
# Default implementation of journal editing permission |
||||
# Is overridden if defined in the journalized model directly |
||||
def journal_editable_by?(user) |
||||
return true if user.admin? |
||||
if respond_to? :editable_by? |
||||
editable_by? user |
||||
else |
||||
permission = :"edit_#{self.class.to_s.pluralize.downcase}" |
||||
p = @project || (project if respond_to? :project) |
||||
options = { :global => p.present? } |
||||
user.allowed_to? permission, p, options |
||||
end |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,60 @@ |
||||
# This file included as part of the acts_as_journalized plugin for |
||||
# the redMine project management 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. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
# |
||||
# The original copyright and license conditions are: |
||||
# Copyright (c) 2009 Steve Richert |
||||
# |
||||
# Permission is hereby granted, free of charge, to any person obtaining |
||||
# a copy of this software and associated documentation files (the |
||||
# "Software"), to deal in the Software without restriction, including |
||||
# without limitation the rights to use, copy, modify, merge, publish, |
||||
# distribute, sublicense, and/or sell copies of the Software, and to |
||||
# permit persons to whom the Software is furnished to do so, subject to |
||||
# the following conditions: |
||||
# |
||||
# The above copyright notice and this permission notice shall be |
||||
# included in all copies or substantial portions of the Software. |
||||
# |
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE |
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION |
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
||||
|
||||
module Redmine::Acts::Journalized |
||||
# Ties into the existing ActiveRecord::Base#reload method to ensure that journal information |
||||
# is properly reset. |
||||
module Reload |
||||
def self.included(base) # :nodoc: |
||||
base.class_eval do |
||||
include InstanceMethods |
||||
|
||||
alias_method_chain :reload, :journals |
||||
end |
||||
end |
||||
|
||||
# Adds instance methods into ActiveRecord::Base to tap into the +reload+ method. |
||||
module InstanceMethods |
||||
# Overrides ActiveRecord::Base#reload, resetting the instance-variable-cached journal number |
||||
# before performing the original +reload+ method. |
||||
def reload_with_journals(*args) |
||||
reset_journal |
||||
reload_without_journals(*args) |
||||
end |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,65 @@ |
||||
# This file included as part of the acts_as_journalized plugin for |
||||
# the redMine project management 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. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
# |
||||
# The original copyright and license conditions are: |
||||
# Copyright (c) 2009 Steve Richert |
||||
# |
||||
# Permission is hereby granted, free of charge, to any person obtaining |
||||
# a copy of this software and associated documentation files (the |
||||
# "Software"), to deal in the Software without restriction, including |
||||
# without limitation the rights to use, copy, modify, merge, publish, |
||||
# distribute, sublicense, and/or sell copies of the Software, and to |
||||
# permit persons to whom the Software is furnished to do so, subject to |
||||
# the following conditions: |
||||
# |
||||
# The above copyright notice and this permission notice shall be |
||||
# included in all copies or substantial portions of the Software. |
||||
# |
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE |
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION |
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
||||
|
||||
module Redmine::Acts::Journalized |
||||
# Adds the ability to "reset" (or hard revert) a journaled ActiveRecord::Base instance. |
||||
module Reset |
||||
def self.included(base) # :nodoc: |
||||
base.class_eval do |
||||
include InstanceMethods |
||||
end |
||||
end |
||||
|
||||
# Adds the instance methods required to reset an object to a previous journal. |
||||
module InstanceMethods |
||||
# Similar to +revert_to!+, the +reset_to!+ method reverts an object to a previous journal, |
||||
# only instead of creating a new record in the journal history, +reset_to!+ deletes all of |
||||
# the journal history that occurs after the journal reverted to. |
||||
# |
||||
# The action taken on each journal record after the point of rejournal is determined by the |
||||
# <tt>:dependent</tt> option given to the +journaled+ method. See the +journaled+ method |
||||
# documentation for more details. |
||||
def reset_to!(value) |
||||
if saved = skip_journal{ revert_to!(value) } |
||||
journals.send(:delete_records, journals.after(value)) |
||||
reset_journal |
||||
end |
||||
saved |
||||
end |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,110 @@ |
||||
# This file included as part of the acts_as_journalized plugin for |
||||
# the redMine project management 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. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
# |
||||
# The original copyright and license conditions are: |
||||
# Copyright (c) 2009 Steve Richert |
||||
# |
||||
# Permission is hereby granted, free of charge, to any person obtaining |
||||
# a copy of this software and associated documentation files (the |
||||
# "Software"), to deal in the Software without restriction, including |
||||
# without limitation the rights to use, copy, modify, merge, publish, |
||||
# distribute, sublicense, and/or sell copies of the Software, and to |
||||
# permit persons to whom the Software is furnished to do so, subject to |
||||
# the following conditions: |
||||
# |
||||
# The above copyright notice and this permission notice shall be |
||||
# included in all copies or substantial portions of the Software. |
||||
# |
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE |
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION |
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
||||
|
||||
module Redmine::Acts::Journalized |
||||
# Enables versioned ActiveRecord::Base instances to revert to a previously saved version. |
||||
module Reversion |
||||
def self.included(base) # :nodoc: |
||||
base.class_eval do |
||||
include InstanceMethods |
||||
end |
||||
end |
||||
|
||||
# Provides the base instance methods required to revert a journaled instance. |
||||
module InstanceMethods |
||||
# Returns the current version number for the versioned object. |
||||
def version |
||||
@version ||= last_version |
||||
end |
||||
|
||||
def last_journal |
||||
journals.last |
||||
end |
||||
|
||||
# Accepts a value corresponding to a specific journal record, builds a history of changes |
||||
# between that journal and the current journal, and then iterates over that history updating |
||||
# the object's attributes until the it's reverted to its prior state. |
||||
# |
||||
# The single argument should adhere to one of the formats as documented in the +at+ method of |
||||
# VestalVersions::Versions. |
||||
# |
||||
# After the object is reverted to the target journal, it is not saved. In order to save the |
||||
# object after the rejournal, use the +revert_to!+ method. |
||||
# |
||||
# The journal number of the object will reflect whatever journal has been reverted to, and |
||||
# the return value of the +revert_to+ method is also the target journal number. |
||||
def revert_to(value) |
||||
to_number = journals.journal_at(value) |
||||
|
||||
changes_between(journal, to_number).each do |attribute, change| |
||||
write_attribute(attribute, change.last) |
||||
end |
||||
|
||||
reset_journal(to_number) |
||||
end |
||||
|
||||
# Behaves similarly to the +revert_to+ method except that it automatically saves the record |
||||
# after the rejournal. The return value is the success of the save. |
||||
def revert_to!(value) |
||||
revert_to(value) |
||||
reset_journal if saved = save |
||||
saved |
||||
end |
||||
|
||||
# Returns a boolean specifying whether the object has been reverted to a previous journal or |
||||
# if the object represents the latest journal in the journal history. |
||||
def reverted? |
||||
version != last_version |
||||
end |
||||
|
||||
private |
||||
# Returns the number of the last created journal in the object's journal history. |
||||
# |
||||
# If no associated journals exist, the object is considered at version 0. |
||||
def last_version |
||||
@last_version ||= journals.maximum(:version) || 0 |
||||
end |
||||
|
||||
# Clears the cached version number instance variables so that they can be recalculated. |
||||
# Useful after a new version is created. |
||||
def reset_journal(version = nil) |
||||
@last_version = nil if version.nil? |
||||
@version = version |
||||
end |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,115 @@ |
||||
# This file is part of the acts_as_journalized plugin for the redMine |
||||
# project management software |
||||
# |
||||
# Copyright (C) 2010 Finn GmbH, http://finn.de |
||||
# |
||||
# 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. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
|
||||
# These hooks make sure journals are properly created and updated with Redmine user detail, |
||||
# notes and associated custom fields |
||||
module Redmine::Acts::Journalized |
||||
module SaveHooks |
||||
def self.included(base) |
||||
base.extend ClassMethods |
||||
|
||||
base.class_eval do |
||||
before_save :init_journal |
||||
after_save :reset_instance_variables |
||||
|
||||
attr_reader :journal_notes, :journal_user |
||||
end |
||||
end |
||||
|
||||
# Saves the current custom values, notes and journal to include them in the next journal |
||||
# Called before save |
||||
def init_journal(user = User.current, notes = "") |
||||
@journal_notes ||= notes |
||||
@journal_user ||= user |
||||
@associations_before_save ||= {} |
||||
|
||||
@associations = {} |
||||
save_possible_association :custom_values, :key => :custom_field_id, :value => :value |
||||
save_possible_association :attachments, :key => :id, :value => :filename |
||||
|
||||
@current_journal ||= last_journal |
||||
end |
||||
|
||||
# Saves the notes and custom value changes in the last Journal |
||||
# Called before create_journal |
||||
def update_journal |
||||
unless (@associations || {}).empty? |
||||
changed_associations = {} |
||||
changed_associations.merge!(possibly_updated_association :custom_values) |
||||
changed_associations.merge!(possibly_updated_association :attachments) |
||||
end |
||||
|
||||
unless changed_associations.blank? |
||||
update_extended_journal_contents(changed_associations) |
||||
end |
||||
end |
||||
|
||||
def reset_instance_variables |
||||
if last_journal != @current_journal |
||||
if last_journal.user != @journal_user |
||||
last_journal.update_attribute(:user_id, @journal_user.id) |
||||
end |
||||
end |
||||
@associations_before_save = @current_journal = @journal_notes = @journal_user = nil |
||||
end |
||||
|
||||
def save_possible_association(method, options) |
||||
@associations[method] = options |
||||
if self.respond_to? method |
||||
@associations_before_save[method] ||= send(method).inject({}) do |hash, cv| |
||||
hash[cv.send(options[:key])] = cv.send(options[:value]) |
||||
hash |
||||
end |
||||
end |
||||
end |
||||
|
||||
def possibly_updated_association(method) |
||||
if @associations_before_save[method] |
||||
# Has custom values from init_journal_notes |
||||
return changed_associations(method, @associations_before_save[method]) |
||||
end |
||||
{} |
||||
end |
||||
|
||||
# Saves the notes and changed custom values to the journal |
||||
# Creates a new journal, if no immediate attributes were changed |
||||
def update_extended_journal_contents(changed_associations) |
||||
journal_changes.merge!(changed_associations) |
||||
end |
||||
|
||||
def changed_associations(method, previous) |
||||
send(method).reload # Make sure the associations are reloaded |
||||
send(method).inject({}) do |hash, c| |
||||
key = c.send(@associations[method][:key]) |
||||
new_value = c.send(@associations[method][:value]) |
||||
|
||||
if previous[key].blank? && new_value.blank? |
||||
# The key was empty before, don't add a blank value |
||||
elsif previous[key] != new_value |
||||
# The key's value changed |
||||
hash["#{method}#{key}"] = [previous[key], new_value] |
||||
end |
||||
hash |
||||
end |
||||
end |
||||
|
||||
module ClassMethods |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,86 @@ |
||||
# This file included as part of the acts_as_journalized plugin for |
||||
# the redMine project management 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. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
# |
||||
# The original copyright and license conditions are: |
||||
# Copyright (c) 2009 Steve Richert |
||||
# |
||||
# Permission is hereby granted, free of charge, to any person obtaining |
||||
# a copy of this software and associated documentation files (the |
||||
# "Software"), to deal in the Software without restriction, including |
||||
# without limitation the rights to use, copy, modify, merge, publish, |
||||
# distribute, sublicense, and/or sell copies of the Software, and to |
||||
# permit persons to whom the Software is furnished to do so, subject to |
||||
# the following conditions: |
||||
# |
||||
# The above copyright notice and this permission notice shall be |
||||
# included in all copies or substantial portions of the Software. |
||||
# |
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE |
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION |
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
||||
|
||||
module Redmine::Acts::Journalized |
||||
# Provides a way for information to be associated with specific journals as to who was |
||||
# responsible for the associated update to the parent. |
||||
module Users |
||||
def self.included(base) # :nodoc: |
||||
Journal.send(:include, JournalMethods) |
||||
|
||||
base.class_eval do |
||||
include InstanceMethods |
||||
|
||||
attr_accessor :updated_by |
||||
alias_method_chain :journal_attributes, :user |
||||
end |
||||
end |
||||
|
||||
# Methods added to journaled ActiveRecord::Base instances to enable journaling with additional |
||||
# user information. |
||||
module InstanceMethods |
||||
private |
||||
# Overrides the +journal_attributes+ method to include user information passed into the |
||||
# parent object, by way of a +updated_by+ attr_accessor. |
||||
def journal_attributes_with_user |
||||
journal_attributes_without_user.merge(:user => updated_by || User.current) |
||||
end |
||||
end |
||||
|
||||
# Instance methods added to Redmine::Acts::Journalized::Journal to accomodate incoming |
||||
# user information. |
||||
module JournalMethods |
||||
def self.included(base) # :nodoc: |
||||
base.class_eval do |
||||
belongs_to :user |
||||
|
||||
alias_method_chain :user=, :name |
||||
end |
||||
end |
||||
|
||||
# Overrides the +user=+ method created by the polymorphic +belongs_to+ user association. |
||||
# Based on the class of the object given, either the +user+ association columns or the |
||||
# +user_name+ string column is populated. |
||||
def user_with_name=(value) |
||||
case value |
||||
when ActiveRecord::Base then self.user_without_name = value |
||||
else self.user = User.find_by_login(value) |
||||
end |
||||
end |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,67 @@ |
||||
# This file included as part of the acts_as_journalized plugin for |
||||
# the redMine project management 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. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
# |
||||
# The original copyright and license conditions are: |
||||
# Copyright (c) 2009 Steve Richert |
||||
# |
||||
# Permission is hereby granted, free of charge, to any person obtaining |
||||
# a copy of this software and associated documentation files (the |
||||
# "Software"), to deal in the Software without restriction, including |
||||
# without limitation the rights to use, copy, modify, merge, publish, |
||||
# distribute, sublicense, and/or sell copies of the Software, and to |
||||
# permit persons to whom the Software is furnished to do so, subject to |
||||
# the following conditions: |
||||
# |
||||
# The above copyright notice and this permission notice shall be |
||||
# included in all copies or substantial portions of the Software. |
||||
# |
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE |
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION |
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
||||
|
||||
module Redmine::Acts::Journalized |
||||
# Simply adds a flag to determine whether a model class if journaled. |
||||
module Versioned |
||||
def self.extended(base) # :nodoc: |
||||
base.class_eval do |
||||
class << self |
||||
alias_method_chain :acts_as_journalized, :flag |
||||
end |
||||
end |
||||
end |
||||
|
||||
# Overrides the +journaled+ method to first define the +journaled?+ class method before |
||||
# deferring to the original +journaled+. |
||||
def acts_as_journalized_with_flag(*args) |
||||
acts_as_journalized_without_flag(*args) |
||||
|
||||
class << self |
||||
def journaled? |
||||
true |
||||
end |
||||
end |
||||
end |
||||
|
||||
# For all ActiveRecord::Base models that do not call the +journaled+ method, the +journaled?+ |
||||
# method will return false. |
||||
def journaled? |
||||
false |
||||
end |
||||
end |
||||
end |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue