#218 Recycle bin

This commit is contained in:
Karel Pičman 2014-05-23 13:26:11 +02:00
parent eda5637d45
commit 3e8b141cf6
27 changed files with 558 additions and 106 deletions

View File

@ -110,6 +110,16 @@ class DmsfController < ApplicationController
@ajax_upload_size = Setting.plugin_redmine_dmsf['dmsf_max_ajax_upload_filesize'].present? ? Setting.plugin_redmine_dmsf['dmsf_max_ajax_upload_filesize'] : 100 @ajax_upload_size = Setting.plugin_redmine_dmsf['dmsf_max_ajax_upload_filesize'].present? ? Setting.plugin_redmine_dmsf['dmsf_max_ajax_upload_filesize'] : 100
end end
def trash
@folder_manipulation_allowed = User.current.allowed_to? :folder_manipulation, @project
@file_manipulation_allowed = User.current.allowed_to? :file_manipulation, @project
@file_delete_allowed = User.current.allowed_to? :file_delete, @project
@subfolders = @project.dmsf_folders.deleted
@files = @project.dmsf_files.deleted
@dir_links = @project.folder_links.deleted
@file_links = @project.file_links.deleted
end
def download_email_entries def download_email_entries
send_file( send_file(
params[:path], params[:path],
@ -319,17 +329,19 @@ class DmsfController < ApplicationController
end end
def delete def delete
@delete_folder = DmsfFolder.visible.find(params[:delete_folder_id]) if @folder.delete
if @delete_folder flash[:notice] = l(:notice_folder_deleted)
if @delete_folder.delete else
flash[:notice] = l(:notice_folder_deleted) flash[:error] = folder.errors[:base][0]
else
flash[:error] = @delete_folder.errors[:base][0]
end
end end
redirect_to dmsf_folder_path(:id => @project, :folder_id => @delete_folder.dmsf_folder_id) redirect_to dmsf_folder_path(:id => @project, :folder_id => @folder)
rescue DmsfAccessError end
render_403
def restore
if @folder.restore
flash[:notice] = l(:notice_dmsf_folder_restored)
end
redirect_to :back
end end
def edit_root def edit_root
@ -493,13 +505,13 @@ class DmsfController < ApplicationController
end end
def find_folder def find_folder
@folder = DmsfFolder.visible.find(params[:folder_id]) if params.keys.include?('folder_id') @folder = DmsfFolder.find params[:folder_id] if params[:folder_id].present?
rescue DmsfAccessError rescue DmsfAccessError
render_403 render_403
end end
def find_parent def find_parent
@parent = DmsfFolder.visible.find(params[:parent_id]) if params.keys.include?('parent_id') @parent = DmsfFolder.visible.find params[:parent_id] if params[:parent_id].present?
rescue DmsfAccessError rescue DmsfAccessError
render_403 render_403
end end

View File

@ -1,6 +1,7 @@
# Redmine plugin for Document Management System "Features" # Redmine plugin for Document Management System "Features"
# #
# Copyright (C) 2011 Vít Jonáš <vit.jonas@gmail.com> # Copyright (C) 2011 Vít Jonáš <vit.jonas@gmail.com>
# Copyright (C) 2011-14 Karel Pičman <karel.picman@kontron.com>
# #
# 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
@ -29,24 +30,16 @@ class DmsfFilesController < ApplicationController
helper :dmsf_workflows helper :dmsf_workflows
def show def show
@revision = @file.last_revision # The download is put here to provide more clear and usable links
# download is put here to provide more clear and usable links
if params.has_key?(:download) if params.has_key?(:download)
if @file.deleted if params[:download].blank?
render_404 @revision = @file.last_revision
return else
end @revision = DmsfFileRevision.find(params[:download].to_i)
if params[:download].present?
@revision = DmsfFileRevision.visible.find(params[:download].to_i)
if @revision.file != @file if @revision.file != @file
render_403 render_403
return return
end end
if @revision.deleted
render_404
return
end
end end
check_project(@revision.file) check_project(@revision.file)
begin begin
@ -58,8 +51,8 @@ class DmsfFilesController < ApplicationController
return return
end end
@revision = @file.last_revision
@file_delete_allowed = User.current.allowed_to?(:file_delete, @project) @file_delete_allowed = User.current.allowed_to?(:file_delete, @project)
@revision_pages = Paginator.new @file.revisions.visible.count, params['per_page'] ? params['per_page'].to_i : 25, params['page'] @revision_pages = Paginator.new @file.revisions.visible.count, params['per_page'] ? params['per_page'].to_i : 25, params['page']
render :layout => !request.xhr? render :layout => !request.xhr?
@ -139,20 +132,19 @@ class DmsfFilesController < ApplicationController
end end
def delete def delete
unless User.current.allowed_to?(:file_delete, @project)
render _403
return
end
if @file if @file
if @file.delete commit = params[:commit] == 'yes'
if @file.delete(commit)
flash[:notice] = l(:notice_file_deleted) flash[:notice] = l(:notice_file_deleted)
log_activity('deleted') if commit
begin log_activity('deleted')
DmsfMailer.get_notify_users(User.current, [@file]).each do |u| begin
DmsfMailer.files_deleted(u, @project, [@file]).deliver DmsfMailer.get_notify_users(User.current, [@file]).each do |u|
DmsfMailer.files_deleted(u, @project, [@file]).deliver
end
rescue Exception => e
Rails.logger.error "Could not send email notifications: #{e.message}"
end end
rescue Exception => e
Rails.logger.error "Could not send email notifications: #{e.message}"
end end
else else
@file.errors.each do |e, msg| @file.errors.each do |e, msg|
@ -160,16 +152,16 @@ class DmsfFilesController < ApplicationController
end end
end end
end end
redirect_to dmsf_folder_path(:id => @project, :folder_id => @file.folder) if commit
redirect_to :back
else
redirect_to dmsf_folder_path(:id => @project, :folder_id => @file.folder)
end
end end
def delete_revision def delete_revision
unless User.current.allowed_to?(:file_delete, @project) if @revision # && !@revision.deleted
render _403 if @revision.delete(true)
return
end
if @revision && !@revision.deleted
if @revision.delete
flash[:notice] = l(:notice_revision_deleted) flash[:notice] = l(:notice_revision_deleted)
log_activity('deleted') log_activity('deleted')
else else
@ -225,6 +217,14 @@ class DmsfFilesController < ApplicationController
redirect_to :back redirect_to :back
end end
def restore
if @file.restore
log_activity('restored')
flash[:notice] = l(:notice_dmsf_file_restored)
end
redirect_to :back
end
private private
def log_activity(action) def log_activity(action)
@ -243,7 +243,7 @@ class DmsfFilesController < ApplicationController
end end
def find_file def find_file
@file = DmsfFile.visible.find(params[:id]) @file = DmsfFile.find params[:id]
@project = @file.project @project = @file.project
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
render_404 render_404

View File

@ -145,11 +145,16 @@ class DmsfLinksController < ApplicationController
end end
def destroy def destroy
begin if @dmsf_link.delete
@dmsf_link.destroy
flash[:notice] = l(:notice_successful_delete) flash[:notice] = l(:notice_successful_delete)
rescue end
flash[:error] = l(:error_unable_delete_dmsf_workflow)
redirect_to :back
end
def restore
if @dmsf_link.restore
flash[:notice] = l(:notice_dmsf_link_restored)
end end
redirect_to :back redirect_to :back

View File

@ -33,6 +33,7 @@ class DmsfFile < ActiveRecord::Base
belongs_to :project belongs_to :project
belongs_to :folder, :class_name => 'DmsfFolder', :foreign_key => 'dmsf_folder_id' belongs_to :folder, :class_name => 'DmsfFolder', :foreign_key => 'dmsf_folder_id'
belongs_to :deleted_by_user, :class_name => 'User', :foreign_key => 'deleted_by_user_id' belongs_to :deleted_by_user, :class_name => 'User', :foreign_key => 'deleted_by_user_id'
has_many :revisions, :class_name => 'DmsfFileRevision', :foreign_key => 'dmsf_file_id', has_many :revisions, :class_name => 'DmsfFileRevision', :foreign_key => 'dmsf_file_id',
:order => "#{DmsfFileRevision.table_name}.major_version DESC, #{DmsfFileRevision.table_name}.minor_version DESC, #{DmsfFileRevision.table_name}.updated_at DESC", :order => "#{DmsfFileRevision.table_name}.major_version DESC, #{DmsfFileRevision.table_name}.minor_version DESC, #{DmsfFileRevision.table_name}.updated_at DESC",
:dependent => :destroy :dependent => :destroy
@ -43,7 +44,8 @@ class DmsfFile < ActiveRecord::Base
has_many :referenced_links, :class_name => 'DmsfLink', :foreign_key => 'target_id', has_many :referenced_links, :class_name => 'DmsfLink', :foreign_key => 'target_id',
:conditions => {:target_type => DmsfFile.model_name}, :dependent => :destroy :conditions => {:target_type => DmsfFile.model_name}, :dependent => :destroy
scope :visible, lambda {|*args| where(DmsfFile.visible_condition(args.shift || User.current, *args)).readonly(false)} scope :visible, where('NOT deleted')
scope :deleted, where('NOT NOT deleted')
validates :name, :presence => true validates :name, :presence => true
validates_format_of :name, :with => DmsfFolder.invalid_characters, validates_format_of :name, :with => DmsfFolder.invalid_characters,
@ -51,11 +53,6 @@ class DmsfFile < ActiveRecord::Base
validate :validates_name_uniqueness validate :validates_name_uniqueness
def self.visible_condition(user, options = {})
query = DmsfFile.where(:deleted => false)
query.where_values.map {|v| v.respond_to?(:to_sql) ? v.to_sql : v.to_s}.join(' AND ')
end
def validates_name_uniqueness def validates_name_uniqueness
existing_file = DmsfFile.visible.find_file_by_name(self.project, self.folder, self.name) existing_file = DmsfFile.visible.find_file_by_name(self.project, self.folder, self.name)
errors.add(:name, l('activerecord.errors.messages.taken')) unless errors.add(:name, l('activerecord.errors.messages.taken')) unless
@ -104,19 +101,19 @@ class DmsfFile < ActiveRecord::Base
@last_revision = new_revision @last_revision = new_revision
end end
def delete def delete(commit)
if locked_for_user? if locked_for_user?
Rails.logger.info l(:error_file_is_locked) Rails.logger.info l(:error_file_is_locked)
errors[:base] << l(:error_file_is_locked) errors[:base] << l(:error_file_is_locked)
return false return false
end end
begin begin
if Setting.plugin_redmine_dmsf['dmsf_really_delete_files'] # Revisions and links of a deleted file SHOULD be deleted too
self.revisions.visible.each {|r| r.delete(true)} self.revisions.each { |r| r.delete(commit, true) }
self.referenced_links.each { |l| l.delete(commit) }
if commit
self.destroy self.destroy
else else
# Revisions of a deleted file SHOULD be deleted too
self.revisions.visible.each {|r| r.delete }
self.deleted = true self.deleted = true
self.deleted_by_user = User.current self.deleted_by_user = User.current
save save
@ -127,6 +124,14 @@ class DmsfFile < ActiveRecord::Base
end end
end end
def restore
self.revisions.each { |r| r.restore }
self.referenced_links.each { |l| l.restore }
self.deleted = false
self.deleted_by_user = nil
save
end
def title def title
self.last_revision ? self.last_revision.title : self.name self.last_revision ? self.last_revision.title : self.name
end end

View File

@ -1,6 +1,7 @@
# Redmine plugin for Document Management System "Features" # Redmine plugin for Document Management System "Features"
# #
# Copyright (C) 2011 Vít Jonáš <vit.jonas@gmail.com> # Copyright (C) 2011 Vít Jonáš <vit.jonas@gmail.com>
# Copyright (C) 2011-14 Karel Pičman <karel.picman@kontron.com>
# #
# 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
@ -28,16 +29,14 @@ class DmsfFileRevision < ActiveRecord::Base
has_many :dmsf_workflow_step_assignment, :dependent => :destroy has_many :dmsf_workflow_step_assignment, :dependent => :destroy
# Returns a list of revisions that are not deleted here, or deleted at parent level either # Returns a list of revisions that are not deleted here, or deleted at parent level either
scope :visible, lambda {|*args| joins(:file).where(DmsfFile.visible_condition(args.shift || User.current, *args)).where("#{self.table_name}.deleted = :false", :false => false ).readonly(false) } scope :visible, where('NOT deleted')
acts_as_customizable acts_as_customizable
acts_as_event :title => Proc.new {|o| "#{l(:label_dmsf_updated)}: #{o.file.dmsf_path_str}"}, acts_as_event :title => Proc.new {|o| "#{l(:label_dmsf_updated)}: #{o.file.dmsf_path_str}"},
:url => Proc.new {|o| {:controller => 'dmsf_files', :action => 'show', :id => o.file}}, :url => Proc.new {|o| {:controller => 'dmsf_files', :action => 'show', :id => o.file}},
:datetime => Proc.new {|o| o.updated_at }, :datetime => Proc.new {|o| o.updated_at },
:description => Proc.new {|o| o.comment }, :description => Proc.new {|o| o.comment },
:author => Proc.new {|o| o.user } :author => Proc.new {|o| o.user }
acts_as_activity_provider :type => 'dmsf_files', acts_as_activity_provider :type => 'dmsf_files',
:timestamp => "#{DmsfFileRevision.table_name}.updated_at", :timestamp => "#{DmsfFileRevision.table_name}.updated_at",
:author_key => "#{DmsfFileRevision.table_name}.user_id", :author_key => "#{DmsfFileRevision.table_name}.user_id",
@ -61,23 +60,21 @@ class DmsfFileRevision < ActiveRecord::Base
remove_extension(filename).gsub(/_+/, ' '); remove_extension(filename).gsub(/_+/, ' ');
end end
def delete(delete_all = false) def delete(commit = false, force = true)
if self.file.locked_for_user? if self.file.locked_for_user?
errors[:base] << l(:error_file_is_locked) errors[:base] << l(:error_file_is_locked)
return false return false
end end
if !delete_all && self.file.revisions.length <= 1 if !commit && (!force && (self.file.revisions.length <= 1))
errors[:base] << l(:error_at_least_one_revision_must_be_present) errors[:base] << l(:error_at_least_one_revision_must_be_present)
return false return false
end end
dependent = DmsfFileRevision.where(:source_dmsf_file_revision_id => self.id, :deleted => false).all dependent = DmsfFileRevision.where(:source_dmsf_file_revision_id => self.id).all
dependent.each do |d| dependent.each do |d|
d.source_revision = self.source_revision d.source_revision = self.source_revision
d.save! d.save!
end end
if Setting.plugin_redmine_dmsf['dmsf_really_delete_files'] if commit
dependencies = DmsfFileRevision.where(:disk_filename => self.disk_filename).all.count
File.delete(self.disk_file) if dependencies <= 1 && File.exist?(self.disk_file)
self.destroy self.destroy
else else
self.deleted = true self.deleted = true
@ -86,6 +83,12 @@ class DmsfFileRevision < ActiveRecord::Base
end end
end end
def restore
self.deleted = false
self.deleted_by_user = nil
save
end
def destroy def destroy
if Setting.plugin_redmine_dmsf['dmsf_really_delete_files'] if Setting.plugin_redmine_dmsf['dmsf_really_delete_files']
dependencies = DmsfFileRevision.where(:disk_filename => self.disk_filename).all.count dependencies = DmsfFileRevision.where(:disk_filename => self.disk_filename).all.count

View File

@ -27,11 +27,13 @@ class DmsfFolder < ActiveRecord::Base
belongs_to :project belongs_to :project
belongs_to :folder, :class_name => 'DmsfFolder', :foreign_key => 'dmsf_folder_id' belongs_to :folder, :class_name => 'DmsfFolder', :foreign_key => 'dmsf_folder_id'
belongs_to :deleted_by_user, :class_name => 'User', :foreign_key => 'deleted_by_user_id'
belongs_to :user
has_many :subfolders, :class_name => 'DmsfFolder', :foreign_key => 'dmsf_folder_id', has_many :subfolders, :class_name => 'DmsfFolder', :foreign_key => 'dmsf_folder_id',
:dependent => :destroy :dependent => :destroy
has_many :files, :class_name => 'DmsfFile', :foreign_key => 'dmsf_folder_id', has_many :files, :class_name => 'DmsfFile', :foreign_key => 'dmsf_folder_id',
:dependent => :destroy :dependent => :destroy
belongs_to :user
has_many :folder_links, :class_name => 'DmsfLink', :foreign_key => 'dmsf_folder_id', has_many :folder_links, :class_name => 'DmsfLink', :foreign_key => 'dmsf_folder_id',
:conditions => {:target_type => DmsfFolder.model_name}, :dependent => :destroy :conditions => {:target_type => DmsfFolder.model_name}, :dependent => :destroy
has_many :file_links, :class_name => 'DmsfLink', :foreign_key => 'dmsf_folder_id', has_many :file_links, :class_name => 'DmsfLink', :foreign_key => 'dmsf_folder_id',
@ -43,7 +45,8 @@ class DmsfFolder < ActiveRecord::Base
:conditions => {:entity_type => 1}, :conditions => {:entity_type => 1},
:dependent => :destroy :dependent => :destroy
scope :visible, lambda {|*args| {:conditions => '' }} #For future use, however best to be referenced now scope :visible, where('NOT deleted')
scope :deleted, where('NOT NOT deleted')
acts_as_customizable acts_as_customizable
@ -90,7 +93,15 @@ class DmsfFolder < ActiveRecord::Base
errors[:base] << l(:error_folder_is_not_empty) errors[:base] << l(:error_folder_is_not_empty)
return false return false
end end
destroy self.deleted = true
self.deleted_by_user = User.current
self.save
end
def restore
self.deleted = false
self.deleted_by_user = nil
self.save
end end
def dmsf_path def dmsf_path
@ -271,4 +282,3 @@ class DmsfFolder < ActiveRecord::Base
end end
end end

View File

@ -21,10 +21,14 @@ class DmsfLink < ActiveRecord::Base
belongs_to :project belongs_to :project
belongs_to :dmsf_folder belongs_to :dmsf_folder
belongs_to :deleted_by_user, :class_name => 'User', :foreign_key => 'deleted_by_user_id'
validates :name, :presence => true validates :name, :presence => true
validates :target_id, :presence => true validates :target_id, :presence => true
validates_length_of :name, :maximum => 255 validates_length_of :name, :maximum => 255
scope :visible, where('NOT deleted') scope :visible, where('NOT deleted')
scope :deleted, where('NOT NOT deleted')
def target_folder_id def target_folder_id
if self.target_type == DmsfFolder.model_name if self.target_type == DmsfFolder.model_name
@ -97,4 +101,20 @@ class DmsfLink < ActiveRecord::Base
link link
end end
def delete(commit = false)
if commit
self.destroy
else
self.deleted = true
self.deleted_by_user = User.current
save
end
end
def restore
self.deleted = false
self.deleted_by_user = nil
save
end
end end

View File

@ -1,7 +1,7 @@
<%#= <%#=
# Redmine plugin for Document Management System "Features" # Redmine plugin for Document Management System "Features"
# #
# Copyright (C) 2014 Karel Pičman <karel.picman@kontron.com> # Copyright (C) 2011-14 Karel Pičman <karel.picman@kontron.com>
# #
# 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
@ -85,7 +85,7 @@
:class => 'icon icon-del') %> :class => 'icon icon-del') %>
<% else %> <% else %>
<%= link_to('', <%= link_to('',
delete_dmsf_path(:id => project, :folder_id => @folder, :delete_folder_id => subfolder), delete_dmsf_path(:id => project, :folder_id => subfolder),
:data => {:confirm => l(:text_are_you_sure)}, :data => {:confirm => l(:text_are_you_sure)},
:title => l(:title_delete), :title => l(:title_delete),
:class => 'icon icon-del') %> :class => 'icon icon-del') %>

View File

@ -0,0 +1,58 @@
<%#=
# Redmine plugin for Document Management System "Features"
#
# Copyright (C) 2011-14 Karel Pičman <karel.picman@kontron.com>
#
# 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.
%>
<td class="check"><%= check_box_tag(name, id, false,
:title => l(:title_check_for_zip_download_or_email)) %></td>
<td class="title">
<%= content_tag(:span, h(title),
:title => h(title),
:class => 'icon icon-folder') %>
<% if link %>
<div class="filename" title="<%= l(:label_target_folder)%>"><%= link.path %></div>
<% else %>
<div class="filename" title="<%= l(:title_number_of_files_in_directory)%>">[<%= subfolder.files.visible.count + subfolder.file_links.visible.count %>]</div>
<% end %>
</td>
<td class="size"></td>
<td class="modified">
<%= format_time(subfolder.updated_at) %>
</td>
<td class="version"></td>
<td class="workflow"></td>
<td class="author"><%= h(subfolder.user) %></td>
<td class="actions">
<% if @folder_manipulation_allowed %>
<div class="right_icon_box">
<%= link_to('',
restore_dmsf_path(:id => project, :folder_id => subfolder),
:title => l(:title_restore),
:class => 'icon icon-dmsf-restore') %>
<%= link_to('',
delete_dmsf_path(:id => project, :folder_id => subfolder, :commit => 'yes'),
:data => {:confirm => l(:text_are_you_sure)},
:title => l(:title_delete),
:class => 'icon icon-dmsf-rev-delete') %>
</div>
<% end %>
</td>
<td class="hidden">0</td>
<td class="hidden">0</td>
<td class="hidden"><%= subfolder.updated_at.to_i %></td>
<td class="hidden">0</td>

View File

@ -0,0 +1,60 @@
<%#=
# Redmine plugin for Document Management System "Features"
#
# Copyright (C) 2011-14 Karel Pičman <karel.picman@kontron.com>
#
# 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.
%>
<td class="check"><%= check_box_tag(name, id, false,
:title => l(:title_check_for_zip_download_or_email)) %></td>
<td class="title">
<% file_download_url = url_for({:only_path => false, :controller => :dmsf_files, :action => 'show', :id => file, :download => ''}) %>
<%= link_to(h(title),
file_download_url,
:class => "icon icon-file #{DmsfHelper.filetype_css(file.name)}",
:title => l(:title_title_version_version_download, :title => h(file.title), :version => file.version),
'data-downloadurl' => "#{file.last_revision.detect_content_type}:#{h(file.name)}:#{file_download_url}") %>
<div class="filename" title="<%= l(:title_filename_for_download)%>"><%= h(link ? link.path : file.display_name) %></div>
</td>
<td class="size"><%= number_to_human_size(file.last_revision.size) %></td>
<td class="modified">
<%= format_time(file.last_revision.updated_at) %>
</td>
<td class="version"><%= file.last_revision.version %></td>
<td class="workflow">
<%= file.last_revision.workflow_str(false) %>
</td>
<td class="author"><%= h(file.last_revision.user) %></td>
<td class="actions">
<% if @file_manipulation_allowed %>
<div class="right_icon_box">
<%= link_to('',
restore_dmsf_file_path(:id => file),
:title => l(:title_restore),
:class => 'icon icon-dmsf-restore') %>
<%= link_to('',
dmsf_file_path(:id => file, :commit => 'yes'),
:data => {:confirm => l(:text_are_you_sure)},
:method => :delete,
:title => l(:title_delete),
:class => 'icon icon-dmsf-rev-delete') %>
</div>
<% end %>
</td>
<td class="hidden">1</td>
<td class="hidden"><%= file.last_revision.size %></td>
<td class="hidden"><%= file.last_revision.updated_at.to_i %></td>
<td class="hidden"><%= file.last_revision.iversion %></td>

View File

@ -89,6 +89,10 @@
:title => l(:link_create_folder), :title => l(:link_create_folder),
:class => 'icon icon-add') unless @locked_for_user %> :class => 'icon icon-add') unless @locked_for_user %>
<% end %> <% end %>
<%= link_to(l(:link_trash_bin),
trash_dmsf_path(@project),
:title => l(:link_trash_bin),
:class => 'icon icon-del') unless @locked_for_user unless @folder %>
</div> </div>
<%= render(:partial => 'path', :locals => {:folder => @folder, :filename => nil}) %> <%= render(:partial => 'path', :locals => {:folder => @folder, :filename => nil}) %>

View File

@ -0,0 +1,185 @@
<%#=
# Redmine plugin for Document Management System "Features"
#
# Copyright (C) 2011-14 Karel Pičman <karel.picman@kontron.com>
#
# 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.
%>
<% html_title(l(:dmsf)) %>
<div class="contextual">
</div>
<h2><%= l(:link_trash_bin) %></h2>
<div class="dmsf-header">
<div class="wiki">
<%= textilizable(@folder ? @folder.description : @project.dmsf_description) %>
</div>
</div>
<%= error_messages_for('dmsf_workflow') %>
<%= form_tag({:action => :entries_operation, :id => @project, :folder_id => @folder}, :method => :post,
:class => 'dmfs_entries', :id => 'entries_form') do %>
<%= hidden_field_tag('action') %>
<div class="controls" style="float: left">
<%= submit_tag(l(:submit_download), :title => l(:title_download_checked), :name => 'download_entries') %>
<% if @file_manipulation_allowed && @folder_manipulation_allowed && !@locked_for_user %>
<button type="button" id="entries_delete_button" title="<%= l(:title_delete_checked) %>"><%= l(:button_delete) %></button>
<% end %>
</div>
<table class="display entries" id="browser">
<thead>
<tr id="root">
<th class="check">
<input id="check_all_entries" title="<%= l(:title_check_uncheck_all_for_zip_download_or_email) %>" type="checkbox" />
</th>
<th><%= l(:link_title) %></th>
<th><%= l(:link_size) %></th>
<th><%= l(:link_modified) %></th>
<th title="<%= l(:label_version) %>"><%= l(:link_ver) %></th>
<th><%= l(:link_workflow) %></th>
<th><%= l(:link_author) %></th>
<th></th>
<th class="hidden"></th>
<th class="hidden"></th>
<th class="hidden"></th>
<th class="hidden"></th>
</tr>
</thead>
<tbody>
<% @subfolders.each do |subfolder| %>
<tr class="dir">
<%= render(:partial => 'dir_trash',
:locals => {
:project => @project,
:subfolder => subfolder,
:link => nil,
:id => subfolder.id,
:name => 'subfolders[]',
:title => subfolder.title }) %>
</tr>
<% end %>
<% @dir_links.each do |link| %>
<tr class="gray">
<%= render(:partial => 'dir_trash',
:locals => {
:project => link.target_project,
:subfolder => link.target_folder,
:link => link,
:id => link.id,
:name => 'dir_links[]',
:title => link.name }) %>
</tr>
<% end %>
<% @files.each do |file| %>
<% unless file.last_revision %>
<% Rails.logger.error "Error: dmsf_file id #{file.id} has no revision!" %>
<% next %>
<% end %>
<tr class="file">
<%= render(:partial => 'file_trash', :locals => {
:project => @project,
:file => file,
:link => nil,
:id => file.id,
:name => 'files[]',
:title => file.title }) %>
</tr>
<% end %>
<% @file_links.each do |link| %>
<% unless link.target_file.last_revision %>
<% Rails.logger.error "Error: dmsf_file id #{link.target_id} has no revision!" %>
<% next %>
<% end %>
<tr class="gray">
<%= render(:partial => 'file_trash', :locals => {
:project => link.target_project,
:file => link.target_file,
:link => link,
:id => link.id,
:name => 'file_links[]',
:title => link.name }) %>
</tr>
<% end %>
</tbody>
</table>
<% end %>
<script type="text/javascript">
$('#entries_delete_button').click(function() {
if(window.confirm("<%= l(:text_are_you_sure) %>")) {
$('#entries_form').attr('action', "<%= delete_entries_path(:id => @project, :folder_id => @folder) %>");
$('#entries_form').submit();
}
});
$('.list_cf').change(function() {
$('#entries_form').attr('action', "<%= tag_changed_path(:id => @project, :folder_id => @folder) %>");
$('#entries_form').submit();
});
$('#check_all_entries').click(function() {
$('input[type=checkbox]', $('#browser > tbody')).prop('checked', this.checked);
});
</script>
<%
if I18n.locale && !I18n.locale.to_s.match(/^en.*/)
sUrl = "jquery.dataTables/#{I18n.locale.to_s.downcase}.json"
else
sUrl = 'jquery.dataTables/en.json'
end
%>
<% content_for :header_tags do %>
<%= stylesheet_link_tag 'jquery.dataTables/jquery-ui.dataTables.css', :plugin => 'redmine_dmsf' %>
<%= javascript_include_tag 'jquery-1.6.1.min.js', :plugin => 'redmine_dmsf' %>
<%= javascript_include_tag 'jquery-ui-1.8.13.min.js', :plugin => 'redmine_dmsf' %>
<%= javascript_include_tag 'jquery.dataTables/jquery.dataTables.min.js', :plugin => 'redmine_dmsf' %>
<script type="text/javascript">
$(document).ready(function() {
$('#browser').dataTable({
'bJQueryUI': true,
'oLanguage': {
'sUrl': "<%= plugin_asset_path(:redmine_dmsf, 'javascripts', sUrl) %>"
},
'bAutoWidth': false,
'bPaginate': false,
'aaSorting': [[1,'asc']],
'aaSortingFixed': [[8,'asc']],
'aoColumnDefs': [
{ 'bSearchable': false, 'aTargets': [0, 7, 8, 9] },
{ 'bSortable': false, 'aTargets': [0, 7, 8] },
{ 'iDataSort': 9, 'aTargets': [ 2 ] },
{ 'iDataSort': 10, 'aTargets': [ 3 ] },
{ 'iDataSort': 11, 'aTargets': [ 4 ] }
],
'fnInitComplete': function() {
jQuery('div.controls').prependTo(jQuery('#browser_wrapper div.fg-toolbar')[0]);
},
'fnInfoCallback': function( oSettings, iStart, iEnd, iMax, iTotal, sPre ) {
return "<%= l(:label_number_of_folders)%>: <%= @subfolders.count + @dir_links.count %>, <%= l(:label_number_of_documents)%>: <%= @files.count + @file_links.count %>";
}
});
$('#entries_form').submit(function () {
$(this).removeAttr('data-submitted');
});
});
</script>
<% end %>

BIN
assets/images/restore.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 806 B

View File

@ -292,6 +292,7 @@ table.list td.step {
.icon-dmsf-rev-download { background-image: url(../images/rev_download.png); } .icon-dmsf-rev-download { background-image: url(../images/rev_download.png); }
.icon-dmsf-rev-downloads { background-image: url(../images/rev_downloads.png); } .icon-dmsf-rev-downloads { background-image: url(../images/rev_downloads.png); }
.icon-dmsf-rev-delete { background-image: url(../images/rev_delete.png); } .icon-dmsf-rev-delete { background-image: url(../images/rev_delete.png); }
.icon-dmsf-restore { background-image: url(../images/restore.png); }
.icon-edit-gray { background-image: url(../images/edit_gray.png); } .icon-edit-gray { background-image: url(../images/edit_gray.png); }
.icon-notification-on-gray { background-image: url(../images/notify_gray.png); } .icon-notification-on-gray { background-image: url(../images/notify_gray.png); }

View File

@ -298,6 +298,12 @@ cs:
note_display_notified_recipients: Uživatel bude informován o příjemcích právě odeslané emailové notifikace. note_display_notified_recipients: Uživatel bude informován o příjemcích právě odeslané emailové notifikace.
warning_email_notifications: "Notifikační email poslán na uživatele %{to}" warning_email_notifications: "Notifikační email poslán na uživatele %{to}"
link_trash_bin: Koš
title_restore: Obnovit
notice_dmsf_file_restored: Document byl úspěšně obnoven
notice_dmsf_folder_restored: Adresář byl úspěšně obnoven
notice_dmsf_link_restored: Odkaz byl úspěšně obnoven
my: my:
blocks: blocks:
locked_documents: Zamčené dokumenty locked_documents: Zamčené dokumenty

View File

@ -298,6 +298,12 @@ de:
note_display_notified_recipients: The user will be informed about all recipients of just sent the email notification. note_display_notified_recipients: The user will be informed about all recipients of just sent the email notification.
warning_email_notifications: "Email notifications sent to %{to}" warning_email_notifications: "Email notifications sent to %{to}"
link_trash_bin: Trash bin
title_restore: Restore
notice_dmsf_file_restored: The document has been successfully restored
notice_dmsf_folder_restored: The folder has been successfully restored
notice_dmsf_link_restored: The link has been successfully restored
my: my:
blocks: blocks:
locked_documents: Locked documents locked_documents: Locked documents

View File

@ -298,6 +298,12 @@ en:
note_display_notified_recipients: The user will be informed about all recipients of just sent the email notification. note_display_notified_recipients: The user will be informed about all recipients of just sent the email notification.
warning_email_notifications: "Email notifications sent to %{to}" warning_email_notifications: "Email notifications sent to %{to}"
link_trash_bin: Trash bin
title_restore: Restore
notice_dmsf_file_restored: The document has been successfully restored
notice_dmsf_folder_restored: The folder has been successfully restored
notice_dmsf_link_restored: The link has been successfully restored
my: my:
blocks: blocks:
locked_documents: Locked documents locked_documents: Locked documents

View File

@ -298,6 +298,12 @@ es:
note_display_notified_recipients: The user will be informed about all recipients of just sent the email notification. note_display_notified_recipients: The user will be informed about all recipients of just sent the email notification.
warning_email_notifications: "Email notifications sent to %{to}" warning_email_notifications: "Email notifications sent to %{to}"
link_trash_bin: Trash bin
title_restore: Restore
notice_dmsf_file_restored: The document has been successfully restored
notice_dmsf_folder_restored: The folder has been successfully restored
notice_dmsf_link_restored: The link has been successfully restored
my: my:
blocks: blocks:
locked_documents: Locked documents locked_documents: Locked documents

View File

@ -298,6 +298,12 @@ fr:
note_display_notified_recipients: "L'utilisateur sera informé de tous les destinataires à qui un email de notifcation a été envoyé" note_display_notified_recipients: "L'utilisateur sera informé de tous les destinataires à qui un email de notifcation a été envoyé"
warning_email_notifications: "Email de notification envoyé à %{to}" warning_email_notifications: "Email de notification envoyé à %{to}"
link_trash_bin: Trash bin
title_restore: Restore
notice_dmsf_file_restored: The document has been successfully restored
notice_dmsf_folder_restored: The folder has been successfully restored
notice_dmsf_link_restored: The link has been successfully restored
my: my:
blocks: blocks:
locked_documents: Documents verrouillés locked_documents: Documents verrouillés

View File

@ -298,6 +298,12 @@ ja:
note_display_notified_recipients: The user will be informed about all recipients of just sent the email notification. note_display_notified_recipients: The user will be informed about all recipients of just sent the email notification.
warning_email_notifications: "Email notifications sent to %{to}" warning_email_notifications: "Email notifications sent to %{to}"
link_trash_bin: Trash bin
title_restore: Restore
notice_dmsf_file_restored: The document has been successfully restored
notice_dmsf_folder_restored: The folder has been successfully restored
notice_dmsf_link_restored: The link has been successfully restored
my: my:
blocks: blocks:
locked_documents: Locked documents locked_documents: Locked documents

View File

@ -298,6 +298,12 @@ ru:
note_display_notified_recipients: The user will be informed about all recipients of just sent the email notification. note_display_notified_recipients: The user will be informed about all recipients of just sent the email notification.
warning_email_notifications: "Email notifications sent to %{to}" warning_email_notifications: "Email notifications sent to %{to}"
link_trash_bin: Trash bin
title_restore: Restore
notice_dmsf_file_restored: The document has been successfully restored
notice_dmsf_folder_restored: The folder has been successfully restored
notice_dmsf_link_restored: The link has been successfully restored
my: my:
blocks: blocks:
locked_documents: Locked documents locked_documents: Locked documents

View File

@ -298,6 +298,12 @@ sl:
note_display_notified_recipients: The user will be informed about all recipients of just sent the email notification. note_display_notified_recipients: The user will be informed about all recipients of just sent the email notification.
warning_email_notifications: "Email notifications sent to %{to}" warning_email_notifications: "Email notifications sent to %{to}"
link_trash_bin: Trash bin
title_restore: Restore
notice_dmsf_file_restored: The document has been successfully restored
notice_dmsf_folder_restored: The folder has been successfully restored
notice_dmsf_link_restored: The link has been successfully restored
my: my:
blocks: blocks:
locked_documents: Locked documents locked_documents: Locked documents

View File

@ -298,6 +298,12 @@ zh:
note_display_notified_recipients: The user will be informed about all recipients of just sent the email notification. note_display_notified_recipients: The user will be informed about all recipients of just sent the email notification.
warning_email_notifications: "Email notifications sent to %{to}" warning_email_notifications: "Email notifications sent to %{to}"
link_trash_bin: Trash bin
title_restore: Restore
notice_dmsf_file_restored: The document has been successfully restored
notice_dmsf_folder_restored: The folder has been successfully restored
notice_dmsf_link_restored: The link has been successfully restored
my: my:
blocks: blocks:
locked_documents: Locked documents locked_documents: Locked documents

View File

@ -42,6 +42,8 @@ RedmineApp::Application.routes.draw do
get '/projects/:id/dmsf/new', :controller => 'dmsf', :action => 'new', :as => 'new_dmsf' get '/projects/:id/dmsf/new', :controller => 'dmsf', :action => 'new', :as => 'new_dmsf'
get '/projects/:id/dmsf/edit', :controller=> 'dmsf', :action => 'edit', :as => 'edit_dmsf' get '/projects/:id/dmsf/edit', :controller=> 'dmsf', :action => 'edit', :as => 'edit_dmsf'
get '/projects/:id/dmsf/edit/root', :controller=> 'dmsf', :action => 'edit_root', :as => 'edit_root_dmsf' get '/projects/:id/dmsf/edit/root', :controller=> 'dmsf', :action => 'edit_root', :as => 'edit_root_dmsf'
get '/projects/:id/dmsf/trash', :controller => 'dmsf', :action => 'trash', :as => 'trash_dmsf'
get '/projects/:id/dmsf/restore', :controller => 'dmsf', :action => 'restore', :as => 'restore_dmsf'
# #
# dmsf_state controller # dmsf_state controller
@ -49,7 +51,6 @@ RedmineApp::Application.routes.draw do
## ##
post '/projects/:id/dmsf/state', :controller => 'dmsf_state', :action => 'user_pref_save' post '/projects/:id/dmsf/state', :controller => 'dmsf_state', :action => 'user_pref_save'
# #
# dmsf_upload controller # dmsf_upload controller
# /projects/<project>/dmsf/upload - dmsf_upload controller # /projects/<project>/dmsf/upload - dmsf_upload controller
@ -74,6 +75,7 @@ RedmineApp::Application.routes.draw do
get '/dmsf/files/:id/download/:download', :controller => 'dmsf_files', :action => 'show', :as => 'download_revision' get '/dmsf/files/:id/download/:download', :controller => 'dmsf_files', :action => 'show', :as => 'download_revision'
get '/dmsf/files/:id', :controller => 'dmsf_files', :action => 'show', :as => 'dmsf_file' get '/dmsf/files/:id', :controller => 'dmsf_files', :action => 'show', :as => 'dmsf_file'
delete '/dmsf/files/:id', :controller => 'dmsf_files', :action => 'delete' delete '/dmsf/files/:id', :controller => 'dmsf_files', :action => 'delete'
get '/dmsf/files/:id/restore', :controller => 'dmsf_files', :action => 'restore', :as => 'restore_dmsf_file'
# Just to keep backward compatibility with old external direct links # Just to keep backward compatibility with old external direct links
get '/dmsf_files/:id', :controller => 'dmsf_files', :action => 'show' get '/dmsf_files/:id', :controller => 'dmsf_files', :action => 'show'
@ -124,6 +126,7 @@ RedmineApp::Application.routes.draw do
# Links # Links
resources :dmsf_links do resources :dmsf_links do
member do member do
get 'restore'
end end
end end

View File

@ -0,0 +1,32 @@
# Redmine plugin for Document Management System "Features"
#
# Copyright (C) 2011-14 Karel Pičman <karel.picman@kontron.com>
#
# 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 TrashBin < ActiveRecord::Migration
def up
# DMSF - project's root folder notification
add_column :dmsf_folders, :deleted, :boolean, :default => false, :null => false
add_column :dmsf_folders, :deleted_by_user_id, :integer
DmsfFolder.connection.execute('UPDATE dmsf_folders SET deleted = 0')
end
def down
remove_column :dmsf_folders, :deleted
remove_column :dmsf_folders, :deleted_by_user_id
end
end

View File

@ -64,14 +64,14 @@ Redmine::Plugin.register :redmine_dmsf do
:dmsf_workflows => [:log]}, :dmsf_workflows => [:log]},
:read => true :read => true
permission :folder_manipulation, permission :folder_manipulation,
{:dmsf => [:new, :create, :delete, :edit, :save, :edit_root, :save_root, :lock, :unlock, :notify_activate, :notify_deactivate, :delete_entries]} {:dmsf => [:new, :create, :delete, :edit, :save, :edit_root, :save_root, :lock, :unlock, :notify_activate, :notify_deactivate, :delete_entries, :restore]}
permission :file_manipulation, permission :file_manipulation,
{:dmsf_files => [:create_revision, :lock, :unlock, :delete_revision, :notify_activate, :notify_deactivate], {:dmsf_files => [:create_revision, :lock, :unlock, :delete_revision, :notify_activate, :notify_deactivate, :restore],
:dmsf_upload => [:upload_files, :upload_file, :commit_files], :dmsf_upload => [:upload_files, :upload_file, :commit_files],
:dmsf_workflows => [:action, :new_action, :autocomplete_for_user, :start, :assign, :assignment], :dmsf_workflows => [:action, :new_action, :autocomplete_for_user, :start, :assign, :assignment],
:dmsf_links => [:new, :create, :destroy] :dmsf_links => [:new, :create, :destroy]
} }
permission :file_delete, {:dmsf_files => [:delete]} permission :file_delete, { :dmsf => [:trash], :dmsf_files => [:delete]}
permission :manage_workflows, permission :manage_workflows,
{:dmsf_workflows => [:index, :new, :create, :destroy, :show, :add_step, :remove_step, :reorder_steps, :update]} {:dmsf_workflows => [:index, :new, :create, :destroy, :show, :add_step, :remove_step, :reorder_steps, :update]}
permission :force_file_unlock, {} permission :force_file_unlock, {}

View File

@ -292,7 +292,7 @@ class DmsfLinksControllerTest < RedmineDmsf::Test::TestCase
end end
def test_destroy def test_destroy
assert_difference 'DmsfLink.count', -1 do assert_difference 'DmsfLink.visible.count', -1 do
delete :destroy, :project_id => @project1.id, :id => @file_link.id delete :destroy, :project_id => @project1.id, :id => @file_link.id
end end
assert_redirected_to dmsf_folder_path(:id => @project1.id, :folder_id => @folder1.id) assert_redirected_to dmsf_folder_path(:id => @project1.id, :folder_id => @folder1.id)