Linking Issues and DMSF Documents #48

This commit is contained in:
Karel Picman 2017-01-26 10:20:39 +01:00
parent 94fad6275b
commit 65820f97f3
26 changed files with 720 additions and 365 deletions

View File

@ -66,7 +66,7 @@ class DmsfController < ApplicationController
@file_manipulation_allowed = User.current.allowed_to? :file_manipulation, @project
@file_delete_allowed = User.current.allowed_to? :file_delete, @project
@subfolders = DmsfFolder.deleted.where(:project_id => @project.id)
@files = DmsfFile.deleted.where(:project_id => @project.id)
@files = DmsfFile.deleted.where(:container_id => @project.id, :container_type => 'Project')
@dir_links = DmsfLink.deleted.where(:project_id => @project.id, :target_type => DmsfFolder.model_name.to_s)
@file_links = DmsfLink.deleted.where(:project_id => @project.id, :target_type => DmsfFile.model_name.to_s)
@url_links = DmsfLink.deleted.where(:project_id => @project.id, :target_type => 'DmsfUrl')

View File

@ -3,7 +3,7 @@
# Redmine plugin for Document Management System "Features"
#
# Copyright (C) 2011 Vít Jonáš <vit.jonas@gmail.com>
# Copyright (C) 2011-16 Karel Pičman <karel.picman@kontron.com>
# 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
@ -24,8 +24,9 @@ class DmsfUploadController < ApplicationController
menu_item :dmsf
before_filter :find_project
before_filter :authorize
before_filter :find_project, :except => [:upload]
before_filter :authorize, :except => [:upload]
before_filter :authorize_global, :only => [:upload]
before_filter :find_folder, :except => [:upload_file, :upload, :commit]
helper :all
@ -34,7 +35,7 @@ class DmsfUploadController < ApplicationController
accept_api_auth :upload, :commit
def upload_files
uploaded_files = params[:attachments]
uploaded_files = params[:dmsf_attachments]
@uploads = []
if uploaded_files && uploaded_files.is_a?(Hash)
# standard file input uploads
@ -134,129 +135,12 @@ class DmsfUploadController < ApplicationController
private
def commit_files_internal(commited_files)
if commited_files && commited_files.is_a?(Hash)
@files = []
failed_uploads = []
commited_files.each_value do |commited_file|
name = commited_file[:name]
new_revision = DmsfFileRevision.new
file = DmsfFile.visible.find_file_by_name(@project, @folder, name)
unless file
link = DmsfLink.find_link_by_file_name(@project, @folder, name)
file = link.target_file if link
end
unless file
file = DmsfFile.new
file.project = @project
file.name = name
file.dmsf_folder = @folder
file.notification = Setting.plugin_redmine_dmsf[:dmsf_default_notifications].present?
new_revision.minor_version = 0
new_revision.major_version = 0
else
if file.last_revision
last_revision = file.last_revision
new_revision.source_revision = last_revision
new_revision.major_version = last_revision.major_version
new_revision.minor_version = last_revision.minor_version
else
new_revision.minor_version = 0
new_revision.major_version = 0
end
end
if file.locked_for_user?
failed_uploads.push(commited_file)
next
end
commited_disk_filepath = "#{DmsfHelper.temp_dir}/#{commited_file[:disk_filename].gsub(/[\/\\]/,'')}"
new_revision.dmsf_file = file
new_revision.user = User.current
new_revision.name = name
new_revision.title = commited_file[:title]
new_revision.description = commited_file[:description]
new_revision.comment = commited_file[:comment]
version = commited_file[:version].to_i
if version == 3
new_revision.major_version = commited_file[:custom_version_major].to_i
new_revision.minor_version = commited_file[:custom_version_minor].to_i
else
new_revision.increase_version(version)
end
new_revision.mime_type = Redmine::MimeType.of(new_revision.name)
new_revision.size = File.size(commited_disk_filepath)
new_revision.digest = DmsfFileRevision.create_digest commited_disk_filepath
# Need to save file first to generate id for it in case of creation.
# File id is needed to properly generate revision disk filename
if commited_file[:dmsf_file_revision].present?
commited_file[:dmsf_file_revision][:custom_field_values].each_with_index do |v, i|
new_revision.custom_field_values[i].value = v[1]
end
end
if new_revision.valid? && file.save
new_revision.disk_filename = new_revision.new_storage_filename
else
failed_uploads.push(commited_file)
next
end
if new_revision.save
new_revision.assign_workflow(commited_file[:dmsf_workflow_id])
begin
FileUtils.mv(commited_disk_filepath, new_revision.disk_file)
file.set_last_revision new_revision
@files.push(file)
rescue Exception => e
Rails.logger.error e.message
flash[:error] = e.message
failed_uploads.push(file)
end
else
failed_uploads.push(commited_file)
end
end
unless @files.empty?
@files.each { |file| log_activity(file, 'uploaded') if file }
if (@folder && @folder.notification?) || (!@folder && @project.dmsf_notification?)
begin
recipients = DmsfMailer.get_notify_users(@project, @files)
recipients.each do |u|
DmsfMailer.files_updated(u, @project, @files).deliver
end
if Setting.plugin_redmine_dmsf[:dmsf_display_notified_recipients] == '1'
unless recipients.empty?
to = recipients.collect{ |r| r.name }.first(DMSF_MAX_NOTIFICATION_RECEIVERS_INFO).join(', ')
to << ((recipients.count > DMSF_MAX_NOTIFICATION_RECEIVERS_INFO) ? ',...' : '.')
flash[:warning] = l(:warning_email_notifications, :to => to)
end
end
rescue Exception => e
Rails.logger.error "Could not send email notifications: #{e.message}"
end
end
end
unless failed_uploads.empty?
flash[:warning] = l(:warning_some_files_were_not_commited, :files => failed_uploads.map{|u| u['name']}.join(', '))
end
end
DmsfUploadHelper.commit_files_internal(commited_files, @project, @folder)
respond_to do |format|
format.js
format.api {
render_validation_errors(failed_uploads) unless failed_uploads.empty?
}
format.api { render_validation_errors(failed_uploads) unless failed_uploads.empty? }
format.html { redirect_to dmsf_folder_path(:id => @project, :folder_id => @folder) }
end
end
def log_activity(file, action)
Rails.logger.info "#{Time.now.strftime('%Y-%m-%d %H:%M:%S')} #{User.current.login}@#{request.remote_ip}/#{request.env['HTTP_X_FORWARDED_FOR']}: #{action} dmsf://#{file.project.identifier}/#{file.id}/#{file.last_revision.id}"
end
def find_folder

View File

@ -0,0 +1,146 @@
# encoding: utf-8
#
# Redmine plugin for Document Management System "Features"
#
# Copyright (C) 2011-17 Karel Pičman <karel.picman@lbcfree.net>
#
# 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.
module DmsfUploadHelper
def self.commit_files_internal(commited_files, container, folder = nil)
if container.is_a?(Project)
project = container
else
project = container.project
end
if commited_files && commited_files.is_a?(Hash)
files = []
failed_uploads = []
commited_files.each_value do |commited_file|
name = commited_file[:name]
new_revision = DmsfFileRevision.new
file = DmsfFile.visible.find_file_by_name(container, folder, name)
unless file
link = DmsfLink.find_link_by_file_name(project, folder, name)
file = link.target_file if link
end
unless file
file = DmsfFile.new
file.container_type = container.class.name.demodulize
file.container_id = container.id
#file.project = project if container_type == 'Project'
file.name = name
file.dmsf_folder = folder
file.notification = Setting.plugin_redmine_dmsf[:dmsf_default_notifications].present?
new_revision.minor_version = 0
new_revision.major_version = 0
else
if file.last_revision
last_revision = file.last_revision
new_revision.source_revision = last_revision
new_revision.major_version = last_revision.major_version
new_revision.minor_version = last_revision.minor_version
else
new_revision.minor_version = 0
new_revision.major_version = 0
end
end
if file.locked_for_user?
failed_uploads.push(commited_file)
next
end
commited_disk_filepath = "#{DmsfHelper.temp_dir}/#{commited_file[:disk_filename].gsub(/[\/\\]/,'')}"
new_revision.dmsf_file = file
new_revision.user = User.current
new_revision.name = name
new_revision.title = commited_file[:title]
new_revision.description = commited_file[:description]
new_revision.comment = commited_file[:comment]
version = commited_file[:version].to_i
if version == 3
new_revision.major_version = commited_file[:custom_version_major].to_i
new_revision.minor_version = commited_file[:custom_version_minor].to_i
else
new_revision.increase_version(version)
end
new_revision.mime_type = Redmine::MimeType.of(new_revision.name)
new_revision.size = File.size(commited_disk_filepath)
new_revision.digest = DmsfFileRevision.create_digest commited_disk_filepath
# Need to save file first to generate id for it in case of creation.
# File id is needed to properly generate revision disk filename
if commited_file[:dmsf_file_revision].present?
commited_file[:dmsf_file_revision][:custom_field_values].each_with_index do |v, i|
new_revision.custom_field_values[i].value = v[1]
end
end
if new_revision.valid? && file.save
new_revision.disk_filename = new_revision.new_storage_filename
else
Rails.logger.error (new_revision.errors + file.errors).full_messages.to_sentence
failed_uploads.push(commited_file)
next
end
if new_revision.save
new_revision.assign_workflow(commited_file[:dmsf_workflow_id])
begin
FileUtils.mv(commited_disk_filepath, new_revision.disk_file)
file.set_last_revision new_revision
files.push(file)
rescue Exception => e
Rails.logger.error e.message
#flash[:error] = e.message
failed_uploads.push(file)
end
else
failed_uploads.push(commited_file)
end
end
#unless files.empty?
#files.each do |file|
#Rails.logger.info "#{Time.now.strftime('%Y-%m-%d %H:%M:%S')} #{User.current.login}: uploaded dmsf://#{file.project.identifier}/#{file.id}/#{file.last_revision.id}"
#end
#end
if container.is_a?(Project) && ((folder && folder.notification?) || (!folder && project.dmsf_notification?))
begin
recipients = DmsfMailer.get_notify_users(project, files)
recipients.each do |u|
DmsfMailer.files_updated(u, project, files).deliver
end
if Setting.plugin_redmine_dmsf[:dmsf_display_notified_recipients] == '1'
unless recipients.empty?
to = recipients.collect{ |r| r.name }.first(DMSF_MAX_NOTIFICATION_RECEIVERS_INFO).join(', ')
to << ((recipients.count > DMSF_MAX_NOTIFICATION_RECEIVERS_INFO) ? ',...' : '.')
#flash[:warning] = l(:warning_email_notifications, :to => to)
end
end
rescue Exception => e
Rails.logger.error "Could not send email notifications: #{e.message}"
end
end
end
unless failed_uploads.empty?
#flash[:warning] = l(:warning_some_files_were_not_commited, :files => failed_uploads.map{|u| u['name']}.join(', '))
end
end
end

View File

@ -3,7 +3,7 @@
# Redmine plugin for Document Management System "Features"
#
# Copyright (C) 2011 Vít Jonáš <vit.jonas@gmail.com>
# Copyright (C) 2011-16 Karel Pičman <karel.picman@kontron.com>
# 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
@ -32,7 +32,6 @@ class DmsfFile < ActiveRecord::Base
include RedmineDmsf::Lockable
belongs_to :project
belongs_to :dmsf_folder
belongs_to :deleted_by_user, :class_name => 'User', :foreign_key => 'deleted_by_user_id'
@ -56,10 +55,11 @@ class DmsfFile < ActiveRecord::Base
validate :validates_name_uniqueness
attr_accessible :project, :project_id
def validates_name_uniqueness
existing_file = DmsfFile.visible.find_file_by_name(self.project, self.dmsf_folder, self.name)
errors.add(:name, l('activerecord.errors.messages.taken')) unless
existing_file.nil? || existing_file.id == self.id
existing_file = DmsfFile.visible.findn_file_by_name(self.container_id, self.container_type, self.dmsf_folder, self.name)
errors.add(:name, l('activerecord.errors.messages.taken')) unless (existing_file.nil? || existing_file.id == self.id)
end
acts_as_event :title => Proc.new { |o| o.name },
@ -115,9 +115,14 @@ class DmsfFile < ActiveRecord::Base
@@storage_path = path
end
def self.find_file_by_name(project, folder, name)
def self.find_file_by_name(container, folder, name)
self.findn_file_by_name(container.id, container.class.name.demodulize, folder, name)
end
def self.findn_file_by_name(container_id, container_type, folder, name)
where(
:project_id => project,
:container_id => container_id,
:container_type => container_type,
:dmsf_folder_id => folder ? folder.id : nil,
:name => name).visible.first
end
@ -273,6 +278,7 @@ class DmsfFile < ActiveRecord::Base
file = DmsfFile.new
file.dmsf_folder = folder
file.container_type = 'Project'
file.project = project
file.name = self.name
file.notification = Setting.plugin_redmine_dmsf['dmsf_default_notifications'].present?
@ -533,4 +539,39 @@ class DmsfFile < ActiveRecord::Base
csv
end
def project
unless @project
case self.container_type
when 'Project'
@project = Project.find_by_id(self.container_id)
when 'Issue'
issue = Issue.find_by_id(self.container_id)
@project = issue.project if issue
end
end
@project
end
def project=(project)
case self.container_type
when 'Project'
self.container_id = project.id
else
raise Exception.new('The container type is not project!')
end
end
def project_id
self.project.id if self.project
end
def project_id=(project_id)
case self.container_type
when 'Project'
self.container_id = project_id
else
raise Exception.new('The container type is not project!')
end
end
end

View File

@ -51,8 +51,8 @@ class DmsfFileRevision < ActiveRecord::Base
:scope => select("#{DmsfFileRevision.table_name}.*").
joins(
"INNER JOIN #{DmsfFile.table_name} ON #{DmsfFileRevision.table_name}.dmsf_file_id = #{DmsfFile.table_name}.id " +
"INNER JOIN #{Project.table_name} ON #{DmsfFile.table_name}.project_id = #{Project.table_name}.id").
where("#{DmsfFile.table_name}.deleted = ?", STATUS_ACTIVE)
"INNER JOIN #{Project.table_name} ON #{DmsfFile.table_name}.container_id = #{Project.table_name}.id").
where("#{DmsfFile.table_name}.deleted = ? AND #{DmsfFile.table_name}.container_type = ?", STATUS_ACTIVE, 'Project')
validates :title, :presence => true
validates_format_of :name, :with => DmsfFolder::INVALID_CHARACTERS,

View File

@ -3,7 +3,7 @@
# Redmine plugin for Document Management System "Features"
#
# Copyright (C) 2011 Vít Jonáš <vit.jonas@gmail.com>
# Copyright (C) 2011-16 Karel Pičman <karel.picman@kontron.com>
# 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
@ -44,6 +44,6 @@ class DmsfFileRevisionAccess < ActiveRecord::Base
joins(
"INNER JOIN #{DmsfFileRevision.table_name} ON #{DmsfFileRevisionAccess.table_name}.dmsf_file_revision_id = #{DmsfFileRevision.table_name}.id " +
"INNER JOIN #{DmsfFile.table_name} ON #{DmsfFileRevision.table_name}.dmsf_file_id = #{DmsfFile.table_name}.id " +
"INNER JOIN #{Project.table_name} ON #{DmsfFile.table_name}.project_id = #{Project.table_name}.id").
where("#{DmsfFile.table_name}.deleted = ?", DmsfFile::STATUS_ACTIVE)
"INNER JOIN #{Project.table_name} ON #{DmsfFile.table_name}.container_id = #{Project.table_name}.id").
where("#{DmsfFile.table_name}.deleted = ? AND #{DmsfFile.table_name}.container_type = ?", DmsfFile::STATUS_ACTIVE, 'Project')
end

View File

@ -76,7 +76,8 @@ class DmsfUpload
@minor_version = 0
@workflow = nil
file = DmsfFile.new
file.project = @project
file.container_type = 'Project'
file.project = project
revision = DmsfFileRevision.new
revision.dmsf_file = file
@custom_values = revision.custom_field_values

View File

@ -106,9 +106,8 @@ class DmsfWorkflow < ActiveRecord::Base
def delegates(q, dmsf_workflow_step_assignment_id, dmsf_file_revision_id)
if dmsf_workflow_step_assignment_id && dmsf_file_revision_id
sql = [
'id NOT IN (SELECT a.user_id FROM dmsf_workflow_step_assignments a WHERE id = ?) AND id IN (SELECT m.user_id FROM members m JOIN dmsf_files f ON f.project_id = m.project_id JOIN dmsf_file_revisions r ON r.dmsf_file_id = f.id WHERE r.id = ?)',
dmsf_workflow_step_assignment_id,
dmsf_file_revision_id]
'id NOT IN (SELECT a.user_id FROM dmsf_workflow_step_assignments a WHERE id = ?) AND id IN (SELECT m.user_id FROM members m JOIN dmsf_files f ON f.container_id = m.project_id JOIN dmsf_file_revisions r ON r.dmsf_file_id = f.id WHERE r.id = ? AND container_type = ?)',
dmsf_workflow_step_assignment_id, dmsf_file_revision_id, 'Project']
elsif project
sql = ['id IN (SELECT user_id FROM members WHERE project_id = ?)', project.id]
else

View File

@ -178,7 +178,7 @@
<% if @folder %>
return "<%= "#{l(:label_number_of_folders)}: #{@folder.deep_folder_count} #{l(:label_number_of_documents)}: #{@folder.deep_file_count}" %>";
<% else %>
return "<%= "#{l(:label_number_of_folders)}: #{DmsfFolder.visible.where(:project_id => @project.id).count + DmsfLink.visible.where(:project_id => @project.id, :target_type => 'DmsfFolder').count}, #{l(:label_number_of_documents)}: #{DmsfFile.visible.where(:project_id => @project.id).count + DmsfLink.visible.where(:project_id => @project.id, :target_type => 'DmsfFile').count + DmsfLink.visible.where(:project_id => @project.id, :target_type => 'DmsfUrl').count}" %>";
return "<%= "#{l(:label_number_of_folders)}: #{DmsfFolder.visible.where(:project_id => @project.id).count + DmsfLink.visible.where(:project_id => @project.id, :target_type => 'DmsfFolder').count}, #{l(:label_number_of_documents)}: #{DmsfFile.visible.where(:container_id => @project.id, :container_type => 'Project').count + DmsfLink.visible.where(:project_id => @project.id, :target_type => 'DmsfFile').count + DmsfLink.visible.where(:project_id => @project.id, :target_type => 'DmsfUrl').count}" %>";
<% end %>
<% else %>
return "<%= "#{l(:label_number_of_folders)}: #{@subfolders.count + @dir_links.count}, #{l(:label_number_of_documents)}: #{@files.count + @file_links.count + @url_links.count}" %>";

View File

@ -0,0 +1,44 @@
<%
# 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.
%>
<hr/>
<div class="attachments">
<% for dmsf_file in dmsf_files %>
<p>
<% file_view_url = url_for({:controller => :dmsf_files, :action => 'view', :id => dmsf_file}) %>
<%= link_to(h(dmsf_file.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}") %>
<%= " - #{dmsf_file.description}" unless dmsf_file.description.blank? %>
<span class="size">(<%= number_to_human_size dmsf_file.last_revision.size %>)</span>
<%= link_to(image_tag('delete.png'),
dmsf_file_path(:id => dmsf_file),
:data => {:confirm => l(:text_are_you_sure)},
:method => :delete,
:title => l(:title_delete)) %>
<span class="author"><%= dmsf_file.last_revision.user %>, <%= format_time(dmsf_file.last_revision.updated_at) %></span>
</p>
<% end %>
</div>

View File

@ -1,36 +0,0 @@
<% hide = false %>
<span id="attachments_fields">
<% if defined?(container) && container && container.saved_attachments %>
<% container.saved_attachments.each_with_index do |attachment, i| %>
<span id="attachments_p<%= i %>">
<%= text_field_tag("attachments[p#{i}][filename]", attachment.filename, :class => 'filename') %>
<%= hidden_field_tag "attachments[p#{i}][token]", "#{attachment.token}" %>
<% hide = true %>
</span>
<% end %>
<% end %>
</span>
<% unless hide %>
<span class="add_attachment">
<%= file_field_tag 'attachments[dummy][file]',
:id => nil,
:class => 'file_selector',
:multiple => false,
:onchange => 'addInputFile(this);',
:data => {
:max_file_size => Setting.attachment_max_size.to_i.kilobytes,
:max_file_size_message => l(:error_attachment_too_big, :max_size => number_to_human_size(Setting.attachment_max_size.to_i.kilobytes)),
:max_file_count_message => l(:error_maximum_upload_filecount, :filecount => 1),
:max_concurrent_uploads => Redmine::Configuration['max_concurrent_ajax_uploads'].to_i,
:upload_path => uploads_path(:format => 'js'),
:description_placeholder => l(:label_optional_description)
} %>
(<%= l(:label_max_size) %>: <%= number_to_human_size(Setting.attachment_max_size.to_i.kilobytes) %>)
</span>
<% end %>
<% content_for :header_tags do %>
<%= javascript_include_tag 'attachments_dmsf', :plugin => 'redmine_dmsf' %>
<% end %>

View File

@ -0,0 +1,49 @@
<%
# 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.
%>
<span id="dmsf_attachments_fields">
<% if defined?(container) && container && container.saved_attachments %>
<% container.saved_attachments.each_with_index do |attachment, 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}][description]", attachment.description, :maxlength => 255, :placeholder => l(:label_optional_description), :class => 'description') +
link_to('&nbsp;'.html_safe, attachment_path(attachment, :attachment_id => "p#{i}", :format => 'js'), :method => 'delete', :remote => true, :class => 'remove-upload') %>
<%= hidden_field_tag "dmsf_attachments[p#{i}][token]", "#{attachment.token}" %>
</span>
<% end %>
<% end %>
</span>
<span class="dmsf_add_attachment">
<%= file_field_tag 'dmsf_attachments[dummy][file]',
:id => nil,
:class => 'file_selector',
:multiple => true,
:onchange => 'dmsfAddInputFiles(this);',
:data => {
:max_file_size => Setting.attachment_max_size.to_i.kilobytes,
:max_file_size_message => l(:error_attachment_too_big, :max_size => number_to_human_size(Setting.attachment_max_size.to_i.kilobytes)),
:max_concurrent_uploads => Redmine::Configuration['max_concurrent_ajax_uploads'].to_i,
:upload_path => dmsf_uploads_path(:format => 'js'),
:description_placeholder => l(:label_optional_description)
} %>
(<%= l(:label_max_size) %>: <%= number_to_human_size(Setting.attachment_max_size.to_i.kilobytes) %>)
</span>

View File

@ -36,11 +36,11 @@
<%= form_tag({:controller => 'dmsf_upload', :action => 'upload_files', :id => @project, :folder_id => @folder},
:id => 'uploadform', :method => :post, :multipart => true) do %>
<div id="uploader">
<div class="box">
<div id="dmsf_uploader">
<div class="dmsf_uploader">
<p>
<label><%= l(:label_attachment_plural) %></label>
<%= render :partial => 'attachments/form' %>
<label><%= l(:label_document_plural) %></label>
<%= render :partial => 'dmsf_upload/form' %>
</p>
</div>
<%= submit_tag l(:label_upload) %>
@ -49,14 +49,14 @@
<script type="text/javascript">
var originalUploaderContent;
var uploader = $('#uploader');
var uploader = $('#dmsf_uploader');
originalUploaderContent = uploader.html();
$('#uploader_select').change(function() {
if($(this).val() === '2') {
uploader.html(originalUploaderContent);
setupFileDrop();
dmsfSetupFileDrop();
} else {
initPlUploader(uploader);
}

View File

@ -0,0 +1,36 @@
<%
# 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 fileSpan = $('#dmsf_attachments_<%= j params[:attachment_id] %>');
<% if @attachment.new_record? %>
fileSpan.hide();
alert("<%= escape_javascript @attachment.errors.full_messages.join(', ') %>");
<% else %>
$('<input>', { type: 'hidden', name: 'dmsf_attachments[<%= j params[:attachment_id] %>][token]' } ).val('<%= j @attachment.token %>').appendTo(fileSpan);
fileSpan.find('a.remove-upload')
.attr({
"data-remote": true,
"data-method": 'delete',
href: '<%= j attachment_path(@attachment, :attachment_id => params[:attachment_id], :format => 'js') %>'
})
.off('click');
<% end %>

View File

@ -1,206 +1,191 @@
/* Redmine - project management software
Copyright (C) 2006-2016 Jean-Philippe Lang */
Copyright (C) 2006-2016 Jean-Philippe Lang */
function addFile(inputEl, file, eagerUpload) {
function dmsfAddFile(inputEl, file, eagerUpload) {
if ($('#attachments_fields').children().length < 10) {
if ($('#dmsf_attachments_fields').children().length < 10) {
var attachmentId = addFile.nextAttachmentId++;
var attachmentId = dmsfAddFile.nextAttachmentId++;
var fileSpan = $('<span>', { id: 'attachments_' + attachmentId });
var fileSpan = $('<span>', { id: 'dmsf_attachments_' + attachmentId });
fileSpan.append(
$('<input>', { type: 'text', 'class': 'filename readonly', name: 'attachments[' + attachmentId + '][filename]', readonly: 'readonly'} ).val(file.name)
).appendTo('#attachments_fields');
fileSpan.append(
$('<input>', { type: 'text', 'class': 'filename readonly', name: 'dmsf_attachments[' + attachmentId + '][filename]', readonly: 'readonly'} ).val(file.name),
$('<input>', { type: 'text', 'class': 'description', name: 'dmsf_attachments[' + attachmentId + '][description]', maxlength: 255, placeholder: $(inputEl).data('description-placeholder') } ).toggle(!eagerUpload),
$('<a>&nbsp</a>').attr({ href: "#", 'class': 'remove-upload' }).click(dmsfRemoveFile).toggle(!eagerUpload)
).appendTo('#dmsf_attachments_fields');
if(eagerUpload) {
ajaxUpload(file, attachmentId, fileSpan, inputEl);
}
toggleFileAdding(false);
$('#dmsf_file_revision_name').val(file.name);
return attachmentId;
}
return null;
}
addFile.nextAttachmentId = 1;
function ajaxUpload(file, attachmentId, fileSpan, inputEl) {
function onLoadstart(e) {
fileSpan.removeClass('ajax-waiting');
fileSpan.addClass('ajax-loading');
$('input:submit', $(this).parents('form')).attr('disabled', 'disabled');
}
function onProgress(e) {
if(e.lengthComputable) {
this.progressbar( 'value', e.loaded * 100 / e.total );
}
}
function actualUpload(file, attachmentId, fileSpan, inputEl) {
ajaxUpload.uploading++;
uploadBlob(file, $(inputEl).data('upload-path'), attachmentId, {
loadstartEventHandler: onLoadstart.bind(progressSpan),
progressEventHandler: onProgress.bind(progressSpan)
})
.done(function(result) {
progressSpan.progressbar( 'value', 100 ).remove();
fileSpan.find('input.description, a').css('display', 'inline-block');
})
.fail(function(result) {
progressSpan.text(result.statusText);
}).always(function() {
ajaxUpload.uploading--;
fileSpan.removeClass('ajax-loading');
var form = fileSpan.parents('form');
if (form.queue('upload').length == 0 && ajaxUpload.uploading == 0) {
$('input:submit', form).removeAttr('disabled');
if(eagerUpload) {
dmsfAjaxUpload(file, attachmentId, fileSpan, inputEl);
}
form.dequeue('upload');
});
}
var progressSpan = $('<div>').insertAfter(fileSpan.find('input.filename'));
progressSpan.progressbar();
fileSpan.addClass('ajax-waiting');
var maxSyncUpload = $(inputEl).data('max-concurrent-uploads');
if(maxSyncUpload == null || maxSyncUpload <= 0 || ajaxUpload.uploading < maxSyncUpload)
actualUpload(file, attachmentId, fileSpan, inputEl);
else
$(inputEl).parents('form').queue('upload', actualUpload.bind(this, file, attachmentId, fileSpan, inputEl));
}
ajaxUpload.uploading = 0;
function removeFile() {
$(this).parent('span').remove();
toggleFileAdding(true);
return false;
}
function uploadBlob(blob, uploadUrl, attachmentId, options) {
var actualOptions = $.extend({
loadstartEventHandler: $.noop,
progressEventHandler: $.noop
}, options);
uploadUrl = uploadUrl + '?attachment_id=' + attachmentId;
if (blob instanceof window.File) {
uploadUrl += '&filename=' + encodeURIComponent(blob.name);
uploadUrl += '&content_type=' + encodeURIComponent(blob.type);
}
return $.ajax(uploadUrl, {
type: 'POST',
contentType: 'application/octet-stream',
beforeSend: function(jqXhr, settings) {
jqXhr.setRequestHeader('Accept', 'application/js');
// attach proper File object
settings.data = blob;
},
xhr: function() {
var xhr = $.ajaxSettings.xhr();
xhr.upload.onloadstart = actualOptions.loadstartEventHandler;
xhr.upload.onprogress = actualOptions.progressEventHandler;
return xhr;
},
data: blob,
cache: false,
processData: false
});
}
function addInputFile(inputEl) {
var clearedFileInput = $(inputEl).clone().val('');
if ($.ajaxSettings.xhr().upload && inputEl.files) {
// upload files using ajax
uploadAndAttachFiles(inputEl.files, inputEl);
$(inputEl).remove();
} else {
// browser not supporting the file API, upload on form submission
var attachmentId;
var aFilename = inputEl.value.split(/\/|\\/);
attachmentId = addFile(inputEl, { name: aFilename[ aFilename.length - 1 ] }, false);
if (attachmentId) {
$(inputEl).attr({ name: 'attachments[' + attachmentId + '][file]', style: 'display:none;' }).appendTo('#attachments_' + attachmentId);
return attachmentId;
}
}
clearedFileInput.insertAfter('#attachments_fields');
toggleFileAdding(false);
return null;
}
function uploadAndAttachFiles(files, inputEl) {
dmsfAddFile.nextAttachmentId = 1;
var maxFileSize = $(inputEl).data('max-file-size');
var maxFileSizeExceeded = $(inputEl).data('max-file-size-message');
var maxFileCountExceeded = $(inputEl).data('max-file-count-message');
function dmsfAjaxUpload(file, attachmentId, fileSpan, inputEl) {
var sizeExceeded = false;
$.each(files, function() {
if (this.size && maxFileSize != null && this.size > parseInt(maxFileSize)) {sizeExceeded=true;}
});
if((files.length > 1) || (!$('input.file_selector').is(':visible'))){
window.alert(maxFileCountExceeded);
}
else if (sizeExceeded) {
window.alert(maxFileSizeExceeded);
} else {
$.each(files, function() {addFile(inputEl, this, true);});
}
function onLoadstart(e) {
fileSpan.removeClass('ajax-waiting');
fileSpan.addClass('ajax-loading');
$('input:submit', $(this).parents('form')).attr('disabled', 'disabled');
}
function onProgress(e) {
if(e.lengthComputable) {
this.progressbar( 'value', e.loaded * 100 / e.total );
}
}
function actualUpload(file, attachmentId, fileSpan, inputEl) {
dmsfAjaxUpload.uploading++;
dmsfUploadBlob(file, $(inputEl).data('upload-path'), attachmentId, {
loadstartEventHandler: onLoadstart.bind(progressSpan),
progressEventHandler: onProgress.bind(progressSpan)
})
.done(function(result) {
progressSpan.progressbar( 'value', 100 ).remove();
fileSpan.find('input.description, a').css('display', 'inline-block');
})
.fail(function(result) {
progressSpan.text(result.statusText);
}).always(function() {
dmsfAjaxUpload.uploading--;
fileSpan.removeClass('ajax-loading');
var form = fileSpan.parents('form');
if (form.queue('upload').length == 0 && dmsfAjaxUpload.uploading == 0) {
$('input:submit', form).removeAttr('disabled');
}
form.dequeue('upload');
});
}
var progressSpan = $('<div>').insertAfter(fileSpan.find('input.filename'));
progressSpan.progressbar();
fileSpan.addClass('ajax-waiting');
var maxSyncUpload = $(inputEl).data('max-concurrent-uploads');
if(maxSyncUpload == null || maxSyncUpload <= 0 || dmsfAjaxUpload.uploading < maxSyncUpload)
actualUpload(file, attachmentId, fileSpan, inputEl);
else
$(inputEl).parents('form').queue('upload', actualUpload.bind(this, file, attachmentId, fileSpan, inputEl));
}
function toggleFileAdding(toggle){
dmsfAjaxUpload.uploading = 0;
$('input.file_selector').toggle(toggle);
$('span.add_attachment').toggle(toggle);
function dmsfRemoveFile() {
$(this).parent('span').remove();
return false;
}
function handleFileDropEvent(e) {
function dmsfUploadBlob(blob, uploadUrl, attachmentId, options) {
$(this).removeClass('fileover');
blockEventPropagation(e);
var actualOptions = $.extend({
loadstartEventHandler: $.noop,
progressEventHandler: $.noop
}, options);
if ($.inArray('Files', e.dataTransfer.types) > -1) {
uploadAndAttachFiles(e.dataTransfer.files, $('input:file.file_selector'));
}
uploadUrl = uploadUrl + '?attachment_id=' + attachmentId;
if (blob instanceof window.File) {
uploadUrl += '&filename=' + encodeURIComponent(blob.name);
uploadUrl += '&content_type=' + encodeURIComponent(blob.type);
}
return $.ajax(uploadUrl, {
type: 'POST',
contentType: 'application/octet-stream',
beforeSend: function(jqXhr, settings) {
jqXhr.setRequestHeader('Accept', 'application/js');
// attach proper File object
settings.data = blob;
},
xhr: function() {
var xhr = $.ajaxSettings.xhr();
xhr.upload.onloadstart = actualOptions.loadstartEventHandler;
xhr.upload.onprogress = actualOptions.progressEventHandler;
return xhr;
},
data: blob,
cache: false,
processData: false
});
}
function dragOverHandler(e) {
function dmsfAddInputFiles(inputEl) {
var clearedFileInput = $(inputEl).clone().val('');
if ($.ajaxSettings.xhr().upload && inputEl.files) {
// upload files using ajax
dmsfUploadAndAttachFiles(inputEl.files, inputEl);
$(inputEl).remove();
} else {
// browser not supporting the file API, upload on form submission
var attachmentId;
var aFilename = inputEl.value.split(/\/|\\/);
attachmentId = dmsfAddFile(inputEl, { name: aFilename[ aFilename.length - 1 ] }, false);
if (attachmentId) {
$(inputEl).attr({ name: 'dmsf_attachments[' + attachmentId + '][file]', style: 'display:none;' }).appendTo('#dmsf_attachments_' + attachmentId);
}
}
clearedFileInput.insertAfter('#dmsf_attachments_fields');
}
function dmsfUploadAndAttachFiles(files, inputEl) {
var maxFileSize = $(inputEl).data('max-file-size');
var maxFileSizeExceeded = $(inputEl).data('max-file-size-message');
var sizeExceeded = false;
$.each(files, function() {
if (this.size && maxFileSize != null && this.size > parseInt(maxFileSize)) {sizeExceeded=true;}
});
if (sizeExceeded) {
window.alert(maxFileSizeExceeded);
} else {
$.each(files, function() {dmsfAddFile(inputEl, this, true);});
}
}
function dmsfHandleFileDropEvent(e) {
$(this).removeClass('fileover');
blockEventPropagation(e);
if ($.inArray('Files', e.dataTransfer.types) > -1) {
dmsfUploadAndAttachFiles(e.dataTransfer.files, $('input:file.file_selector'));
}
}
function dmsfDragOverHandler(e) {
$(this).addClass('fileover');
blockEventPropagation(e);
}
function dragOutHandler(e) {
$(this).removeClass('fileover');
blockEventPropagation(e);
function dmsfDragOutHandler(e) {
$(this).removeClass('fileover');
blockEventPropagation(e);
}
function setupFileDrop() {
if (window.File && window.FileList && window.ProgressEvent && window.FormData) {
function dmsfSetupFileDrop() {
if (window.File && window.FileList && window.ProgressEvent && window.FormData) {
$.event.fixHooks.drop = { props: [ 'dataTransfer' ] };
$.event.fixHooks.drop = { props: [ 'dataTransfer' ] };
$('form div.box').has('input:file').each(function() {
$(this).on({
dragover: dragOverHandler,
dragleave: dragOutHandler,
drop: handleFileDropEvent
});
});
}
$('form div.dmsf_uploader').has('input:file').each(function() {
$(this).on({
dragover: dmsfDragOverHandler,
dragleave: dmsfDragOutHandler,
drop: dmsfHandleFileDropEvent
});
});
}
}
$(document).ready(setupFileDrop);
$(document).ready(dmsfSetupFileDrop);

View File

@ -252,12 +252,28 @@
.dmsf_parent_container {
overflow: hidden;
/*padding: 20px 0px 0px 0px;*/
}
.dmsf_child_container {
float: left;
text-align: left;
/*padding: 0px 10px 0px 0px;
width: 200px;*/
}
/* DMSF file upload */
.dmsf_uploader{
padding:6px;
margin-bottom: 10px;
background-color:#f6f6f6;
color:#505050;
line-height:1.5em;
border: 1px solid #e4e4e4;
word-wrap: break-word;
border-radius: 3px;
}
#dmsf_attachments_fields input.description {margin-left:4px; width:340px;}
#dmsf_attachments_fields span {display:block; white-space:nowrap;}
#dmsf_attachments_fields input.filename {border:0; height:1.8em; width:250px; color:#555; background-color:inherit; background:url(../../../images/attachment.png) no-repeat 1px 50%; padding-left:18px;}
#dmsf_attachments_fields .ajax-waiting input.filename {background:url(../../../images/hourglass.png) 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; }

View File

@ -64,6 +64,7 @@ RedmineApp::Application.routes.draw do
post '/projects/:id/dmsf/upload', :controller => 'dmsf_upload', :action => 'upload'
post '/projects/:id/dmsf/upload/commit', :controller => 'dmsf_upload', :action => 'commit_files'
post '/projects/:id/dmsf/commit', :controller => 'dmsf_upload', :action => 'commit'
match 'dmsf_uploads', :to => 'dmsf_upload#upload', :via => :post
#
# dmsf_files controller

View File

@ -0,0 +1,36 @@
# 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.
class DmsfFileContainer < ActiveRecord::Migration
def up
remove_index :dmsf_files, :project_id
rename_column :dmsf_files, :project_id, :container_id
add_column :dmsf_files, :container_type, :string, :limit => 30, :null => false, :default => 'Project'
DmsfFile.update_all(:container_type => 'Project')
add_index :dmsf_files, [:container_id, :container_type]
end
def down
remove_index :dmsf_files, [:container_id, :container_type]
remove_column :dmsf_files, :container_type
rename_column :dmsf_files, :container_id, :project_id
add_index :dmsf_files, :project_id
end
end

View File

@ -4,7 +4,7 @@
#
# Copyright (C) 2011 Vít Jonáš <vit.jonas@gmail.com>
# Copyright (C) 2012 Daniel Munn <dan.munn@munnster.co.uk>
# Copyright (C) 2011-16 Karel Pičman <karel.picman@kontron.com>
# 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
@ -31,6 +31,7 @@ require 'redmine_dmsf/patches/project_patch'
require 'redmine_dmsf/patches/project_tabs_extended'
require 'redmine_dmsf/patches/user_preference_patch'
require 'redmine_dmsf/patches/user_patch'
require 'redmine_dmsf/patches/issue_patch'
# Load up classes that make up our WebDAV solution ontop of DAV4Rack
require 'redmine_dmsf/webdav/base_resource'
@ -51,10 +52,12 @@ require 'redmine_dmsf/errors/dmsf_lock_error.rb'
require 'redmine_dmsf/errors/dmsf_zip_max_file_error.rb'
# Hooks
require 'redmine_dmsf/hooks/controllers/search_controller_hooks'
require 'redmine_dmsf/hooks/controllers/issues_controller_hooks'
require 'redmine_dmsf/hooks/views/view_projects_form_hook'
require 'redmine_dmsf/hooks/views/base_view_hooks'
require 'redmine_dmsf/hooks/controllers/search_controller_hooks'
require 'redmine_dmsf/hooks/views/my_account_view_hooks'
require 'redmine_dmsf/hooks/views/issue_view_hooks'
# Macros
require 'redmine_dmsf/macros'

View File

@ -0,0 +1,52 @@
# 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.
module RedmineDmsf
module Hooks
include Redmine::Hook
#include ::DmsfUploadHelper
#helper :dmsf_upload
class ControllerIssuesHook < RedmineDmsf::Hooks::Listener
def controller_issues_new_after_save(context={})
if context.is_a?(Hash)
issue = context[:issue]
params = context[:params]
uploaded_files = params[:dmsf_attachments]
uploads = []
if uploaded_files && uploaded_files.is_a?(Hash)
# standard file input uploads
uploaded_files.each_value do |uploaded_file|
upload = DmsfUpload.create_from_uploaded_attachment(issue.project, nil, uploaded_file)
#uploads.push(upload) if upload
uploaded_file[:disk_filename] = upload.disk_filename
uploaded_file[:name] = upload.name
uploaded_file[:title] = upload.title
end
DmsfUploadHelper.commit_files_internal uploaded_files, issue
end
end
end
end
end
end

View File

@ -2,7 +2,7 @@
#
# Redmine plugin for Document Management System "Features"
#
# Copyright (C) 2011-16 Karel Pičman <karel.picman@kontron.com>
# 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
@ -28,7 +28,8 @@ module RedmineDmsf
"\n".html_safe + stylesheet_link_tag('redmine_dmsf.css', :plugin => :redmine_dmsf) +
"\n".html_safe + stylesheet_link_tag('select2.min.css', :plugin => :redmine_dmsf) +
"\n".html_safe + javascript_include_tag('select2.min.js', :plugin => :redmine_dmsf) +
"\n".html_safe + javascript_include_tag('redmine_dmsf.js', :plugin => :redmine_dmsf)
"\n".html_safe + javascript_include_tag('redmine_dmsf.js', :plugin => :redmine_dmsf) +
"\n".html_safe + javascript_include_tag('attachments_dmsf.js', :plugin => :redmine_dmsf)
end
end

View File

@ -0,0 +1,51 @@
# 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.
module RedmineDmsf
module Hooks
include Redmine::Hook
class DmsfViewListener < Redmine::Hook::ViewListener
def view_issues_form_details_bottom(context={})
if context.is_a?(Hash) && context[:issue]
issue = context[:issue]
# Add Dmsf upload form
html = "<div class=\"dmsf_uploader\">"
html << '<p>'
html << "<label>#{l(:label_document_plural)}</label>"
html << context[:controller].send(:render_to_string, {:partial => 'dmsf_upload/form', :locals => context})
html << '</p>'
html << '</div>'
html.html_safe
end
end
def view_issues_show_description_bottom(context={})
if context.is_a?(Hash) && context[:issue]
issue = context[:issue]
context[:controller].send(:render_to_string, {:partial => 'dmsf_files/links',
:locals => { :dmsf_files => issue.dmsf_files.to_a }})
end
end
end
end
end

View File

@ -0,0 +1,44 @@
# 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.
require_dependency 'issue'
module RedmineDmsf
module Patches
module IssuePatch
def self.included(base)
base.class_eval do
unloadable
has_many :dmsf_files, -> { where(dmsf_folder_id: nil, container_type: 'Issue').order(:name) },
:class_name => 'DmsfFile', :foreign_key => 'container_id', :dependent => :destroy
end
end
end
end
end
# Apply patch
Rails.configuration.to_prepare do
unless Issue.included_modules.include?(RedmineDmsf::Patches::IssuePatch)
Issue.send(:include, RedmineDmsf::Patches::IssuePatch)
end
end

View File

@ -32,8 +32,8 @@ module RedmineDmsf
unloadable
alias_method_chain :copy, :dmsf
has_many :dmsf_files, -> { where(dmsf_folder_id: nil).order(:name) },
:class_name => 'DmsfFile', :foreign_key => 'project_id', :dependent => :destroy
has_many :dmsf_files, -> { where(dmsf_folder_id: nil, container_type: 'Project').order(:name) },
:class_name => 'DmsfFile', :foreign_key => 'container_id', :dependent => :destroy
has_many :dmsf_folders, -> { where(dmsf_folder_id: nil).order(:title) },
:class_name => 'DmsfFolder', :foreign_key => 'project_id',
:dependent => :destroy

View File

@ -566,6 +566,7 @@ module RedmineDmsf
else
raise BadRequest unless (parent.projectless_path == '/' || (parent.exist? && parent.folder))
f = DmsfFile.new
f.container_type = 'Project'
f.project = project
f.name = basename
f.dmsf_folder = parent.folder

View File

@ -111,6 +111,7 @@ class DmsfConvertDocuments
document.attachments.each do |attachment|
begin
file = DmsfFile.new
file.container_type = 'Project'
file.project = project
file.dmsf_folder = folder