Commit 239eb9c6 authored by jplang's avatar jplang

Private issue notes (#1554).

Adds 2 new permissions for viewing/adding private comments to issues.

git-svn-id: https://svn.redmine.org/redmine/trunk@10547 e93f8b46-1217-0410-a6f0-8f06a7374b81
parent f7a1f392
...@@ -99,8 +99,9 @@ class IssuesController < ApplicationController ...@@ -99,8 +99,9 @@ class IssuesController < ApplicationController
end end
def show def show
@journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC") @journals = @issue.journals.includes(:user, :details).reorder("#{Journal.table_name}.id ASC").all
@journals.each_with_index {|j,i| j.indice = i+1} @journals.each_with_index {|j,i| j.indice = i+1}
@journals.reject!(&:private_notes?) unless User.current.allowed_to?(:view_private_notes, @issue.project)
@journals.reverse! if User.current.wants_comments_in_reverse_order? @journals.reverse! if User.current.wants_comments_in_reverse_order?
@changesets = @issue.changesets.visible.all @changesets = @issue.changesets.visible.all
...@@ -118,7 +119,10 @@ class IssuesController < ApplicationController ...@@ -118,7 +119,10 @@ class IssuesController < ApplicationController
} }
format.api format.api
format.atom { render :template => 'journals/index', :layout => false, :content_type => 'application/atom+xml' } format.atom { render :template => 'journals/index', :layout => false, :content_type => 'application/atom+xml' }
format.pdf { send_data(issue_to_pdf(@issue), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") } format.pdf {
pdf = issue_to_pdf(@issue, :journals => @journals)
send_data(pdf, :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf")
}
end end
end end
...@@ -173,6 +177,7 @@ class IssuesController < ApplicationController ...@@ -173,6 +177,7 @@ class IssuesController < ApplicationController
@conflict = true @conflict = true
if params[:last_journal_id] if params[:last_journal_id]
@conflict_journals = @issue.journals_after(params[:last_journal_id]).all @conflict_journals = @issue.journals_after(params[:last_journal_id]).all
@conflict_journals.reject!(&:private_notes?) unless User.current.allowed_to?(:view_private_notes, @issue.project)
end end
end end
...@@ -354,8 +359,7 @@ private ...@@ -354,8 +359,7 @@ private
@time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project) @time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
@time_entry.attributes = params[:time_entry] @time_entry.attributes = params[:time_entry]
@notes = params[:notes] || (params[:issue].present? ? params[:issue][:notes] : nil) @issue.init_journal(User.current)
@issue.init_journal(User.current, @notes)
issue_attributes = params[:issue] issue_attributes = params[:issue]
if issue_attributes && params[:conflict_resolution] if issue_attributes && params[:conflict_resolution]
...@@ -364,7 +368,7 @@ private ...@@ -364,7 +368,7 @@ private
issue_attributes = issue_attributes.dup issue_attributes = issue_attributes.dup
issue_attributes.delete(:lock_version) issue_attributes.delete(:lock_version)
when 'add_notes' when 'add_notes'
issue_attributes = {} issue_attributes = issue_attributes.slice(:notes)
when 'cancel' when 'cancel'
redirect_to issue_path(@issue) redirect_to issue_path(@issue)
return false return false
......
...@@ -57,10 +57,10 @@ class JournalsController < ApplicationController ...@@ -57,10 +57,10 @@ class JournalsController < ApplicationController
end end
def new def new
journal = Journal.find(params[:journal_id]) if params[:journal_id] @journal = Journal.visible.find(params[:journal_id]) if params[:journal_id]
if journal if @journal
user = journal.user user = @journal.user
text = journal.notes text = @journal.notes
else else
user = @issue.author user = @issue.author
text = @issue.description text = @issue.description
...@@ -69,6 +69,8 @@ class JournalsController < ApplicationController ...@@ -69,6 +69,8 @@ class JournalsController < ApplicationController
text = text.to_s.strip.gsub(%r{<pre>((.|\s)*?)</pre>}m, '[...]') text = text.to_s.strip.gsub(%r{<pre>((.|\s)*?)</pre>}m, '[...]')
@content = "#{ll(Setting.default_language, :text_user_wrote, user)}\n> " @content = "#{ll(Setting.default_language, :text_user_wrote, user)}\n> "
@content << text.gsub(/(\r?\n|\r\n?)/, "\n> ") + "\n\n" @content << text.gsub(/(\r?\n|\r\n?)/, "\n> ") + "\n\n"
rescue ActiveRecord::RecordNotFound
render_404
end end
def edit def edit
...@@ -95,7 +97,7 @@ class JournalsController < ApplicationController ...@@ -95,7 +97,7 @@ class JournalsController < ApplicationController
private private
def find_journal def find_journal
@journal = Journal.find(params[:id]) @journal = Journal.visible.find(params[:id])
@project = @journal.journalized.project @project = @journal.journalized.project
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
render_404 render_404
......
...@@ -28,6 +28,14 @@ class Issue < ActiveRecord::Base ...@@ -28,6 +28,14 @@ class Issue < ActiveRecord::Base
belongs_to :category, :class_name => 'IssueCategory', :foreign_key => 'category_id' belongs_to :category, :class_name => 'IssueCategory', :foreign_key => 'category_id'
has_many :journals, :as => :journalized, :dependent => :destroy has_many :journals, :as => :journalized, :dependent => :destroy
has_many :visible_journals,
:class_name => 'Journal',
:as => :journalized,
:conditions => Proc.new {
["(#{Journal.table_name}.private_notes = ? OR (#{Project.allowed_to_condition(User.current, :view_private_notes)}))", false]
},
:readonly => true
has_many :time_entries, :dependent => :delete_all has_many :time_entries, :dependent => :delete_all
has_and_belongs_to_many :changesets, :order => "#{Changeset.table_name}.committed_on ASC, #{Changeset.table_name}.id ASC" has_and_belongs_to_many :changesets, :order => "#{Changeset.table_name}.committed_on ASC, #{Changeset.table_name}.id ASC"
...@@ -39,7 +47,7 @@ class Issue < ActiveRecord::Base ...@@ -39,7 +47,7 @@ class Issue < ActiveRecord::Base
acts_as_customizable acts_as_customizable
acts_as_watchable acts_as_watchable
acts_as_searchable :columns => ['subject', "#{table_name}.description", "#{Journal.table_name}.notes"], acts_as_searchable :columns => ['subject', "#{table_name}.description", "#{Journal.table_name}.notes"],
:include => [:project, :journals], :include => [:project, :visible_journals],
# sort by id so that limited eager loading doesn't break with postgresql # sort by id so that limited eager loading doesn't break with postgresql
:order_column => "#{table_name}.id" :order_column => "#{table_name}.id"
acts_as_event :title => Proc.new {|o| "#{o.tracker.name} ##{o.id} (#{o.status}): #{o.subject}"}, acts_as_event :title => Proc.new {|o| "#{o.tracker.name} ##{o.id} (#{o.status}): #{o.subject}"},
...@@ -52,6 +60,7 @@ class Issue < ActiveRecord::Base ...@@ -52,6 +60,7 @@ class Issue < ActiveRecord::Base
DONE_RATIO_OPTIONS = %w(issue_field issue_status) DONE_RATIO_OPTIONS = %w(issue_field issue_status)
attr_reader :current_journal attr_reader :current_journal
delegate :notes, :notes=, :private_notes, :private_notes=, :to => :current_journal, :allow_nil => true
validates_presence_of :subject, :priority, :project, :tracker, :author, :status validates_presence_of :subject, :priority, :project, :tracker, :author, :status
...@@ -335,6 +344,7 @@ class Issue < ActiveRecord::Base ...@@ -335,6 +344,7 @@ class Issue < ActiveRecord::Base
'custom_field_values', 'custom_field_values',
'custom_fields', 'custom_fields',
'lock_version', 'lock_version',
'notes',
:if => lambda {|issue, user| issue.new_record? || user.allowed_to?(:edit_issues, issue.project) } :if => lambda {|issue, user| issue.new_record? || user.allowed_to?(:edit_issues, issue.project) }
safe_attributes 'status_id', safe_attributes 'status_id',
...@@ -342,8 +352,15 @@ class Issue < ActiveRecord::Base ...@@ -342,8 +352,15 @@ class Issue < ActiveRecord::Base
'fixed_version_id', 'fixed_version_id',
'done_ratio', 'done_ratio',
'lock_version', 'lock_version',
'notes',
:if => lambda {|issue, user| issue.new_statuses_allowed_to(user).any? } :if => lambda {|issue, user| issue.new_statuses_allowed_to(user).any? }
safe_attributes 'notes',
:if => lambda {|issue, user| user.allowed_to?(:add_issue_notes, issue.project)}
safe_attributes 'private_notes',
:if => lambda {|issue, user| !issue.new_record? && user.allowed_to?(:set_notes_private, issue.project)}
safe_attributes 'watcher_user_ids', safe_attributes 'watcher_user_ids',
:if => lambda {|issue, user| issue.new_record? && user.allowed_to?(:add_issue_watchers, issue.project)} :if => lambda {|issue, user| issue.new_record? && user.allowed_to?(:add_issue_watchers, issue.project)}
...@@ -715,8 +732,8 @@ class Issue < ActiveRecord::Base ...@@ -715,8 +732,8 @@ class Issue < ActiveRecord::Base
end end
end end
# Returns the mail adresses of users that should be notified # Returns the users that should be notified
def recipients def notified_users
notified = [] notified = []
# Author and assignee are always notified unless they have been # Author and assignee are always notified unless they have been
# locked or don't want to be notified # locked or don't want to be notified
...@@ -733,7 +750,12 @@ class Issue < ActiveRecord::Base ...@@ -733,7 +750,12 @@ class Issue < ActiveRecord::Base
notified.uniq! notified.uniq!
# Remove users that can not view the issue # Remove users that can not view the issue
notified.reject! {|user| !visible?(user)} notified.reject! {|user| !visible?(user)}
notified.collect(&:mail) notified
end
# Returns the email addresses that should be notified
def recipients
notified_users.collect(&:mail)
end end
# Returns the number of hours spent on this issue # Returns the number of hours spent on this issue
......
...@@ -37,10 +37,15 @@ class Journal < ActiveRecord::Base ...@@ -37,10 +37,15 @@ class Journal < ActiveRecord::Base
:conditions => "#{Journal.table_name}.journalized_type = 'Issue' AND" + :conditions => "#{Journal.table_name}.journalized_type = 'Issue' AND" +
" (#{JournalDetail.table_name}.prop_key = 'status_id' OR #{Journal.table_name}.notes <> '')"} " (#{JournalDetail.table_name}.prop_key = 'status_id' OR #{Journal.table_name}.notes <> '')"}
scope :visible, lambda {|*args| { before_create :split_private_notes
:include => {:issue => :project},
:conditions => Issue.visible_condition(args.shift || User.current, *args) scope :visible, lambda {|*args|
}} user = args.shift || User.current
includes(:issue => :project).
where(Issue.visible_condition(user, *args)).
where("(#{Journal.table_name}.private_notes = ? OR (#{Project.allowed_to_condition(user, :view_private_notes, *args)}))", false)
}
def save(*args) def save(*args)
# Do not save an empty journal # Do not save an empty journal
...@@ -75,6 +80,7 @@ class Journal < ActiveRecord::Base ...@@ -75,6 +80,7 @@ class Journal < ActiveRecord::Base
s = 'journal' s = 'journal'
s << ' has-notes' unless notes.blank? s << ' has-notes' unless notes.blank?
s << ' has-details' unless details.blank? s << ' has-details' unless details.blank?
s << ' private-notes' if private_notes?
s s
end end
...@@ -85,4 +91,33 @@ class Journal < ActiveRecord::Base ...@@ -85,4 +91,33 @@ class Journal < ActiveRecord::Base
def notify=(arg) def notify=(arg)
@notify = arg @notify = arg
end end
def recipients
notified = journalized.notified_users
if private_notes?
notified = notified.select {|user| user.allowed_to?(:view_private_notes, journalized.project)}
end
notified.map(&:mail)
end
private
def split_private_notes
if private_notes?
if notes.present?
if details.any?
# Split the journal (notes/changes) so we don't have half-private journals
journal = Journal.new(:journalized => journalized, :user => user, :notes => nil, :private_notes => false)
journal.details = details
journal.save
self.details = []
self.created_on = journal.created_on
end
else
# Blank notes should not be private
self.private_notes = false
end
end
true
end
end end
...@@ -181,7 +181,7 @@ class MailHandler < ActionMailer::Base ...@@ -181,7 +181,7 @@ class MailHandler < ActionMailer::Base
end end
# Adds a note to an existing issue # Adds a note to an existing issue
def receive_issue_reply(issue_id) def receive_issue_reply(issue_id, from_journal=nil)
issue = Issue.find_by_id(issue_id) issue = Issue.find_by_id(issue_id)
return unless issue return unless issue
# check permission # check permission
...@@ -196,6 +196,10 @@ class MailHandler < ActionMailer::Base ...@@ -196,6 +196,10 @@ class MailHandler < ActionMailer::Base
@@handler_options[:issue].clear @@handler_options[:issue].clear
journal = issue.init_journal(user) journal = issue.init_journal(user)
if from_journal && from_journal.private_notes?
# If the received email was a reply to a private note, make the added note private
issue.private_notes = true
end
issue.safe_attributes = issue_attributes_from_keywords(issue) issue.safe_attributes = issue_attributes_from_keywords(issue)
issue.safe_attributes = {'custom_field_values' => custom_field_values_from_keywords(issue)} issue.safe_attributes = {'custom_field_values' => custom_field_values_from_keywords(issue)}
journal.notes = cleaned_up_text_body journal.notes = cleaned_up_text_body
...@@ -211,7 +215,7 @@ class MailHandler < ActionMailer::Base ...@@ -211,7 +215,7 @@ class MailHandler < ActionMailer::Base
def receive_journal_reply(journal_id) def receive_journal_reply(journal_id)
journal = Journal.find_by_id(journal_id) journal = Journal.find_by_id(journal_id)
if journal && journal.journalized_type == 'Issue' if journal && journal.journalized_type == 'Issue'
receive_issue_reply(journal.journalized_id) receive_issue_reply(journal.journalized_id, journal)
end end
end end
......
...@@ -62,7 +62,7 @@ class Mailer < ActionMailer::Base ...@@ -62,7 +62,7 @@ class Mailer < ActionMailer::Base
message_id journal message_id journal
references issue references issue
@author = journal.user @author = journal.user
recipients = issue.recipients recipients = journal.recipients
# Watchers in cc # Watchers in cc
cc = issue.watcher_recipients - recipients cc = issue.watcher_recipients - recipients
s = "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] " s = "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] "
......
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
</div> </div>
<p> <p>
<label><%= radio_button_tag 'conflict_resolution', 'overwrite' %> <%= l(:text_issue_conflict_resolution_overwrite) %></label><br /> <label><%= radio_button_tag 'conflict_resolution', 'overwrite' %> <%= l(:text_issue_conflict_resolution_overwrite) %></label><br />
<% if @notes.present? %> <% if @issue.notes.present? %>
<label><%= radio_button_tag 'conflict_resolution', 'add_notes' %> <%= l(:text_issue_conflict_resolution_add_notes) %></label><br /> <label><%= radio_button_tag 'conflict_resolution', 'add_notes' %> <%= l(:text_issue_conflict_resolution_add_notes) %></label><br />
<% end %> <% end %>
<label><%= radio_button_tag 'conflict_resolution', 'cancel' %> <%= l(:text_issue_conflict_resolution_cancel, :link => link_to_issue(@issue, :subject => false)).html_safe %></label> <label><%= radio_button_tag 'conflict_resolution', 'cancel' %> <%= l(:text_issue_conflict_resolution_cancel, :link => link_to_issue(@issue, :subject => false)).html_safe %></label>
......
...@@ -27,11 +27,18 @@ ...@@ -27,11 +27,18 @@
<% end %> <% end %>
<fieldset><legend><%= l(:field_notes) %></legend> <fieldset><legend><%= l(:field_notes) %></legend>
<%= text_area_tag 'notes', @notes, :cols => 60, :rows => 10, :class => 'wiki-edit' %> <%= f.text_area :notes, :cols => 60, :rows => 10, :class => 'wiki-edit', :no_label => true %>
<%= wikitoolbar_for 'notes' %> <%= wikitoolbar_for 'issue_notes' %>
<% if @issue.safe_attribute? 'private_notes' %>
<label for="issue_private_notes"><%= f.check_box :private_notes, :no_label => true %> <%= l(:field_private_notes) %></label>
<% end %>
<%= call_hook(:view_issues_edit_notes_bottom, { :issue => @issue, :notes => @notes, :form => f }) %> <%= call_hook(:view_issues_edit_notes_bottom, { :issue => @issue, :notes => @notes, :form => f }) %>
</fieldset>
<p><%=l(:label_attachment_plural)%><br /><%= render :partial => 'attachments/form', :locals => {:container => @issue} %></p> <fieldset><legend><%= l(:label_attachment_plural) %></legend>
<p><%= render :partial => 'attachments/form', :locals => {:container => @issue} %></p>
</fieldset> </fieldset>
</div> </div>
......
...@@ -48,7 +48,7 @@ api.issue do ...@@ -48,7 +48,7 @@ api.issue do
end if include_in_api_response?('changesets') && User.current.allowed_to?(:view_changesets, @project) end if include_in_api_response?('changesets') && User.current.allowed_to?(:view_changesets, @project)
api.array :journals do api.array :journals do
@issue.journals.each do |journal| @journals.each do |journal|
api.journal :id => journal.id do api.journal :id => journal.id do
api.user(:id => journal.user_id, :name => journal.user.name) unless journal.user.nil? api.user(:id => journal.user_id, :name => journal.user.name) unless journal.user.nil?
api.notes journal.notes api.notes journal.notes
......
$('#notes').val("<%= raw escape_javascript(@content) %>"); $('#issue_notes').val("<%= raw escape_javascript(@content) %>");
<%
# when quoting a private journal, check the private checkbox
if @journal && @journal.private_notes?
%>
$('#issue_private_notes').attr('checked', true);
<% end %>
showAndScrollTo("update", "notes"); showAndScrollTo("update", "notes");
$('#notes').scrollTop = $('#notes').scrollHeight - $('#notes').clientHeight; $('#notes').scrollTop = $('#notes').scrollHeight - $('#notes').clientHeight;
...@@ -331,6 +331,7 @@ en: ...@@ -331,6 +331,7 @@ en:
field_core_fields: Standard fields field_core_fields: Standard fields
field_timeout: "Timeout (in seconds)" field_timeout: "Timeout (in seconds)"
field_board_parent: Parent forum field_board_parent: Parent forum
field_private_notes: Private notes
setting_app_title: Application title setting_app_title: Application title
setting_app_subtitle: Application subtitle setting_app_subtitle: Application subtitle
...@@ -416,6 +417,8 @@ en: ...@@ -416,6 +417,8 @@ en:
permission_add_issue_notes: Add notes permission_add_issue_notes: Add notes
permission_edit_issue_notes: Edit notes permission_edit_issue_notes: Edit notes
permission_edit_own_issue_notes: Edit own notes permission_edit_own_issue_notes: Edit own notes
permission_view_private_notes: View private notes
permission_set_notes_private: Set notes as private
permission_move_issues: Move issues permission_move_issues: Move issues
permission_delete_issues: Delete issues permission_delete_issues: Delete issues
permission_manage_public_queries: Manage public queries permission_manage_public_queries: Manage public queries
......
...@@ -330,6 +330,7 @@ fr: ...@@ -330,6 +330,7 @@ fr:
field_core_fields: Champs standards field_core_fields: Champs standards
field_timeout: "Timeout (en secondes)" field_timeout: "Timeout (en secondes)"
field_board_parent: Forum parent field_board_parent: Forum parent
field_private_notes: Notes privées
setting_app_title: Titre de l'application setting_app_title: Titre de l'application
setting_app_subtitle: Sous-titre de l'application setting_app_subtitle: Sous-titre de l'application
...@@ -411,6 +412,8 @@ fr: ...@@ -411,6 +412,8 @@ fr:
permission_add_issue_notes: Ajouter des notes permission_add_issue_notes: Ajouter des notes
permission_edit_issue_notes: Modifier les notes permission_edit_issue_notes: Modifier les notes
permission_edit_own_issue_notes: Modifier ses propres notes permission_edit_own_issue_notes: Modifier ses propres notes
permission_view_private_notes: Voir les notes privées
permission_set_notes_private: Rendre les notes privées
permission_move_issues: Déplacer les demandes permission_move_issues: Déplacer les demandes
permission_delete_issues: Supprimer les demandes permission_delete_issues: Supprimer les demandes
permission_manage_public_queries: Gérer les requêtes publiques permission_manage_public_queries: Gérer les requêtes publiques
......
class AddJournalsPrivateNotes < ActiveRecord::Migration
def up
add_column :journals, :private_notes, :boolean, :default => false, :null => false
end
def down
remove_column :journals, :private_notes
end
end
...@@ -78,6 +78,8 @@ Redmine::AccessControl.map do |map| ...@@ -78,6 +78,8 @@ Redmine::AccessControl.map do |map|
map.permission :add_issue_notes, {:issues => [:edit, :update], :journals => [:new], :attachments => :upload} map.permission :add_issue_notes, {:issues => [:edit, :update], :journals => [:new], :attachments => :upload}
map.permission :edit_issue_notes, {:journals => :edit}, :require => :loggedin map.permission :edit_issue_notes, {:journals => :edit}, :require => :loggedin
map.permission :edit_own_issue_notes, {:journals => :edit}, :require => :loggedin map.permission :edit_own_issue_notes, {:journals => :edit}, :require => :loggedin
map.permission :view_private_notes, {}, :read => true, :require => :member
map.permission :set_notes_private, {}, :require => :member
map.permission :move_issues, {:issues => [:bulk_edit, :bulk_update]}, :require => :loggedin map.permission :move_issues, {:issues => [:bulk_edit, :bulk_update]}, :require => :loggedin
map.permission :delete_issues, {:issues => :destroy}, :require => :member map.permission :delete_issues, {:issues => :destroy}, :require => :member
# Queries # Queries
......
...@@ -53,6 +53,8 @@ module Redmine ...@@ -53,6 +53,8 @@ module Redmine
:view_issues, :view_issues,
:add_issues, :add_issues,
:edit_issues, :edit_issues,
:view_private_notes,
:set_notes_private,
:manage_issue_relations, :manage_issue_relations,
:manage_subtasks, :manage_subtasks,
:add_issue_notes, :add_issue_notes,
......
...@@ -495,7 +495,7 @@ module Redmine ...@@ -495,7 +495,7 @@ module Redmine
end end
# Returns a PDF string of a single issue # Returns a PDF string of a single issue
def issue_to_pdf(issue) def issue_to_pdf(issue, assoc={})
pdf = ITCPDF.new(current_language) pdf = ITCPDF.new(current_language)
pdf.SetTitle("#{issue.project} - #{issue.tracker} ##{issue.id}") pdf.SetTitle("#{issue.project} - #{issue.tracker} ##{issue.id}")
pdf.alias_nb_pages pdf.alias_nb_pages
...@@ -642,31 +642,28 @@ module Redmine ...@@ -642,31 +642,28 @@ module Redmine
end end
end end
pdf.SetFontStyle('B',9) if assoc[:journals].present?
pdf.RDMCell(190,5, l(:label_history), "B") pdf.SetFontStyle('B',9)
pdf.Ln pdf.RDMCell(190,5, l(:label_history), "B")
indice = 0
for journal in issue.journals.find(
:all, :include => [:user, :details],
:order => "#{Journal.table_name}.created_on ASC")
indice = indice + 1
pdf.SetFontStyle('B',8)
pdf.RDMCell(190,5,
"#" + indice.to_s +
" - " + format_time(journal.created_on) +
" - " + journal.user.name)
pdf.Ln pdf.Ln
pdf.SetFontStyle('I',8) assoc[:journals].each do |journal|
details_to_strings(journal.details, true).each do |string| pdf.SetFontStyle('B',8)
pdf.RDMMultiCell(190,5, "- " + string) title = "##{journal.indice} - #{format_time(journal.created_on)} - #{journal.user}"
end title << " (#{l(:field_private_notes)})" if journal.private_notes?
if journal.notes? pdf.RDMCell(190,5, title)
pdf.Ln unless journal.details.empty? pdf.Ln
pdf.SetFontStyle('',8) pdf.SetFontStyle('I',8)
pdf.RDMwriteHTMLCell(190,5,0,0, details_to_strings(journal.details, true).each do |string|
journal.notes.to_s, issue.attachments, "") pdf.RDMMultiCell(190,5, "- " + string)
end
if journal.notes?
pdf.Ln unless journal.details.empty?
pdf.SetFontStyle('',8)
pdf.RDMwriteHTMLCell(190,5,0,0,
journal.notes.to_s, issue.attachments, "")
end
pdf.Ln
end end
pdf.Ln
end end