#48 Attaching DMS links to issues

This commit is contained in:
Karel Picman 2017-05-23 14:55:56 +02:00
parent 5940486c77
commit 10d786c6e4
27 changed files with 433 additions and 213 deletions

View File

@ -37,6 +37,7 @@ Features
* Documents and folders symbolic links * Documents and folders symbolic links
* Document tagging * Document tagging
* Trash bin * Trash bin
* Documents attachable to issues
* Compatible with Redmine 3.3.x * Compatible with Redmine 3.3.x
Dependencies Dependencies
@ -235,9 +236,9 @@ Before installing ensure that the Redmine instance is stopped.
6. Restart the web server. 6. Restart the web server.
7. You should configure the plugin via Redmine interface: Administration -> Plugins -> DMSF -> Configure. 7. You should configure the plugin via Redmine interface: Administration -> Plugins -> DMSF -> Configure.
8. Assign DMSF permissions to appropriate roles. 8. Assign DMSF permissions to appropriate roles.
9. There are two rake tasks: 9. There are a few rake tasks:
a) To convert documents from the standard Redmine document module I) To convert documents from the standard Redmine document module
Available options: Available options:
@ -253,11 +254,42 @@ Before installing ensure that the Redmine instance is stopped.
chown -R www-data:www-data /redmine/files/dmsf chown -R www-data:www-data /redmine/files/dmsf
afterwards) afterwards)
b) To alert all users who are expected to do an approval in the current approval steps II) To alert all users who are expected to do an approval in the current approval steps
Example: Example:
rake redmine:dmsf_alert_approvals RAILS_ENV="production" rake redmine:dmsf_alert_approvals RAILS_ENV="production"
III) To clears the Webdav Cache
Example:
rake redmine:dmsf_clear_webdav_cache RAILS_ENV="production"
IV) To create missing MD5 digest for all file revisions
Available options:
*dry_run - test, no changes to the database
Example:
bundle exec rake redmine:dmsf_create_digests RAILS_ENV="production"
bundle exec rake redmine:dmsf_create_digests dry_run=1 RAILS_ENV="production"
V) To maintain DMSF
* Remove all files with no database record from the document directory
* Remove all links project_id = -1 (added links to an issue which hasn't been created)
Available options:
*dry_run - No physical deletion but to list of all unused files only
Example:
rake redmine:dmsf_maintenance RAILS_ENV="production"
rake redmine:dmsf_maintenance dry_run=1 RAILS_ENV="production"
### Fulltext search (optional) ### Fulltext search (optional)
If you want to use full-text search features, you must setup file content indexing. If you want to use full-text search features, you must setup file content indexing.

View File

@ -728,19 +728,7 @@ class DmsfController < ApplicationController
end end
end end
# Remove system folders you are not allowed to see because you are not allowed to see the issue # Remove system folders you are not allowed to see because you are not allowed to see the issue
@subfolders.delete_if{ |folder| @subfolders = DmsfHelper.visible_folders(@subfolders)
if folder.system
issue_id = folder.title.to_i
if issue_id > 0
issue = Issue.find_by_id issue_id
issue && !issue.visible?(User.current)
else
false
end
else
false
end
}
end end
@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

View File

@ -26,6 +26,7 @@ class DmsfLinksController < ApplicationController
before_filter :find_link_project before_filter :find_link_project
before_filter :authorize before_filter :authorize
before_filter :permissions before_filter :permissions
protect_from_forgery except: :new
def permissions def permissions
if @dmsf_link if @dmsf_link
@ -43,109 +44,80 @@ class DmsfLinksController < ApplicationController
def new def new
@dmsf_link = DmsfLink.new @dmsf_link = DmsfLink.new
@dmsf_link.project_id = params[:project_id] @dmsf_link.project_id = params[:project_id]
@dmsf_link.dmsf_folder_id = params[:dmsf_folder_id]
if params[:dmsf_link].present? @dmsf_file_id = params[:dmsf_file_id]
# Reload @type = params[:type]
@dmsf_link.dmsf_folder_id = params[:dmsf_link][:dmsf_folder_id] @dmsf_link.target_project_id = params[:project_id]
@dmsf_file_id = params[:dmsf_link][:dmsf_file_id] @dmsf_link.project_id = params[:project_id]
@type = params[:dmsf_link][:type] @target_folder_id = params[:dmsf_folder_id].to_i if params[:dmsf_folder_id].present?
@link_external = (@type == 'link_from') && (params[:external_link] == 'true') if @type == 'link_to'
@dmsf_link.target_project_id = params[:dmsf_link][:target_project_id] if @dmsf_file_id
@target_folder_id = params[:dmsf_link][:target_folder_id].to_i if params[:reload].blank? && DmsfLinksHelper.is_a_number?(params[:dmsf_link][:target_folder_id]) file = DmsfFile.find_by_id @dmsf_file_id
if @type == 'link_to' @dmsf_link.name = file.title if file
if params[:dmsf_link][:dmsf_file_id].present?
file = DmsfFile.find_by_id params[:dmsf_link][:dmsf_file_id]
@dmsf_link.name = file.title if file
else
folder = DmsfFolder.find_by_id params[:dmsf_link][:dmsf_folder_id]
@dmsf_link.name = folder.title if folder
end
else else
if params[:dmsf_link][:target_file_id].present? folder = DmsfFolder.find_by_id @target_folder_id
@target_file_id = params[:dmsf_link][:target_file_id] @dmsf_link.name = folder.title if folder
file = DmsfFile.find_by_id @target_file_id
if file
folder = DmsfFolder.find_by_id params[:dmsf_link][:target_folder_id]
if (folder && (folder.project_id == @dmsf_link.target_project_id) && folder.dmsf_files.include?(file)) || folder.nil?
@dmsf_link.name = file.title
end
end
else
folder = DmsfFolder.find_by_id params[:dmsf_link][:target_folder_id]
if folder
if folder.project_id == @dmsf_link.target_project_id
@dmsf_link.name = folder.title if folder
end
end
end
end
else
# Link from/to
@dmsf_link.dmsf_folder_id = params[:dmsf_folder_id]
@dmsf_file_id = params[:dmsf_file_id]
@type = params[:type]
@link_external = false
@dmsf_link.target_project_id = params[:project_id]
@dmsf_link.project_id = params[:project_id]
@target_folder_id = params[:dmsf_folder_id].to_i if params[:dmsf_folder_id].present?
if @type == 'link_to'
if @dmsf_file_id
file = DmsfFile.find_by_id @dmsf_file_id
@dmsf_link.name = file.title if file
else
folder = DmsfFolder.find_by_id @target_folder_id
@dmsf_link.name = folder.title if folder
end
end end
end end
@container = params[:container]
respond_to do |format|
format.html
format.js
end
end
render :layout => !request.xhr? def autocomplete_for_project
respond_to do |format|
format.js
end
end
def autocomplete_for_folder
respond_to do |format|
format.js
end
end end
def create def create
@dmsf_link = DmsfLink.new @dmsf_link = DmsfLink.new
@dmsf_link.user = User.current @dmsf_link.user = User.current
if params[:dmsf_link][:type] == 'link_from' if params[:dmsf_link][:type] == 'link_from'
# Link from # Link from
@dmsf_link.project_id = params[:dmsf_link][:project_id] if params[:dmsf_link][:container].blank?
@dmsf_link.dmsf_folder_id = params[:dmsf_link][:dmsf_folder_id] @dmsf_link.project_id = params[:dmsf_link][:project_id]
@dmsf_link.dmsf_folder_id = params[:dmsf_link][:dmsf_folder_id]
else
# An issue link
@dmsf_link.project_id = -1
@dmsf_link.dmsf_folder_id = nil
end
@dmsf_link.target_project_id = params[:dmsf_link][:target_project_id] @dmsf_link.target_project_id = params[:dmsf_link][:target_project_id]
@link_external = (params[:external_link] == 'true') if (params[:external_link] == 'true')
@dmsf_link.external_url = params[:dmsf_link][:external_url] @dmsf_link.external_url = params[:dmsf_link][:external_url]
if (@link_external)
@dmsf_link.target_type = 'DmsfUrl' @dmsf_link.target_type = 'DmsfUrl'
elsif params[:dmsf_link][:target_file_id].present? elsif params[:dmsf_link][:target_file_id].present?
@dmsf_link.target_id = params[:dmsf_link][:target_file_id] @dmsf_link.target_id = params[:dmsf_link][:target_file_id]
@dmsf_link.target_type = DmsfFile.model_name.to_s @dmsf_link.target_type = DmsfFile.model_name.to_s
else else
@dmsf_link.target_id = DmsfLinksHelper.is_a_number?(params[:dmsf_link][:target_folder_id]) ? params[:dmsf_link][:target_folder_id].to_i : nil @dmsf_link.target_id = DmsfLinksHelper.is_a_number?(
params[:dmsf_link][:target_folder_id]) ? params[:dmsf_link][:target_folder_id].to_i : nil
@dmsf_link.target_id = nil if(@dmsf_link.target_id == 0)
@dmsf_link.target_type = DmsfFolder.model_name.to_s @dmsf_link.target_type = DmsfFolder.model_name.to_s
end end
@dmsf_link.name = params[:dmsf_link][:name] @dmsf_link.name = params[:dmsf_link][:name]
if @dmsf_link.save if @dmsf_link.save
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
redirect_to dmsf_folder_path(:id => @project.id, :folder_id => @dmsf_link.dmsf_folder_id)
else else
@dmsf_file_id = params[:dmsf_link][:dmsf_file_id] flash[:error] = @dmsf_link.errors.full_messages.to_sentence
@type = params[:dmsf_link][:type]
@target_folder_id = params[:dmsf_link][:target_folder_id].to_i if DmsfLinksHelper.is_a_number?(params[:dmsf_link][:target_folder_id])
@target_file_id = @dmsf_link.target_id if @dmsf_link.target_type == DmsfFile.model_name.to_s
render :action => 'new'
end end
else else
# Link to # Link to
@dmsf_link.project_id = params[:dmsf_link][:target_project_id] @dmsf_link.project_id = params[:dmsf_link][:target_project_id]
@dmsf_link.dmsf_folder_id = DmsfLinksHelper.is_a_number?(params[:dmsf_link][:target_folder_id]) ? params[:dmsf_link][:target_folder_id].to_i : nil @dmsf_link.dmsf_folder_id = DmsfLinksHelper.is_a_number?(
params[:dmsf_link][:target_folder_id]) ? params[:dmsf_link][:target_folder_id].to_i : nil
@dmsf_link.dmsf_folder_id = nil if(@dmsf_link.dmsf_folder_id == 0)
@dmsf_link.target_project_id = params[:dmsf_link][:project_id] @dmsf_link.target_project_id = params[:dmsf_link][:project_id]
@link_external = (params[:external_link] == 'true') if params[:dmsf_link][:dmsf_file_id].present?
@dmsf_link.external_url = params[:dmsf_link][:external_url]
if (@link_external)
@dmsf_link.target_type = 'DmsfUrl'
elsif params[:dmsf_link][:dmsf_file_id].present?
@dmsf_link.target_id = params[:dmsf_link][:dmsf_file_id] @dmsf_link.target_id = params[:dmsf_link][:dmsf_file_id]
@dmsf_link.target_type = DmsfFile.model_name.to_s @dmsf_link.target_type = DmsfFile.model_name.to_s
else else
@ -153,24 +125,26 @@ class DmsfLinksController < ApplicationController
@dmsf_link.target_type = DmsfFolder.model_name.to_s @dmsf_link.target_type = DmsfFolder.model_name.to_s
end end
@dmsf_link.name = params[:dmsf_link][:name] @dmsf_link.name = params[:dmsf_link][:name]
if @dmsf_link.save if @dmsf_link.save
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
if params[:dmsf_link][:dmsf_file_id].present?
redirect_to dmsf_file_path(@dmsf_link.target_file)
else
redirect_to edit_dmsf_path(:id => params[:dmsf_link][:project_id], :folder_id => params[:dmsf_link][:dmsf_folder_id])
end
else else
@dmsf_file_id = params[:dmsf_link][:dmsf_file_id] flash[:error] = @dmsf_link.errors.full_messages.to_sentence
@type = params[:dmsf_link][:type]
@target_folder_id = @dmsf_link.dmsf_folder_id
@dmsf_link.target_project_id = @dmsf_link.project.id
@dmsf_link.project_id = params[:dmsf_link][:project_id]
@dmsf_link.dmsf_folder_id = params[:dmsf_link][:dmsf_folder_id]
render :action => 'new'
end end
end end
respond_to do |format|
format.html {
if params[:dmsf_link][:type] == 'link_from'
redirect_to dmsf_folder_path(:id => @project.id, :folder_id => @dmsf_link.dmsf_folder_id)
else
if params[:dmsf_link][:dmsf_file_id].present?
redirect_to dmsf_file_path(@dmsf_link.target_file)
else
redirect_to edit_dmsf_path(:id => params[:dmsf_link][:project_id], :folder_id => params[:dmsf_link][:dmsf_folder_id])
end
end
}
format.js
end
end end
def destroy def destroy

View File

@ -89,9 +89,30 @@ module DmsfHelper
'plupload/js/i18n/en.js' 'plupload/js/i18n/en.js'
end end
def self.visible_folders(folders)
allowed = Setting.plugin_redmine_dmsf['dmsf_act_as_attachable']
folders.reject{ |folder|
if folder.system
unless allowed
true
else
issue_id = folder.title.to_i
if issue_id > 0
issue = Issue.find_by_id issue_id
issue && !issue.visible?(User.current)
else
false
end
end
else
false
end
}
end
def self.all_children_sorted(parent, pos, ident) def self.all_children_sorted(parent, pos, ident)
# Folders && files && links # Folders && files && links
nodes = parent.dmsf_folders.visible + parent.dmsf_links.visible + parent.dmsf_files.visible nodes = visible_folders(parent.dmsf_folders.visible.to_a) + parent.dmsf_links.visible + parent.dmsf_files.visible
# Alphabetical and type sort # Alphabetical and type sort
nodes.sort! do |x, y| nodes.sort! do |x, y|
if ((x.is_a?(DmsfFolder) || (x.is_a?(DmsfLink) && x.is_folder?)) && if ((x.is_a?(DmsfFolder) || (x.is_a?(DmsfLink) && x.is_folder?)) &&

View File

@ -37,6 +37,18 @@ module DmsfLinksHelper
# An integer test # An integer test
def self.is_a_number?(s) def self.is_a_number?(s)
s.to_s.match(/\A[+-]?\d+?(\.\d+)?\Z/) == nil ? false : true s.to_s.match(/\A[+-]?\d+?(\.\d+)?\Z/) == nil ? false : true
end end
def files_for_select(project_id, folder_id)
files = []
if folder_id && (folder_id != '0')
folder = DmsfFolder.find_by_id folder_id
files = folder.dmsf_files.visible.to_a if folder
elsif project_id
project = Project.find_by_id project_id
files = project.dmsf_files.visible.to_a if project
end
files
end
end end

View File

@ -210,7 +210,10 @@ class DmsfFolder < ActiveRecord::Base
end end
def self.directory_tree(project, current_folder = nil) def self.directory_tree(project, current_folder = nil)
tree = [[l(:link_documents), nil]] unless project.is_a? Project
project = Project.find_by_id project
end
tree = [[l(:link_documents), 0]]
project.dmsf_folders.visible(false).each do |folder| project.dmsf_folders.visible(false).each do |folder|
unless folder == current_folder unless folder == current_folder
tree.push(["...#{folder.title}", folder.id]) tree.push(["...#{folder.title}", folder.id])

View File

@ -122,8 +122,17 @@ class DmsfLink < ActiveRecord::Base
link link
end end
def container
if self.folder && self.folder.system
Issue.where(:id => self.folder.title.to_i).first
end
end
def delete(commit = false) def delete(commit = false)
if commit if commit
if self.container.is_a?(Issue)
self.container.dmsf_file_removed(self.target_file)
end
self.destroy self.destroy
else else
self.deleted = STATUS_DELETED self.deleted = STATUS_DELETED

View File

@ -59,8 +59,9 @@
<% end %> <% end %>
<% if @file_manipulation_allowed && !@locked_for_user %> <% if @file_manipulation_allowed && !@locked_for_user %>
<%= link_to(l(:label_link_from), <%= link_to(l(:label_link_from),
new_dmsf_link_path(:project_id => @project.id, :dmsf_folder_id => @folder ? @folder.id : @folder, :type => 'link_from'), new_dmsf_link_path(:project_id => @project.id, :dmsf_folder_id => @folder ? @folder.id : @folder,
:title => l(:title_create_link), :class => 'icon icon-link') %> :type => 'link_from'), :title => l(:title_create_link),
:class => 'icon icon-link') %>
<% end %> <% end %>
<%= link_to(l(:link_create_folder), <%= link_to(l(:link_create_folder),
new_dmsf_path(:id => @project, :parent_id => @folder), new_dmsf_path(:id => @project, :parent_id => @folder),
@ -100,7 +101,6 @@
<% custom_value.custom_field.is_required = false %> <% custom_value.custom_field.is_required = false %>
<% custom_value.value = params[:custom_value].present? ? params[:custom_value] : '' %> <% custom_value.value = params[:custom_value].present? ? params[:custom_value] : '' %>
<%= h(custom_value.custom_field.name) %>: <%= h(custom_value.custom_field.name) %>:
<%#= custom_field_tag(:dmsf_folder, custom_value, :style => 'width: auto') %>
<%= custom_value.custom_field.format.edit_tag(self, <%= custom_value.custom_field.format.edit_tag(self,
custom_field_tag_id(:dmsf_folder, custom_value.custom_field), custom_field_tag_id(:dmsf_folder, custom_value.custom_field),
custom_field_tag_name(:dmsf_folder, custom_value.custom_field), custom_field_tag_name(:dmsf_folder, custom_value.custom_field),

View File

@ -78,7 +78,7 @@
<div class="box"> <div class="box">
<p> <p>
<%= label_tag('file_upload', l(:label_new_content)) %> <%= label_tag('file_upload', l(:label_new_content)) %>
<%= render :partial => 'dmsf_upload/form', :locals => { :multiple => false } %> <%= render :partial => 'dmsf_upload/form', :locals => { :multiple => false, :container => nil } %>
</p> </p>
</div> </div>
<p> <p>

View File

@ -20,7 +20,7 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
%> %>
<% if dmsf_files.present? %> <% if dmsf_files.present? || dmsf_links.present? %>
<hr/> <hr/>
<div class="attachments dmsf_parent_container"> <div class="attachments dmsf_parent_container">
<% for dmsf_file in dmsf_files %> <% for dmsf_file in dmsf_files %>
@ -76,4 +76,58 @@
<% end %> <% end %>
<% end %> <% end %>
</div> </div>
<% for dmsf_link in dmsf_links %>
<% dmsf_file = dmsf_link.target_file %>
<% if dmsf_file.last_revision %>
<p class = "dmsf_gray">
<% file_view_url = url_for({:controller => :dmsf_files, :action => 'view', :id => dmsf_file}) %>
<%= link_to(h(dmsf_link.title),
file_view_url,
:target => '_blank',
:class => "icon icon-file #{DmsfHelper.filetype_css(dmsf_file.name)}",
:title => h(dmsf_file.last_revision.try(:tooltip)),
'data-downloadurl' => "#{dmsf_file.last_revision.detect_content_type}:#{h(dmsf_file.name)}:#{file_view_url}") %>
<% if dmsf_file.text? || dmsf_file.image? %>
<%= link_to l(:button_view),
file_view_url,
:class => 'icon-only icon-magnifier',
:title => l(:button_view) %>
<% end %>
<%= " - #{dmsf_file.description}" unless dmsf_file.description.blank? %>
<span class="size">(<%= number_to_human_size dmsf_file.last_revision.size %>)</span>
<% if @issue.attributes_editable? && User.current.allowed_to?(:file_delete, dmsf_file.project) %>
<%= link_to('',
dmsf_link_path(dmsf_link, :commit => 'yes'),
:data => {:confirm => l(:text_are_you_sure)},
:method => :delete,
:title => l(:title_delete),
:class => 'icon icon-del') %>
<% end %>
<span class="author"><%= dmsf_file.last_revision.user %>, <%= format_time(dmsf_file.last_revision.updated_at) %></span>
<span class="dmsf_upload_select">
<% wf = DmsfWorkflow.find_by_id(dmsf_file.last_revision.dmsf_workflow_id) if dmsf_file.last_revision.dmsf_workflow_id %>
<%= render(:partial => 'dmsf_files/approval_workflow_button',
:locals => {:file => dmsf_file,
:file_approval_allowed => User.current.allowed_to?(:file_approval, dmsf_file.project),
:workflows_available => DmsfWorkflow.where(['project_id = ? OR project_id IS NULL', dmsf_file.project.id]).exists?,
:project => dmsf_file.project, :wf => wf }) %>
</span>
</p>
<% end %>
<% end %>
<% if defined?(thumbnails) && thumbnails %>
<% images = dmsf_links.map{ |link| link.target_file }.select(&:image?) %>
<% if images.any? %>
<div class="thumbnails">
<% images.each do |file| %>
<div>
<%= link_to image_tag(dmsf_thumbnail_path(file)),
url_for({:controller => :dmsf_files, :action => 'view', :id => file}),
:alt => dmsf_file.title %>
</div>
<% end %>
</div>
<% end %>
<% end %>
</div>
<% end %> <% end %>

View File

@ -20,40 +20,41 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
%> %>
<% html_title l(:dmsf) %> <h3 class="title"><%= l(:title_create_link) %></h3>
<% if @dmsf_file_id %> <%= labelled_form_for @dmsf_link, :remote => modal do |f| %>
<% file = DmsfFile.find_by_id @dmsf_file_id%>
<% title = file.title if file %>
<% end %>
<%= render(:partial => '/dmsf/path',
:locals => {:folder => @dmsf_link.folder, :filename => nil,
:title => (@type == 'link_from') ? l(:label_link_from) : l(:label_link_to) }) %>
<%= labelled_form_for @dmsf_link do |f| %>
<%= error_messages_for @dmsf_link %> <%= error_messages_for @dmsf_link %>
<%= f.hidden_field :project_id, :value => @dmsf_link.project_id %> <%= f.hidden_field :project_id, :value => @dmsf_link.project_id %>
<%= f.hidden_field :dmsf_folder_id, :value => @dmsf_link.dmsf_folder_id if @dmsf_link.dmsf_folder_id %> <%= f.hidden_field :dmsf_folder_id, :value => @dmsf_link.dmsf_folder_id if @dmsf_link.dmsf_folder_id %>
<%= f.hidden_field :type, :value => @type %> <%= f.hidden_field :type, :value => @type %>
<%= f.hidden_field :dmsf_file_id, :value => @dmsf_file_id %> <%= f.hidden_field :dmsf_file_id, :value => @dmsf_file_id %>
<%= f.hidden_field(:container, :value => @container) if @container %>
<div class="box tabular"> <div class="box tabular">
<% if @type == 'link_from' %> <% if (@type == 'link_from') && !@container %>
<p> <p>
<%= radio_button_tag(:external_link, 'false', @link_external == false) %> <%= l(:label_internal) %><br/> <%= radio_button_tag(:external_link, 'false', true) %> <%= l(:label_internal) %><br/>
<%= radio_button_tag(:external_link, 'true', @link_external) %> <%= l(:label_external) %> <%= radio_button_tag(:external_link, 'true', false) %> <%= l(:label_external) %>
</p> </p>
<% end %> <% end %>
<div id="link_internal" style="<%= @link_external ? 'display:none' : '' %>"> <div id="link_internal">
<p> <p>
<% if @type == 'link_from' %> <% if @type == 'link_from' %>
<label for="dmsf_link[target_project_id]"><%= l(:label_source_project) %><span class="required">*</span></label> <label for="dmsf_link[target_project_id]"><%= l(:label_source_project) %></label>
<% else %> <% else %>
<label for="dmsf_link[target_project_id]"><%= l(:label_target_project) %><span class="required">*</span></label> <label for="dmsf_link[target_project_id]"><%= l(:label_target_project) %></label>
<% end %> <% end %>
<%= select_tag('dmsf_link[target_project_id]', <%= select_tag('dmsf_link[target_project_id]',
project_tree_options_for_select(DmsfFile.allowed_target_projects_on_copy, project_tree_options_for_select(DmsfFile.allowed_target_projects_on_copy,
:selected => @dmsf_link.target_project)) %> :selected => @dmsf_link.target_project), :style => "width=100%") %>
<%= javascript_tag do %>
$('#dmsf_link_target_project_id').change(function(){
$.ajax({
url: '<%= autocomplete_for_project_dmsf_link_path(@project, :format => 'js') %>',
type: 'get',
data: $('#new_dmsf_link').serialize()
});
});
<% end %>
</p> </p>
<p> <p>
<% if @type == 'link_from' %> <% if @type == 'link_from' %>
@ -64,51 +65,50 @@
<%= select_tag('dmsf_link[target_folder_id]', <%= select_tag('dmsf_link[target_folder_id]',
folder_tree_options_for_select(DmsfFolder.directory_tree(@dmsf_link.target_project), folder_tree_options_for_select(DmsfFolder.directory_tree(@dmsf_link.target_project),
:selected => @target_folder_id)) %> :selected => @target_folder_id)) %>
<%= javascript_tag do %>
$('#dmsf_link_target_folder_id').change(function(){
$.ajax({
url: '<%= autocomplete_for_folder_dmsf_link_path(@project, :format => 'js') %>',
type: 'get',
data: $('#new_dmsf_link').serialize()
});
});
<% end %>
</p> </p>
<% if @type == 'link_from' %> <% if @type == 'link_from' %>
<p> <p>
<% if @target_folder_id %> <%= label_tag('dmsf_link[target_file_id]', l(:field_target_file)) %>
<% folder = DmsfFolder.find_by_id @target_folder_id %> <% files = files_for_select(@dmsf_link.target_project.id, @target_folder_id) %>
<% files = folder.dmsf_files.visible if folder %> <%= select_tag('dmsf_link[target_file_id]',
<% else %> options_for_select(DmsfFolder.file_list(files))) %>
<% files = @dmsf_link.target_project.dmsf_files.visible if @dmsf_link.target_project %>
<% end %>
<%= f.select(:target_file_id,
options_for_select(DmsfFolder.file_list(files), @target_file_id)) %>
</p> </p>
<% end %> <% end %>
</div> </div>
<div id="link_external" style="<%= @link_external ? '' : 'display:none' %>"> <div id="link_external" style="display: none">
<p> <p>
<%= f.text_field :external_url, :label => l(:label_link_external_url) %> <%= f.text_field :external_url, :required => true %>
</p> </p>
</div> </div>
<p> <p>
<%= f.text_field :name, :label => l(:label_link_name) %> <%= f.text_field :name, :required => true %>
</p> </p>
</div> </div>
<p><%= f.submit l(:button_create) %></p> <p><%= f.submit l(:button_create), :onclick => 'hideModal(this);' %></p>
<% end %> <% end %>
<script type="text/javascript"> <script type="text/javascript">
$('#dmsf_link_target_project_id').change(function () { // Suggest a link name when a file is selected
$('#content').load("<%= url_for(:action => 'new', :project_id => @project.id, :reload => true) %>", $('#new_dmsf_link').serialize());
});
$('#dmsf_link_target_folder_id').change(function () {
$('#content').load("<%= url_for(:action => 'new', :project_id => @project.id) %>", $('#new_dmsf_link').serialize());
});
$('#dmsf_link_target_file_id').change(function () { $('#dmsf_link_target_file_id').change(function () {
$('#content').load("<%= url_for(:action => 'new', :project_id => @project.id) %>", $('#new_dmsf_link').serialize()); var linkName = $('#dmsf_link_name');
}); var name = linkName.val();
if(name == '') {
linkName.val($('#dmsf_link_target_file_id option:selected').text().replace(/\./g, ''));
}
});
// Internal/External link switch
$("input[name=external_link]:radio").change(function(){ $("input[name=external_link]:radio").change(function(){
$("#link_internal").toggle(); $("#link_internal").toggle();
$("#link_external").toggle(); $("#link_external").toggle();
}); });
</script>
$('#dmsf_link_target_project_id').select2();
$('#dmsf_link_target_folder_id').select2();
$('#dmsf_link_target_file_id').select2();
</script>

View File

@ -0,0 +1,4 @@
$('#dmsf_link_target_file_id').html('<%= escape_javascript(
select_tag('dmsf_link[target_file_id]',
options_for_select(DmsfFolder.file_list(files_for_select(params[:dmsf_link][:target_project_id],
params[:dmsf_link][:target_folder_id]))))) %>');

View File

@ -0,0 +1,4 @@
$('#dmsf_link_target_folder_id').html('<%= escape_javascript(
select_tag('dmsf_link[target_folder_id]',
folder_tree_options_for_select(DmsfFolder.directory_tree(params[:dmsf_link][:target_project_id])))) %>');
$('#dmsf_link_target_folder_id').change();

View File

@ -0,0 +1,27 @@
<%
# encoding: utf-8
#
# Redmine plugin for Document Management System "Features"
#
# Copyright (C) 2011-17 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.
%>
var linksSpan = $("#dmsf_links_fields");
var linkId = "<%= @dmsf_link.id %>";
var linkName = "<%= @dmsf_link.name %>";
dmsfAddLink(linksSpan, linkId, linkName);

View File

@ -20,4 +20,4 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
%> %>
<%= render 'form' %> <%= render :partial => 'form', :locals => { :modal => false } %>

View File

@ -0,0 +1,24 @@
<%
# encoding: utf-8
#
# Redmine plugin for Document Management System "Features"
#
# Copyright (C) 2011-17 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.
%>
$('#ajax-modal').html('<%= escape_javascript(render :partial => 'form', :locals => { :modal => true }) %>');
showModal('ajax-modal', '40%');

View File

@ -21,7 +21,7 @@
%> %>
<span id="dmsf_attachments_fields"> <span id="dmsf_attachments_fields">
<% if defined?(container) && container && container.saved_dmsf_attachments %> <% if defined?(container) && container && container.saved_dmsf_attachments.present? %>
<% container.saved_dmsf_attachments.each_with_index do |attachment, i| %> <% container.saved_dmsf_attachments.each_with_index do |attachment, i| %>
<span id="dmsf_attachments_p<%= i %>"> <span id="dmsf_attachments_p<%= i %>">
<%= text_field_tag("dmsf_attachments[p#{i}][filename]", attachment.filename, :class => 'filename') + <%= text_field_tag("dmsf_attachments[p#{i}][filename]", attachment.filename, :class => 'filename') +
@ -47,3 +47,24 @@
} %> } %>
(<%= l(:label_max_size) %>: <%= number_to_human_size(Setting.attachment_max_size.to_i.kilobytes) %>) (<%= l(:label_max_size) %>: <%= number_to_human_size(Setting.attachment_max_size.to_i.kilobytes) %>)
</span> </span>
<% if container %>
<br/>
<span id="dmsf_links_fields">
<% if container.saved_dmsf_links.present? %>
<% container.saved_dmsf_links.each_with_index do |dmsf_link, index| %>
<span id="dmsf_links_<%= index %>">
<input name="dmsf_links[<%= index %>]" value="<%= dmsf_link.id %>" type="hidden">
<input type="text" class='filename readonly' value="<%= dmsf_link.name %>">
<a href="#" class="remove-upload icon icon-del" onclick="$(this).parent('span').remove();return false;">&nbsp;</a>
<br/>
</span>
<% end %>
<% end %>
</span>
<span class="dmsf_add_link">
<%= link_to l(:label_link_from),
new_dmsf_link_path(:project_id => container.project.id, :type => 'link_from', :container => 'issue'),
:title => l(:title_create_link), :class => 'icon icon-add', :remote => true %>
</span>
<% end %>

View File

@ -40,7 +40,7 @@
<div class="dmsf_uploader"> <div class="dmsf_uploader">
<p> <p>
<label><%= l(:label_document_plural) %></label> <label><%= l(:label_document_plural) %></label>
<%= render :partial => 'dmsf_upload/form', :locals => { :multiple => true } %> <%= render :partial => 'dmsf_upload/form', :locals => { :multiple => true, :container => nil } %>
</p> </p>
</div> </div>
<%= submit_tag l(:label_upload) %> <%= submit_tag l(:label_upload) %>

View File

@ -1,4 +1,5 @@
<%# Redmine plugin for Document Management System "Features" <%
# Redmine plugin for Document Management System "Features"
# #
# Copyright (C) 2011-15 Karel Pičman <karel.picman@kontron.com> # Copyright (C) 2011-15 Karel Pičman <karel.picman@kontron.com>
# #
@ -14,7 +15,8 @@
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.%> # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
%>
$('#ajax-modal').html('<%= escape_javascript(render :partial => 'assign', :locals => {:workflow => @dmsf_workflow}) %>'); $('#ajax-modal').html('<%= escape_javascript(render :partial => 'assign', :locals => {:workflow => @dmsf_workflow}) %>');
showModal('ajax-modal', '30%'); showModal('ajax-modal', '30%');

View File

@ -19,6 +19,24 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/ */
function dmsfAddLink(linksSpan, linkId, linkName) {
if (linksSpan.children().length < 10) {
var nextLinkId = dmsfAddLink.nextLinkId++;
var linkSpan = $('<span>', { id: 'dmsf_links_' + nextLinkId });
linkSpan.append(
"<input name='dmsf_links[" + nextLinkId + "]' value='" + linkId + "' type='hidden'>",
"<input type='text' class='filename readonly' value='" + linkName + "'>",
$('<a>&nbsp;</a>').attr({href: "#", 'class': 'remove-upload icon icon-del'}).click(dmsfRemoveFile),
"<br/>"
).appendTo(linksSpan);
}
}
dmsfAddLink.nextLinkId = 1000;
function dmsfAddFile(inputEl, file, eagerUpload) { function dmsfAddFile(inputEl, file, eagerUpload) {
if ($('#dmsf_attachments_fields').children().length < 10) { if ($('#dmsf_attachments_fields').children().length < 10) {

View File

@ -319,3 +319,5 @@ div.dmsf_revision_inner_box .attribute .label {
#dmsf_attachments_fields .ajax-loading input.filename {background:url(../../../images/loading.gif) no-repeat 0px 50%;} #dmsf_attachments_fields .ajax-loading input.filename {background:url(../../../images/loading.gif) no-repeat 0px 50%;}
#dmsf_attachments_fields div.ui-progressbar { width: 100px; height:14px; margin: 2px 0 -5px 8px; display: inline-block; } #dmsf_attachments_fields div.ui-progressbar { width: 100px; height:14px; margin: 2px 0 -5px 8px; display: inline-block; }
#dmsf_links_fields span {display:block; white-space:nowrap;}
#dmsf_links_fields input.filename {border:0; height:1.8em; width:250px; color:#555; background-color:inherit; background:url(../../../images/link.png) no-repeat 1px 50%; padding-left:18px;}

View File

@ -151,6 +151,8 @@ if Redmine::Plugin.installed? :redmine_dmsf
resources :dmsf_links do resources :dmsf_links do
member do member do
get 'restore' get 'restore'
get 'autocomplete_for_project'
get 'autocomplete_for_folder'
end end
end end

View File

@ -89,7 +89,7 @@ Redmine::Plugin.register :redmine_dmsf do
permission :file_manipulation, permission :file_manipulation,
{ :dmsf_files => [:create_revision, :lock, :unlock, :delete_revision, :notify_activate, :notify_deactivate, :restore], { :dmsf_files => [:create_revision, :lock, :unlock, :delete_revision, :notify_activate, :notify_deactivate, :restore],
:dmsf_upload => [:upload_files, :upload_file, :upload, :commit_files, :commit, :delete_dmsf_attachment], :dmsf_upload => [:upload_files, :upload_file, :upload, :commit_files, :commit, :delete_dmsf_attachment],
:dmsf_links => [:new, :create, :destroy, :restore] } :dmsf_links => [:new, :create, :destroy, :restore, :autocomplete_for_project, :autocomplete_for_folder] }
permission :file_delete, permission :file_delete,
{ :dmsf => [:trash, :delete_entries], { :dmsf => [:trash, :delete_entries],
:dmsf_files => [:delete] } :dmsf_files => [:delete] }

View File

@ -59,6 +59,7 @@ module RedmineDmsf
issue = context[:issue] issue = context[:issue]
params = context[:params] params = context[:params]
issue.save_dmsf_attachments(params[:dmsf_attachments]) issue.save_dmsf_attachments(params[:dmsf_attachments])
issue.save_dmsf_links(params[:dmsf_links])
end end
end end
@ -85,6 +86,21 @@ module RedmineDmsf
DmsfUploadHelper.commit_files_internal uploaded_files, issue.project, system_folder, DmsfUploadHelper.commit_files_internal uploaded_files, issue.project, system_folder,
context[:controller] context[:controller]
end end
dmsf_links = params[:dmsf_links]
if dmsf_links && dmsf_links.is_a?(Hash)
system_folder = issue.system_folder(true)
ids = dmsf_links.map(&:last)
ids.each do |id|
l = DmsfLink.find_by_id(id)
if l
l.project_id = system_folder.project_id
l.dmsf_folder_id = system_folder.id
if l.save
issue.dmsf_file_added l.target_file
end
end
end
end
end end
end end

View File

@ -49,7 +49,8 @@ module RedmineDmsf
if User.current.allowed_to?(:view_dmsf_files, issue.project) && if User.current.allowed_to?(:view_dmsf_files, issue.project) &&
Setting.plugin_redmine_dmsf['dmsf_act_as_attachable'] Setting.plugin_redmine_dmsf['dmsf_act_as_attachable']
context[:controller].send(:render_to_string, {:partial => 'dmsf_files/links', context[:controller].send(:render_to_string, {:partial => 'dmsf_files/links',
:locals => { :dmsf_files => issue.dmsf_files.to_a, :thumbnails => Setting.thumbnails_enabled? }}) :locals => { :dmsf_files => issue.dmsf_files, :dmsf_links => issue.dmsf_links,
:thumbnails => Setting.thumbnails_enabled? }})
end end
end end
end end

View File

@ -34,10 +34,12 @@ module RedmineDmsf
def save_dmsf_attachments(dmsf_attachments) def save_dmsf_attachments(dmsf_attachments)
@saved_dmsf_attachments = [] @saved_dmsf_attachments = []
dmsf_attachments = dmsf_attachments.map(&:last) if dmsf_attachments
dmsf_attachments.each do |dmsf_attachment| dmsf_attachments = dmsf_attachments.map(&:last)
a = Attachment.find_by_token(dmsf_attachment[:token]) dmsf_attachments.each do |dmsf_attachment|
@saved_dmsf_attachments << a if a a = Attachment.find_by_token(dmsf_attachment[:token])
@saved_dmsf_attachments << a if a
end
end end
end end
@ -45,6 +47,21 @@ module RedmineDmsf
@saved_dmsf_attachments || [] @saved_dmsf_attachments || []
end end
def save_dmsf_links(dmsf_links)
@saved_dmsf_links = []
if dmsf_links
ids = dmsf_links.map(&:last)
ids.each do |id|
l = DmsfLink.find_by_id(id)
@saved_dmsf_links << l if l
end
end
end
def saved_dmsf_links
@saved_dmsf_links || []
end
def system_folder(create = false) def system_folder(create = false)
parent = DmsfFolder.system.where(:project_id => self.project_id, :title => '.Issues').first parent = DmsfFolder.system.where(:project_id => self.project_id, :title => '.Issues').first
if create && !parent if create && !parent
@ -73,8 +90,21 @@ module RedmineDmsf
end end
def dmsf_files def dmsf_files
files = []
folder = self.system_folder folder = self.system_folder
folder.dmsf_files if folder if folder
files = folder.dmsf_files.to_a
end
files
end
def dmsf_links
links = []
folder = self.system_folder
if folder
links = folder.dmsf_links
end
links
end end
def delete_system_folder def delete_system_folder
@ -105,6 +135,7 @@ module RedmineDmsf
) )
current_journal.save current_journal.save
end end
end end
end end

View File

@ -18,10 +18,11 @@
desc <<-END_DESC desc <<-END_DESC
DMSF maintenance task DMSF maintenance task
* Remove all files and folders with no database record from the document directory * Remove all files with no database record from the document directory
* Remove all links project_id = -1 (added links to an issue which hasn't been created)
Available options: Available options:
*dry_run - No physical deletion but to list of all unused files and folders only *dry_run - No physical deletion but to list of all unused files only
Example: Example:
rake redmine:dmsf_maintenance RAILS_ENV="production" rake redmine:dmsf_maintenance RAILS_ENV="production"
@ -34,7 +35,7 @@ namespace :redmine do
begin begin
STDERR.puts "\n" STDERR.puts "\n"
Dir.chdir(DmsfFile.storage_path) Dir.chdir(DmsfFile.storage_path)
m.files m.files
if m.dry_run if m.dry_run
m.result m.result
else else
@ -54,54 +55,41 @@ class DmsfMaintenance
def initialize def initialize
@dry_run = ENV['dry_run'] @dry_run = ENV['dry_run']
@folders_to_delete = Array.new
@files_to_delete = Array.new @files_to_delete = Array.new
end end
def files def files
Dir.glob("**/*").each do |f| Dir.glob("**/*").each do |f|
if Dir.exist?(f) unless Dir.exist?(f)
check_dir f
else
check_file f check_file f
end end
end end
end end
def result def result
if (@files_to_delete.count == 0) && (@folders_to_delete.count == 0)
puts "\nNo orphens!\n\n"
return
end
# Files # Files
size = 0 size = 0
@files_to_delete.each{ |f| size += File.size(f) } @files_to_delete.each{ |f| size += File.size(f) }
puts "\n#{@files_to_delete.count} files havn't got a coresponding revision and can be deleted" puts "\n#{@files_to_delete.count} files havn't got a coresponding revision and can be deleted."
puts "#{number_to_human_size(size)} can be released\n\n" puts "#{number_to_human_size(size)} can be released.\n\n"
# Links
# Projects size = DmsfLink.where(:project_id => -1).count
puts "\n#{@folders_to_delete.count} directories havn't got coresponding projects and can be deleted\n\n" if(@folders_to_delete.count > 0) puts "#{size} links can be deleted.\n\n"
end end
def clean def clean
if (@files_to_delete.count == 0) && (@folders_to_delete.count == 0)
puts "\nNo orphens!\n\n"
return
end
# Files # Files
size = 0 size = 0
@files_to_delete.each do |f| @files_to_delete.each do |f|
size += File.size(f) size += File.size(f)
File.delete f File.delete f
end end
puts "\n#{@files_to_delete.count} files hadn't got a coresponding revision and have been be deleted" if(@files_to_delete.count > 0) puts "\n#{@files_to_delete.count} files hadn't got a coresponding revision and have been be deleted."
puts "#{number_to_human_size(size)} has been released\n\n" if(@files_to_delete.count > 0) puts "#{number_to_human_size(size)} has been released\n\n"
# Links
# Projects size = DmsfLink.where(:project_id => -1).count
@folders_to_delete.each do |d| DmsfLink.delete_all(:project_id => -1)
Dir.delete d puts "#{size} links have been deleted.\n\n"
end
puts "\n#{@folders_to_delete.count} directories hadn't got a coresponding projects and have been deleted\n\n" if(@folders_to_delete.count > 0)
end end
private private
@ -118,18 +106,5 @@ class DmsfMaintenance
STDERR.puts "\t#{file} doesn't seem to be a DMSF file!" STDERR.puts "\t#{file} doesn't seem to be a DMSF file!"
end end
end end
def check_dir(directory)
name = Pathname.new(directory).basename.to_s
if name =~ /^p_(.*)/
p = Project.find_by_identifier $1
unless p
@folders_to_delete << name
puts "\t#{name}"
end
else
STDERR.puts "\t#{directory} doesn't seem to be a DMSF folder!"
end
end
end end