From 3e8b141cf6bb2f40dd8124eb1e19da5396cf4182 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Pi=C4=8Dman?= Date: Fri, 23 May 2014 13:26:11 +0200 Subject: [PATCH] #218 Recycle bin --- app/controllers/dmsf_controller.rb | 38 ++-- app/controllers/dmsf_files_controller.rb | 82 ++++---- app/controllers/dmsf_links_controller.rb | 17 +- app/models/dmsf_file.rb | 31 +-- app/models/dmsf_file_revision.rb | 33 ++-- app/models/dmsf_folder.rb | 26 ++- app/models/dmsf_link.rb | 20 ++ app/views/dmsf/_dir.html.erb | 6 +- app/views/dmsf/_dir_trash.html.erb | 58 ++++++ app/views/dmsf/_file_trash.html.erb | 60 ++++++ app/views/dmsf/show.html.erb | 6 +- app/views/dmsf/trash.html.erb | 185 ++++++++++++++++++ assets/images/restore.png | Bin 0 -> 806 bytes assets/stylesheets/dmsf.css | 1 + config/locales/cs.yml | 6 + config/locales/de.yml | 6 + config/locales/en.yml | 6 + config/locales/es.yml | 6 + config/locales/fr.yml | 6 + config/locales/ja.yml | 6 + config/locales/ru.yml | 6 + config/locales/sl.yml | 6 + config/locales/zh.yml | 6 + config/routes.rb | 7 +- db/migrate/20140519133201_trash_bin.rb | 32 +++ init.rb | 6 +- test/functional/dmsf_links_controller_test.rb | 2 +- 27 files changed, 558 insertions(+), 106 deletions(-) create mode 100644 app/views/dmsf/_dir_trash.html.erb create mode 100644 app/views/dmsf/_file_trash.html.erb create mode 100644 app/views/dmsf/trash.html.erb create mode 100644 assets/images/restore.png create mode 100644 db/migrate/20140519133201_trash_bin.rb diff --git a/app/controllers/dmsf_controller.rb b/app/controllers/dmsf_controller.rb index 424c4562..556b1b57 100644 --- a/app/controllers/dmsf_controller.rb +++ b/app/controllers/dmsf_controller.rb @@ -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 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 send_file( params[:path], @@ -318,18 +328,20 @@ class DmsfController < ApplicationController end end - def delete - @delete_folder = DmsfFolder.visible.find(params[:delete_folder_id]) - if @delete_folder - if @delete_folder.delete - flash[:notice] = l(:notice_folder_deleted) - else - flash[:error] = @delete_folder.errors[:base][0] - end + def delete + if @folder.delete + flash[:notice] = l(:notice_folder_deleted) + else + flash[:error] = folder.errors[:base][0] end - redirect_to dmsf_folder_path(:id => @project, :folder_id => @delete_folder.dmsf_folder_id) - rescue DmsfAccessError - render_403 + redirect_to dmsf_folder_path(:id => @project, :folder_id => @folder) + end + + def restore + if @folder.restore + flash[:notice] = l(:notice_dmsf_folder_restored) + end + redirect_to :back end def edit_root @@ -493,13 +505,13 @@ class DmsfController < ApplicationController end 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 render_403 end 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 render_403 end diff --git a/app/controllers/dmsf_files_controller.rb b/app/controllers/dmsf_files_controller.rb index b3066160..93605686 100644 --- a/app/controllers/dmsf_files_controller.rb +++ b/app/controllers/dmsf_files_controller.rb @@ -1,6 +1,7 @@ # Redmine plugin for Document Management System "Features" # # Copyright (C) 2011 Vít Jonáš +# Copyright (C) 2011-14 Karel Pičman # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -29,24 +30,16 @@ class DmsfFilesController < ApplicationController helper :dmsf_workflows def show - @revision = @file.last_revision - - # download is put here to provide more clear and usable links + # The download is put here to provide more clear and usable links if params.has_key?(:download) - if @file.deleted - render_404 - return - end - if params[:download].present? - @revision = DmsfFileRevision.visible.find(params[:download].to_i) + if params[:download].blank? + @revision = @file.last_revision + else + @revision = DmsfFileRevision.find(params[:download].to_i) if @revision.file != @file render_403 return - end - if @revision.deleted - render_404 - return - end + end end check_project(@revision.file) begin @@ -56,10 +49,10 @@ class DmsfFilesController < ApplicationController render_404 end return - end - - @file_delete_allowed = User.current.allowed_to?(:file_delete, @project) + end + @revision = @file.last_revision + @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'] render :layout => !request.xhr? @@ -138,38 +131,37 @@ class DmsfFilesController < ApplicationController redirect_to :back end - def delete - unless User.current.allowed_to?(:file_delete, @project) - render _403 - return - end + def delete if @file - if @file.delete + commit = params[:commit] == 'yes' + if @file.delete(commit) flash[:notice] = l(:notice_file_deleted) - log_activity('deleted') - begin - DmsfMailer.get_notify_users(User.current, [@file]).each do |u| - DmsfMailer.files_deleted(u, @project, [@file]).deliver + if commit + log_activity('deleted') + begin + 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 - rescue Exception => e - Rails.logger.error "Could not send email notifications: #{e.message}" end else @file.errors.each do |e, msg| flash[:error] = msg 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 - def delete_revision - unless User.current.allowed_to?(:file_delete, @project) - render _403 - return - end - if @revision && !@revision.deleted - if @revision.delete + def delete_revision + if @revision # && !@revision.deleted + if @revision.delete(true) flash[:notice] = l(:notice_revision_deleted) log_activity('deleted') else @@ -224,6 +216,14 @@ class DmsfFilesController < ApplicationController end redirect_to :back end + + def restore + if @file.restore + log_activity('restored') + flash[:notice] = l(:notice_dmsf_file_restored) + end + redirect_to :back + end private @@ -242,8 +242,8 @@ class DmsfFilesController < ApplicationController :disposition => 'attachment') end - def find_file - @file = DmsfFile.visible.find(params[:id]) + def find_file + @file = DmsfFile.find params[:id] @project = @file.project rescue ActiveRecord::RecordNotFound render_404 @@ -263,4 +263,4 @@ class DmsfFilesController < ApplicationController end end -end +end \ No newline at end of file diff --git a/app/controllers/dmsf_links_controller.rb b/app/controllers/dmsf_links_controller.rb index 92beba27..ff449c6c 100644 --- a/app/controllers/dmsf_links_controller.rb +++ b/app/controllers/dmsf_links_controller.rb @@ -144,17 +144,22 @@ class DmsfLinksController < ApplicationController end end - def destroy - begin - @dmsf_link.destroy + def destroy + if @dmsf_link.delete flash[:notice] = l(:notice_successful_delete) - rescue - flash[:error] = l(:error_unable_delete_dmsf_workflow) end redirect_to :back end - + + def restore + if @dmsf_link.restore + flash[:notice] = l(:notice_dmsf_link_restored) + end + + redirect_to :back + end + private def find_link_project diff --git a/app/models/dmsf_file.rb b/app/models/dmsf_file.rb index df0b3953..b27b9329 100644 --- a/app/models/dmsf_file.rb +++ b/app/models/dmsf_file.rb @@ -33,6 +33,7 @@ class DmsfFile < ActiveRecord::Base belongs_to :project 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' + 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", :dependent => :destroy @@ -42,8 +43,9 @@ class DmsfFile < ActiveRecord::Base :dependent => :destroy has_many :referenced_links, :class_name => 'DmsfLink', :foreign_key => 'target_id', :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_format_of :name, :with => DmsfFolder.invalid_characters, @@ -51,11 +53,6 @@ class DmsfFile < ActiveRecord::Base 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 existing_file = DmsfFile.visible.find_file_by_name(self.project, self.folder, self.name) errors.add(:name, l('activerecord.errors.messages.taken')) unless @@ -104,19 +101,19 @@ class DmsfFile < ActiveRecord::Base @last_revision = new_revision end - def delete + def delete(commit) if locked_for_user? Rails.logger.info l(:error_file_is_locked) errors[:base] << l(:error_file_is_locked) return false end begin - if Setting.plugin_redmine_dmsf['dmsf_really_delete_files'] - self.revisions.visible.each {|r| r.delete(true)} + # Revisions and links of a deleted file SHOULD be deleted too + self.revisions.each { |r| r.delete(commit, true) } + self.referenced_links.each { |l| l.delete(commit) } + if commit self.destroy else - # Revisions of a deleted file SHOULD be deleted too - self.revisions.visible.each {|r| r.delete } self.deleted = true self.deleted_by_user = User.current save @@ -127,6 +124,14 @@ class DmsfFile < ActiveRecord::Base 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 self.last_revision ? self.last_revision.title : self.name end @@ -383,4 +388,4 @@ class DmsfFile < ActiveRecord::Base self.name end -end +end \ No newline at end of file diff --git a/app/models/dmsf_file_revision.rb b/app/models/dmsf_file_revision.rb index 3ab0f8ea..a22392cd 100644 --- a/app/models/dmsf_file_revision.rb +++ b/app/models/dmsf_file_revision.rb @@ -1,6 +1,7 @@ # Redmine plugin for Document Management System "Features" # -# Copyright (C) 2011 Vít Jonáš +# Copyright (C) 2011 Vít Jonáš +# Copyright (C) 2011-14 Karel Pičman # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -27,17 +28,15 @@ class DmsfFileRevision < ActiveRecord::Base has_many :access, :class_name => 'DmsfFileRevisionAccess', :foreign_key => 'dmsf_file_revision_id', :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 - 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) } + # Returns a list of revisions that are not deleted here, or deleted at parent level either + scope :visible, where('NOT deleted') acts_as_customizable - 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}}, :datetime => Proc.new {|o| o.updated_at }, :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', :timestamp => "#{DmsfFileRevision.table_name}.updated_at", :author_key => "#{DmsfFileRevision.table_name}.user_id", @@ -60,24 +59,22 @@ class DmsfFileRevision < ActiveRecord::Base def self.filename_to_title(filename) remove_extension(filename).gsub(/_+/, ' '); end - - def delete(delete_all = false) + + def delete(commit = false, force = true) if self.file.locked_for_user? errors[:base] << l(:error_file_is_locked) return false - end - if !delete_all && self.file.revisions.length <= 1 + end + if !commit && (!force && (self.file.revisions.length <= 1)) errors[:base] << l(:error_at_least_one_revision_must_be_present) return false 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| d.source_revision = self.source_revision d.save! end - if Setting.plugin_redmine_dmsf['dmsf_really_delete_files'] - dependencies = DmsfFileRevision.where(:disk_filename => self.disk_filename).all.count - File.delete(self.disk_file) if dependencies <= 1 && File.exist?(self.disk_file) + if commit self.destroy else self.deleted = true @@ -86,6 +83,12 @@ class DmsfFileRevision < ActiveRecord::Base end end + def restore + self.deleted = false + self.deleted_by_user = nil + save + end + def destroy if Setting.plugin_redmine_dmsf['dmsf_really_delete_files'] dependencies = DmsfFileRevision.where(:disk_filename => self.disk_filename).all.count @@ -233,4 +236,4 @@ class DmsfFileRevision < ActiveRecord::Base parts.size == 2 ? parts[0].to_i * 1000 + parts[1].to_i : 0 end -end +end \ No newline at end of file diff --git a/app/models/dmsf_folder.rb b/app/models/dmsf_folder.rb index 23011c42..26293831 100644 --- a/app/models/dmsf_folder.rb +++ b/app/models/dmsf_folder.rb @@ -27,11 +27,13 @@ class DmsfFolder < ActiveRecord::Base belongs_to :project 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', :dependent => :destroy has_many :files, :class_name => 'DmsfFile', :foreign_key => 'dmsf_folder_id', - :dependent => :destroy - belongs_to :user + :dependent => :destroy has_many :folder_links, :class_name => 'DmsfLink', :foreign_key => 'dmsf_folder_id', :conditions => {:target_type => DmsfFolder.model_name}, :dependent => :destroy has_many :file_links, :class_name => 'DmsfLink', :foreign_key => 'dmsf_folder_id', @@ -42,8 +44,9 @@ class DmsfFolder < ActiveRecord::Base :order => "#{DmsfLock.table_name}.updated_at DESC", :conditions => {:entity_type => 1}, :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 @@ -89,8 +92,16 @@ class DmsfFolder < ActiveRecord::Base elsif !self.subfolders.visible.empty? || !self.files.visible.empty? errors[:base] << l(:error_folder_is_not_empty) return false - end - destroy + end + 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 def dmsf_path @@ -270,5 +281,4 @@ class DmsfFolder < ActiveRecord::Base end end -end - +end \ No newline at end of file diff --git a/app/models/dmsf_link.rb b/app/models/dmsf_link.rb index a216fed1..471741e6 100644 --- a/app/models/dmsf_link.rb +++ b/app/models/dmsf_link.rb @@ -21,10 +21,14 @@ class DmsfLink < ActiveRecord::Base belongs_to :project belongs_to :dmsf_folder + belongs_to :deleted_by_user, :class_name => 'User', :foreign_key => 'deleted_by_user_id' + validates :name, :presence => true validates :target_id, :presence => true validates_length_of :name, :maximum => 255 + scope :visible, where('NOT deleted') + scope :deleted, where('NOT NOT deleted') def target_folder_id if self.target_type == DmsfFolder.model_name @@ -96,5 +100,21 @@ class DmsfLink < ActiveRecord::Base link.save link 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 \ No newline at end of file diff --git a/app/views/dmsf/_dir.html.erb b/app/views/dmsf/_dir.html.erb index eda59eb0..229cf3f7 100644 --- a/app/views/dmsf/_dir.html.erb +++ b/app/views/dmsf/_dir.html.erb @@ -1,7 +1,7 @@ <%#= # Redmine plugin for Document Management System "Features" # -# Copyright (C) 2014 Karel Pičman +# Copyright (C) 2011-14 Karel Pičman # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -85,7 +85,7 @@ :class => 'icon icon-del') %> <% else %> <%= 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)}, :title => l(:title_delete), :class => 'icon icon-del') %> @@ -105,4 +105,4 @@ 0 0 <%= subfolder.updated_at.to_i %> -0 +0 \ No newline at end of file diff --git a/app/views/dmsf/_dir_trash.html.erb b/app/views/dmsf/_dir_trash.html.erb new file mode 100644 index 00000000..f5f52776 --- /dev/null +++ b/app/views/dmsf/_dir_trash.html.erb @@ -0,0 +1,58 @@ +<%#= +# Redmine plugin for Document Management System "Features" +# +# Copyright (C) 2011-14 Karel Pičman +# +# 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. +%> + +<%= check_box_tag(name, id, false, + :title => l(:title_check_for_zip_download_or_email)) %> + + <%= content_tag(:span, h(title), + :title => h(title), + :class => 'icon icon-folder') %> + <% if link %> +
<%= link.path %>
+ <% else %> +
[<%= subfolder.files.visible.count + subfolder.file_links.visible.count %>]
+ <% end %> + + + + <%= format_time(subfolder.updated_at) %> + + + +<%= h(subfolder.user) %> + + <% if @folder_manipulation_allowed %> +
+ <%= 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') %> +
+ <% end %> + +0 +0 +<%= subfolder.updated_at.to_i %> +0 \ No newline at end of file diff --git a/app/views/dmsf/_file_trash.html.erb b/app/views/dmsf/_file_trash.html.erb new file mode 100644 index 00000000..279a4321 --- /dev/null +++ b/app/views/dmsf/_file_trash.html.erb @@ -0,0 +1,60 @@ +<%#= +# Redmine plugin for Document Management System "Features" +# +# Copyright (C) 2011-14 Karel Pičman +# +# 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. +%> + +<%= check_box_tag(name, id, false, + :title => l(:title_check_for_zip_download_or_email)) %> + + <% 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}") %> +
<%= h(link ? link.path : file.display_name) %>
+ +<%= number_to_human_size(file.last_revision.size) %> + + <%= format_time(file.last_revision.updated_at) %> + +<%= file.last_revision.version %> + + <%= file.last_revision.workflow_str(false) %> + +<%= h(file.last_revision.user) %> + + <% if @file_manipulation_allowed %> +
+ <%= 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') %> +
+ <% end %> + +1 +<%= file.last_revision.size %> +<%= file.last_revision.updated_at.to_i %> +<%= file.last_revision.iversion %> \ No newline at end of file diff --git a/app/views/dmsf/show.html.erb b/app/views/dmsf/show.html.erb index 965b6fc6..e8794cbc 100644 --- a/app/views/dmsf/show.html.erb +++ b/app/views/dmsf/show.html.erb @@ -88,7 +88,11 @@ new_dmsf_path(:id => @project, :parent_id => @folder), :title => l(:link_create_folder), :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 %> <%= render(:partial => 'path', :locals => {:folder => @folder, :filename => nil}) %> diff --git a/app/views/dmsf/trash.html.erb b/app/views/dmsf/trash.html.erb new file mode 100644 index 00000000..0e893e36 --- /dev/null +++ b/app/views/dmsf/trash.html.erb @@ -0,0 +1,185 @@ +<%#= +# Redmine plugin for Document Management System "Features" +# +# Copyright (C) 2011-14 Karel Pičman +# +# 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)) %> +
+
+ +

<%= l(:link_trash_bin) %>

+ +
+
+ <%= textilizable(@folder ? @folder.description : @project.dmsf_description) %> +
+
+ +<%= 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') %> +
+ <%= submit_tag(l(:submit_download), :title => l(:title_download_checked), :name => 'download_entries') %> + <% if @file_manipulation_allowed && @folder_manipulation_allowed && !@locked_for_user %> + + <% end %> +
+ + + + + + + + + + + + + + + + + + + <% @subfolders.each do |subfolder| %> + + <%= render(:partial => 'dir_trash', + :locals => { + :project => @project, + :subfolder => subfolder, + :link => nil, + :id => subfolder.id, + :name => 'subfolders[]', + :title => subfolder.title }) %> + + <% end %> + <% @dir_links.each do |link| %> + + <%= render(:partial => 'dir_trash', + :locals => { + :project => link.target_project, + :subfolder => link.target_folder, + :link => link, + :id => link.id, + :name => 'dir_links[]', + :title => link.name }) %> + + <% end %> + <% @files.each do |file| %> + <% unless file.last_revision %> + <% Rails.logger.error "Error: dmsf_file id #{file.id} has no revision!" %> + <% next %> + <% end %> + + <%= render(:partial => 'file_trash', :locals => { + :project => @project, + :file => file, + :link => nil, + :id => file.id, + :name => 'files[]', + :title => file.title }) %> + + <% 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 %> + + <%= render(:partial => 'file_trash', :locals => { + :project => link.target_project, + :file => link.target_file, + :link => link, + :id => link.id, + :name => 'file_links[]', + :title => link.name }) %> + + <% end %> + +
+ + <%= l(:link_title) %><%= l(:link_size) %><%= l(:link_modified) %><%= l(:link_ver) %><%= l(:link_workflow) %><%= l(:link_author) %>
+<% end %> + + + +<% + 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' %> + + +<% end %> \ No newline at end of file diff --git a/assets/images/restore.png b/assets/images/restore.png new file mode 100644 index 0000000000000000000000000000000000000000..d8136f8b0e92f55f752cd537608c5c13aad143b2 GIT binary patch literal 806 zcmV+>1KIqEP)Y^@8QBy(a#}cE2BDydz zywT_fEJ1IQMT$nLBwCrJwqc~pVY;cC)BHhgU+Q#7W@_R z_X>r)N}=4-Tx%$gxOca>9Dr_}KuEYAL?qA7OnKg8w#2wxE-pzD0DyC@5oEFyQ}@@` z-}|k)fWRHtP=RyE=#!pyroq(R6X_`kwnR-#Ozm#_&~=P4ATVg> z@T!2#W`t6yL$%@M`ChXn(#unjjwdDN=j9icG3GuC0CN+Tq?4!8Ud+!gfQZ)h#a*e? z0RTRA8ZXVyFF+fnZp_QSS!}l&B>?#R@l{x6&ecZ%$lKfN7ZGtmCRsB_jE%0Ap+@LaXlL zoWo$KOBbj6<$scA#?5}u40Rc9ZX98ust(4O69Bl)wzQJr8uc|nkVzvBXJ}S#LEYld z!9y!#TD0%ay;xZ9a5&Y1AV^w`>IMJ|44A+Gz%kebzkn!yz2HJw%iH!$Q550f;ek+f zu#+)Py=IG_D2kA6*#f;j`u^=ZB{`0vPV!goTM}5!MCA6Mv?#6V&ATE?|9~%B4HJTQ z1^qY@ci?h%Zo$*FQ(7l5YBjO=aZfoz1;fzcA*yz@ShfmZn#%4yWUDzJ5 kck^@o*EQO{wQTVH1{GZ)w&T3~s{jB107*qoM6N<$f~$abVgLXD literal 0 HcmV?d00001 diff --git a/assets/stylesheets/dmsf.css b/assets/stylesheets/dmsf.css index d37049b1..9c198501 100644 --- a/assets/stylesheets/dmsf.css +++ b/assets/stylesheets/dmsf.css @@ -292,6 +292,7 @@ table.list td.step { .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-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-notification-on-gray { background-image: url(../images/notify_gray.png); } diff --git a/config/locales/cs.yml b/config/locales/cs.yml index da1a01af..e0c02220 100644 --- a/config/locales/cs.yml +++ b/config/locales/cs.yml @@ -297,6 +297,12 @@ cs: label_display_notified_recipients: Zobrazit příjemce notifikací 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}" + + 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: blocks: diff --git a/config/locales/de.yml b/config/locales/de.yml index 8c8ebdaf..17b0e8ca 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -298,6 +298,12 @@ de: 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}" + 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: blocks: locked_documents: Locked documents diff --git a/config/locales/en.yml b/config/locales/en.yml index f86a0183..ee9cd20a 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -298,6 +298,12 @@ en: 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}" + 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: blocks: locked_documents: Locked documents diff --git a/config/locales/es.yml b/config/locales/es.yml index a0e10394..7079d240 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -298,6 +298,12 @@ es: 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}" + 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: blocks: locked_documents: Locked documents diff --git a/config/locales/fr.yml b/config/locales/fr.yml index dce8a1f3..fa6376d2 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -297,6 +297,12 @@ fr: label_display_notified_recipients: Afficher les destinataires notifiés 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}" + + 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: blocks: diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 61e1a02e..81ef8fdc 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -298,6 +298,12 @@ ja: 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}" + 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: blocks: locked_documents: Locked documents diff --git a/config/locales/ru.yml b/config/locales/ru.yml index 0f6d45e3..14d7c0ca 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -298,6 +298,12 @@ ru: 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}" + 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: blocks: locked_documents: Locked documents diff --git a/config/locales/sl.yml b/config/locales/sl.yml index ee1a34cb..0fdd6249 100644 --- a/config/locales/sl.yml +++ b/config/locales/sl.yml @@ -298,6 +298,12 @@ sl: 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}" + 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: blocks: locked_documents: Locked documents diff --git a/config/locales/zh.yml b/config/locales/zh.yml index 03febf8d..0b05287f 100644 --- a/config/locales/zh.yml +++ b/config/locales/zh.yml @@ -298,6 +298,12 @@ zh: 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}" + 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: blocks: locked_documents: Locked documents diff --git a/config/routes.rb b/config/routes.rb index e9eeaf82..161360a5 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -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/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/trash', :controller => 'dmsf', :action => 'trash', :as => 'trash_dmsf' + get '/projects/:id/dmsf/restore', :controller => 'dmsf', :action => 'restore', :as => 'restore_dmsf' # # dmsf_state controller @@ -49,7 +51,6 @@ RedmineApp::Application.routes.draw do ## post '/projects/:id/dmsf/state', :controller => 'dmsf_state', :action => 'user_pref_save' - # # dmsf_upload controller # /projects//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', :controller => 'dmsf_files', :action => 'show', :as => 'dmsf_file' 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 get '/dmsf_files/:id', :controller => 'dmsf_files', :action => 'show' @@ -123,7 +125,8 @@ RedmineApp::Application.routes.draw do # Links resources :dmsf_links do - member do + member do + get 'restore' end end diff --git a/db/migrate/20140519133201_trash_bin.rb b/db/migrate/20140519133201_trash_bin.rb new file mode 100644 index 00000000..071b872d --- /dev/null +++ b/db/migrate/20140519133201_trash_bin.rb @@ -0,0 +1,32 @@ +# Redmine plugin for Document Management System "Features" +# +# Copyright (C) 2011-14 Karel Pičman +# +# 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 \ No newline at end of file diff --git a/init.rb b/init.rb index fa176c8a..94e96f92 100644 --- a/init.rb +++ b/init.rb @@ -64,14 +64,14 @@ Redmine::Plugin.register :redmine_dmsf do :dmsf_workflows => [:log]}, :read => true 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, - {: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_workflows => [:action, :new_action, :autocomplete_for_user, :start, :assign, :assignment], :dmsf_links => [:new, :create, :destroy] } - permission :file_delete, {:dmsf_files => [:delete]} + permission :file_delete, { :dmsf => [:trash], :dmsf_files => [:delete]} permission :manage_workflows, {:dmsf_workflows => [:index, :new, :create, :destroy, :show, :add_step, :remove_step, :reorder_steps, :update]} permission :force_file_unlock, {} diff --git a/test/functional/dmsf_links_controller_test.rb b/test/functional/dmsf_links_controller_test.rb index 41f412c4..4832ff30 100644 --- a/test/functional/dmsf_links_controller_test.rb +++ b/test/functional/dmsf_links_controller_test.rb @@ -292,7 +292,7 @@ class DmsfLinksControllerTest < RedmineDmsf::Test::TestCase end 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 end assert_redirected_to dmsf_folder_path(:id => @project1.id, :folder_id => @folder1.id)