git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@5466 e93f8b46-1217-0410-a6f0-8f06a7374b81tags/1.2.0
def render_issue_subject_with_tree(issue) | def render_issue_subject_with_tree(issue) | ||||
s = '' | s = '' | ||||
ancestors = issue.root? ? [] : issue.ancestors.all | |||||
ancestors = issue.root? ? [] : issue.ancestors.visible.all | |||||
ancestors.each do |ancestor| | ancestors.each do |ancestor| | ||||
s << '<div>' + content_tag('p', link_to_issue(ancestor)) | s << '<div>' + content_tag('p', link_to_issue(ancestor)) | ||||
end | end | ||||
s << '<div>' + content_tag('h3', h(issue.subject)) | |||||
s << '<div>' | |||||
subject = h(issue.subject) | |||||
if issue.is_private? | |||||
subject = content_tag('span', l(:field_is_private), :class => 'private') + ' ' + subject | |||||
end | |||||
s << content_tag('h3', subject) | |||||
s << '</div>' * (ancestors.size + 1) | s << '</div>' * (ancestors.size + 1) | ||||
s | s | ||||
end | end | ||||
def render_descendants_tree(issue) | def render_descendants_tree(issue) | ||||
s = '<form><table class="list issues">' | s = '<form><table class="list issues">' | ||||
issue_list(issue.descendants.sort_by(&:lft)) do |child, level| | |||||
issue_list(issue.descendants.visible.sort_by(&:lft)) do |child, level| | |||||
s << content_tag('tr', | s << content_tag('tr', | ||||
content_tag('td', check_box_tag("ids[]", child.id, false, :id => nil), :class => 'checkbox') + | content_tag('td', check_box_tag("ids[]", child.id, false, :id => nil), :class => 'checkbox') + | ||||
content_tag('td', link_to_issue(child, :truncate => 60), :class => 'subject') + | content_tag('td', link_to_issue(child, :truncate => 60), :class => 'subject') + | ||||
label = l(:field_parent_issue) | label = l(:field_parent_issue) | ||||
value = "##{detail.value}" unless detail.value.blank? | value = "##{detail.value}" unless detail.value.blank? | ||||
old_value = "##{detail.old_value}" unless detail.old_value.blank? | old_value = "##{detail.old_value}" unless detail.old_value.blank? | ||||
when detail.prop_key == 'is_private' | |||||
value = l(detail.value == "0" ? :general_text_No : :general_text_Yes) unless detail.value.blank? | |||||
old_value = l(detail.old_value == "0" ? :general_text_No : :general_text_Yes) unless detail.old_value.blank? | |||||
end | end | ||||
when 'cf' | when 'cf' | ||||
custom_field = CustomField.find_by_id(detail.prop_key) | custom_field = CustomField.find_by_id(detail.prop_key) |
def self.visible_condition(user, options={}) | def self.visible_condition(user, options={}) | ||||
Project.allowed_to_condition(user, :view_issues, options) do |role, user| | Project.allowed_to_condition(user, :view_issues, options) do |role, user| | ||||
case role.issues_visibility | case role.issues_visibility | ||||
when 'default' | |||||
when 'all' | |||||
nil | nil | ||||
when 'default' | |||||
"(#{table_name}.is_private = #{connection.quoted_false} OR #{table_name}.author_id = #{user.id} OR #{table_name}.assigned_to_id = #{user.id})" | |||||
when 'own' | when 'own' | ||||
"(#{table_name}.author_id = #{user.id} OR #{table_name}.assigned_to_id = #{user.id})" | "(#{table_name}.author_id = #{user.id} OR #{table_name}.assigned_to_id = #{user.id})" | ||||
else | else | ||||
def visible?(usr=nil) | def visible?(usr=nil) | ||||
(usr || User.current).allowed_to?(:view_issues, self.project) do |role, user| | (usr || User.current).allowed_to?(:view_issues, self.project) do |role, user| | ||||
case role.issues_visibility | case role.issues_visibility | ||||
when 'default' | |||||
when 'all' | |||||
true | true | ||||
when 'default' | |||||
!self.is_private? || self.author == user || self.assigned_to == user | |||||
when 'own' | when 'own' | ||||
self.author == user || self.assigned_to == user | self.author == user || self.assigned_to == user | ||||
else | else | ||||
'done_ratio', | 'done_ratio', | ||||
:if => lambda {|issue, user| issue.new_statuses_allowed_to(user).any? } | :if => lambda {|issue, user| issue.new_statuses_allowed_to(user).any? } | ||||
safe_attributes 'is_private', | |||||
:if => lambda {|issue, user| | |||||
user.allowed_to?(:set_issues_private, issue.project) || | |||||
(issue.author == user && user.allowed_to?(:set_own_issues_private, issue.project)) | |||||
} | |||||
# Safely sets attributes | # Safely sets attributes | ||||
# Should be called from controllers instead of #attributes= | # Should be called from controllers instead of #attributes= | ||||
# attr_accessible is too rough because we still want things like | # attr_accessible is too rough because we still want things like | ||||
s << ' overdue' if overdue? | s << ' overdue' if overdue? | ||||
s << ' child' if child? | s << ' child' if child? | ||||
s << ' parent' unless leaf? | s << ' parent' unless leaf? | ||||
s << ' private' if is_private? | |||||
s << ' created-by-me' if User.current.logged? && author_id == User.current.id | s << ' created-by-me' if User.current.logged? && author_id == User.current.id | ||||
s << ' assigned-to-me' if User.current.logged? && assigned_to_id == User.current.id | s << ' assigned-to-me' if User.current.logged? && assigned_to_id == User.current.id | ||||
s | s |
class JournalDetail < ActiveRecord::Base | class JournalDetail < ActiveRecord::Base | ||||
belongs_to :journal | belongs_to :journal | ||||
before_save :normalize_values | |||||
private | |||||
def normalize_values | |||||
self.value = normalize(value) | |||||
self.old_value = normalize(old_value) | |||||
end | |||||
def normalize(v) | |||||
if v == true | |||||
"1" | |||||
elsif v == false | |||||
"0" | |||||
else | |||||
v | |||||
end | |||||
end | |||||
end | end |
BUILTIN_ANONYMOUS = 2 | BUILTIN_ANONYMOUS = 2 | ||||
ISSUES_VISIBILITY_OPTIONS = [ | ISSUES_VISIBILITY_OPTIONS = [ | ||||
['default', :label_issues_visibility_all], | |||||
['all', :label_issues_visibility_all], | |||||
['default', :label_issues_visibility_public], | |||||
['own', :label_issues_visibility_own] | ['own', :label_issues_visibility_own] | ||||
] | ] | ||||
<%= call_hook(:view_issues_form_details_top, { :issue => @issue, :form => f }) %> | <%= call_hook(:view_issues_form_details_top, { :issue => @issue, :form => f }) %> | ||||
<div id="issue_descr_fields" <%= 'style="display:none"' unless @issue.new_record? || @issue.errors.any? %>> | <div id="issue_descr_fields" <%= 'style="display:none"' unless @issue.new_record? || @issue.errors.any? %>> | ||||
<% if @issue.safe_attribute_names.include?('is_private') %> | |||||
<p style="float:right; margin-right:1em;"> | |||||
<label class="inline" for="issue_is_private"><%= f.check_box :is_private, :no_label => true %> <%= l(:field_is_private) %></label> | |||||
</p> | |||||
<% end %> | |||||
<p><%= f.select :tracker_id, @project.trackers.collect {|t| [t.name, t.id]}, :required => true %></p> | <p><%= f.select :tracker_id, @project.trackers.collect {|t| [t.name, t.id]}, :required => true %></p> | ||||
<%= observe_field :issue_tracker_id, :url => { :action => :new, :project_id => @project, :id => @issue }, | <%= observe_field :issue_tracker_id, :url => { :action => :new, :project_id => @project, :id => @issue }, | ||||
:update => :attributes, | :update => :attributes, |
field_visible: Visible | field_visible: Visible | ||||
field_warn_on_leaving_unsaved: "Warn me when leaving a page with unsaved text" | field_warn_on_leaving_unsaved: "Warn me when leaving a page with unsaved text" | ||||
field_issues_visibility: Issues visibility | field_issues_visibility: Issues visibility | ||||
field_is_private: Private | |||||
setting_app_title: Application title | setting_app_title: Application title | ||||
setting_app_subtitle: Application subtitle | setting_app_subtitle: Application subtitle | ||||
permission_add_issues: Add issues | permission_add_issues: Add issues | ||||
permission_edit_issues: Edit issues | permission_edit_issues: Edit issues | ||||
permission_manage_issue_relations: Manage issue relations | permission_manage_issue_relations: Manage issue relations | ||||
permission_set_issues_private: Set issues public or private | |||||
permission_set_own_issues_private: Set own issues public or private | |||||
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 | ||||
label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author | label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author | ||||
label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee | label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee | ||||
label_issues_visibility_all: All issues | label_issues_visibility_all: All issues | ||||
label_issues_visibility_public: All non private issues | |||||
label_issues_visibility_own: Issues created by or assigned to the user | label_issues_visibility_own: Issues created by or assigned to the user | ||||
button_login: Login | button_login: Login |
field_visible: Visible | field_visible: Visible | ||||
field_warn_on_leaving_unsaved: "M'avertir lorsque je quitte une page contenant du texte non sauvegardé" | field_warn_on_leaving_unsaved: "M'avertir lorsque je quitte une page contenant du texte non sauvegardé" | ||||
field_issues_visibility: Visibilité des demandes | field_issues_visibility: Visibilité des demandes | ||||
field_is_private: Privée | |||||
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 | ||||
permission_add_issues: Créer des demandes | permission_add_issues: Créer des demandes | ||||
permission_edit_issues: Modifier les demandes | permission_edit_issues: Modifier les demandes | ||||
permission_manage_issue_relations: Gérer les relations | permission_manage_issue_relations: Gérer les relations | ||||
permission_set_issues_private: Rendre les demandes publiques ou privées | |||||
permission_set_own_issues_private: Rendre ses propres demandes publiques ou privées | |||||
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 | ||||
label_additional_workflow_transitions_for_author: Autorisations supplémentaires lorsque l'utilisateur a créé la demande | label_additional_workflow_transitions_for_author: Autorisations supplémentaires lorsque l'utilisateur a créé la demande | ||||
label_additional_workflow_transitions_for_assignee: Autorisations supplémentaires lorsque la demande est assignée à l'utilisateur | label_additional_workflow_transitions_for_assignee: Autorisations supplémentaires lorsque la demande est assignée à l'utilisateur | ||||
label_issues_visibility_all: Toutes les demandes | label_issues_visibility_all: Toutes les demandes | ||||
label_issues_visibility_public: Toutes les demandes non privées | |||||
label_issues_visibility_own: Demandes créées par ou assignées à l'utilisateur | label_issues_visibility_own: Demandes créées par ou assignées à l'utilisateur | ||||
button_login: Connexion | button_login: Connexion |
class AddIssuesIsPrivate < ActiveRecord::Migration | |||||
def self.up | |||||
add_column :issues, :is_private, :boolean, :default => false, :null => false | |||||
end | |||||
def self.down | |||||
remove_column :issues, :is_private | |||||
end | |||||
end |
map.permission :edit_issues, {:issues => [:edit, :update, :bulk_edit, :bulk_update, :update_form], :journals => [:new]} | map.permission :edit_issues, {:issues => [:edit, :update, :bulk_edit, :bulk_update, :update_form], :journals => [:new]} | ||||
map.permission :manage_issue_relations, {:issue_relations => [:new, :destroy]} | map.permission :manage_issue_relations, {:issue_relations => [:new, :destroy]} | ||||
map.permission :manage_subtasks, {} | map.permission :manage_subtasks, {} | ||||
map.permission :set_issues_private, {} | |||||
map.permission :set_own_issues_private, {} | |||||
map.permission :add_issue_notes, {:issues => [:edit, :update], :journals => [:new]} | map.permission :add_issue_notes, {:issues => [:edit, :update], :journals => [:new]} | ||||
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 |
# redMine - project management software | |||||
# Copyright (C) 2006-2007 Jean-Philippe Lang | |||||
# Redmine - project management software | |||||
# Copyright (C) 2006-2011 Jean-Philippe Lang | |||||
# | # | ||||
# This program is free software; you can redistribute it and/or | # This program is free software; you can redistribute it and/or | ||||
# modify it under the terms of the GNU General Public License | # modify it under the terms of the GNU General Public License | ||||
Role.transaction do | Role.transaction do | ||||
# Roles | # Roles | ||||
manager = Role.create! :name => l(:default_role_manager), | manager = Role.create! :name => l(:default_role_manager), | ||||
:issues_visibility => 'all', | |||||
:position => 1 | :position => 1 | ||||
manager.permissions = manager.setable_permissions.collect {|p| p.name} | manager.permissions = manager.setable_permissions.collect {|p| p.name} | ||||
manager.save! | manager.save! |
div.issue div.subject p {margin: 0; margin-bottom: 0.1em; font-size: 90%; color: #999;} | div.issue div.subject p {margin: 0; margin-bottom: 0.1em; font-size: 90%; color: #999;} | ||||
div.issue div.subject>div>p { margin-top: 0.5em; } | div.issue div.subject>div>p { margin-top: 0.5em; } | ||||
div.issue div.subject h3 {margin: 0; margin-bottom: 0.1em;} | div.issue div.subject h3 {margin: 0; margin-bottom: 0.1em;} | ||||
div.issue span.private { position:relative; bottom: 2px; text-transform: uppercase; background: #d22; color: #fff; font-weight:bold; padding: 0px 2px 0px 2px; font-size: 60%; margin-right: 2px; border-radius: 2px; -moz-border-radius: 2px;} | |||||
#issue_tree table.issues, #relations table.issues { border: 0; } | #issue_tree table.issues, #relations table.issues { border: 0; } | ||||
#issue_tree td.checkbox, #relations td.checkbox {display:none;} | #issue_tree td.checkbox, #relations td.checkbox {display:none;} |
filename: changeset_utf8.diff | filename: changeset_utf8.diff | ||||
author_id: 2 | author_id: 2 | ||||
content_type: text/x-diff | content_type: text/x-diff | ||||
attachments_015: | |||||
id: 15 | |||||
created_on: 2010-07-19 21:07:27 +02:00 | |||||
container_type: Issue | |||||
container_id: 14 | |||||
downloads: 0 | |||||
disk_filename: 060719210727_changeset_utf8.diff | |||||
digest: b91e08d0cf966d5c6ff411bd8c4cc3a2 | |||||
filesize: 687 | |||||
filename: private.diff | |||||
author_id: 2 | |||||
content_type: text/x-diff | |||||
description: attachement of a private issue |
root_id: 13 | root_id: 13 | ||||
lft: 1 | lft: 1 | ||||
rgt: 2 | rgt: 2 | ||||
issues_014: | |||||
id: 14 | |||||
created_on: <%= 15.days.ago.to_date.to_s(:db) %> | |||||
project_id: 3 | |||||
updated_on: <%= 15.days.ago.to_date.to_s(:db) %> | |||||
priority_id: 5 | |||||
subject: Private issue on public project | |||||
fixed_version_id: | |||||
category_id: | |||||
description: This is a private issue | |||||
tracker_id: 1 | |||||
assigned_to_id: | |||||
author_id: 2 | |||||
status_id: 1 | |||||
is_private: true | |||||
root_id: 14 | |||||
lft: 1 | |||||
rgt: 2 |
name: Manager | name: Manager | ||||
id: 1 | id: 1 | ||||
builtin: 0 | builtin: 0 | ||||
issues_visibility: default | |||||
issues_visibility: all | |||||
permissions: | | permissions: | | ||||
--- | --- | ||||
- :add_project | - :add_project |
assert_equal 'application/octet-stream', @response.content_type | assert_equal 'application/octet-stream', @response.content_type | ||||
end | end | ||||
def test_show_file_from_private_issue_without_permission | |||||
get :show, :id => 15 | |||||
assert_redirected_to '/login?back_url=http%3A%2F%2Ftest.host%2Fattachments%2F15' | |||||
end | |||||
def test_show_file_from_private_issue_with_permission | |||||
@request.session[:user_id] = 2 | |||||
get :show, :id => 15 | |||||
assert_response :success | |||||
assert_tag 'h2', :content => /private.diff/ | |||||
end | |||||
def test_download_text_file | def test_download_text_file | ||||
get :download, :id => 4 | get :download, :id => 4 | ||||
assert_response :success | assert_response :success |
assert_no_tag :tag => 'a', :content => /Can't print recipes/ | assert_no_tag :tag => 'a', :content => /Can't print recipes/ | ||||
assert_tag :tag => 'a', :content => /Subproject issue/ | assert_tag :tag => 'a', :content => /Subproject issue/ | ||||
end | end | ||||
def test_index_should_list_visible_issues_only | |||||
get :index, :per_page => 100 | |||||
assert_response :success | |||||
assert_not_nil assigns(:issues) | |||||
assert_nil assigns(:issues).detect {|issue| !issue.visible?} | |||||
end | |||||
def test_index_with_project | def test_index_with_project | ||||
Setting.display_subprojects_issues = 0 | Setting.display_subprojects_issues = 0 | ||||
assert_response :redirect | assert_response :redirect | ||||
end | end | ||||
def test_show_should_deny_anonymous_access_to_private_issue | |||||
Issue.update_all(["is_private = ?", true], "id = 1") | |||||
get :show, :id => 1 | |||||
assert_response :redirect | |||||
end | |||||
def test_show_should_deny_non_member_access_without_permission | def test_show_should_deny_non_member_access_without_permission | ||||
Role.non_member.remove_permission!(:view_issues) | Role.non_member.remove_permission!(:view_issues) | ||||
@request.session[:user_id] = 9 | @request.session[:user_id] = 9 | ||||
assert_response 403 | assert_response 403 | ||||
end | end | ||||
def test_show_should_deny_non_member_access_to_private_issue | |||||
Issue.update_all(["is_private = ?", true], "id = 1") | |||||
@request.session[:user_id] = 9 | |||||
get :show, :id => 1 | |||||
assert_response 403 | |||||
end | |||||
def test_show_should_deny_member_access_without_permission | def test_show_should_deny_member_access_without_permission | ||||
Role.find(1).remove_permission!(:view_issues) | Role.find(1).remove_permission!(:view_issues) | ||||
@request.session[:user_id] = 2 | @request.session[:user_id] = 2 | ||||
assert_response 403 | assert_response 403 | ||||
end | end | ||||
def test_show_should_deny_member_access_to_private_issue_without_permission | |||||
Issue.update_all(["is_private = ?", true], "id = 1") | |||||
@request.session[:user_id] = 3 | |||||
get :show, :id => 1 | |||||
assert_response 403 | |||||
end | |||||
def test_show_should_allow_author_access_to_private_issue | |||||
Issue.update_all(["is_private = ?, author_id = 3", true], "id = 1") | |||||
@request.session[:user_id] = 3 | |||||
get :show, :id => 1 | |||||
assert_response :success | |||||
end | |||||
def test_show_should_allow_assignee_access_to_private_issue | |||||
Issue.update_all(["is_private = ?, assigned_to_id = 3", true], "id = 1") | |||||
@request.session[:user_id] = 3 | |||||
get :show, :id => 1 | |||||
assert_response :success | |||||
end | |||||
def test_show_should_allow_member_access_to_private_issue_with_permission | |||||
Issue.update_all(["is_private = ?", true], "id = 1") | |||||
User.find(3).roles_for_project(Project.find(1)).first.update_attribute :issues_visibility, 'all' | |||||
@request.session[:user_id] = 3 | |||||
get :show, :id => 1 | |||||
assert_response :success | |||||
end | |||||
def test_show_should_not_disclose_relations_to_invisible_issues | def test_show_should_not_disclose_relations_to_invisible_issues | ||||
Setting.cross_project_issue_relations = '1' | Setting.cross_project_issue_relations = '1' | ||||
IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(2), :relation_type => 'relates') | IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(2), :relation_type => 'relates') |
issues = Issue.visible(User.anonymous).all | issues = Issue.visible(User.anonymous).all | ||||
assert issues.any? | assert issues.any? | ||||
assert_nil issues.detect {|issue| !issue.project.is_public?} | assert_nil issues.detect {|issue| !issue.project.is_public?} | ||||
assert_nil issues.detect {|issue| issue.is_private?} | |||||
assert_visibility_match User.anonymous, issues | assert_visibility_match User.anonymous, issues | ||||
end | end | ||||
issues = Issue.visible(user).all | issues = Issue.visible(user).all | ||||
assert issues.any? | assert issues.any? | ||||
assert_nil issues.detect {|issue| !issue.project.is_public?} | assert_nil issues.detect {|issue| !issue.project.is_public?} | ||||
assert_nil issues.detect {|issue| issue.is_private?} | |||||
assert_visibility_match user, issues | assert_visibility_match user, issues | ||||
end | end | ||||
user = User.find(9) | user = User.find(9) | ||||
# User should see issues of projects for which he has view_issues permissions only | # User should see issues of projects for which he has view_issues permissions only | ||||
Role.non_member.remove_permission!(:view_issues) | Role.non_member.remove_permission!(:view_issues) | ||||
Member.create!(:principal => user, :project_id => 2, :role_ids => [1]) | |||||
Member.create!(:principal => user, :project_id => 3, :role_ids => [2]) | |||||
issues = Issue.visible(user).all | issues = Issue.visible(user).all | ||||
assert issues.any? | assert issues.any? | ||||
assert_nil issues.detect {|issue| issue.project_id != 2} | |||||
assert_nil issues.detect {|issue| issue.project_id != 3} | |||||
assert_nil issues.detect {|issue| issue.is_private?} | |||||
assert_visibility_match user, issues | assert_visibility_match user, issues | ||||
end | end | ||||
assert issues.any? | assert issues.any? | ||||
# Admin should see issues on private projects that he does not belong to | # Admin should see issues on private projects that he does not belong to | ||||
assert issues.detect {|issue| !issue.project.is_public?} | assert issues.detect {|issue| !issue.project.is_public?} | ||||
# Admin should see private issues of other users | |||||
assert issues.detect {|issue| issue.is_private? && issue.author != user} | |||||
assert_visibility_match user, issues | assert_visibility_match user, issues | ||||
end | end | ||||
# Redmine - project management software | # Redmine - project management software | ||||
# Copyright (C) 2006-2008 Jean-Philippe Lang | |||||
# Copyright (C) 2006-2011 Jean-Philippe Lang | |||||
# | # | ||||
# This program is free software; you can redistribute it and/or | # This program is free software; you can redistribute it and/or | ||||
# modify it under the terms of the GNU General Public License | # modify it under the terms of the GNU General Public License | ||||
end | end | ||||
def attachments_visible?(user=User.current) | def attachments_visible?(user=User.current) | ||||
user.allowed_to?(self.class.attachable_options[:view_permission], self.project) | |||||
(respond_to?(:visible?) ? visible?(user) : true) && | |||||
user.allowed_to?(self.class.attachable_options[:view_permission], self.project) | |||||
end | end | ||||
def attachments_deletable?(user=User.current) | def attachments_deletable?(user=User.current) | ||||
user.allowed_to?(self.class.attachable_options[:delete_permission], self.project) | |||||
(respond_to?(:visible?) ? visible?(user) : true) && | |||||
user.allowed_to?(self.class.attachable_options[:delete_permission], self.project) | |||||
end | end | ||||
def initialize_unsaved_attachments | def initialize_unsaved_attachments |