Easy compatibility

This commit is contained in:
1redmine 2025-06-10 14:16:56 +02:00
parent d04fc0d8d9
commit 6461ad37af
75 changed files with 852 additions and 424 deletions

View File

@ -17,6 +17,8 @@
# You should have received a copy of the GNU General Public License along with Redmine DMSF plugin. If not, see
# <https://www.gnu.org/licenses/>.
require "#{File.dirname(__FILE__)}/../../lib/redmine_dmsf/preview"
# Context menu controller
class DmsfContextMenusController < ApplicationController
helper :context_menus

View File

@ -30,7 +30,7 @@ class DmsfController < ApplicationController
except: %i[new create edit_root save_root add_email append_email autocomplete_for_user digest
reset_digest]
before_action :find_parent, only: %i[new create delete]
before_action :permissions
before_action :permissions?
# Also try to lookup folder by title if this is an API call
before_action :find_folder_by_title, only: [:show]
before_action :query, only: %i[expand_folder show trash empty_trash index]
@ -50,7 +50,7 @@ class DmsfController < ApplicationController
helper :context_menus
helper :watchers
def permissions
def permissions?
if !DmsfFolder.permissions?(@folder, allow_system: false)
render_403
elsif @folder && @project && (@folder.project != @project)
@ -82,7 +82,7 @@ class DmsfController < ApplicationController
@file_manipulation_allowed = User.current.allowed_to?(:file_manipulation, @project)
@trash_enabled = @folder_manipulation_allowed && @file_manipulation_allowed
@notifications = Setting.notified_events.include?('dmsf_legacy_notifications')
@query.dmsf_folder_id = @folder ? @folder.id : nil
@query.dmsf_folder_id = @folder&.id
@query.deleted = false
@query.sub_projects |= RedmineDmsf.dmsf_projects_as_subfolders?
if @folder&.deleted? || (params[:folder_title].present? && !@folder)
@ -363,7 +363,7 @@ class DmsfController < ApplicationController
def lock
if @folder.nil?
flash[:warning] = l(:warning_foler_unlockable)
flash[:warning] = l(:warning_folder_unlockable)
elsif @folder.locked?
flash[:warning] = l(:warning_folder_already_locked)
else
@ -375,7 +375,7 @@ class DmsfController < ApplicationController
def unlock
if @folder.nil?
flash[:warning] = l(:warning_foler_unlockable)
flash[:warning] = l(:warning_folder_unlockable)
elsif !@folder.locked?
flash[:warning] = l(:warning_folder_not_locked)
elsif @folder.locks[0].user == User.current || User.current.allowed_to?(:force_file_unlock, @project)

View File

@ -25,7 +25,7 @@ class DmsfFilesController < ApplicationController
before_action :find_revision, only: %i[delete_revision obsolete_revision]
before_action :find_folder, only: %i[delete create_revision]
before_action :authorize
before_action :permissions
before_action :permissions?
accept_api_auth :show, :view, :delete, :create_revision
@ -38,7 +38,7 @@ class DmsfFilesController < ApplicationController
include QueriesHelper
def permissions
def permissions?
render_403 if @file && !DmsfFolder.permissions?(@file.dmsf_folder, allow_system: true, file: true)
true
end

View File

@ -24,11 +24,11 @@ class DmsfFolderPermissionsController < ApplicationController
if: -> { params[:dmsf_folder_id].present? }
before_action :find_project
before_action :authorize
before_action :permissions
before_action :permissions?
helper :dmsf
def permissions
def permissions?
render_403 unless DmsfFolder.permissions?(@dmsf_folder)
true
end

View File

@ -25,7 +25,7 @@ class DmsfLinksController < ApplicationController
before_action :find_link_project
before_action :find_folder, only: [:destroy]
before_action :authorize
before_action :permissions
before_action :permissions?
protect_from_forgery except: :new
@ -33,7 +33,7 @@ class DmsfLinksController < ApplicationController
helper :dmsf
def permissions
def permissions?
render_403 if @dmsf_link && !DmsfFolder.permissions?(@dmsf_link.dmsf_folder)
true
end

View File

@ -25,7 +25,7 @@ class DmsfUploadController < ApplicationController
before_action :authorize, except: %i[upload delete_dmsf_attachment delete_dmsf_link_attachment]
before_action :authorize_global, only: %i[upload delete_dmsf_attachment delete_dmsf_link_attachment]
before_action :find_folder, except: %i[upload commit delete_dmsf_attachment delete_dmsf_link_attachment]
before_action :permissions, except: %i[upload commit delete_dmsf_attachment delete_dmsf_link_attachment]
before_action :permissions?, except: %i[upload commit delete_dmsf_attachment delete_dmsf_link_attachment]
helper :custom_fields
helper :dmsf_workflows
@ -33,7 +33,7 @@ class DmsfUploadController < ApplicationController
accept_api_auth :upload, :commit
def permissions
def permissions?
render_403 unless DmsfFolder.permissions?(@folder)
true
end
@ -107,7 +107,7 @@ class DmsfUploadController < ApplicationController
@folder = DmsfFolder.visible.find_by(id: attachments[:folder_id]) if attachments[:folder_id].present?
# standard file input uploads
uploaded_files = attachments.select { |key, _| key == 'uploaded_file' }
uploaded_files = attachments.slice('uploaded_file')
uploaded_files.each_value do |uploaded_file|
upload = DmsfUpload.create_from_uploaded_attachment(@project, @folder, uploaded_file)
next unless upload

View File

@ -26,7 +26,7 @@ class DmsfWorkflowsController < ApplicationController
before_action :find_model_object, except: %i[create new index assign assignment]
before_action :find_project
before_action :authorize_custom
before_action :permissions, only: %i[new_action assignment start]
before_action :permissions?, only: %i[new_action assignment start]
before_action :approver_candidates, only: %i[remove_step show reorder_steps add_step]
before_action :prevent_from_editing, only: %i[destroy remove_step update add_step update_step reorder_steps]
@ -34,7 +34,7 @@ class DmsfWorkflowsController < ApplicationController
helper :dmsf
def permissions
def permissions?
revision = DmsfFileRevision.find_by(id: params[:dmsf_file_revision_id]) if params[:dmsf_file_revision_id].present?
render_403 unless revision&.dmsf_file || DmsfFolder.permissions?(revision&.dmsf_file&.dmsf_folder)
true
@ -76,7 +76,7 @@ class DmsfWorkflowsController < ApplicationController
{ dmsf_file_revision: revision, step_action: params[:step_action] })
if (result.blank? || result.first) && action.save
if revision
if @dmsf_workflow.try_finish revision, action, (params[:step_action].to_i / 10)
if @dmsf_workflow.try_finish? revision, action, (params[:step_action].to_i / 10)
if revision.dmsf_file
begin
revision.dmsf_file.unlock!(force_file_unlock_allowed: true) unless RedmineDmsf.dmsf_keep_documents_locked?
@ -413,8 +413,8 @@ class DmsfWorkflowsController < ApplicationController
if request.put?
if @assigned
flash[:error] = l(:error_dmsf_workflow_assigned)
elsif !@dmsf_workflow.reorder_steps(params[:step].to_i, params[:dmsf_workflow][:position].to_i)
flash[:error] = l(:notice_cannot_renumber_steps)
else
@dmsf_workflow.reorder_steps params[:step].to_i, params[:dmsf_workflow][:position].to_i
end
end
respond_to do |format|

View File

@ -86,9 +86,7 @@ module DmsfHelper
def email_entry_tmp_file_path(entry)
sanitized_entry = DmsfHelper.sanitize_filename(entry)
file_name = "#{RedmineDmsf::DmsfZip::FILE_PREFIX}#{sanitized_entry}.zip"
# rubocop:disable Rails/FilePath
File.join(Rails.root.to_s, 'tmp', file_name)
# rubocop:enable Rails/FilePath
Rails.root.join 'tmp', file_name
end
# Extracts the variable part of the temp file name to be used as identifier in the
@ -96,6 +94,6 @@ module DmsfHelper
def tmp_entry_identifier(zipped_content)
path = Pathname.new(zipped_content)
zipped_file = path.basename(path.extname).to_s
zipped_file.delete_prefix(RedmineDmsf::DmsfZip::FILE_PREFIX)
zipped_file.delete_prefix RedmineDmsf::DmsfZip::FILE_PREFIX
end
end

View File

@ -32,15 +32,22 @@ module DmsfQueriesHelper
file = DmsfFile.find_by(id: item.id)
if file&.locked?
return content_tag(:span, val) +
content_tag('span', sprite_icon('unlock', nil, icon_only: true, size: '12'),
title: l(:title_locked_by_user, user: file.locked_by))
link_to(sprite_icon('unlock', nil, icon_only: true, size: '12'),
unlock_dmsf_files_path(id: file,
back_url: dmsf_folder_path(id: file.project,
folder_id: file.dmsf_folder)),
title: l(:title_locked_by_user, user: file.locked_by), class: 'icon icon-unlock')
end
when 'folder'
folder = DmsfFolder.find_by(id: item.id)
if folder&.locked?
return content_tag(:span, val) +
content_tag('span', sprite_icon('unlock', nil, icon_only: true, size: '12'),
title: l(:title_locked_by_user, user: folder.locked_by))
link_to(sprite_icon('unlock', nil, icon_only: true, size: '12'),
unlock_dmsf_path(id: folder.project,
folder_id: folder.id,
back_url: dmsf_folder_path(id: folder.project,
folder_id: folder.dmsf_folder)),
title: l(:title_locked_by_user, user: folder.locked_by), class: 'icon icon-unlock')
end
end
content_tag(:span, val) + content_tag(:span, '', class: 'icon icon-none')

View File

@ -18,6 +18,7 @@
# <https://www.gnu.org/licenses/>.
require "#{File.dirname(__FILE__)}/../../lib/redmine_dmsf/lockable"
require "#{File.dirname(__FILE__)}/../../lib/redmine_dmsf/plugin"
require 'English'
# File
@ -569,15 +570,17 @@ class DmsfFile < ApplicationRecord
end
def assigned?(user)
if last_revision&.dmsf_workflow
return false unless last_revision&.dmsf_workflow
last_revision.dmsf_workflow.next_assignments(last_revision.id).each do |assignment|
return true if assignment.user == user
end
end
false
end
def custom_value(custom_field)
return nill unless last_revision
last_revision.custom_field_values.each do |cv|
return cv if cv.custom_field == custom_field
end

View File

@ -127,7 +127,7 @@ class DmsfFileRevision < ApplicationRecord
errors.add :base, l(:error_file_is_locked)
return false
end
if !commit && (!force && (dmsf_file.dmsf_file_revisions.length <= 1))
if !commit && !force && (dmsf_file.dmsf_file_revisions.length <= 1)
errors.add :base, l(:error_at_least_one_revision_must_be_present)
return false
end

View File

@ -127,21 +127,13 @@ class DmsfFolder < ApplicationRecord
if folder.dmsf_folder_permissions.any?
role_ids = User.current.roles_for_project(folder.project).map(&:id)
role_permission_ids = folder.dmsf_folder_permissions.roles.map(&:object_id)
if RUBY_VERSION < '3.1' # intersect? method added in Ruby 3.1, though we support 2.7 too
return true if role_ids.intersection(role_permission_ids).any?
elsif role_ids.intersect?(role_permission_ids)
return true
end
return true if role_ids.intersect?(role_permission_ids)
principal_ids = folder.dmsf_folder_permissions.users.map(&:object_id)
return true if principal_ids.include?(User.current.id)
user_group_ids = User.current.groups.map(&:id)
if RUBY_VERSION < '3.1' # intersect? method added in Ruby 3.1, though we support 2.7 too
principal_ids.intersection(user_group_ids).any?
else
principal_ids.intersect?(user_group_ids)
end
principal_ids.intersect? user_group_ids
else
DmsfFolder.permissions? folder.dmsf_folder, allow_system: allow_system, file: file
end

View File

@ -110,7 +110,7 @@ class DmsfLink < ApplicationRecord
link.name = name
link.external_url = external_url
link.project_id = project.id
link.dmsf_folder_id = folder ? folder.id : nil
link.dmsf_folder_id = folder&.id
link.user = User.current
link.save!
link

View File

@ -109,7 +109,6 @@ class DmsfWorkflow < ApplicationRecord
end
end
end
true
end
def delegates(query, dmsf_workflow_step_assignment_id, dmsf_file_revision_id)
@ -186,7 +185,7 @@ class DmsfWorkflow < ApplicationRecord
end
end
def try_finish(revision, action, user_id)
def try_finish?(revision, action, user_id)
case action.action
when DmsfWorkflowStepAction::ACTION_APPROVE
assignments = next_assignments(revision.id)
@ -216,7 +215,7 @@ class DmsfWorkflow < ApplicationRecord
def copy_to(project, name = nil)
new_wf = dup
new_wf.name = name if name
new_wf.project_id = project ? project.id : nil
new_wf.project_id = project&.id
new_wf.author = User.current
if new_wf.save
dmsf_workflow_steps.each do |step|

View File

@ -17,7 +17,7 @@
# <https://www.gnu.org/licenses/>.
%>
<h2>
<h2 class="dmsf-header">
<% if folder %>
<%= link_to l(:link_documents), dmsf_folder_path(id: @project) %>
<% folder.dmsf_path.each do |path_element| %>

View File

@ -39,7 +39,10 @@
<%= check_box_tag('ids[]', "#{node.type}-#{node.id}", false, id: nil) unless node.system %>
</td>
<% query.inline_columns.each do |column| %>
<%= content_tag 'td', column_content(column, node), class: column.css_classes %>
<% classes = column.css_classes.to_s.dup %>
<% classes << ' dmsf-gray' if node.type.match?(/link$/) %>
<% classes << ' dmsf-system' if node.system %>
<%= content_tag 'td', column_content(column, node), class: classes %>
<% end %>
<td class="buttons">
<% unless node.system %>

View File

@ -21,7 +21,7 @@
<div class="contextual">
<% if @file_delete_allowed %>
<%= delete_link empty_trash_path(id: @project), {}, l(:label_empty_trash_bin) %>
<%= link_to sprite_icon('del', l(:label_empty_trash_bin)), empty_trash_path(id: @project) %>
<% end %>
</div>

View File

@ -17,7 +17,7 @@
# <https://www.gnu.org/licenses/>.
%>
<div class="box tabular">
<div id="dmsf_new_revision" class="box tabular">
<strong><%= l(:heading_new_revision) %>
<a href="#" id="new_revision_form_content_toggle" data-cy="toggle__new_revision_from_content--dmsf">[+]</a>
</strong>

View File

@ -60,9 +60,11 @@
<%= render partial: 'file_new_revision' %>
<% end %>
<div class="dmsf-id-box">
<div>
<div class="dmsf-id-box">
<strong><%= label_tag '', l(:label_document) %></strong>
#<%= @file.id %>
</div>
</div>
<h3><%= l(:heading_revisions) %></h3>
@ -163,7 +165,6 @@
</div>
</div>
</div>
<br>
<% end %>
<span class="pagination"><%= pagination_links_full @revision_pages, @revision_count %></span>

View File

@ -4,7 +4,8 @@
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Wiki formatting</title>
<%= stylesheet_link_tag 'wiki_syntax.css' %>
<%= stylesheet_link_tag 'dmsf_help.css', plugin: :redmine_dmsf %>
<% plugin = defined?(EasyExtensions) ? nil : :redmine_dmsf %>
<%= stylesheet_link_tag 'dmsf_help.css', plugin: plugin %>
</head>
<body>

View File

@ -66,6 +66,7 @@
options_for_select(options, selected: @project.default_dmsf_query_id) %>
<em class="info"><%= l('text_allowed_queries_to_select') %></em>
</p>
<%= call_hook(:view_dmsf_state_user_pref, { project: @project }) %>
</fieldset>
<div class="form-actions">

View File

@ -0,0 +1,23 @@
<%
# encoding: utf-8
#
# Redmine plugin for Document Management System "Features"
#
# Karel Pičman <karel.picman@kontron.com>
#
# This file is part of Redmine DMSF plugin.
#
# Redmine DMSF plugin 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 3 of the License, or (at your option) any
# later version.
#
# Redmine DMSF plugin 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 Redmine DMSF plugin. If not, see
# <https://www.gnu.org/licenses/>.
%>
<%= stylesheet_link_tag('easy_dmsf') %>
<%= javascript_include_tag('easy_dmsf', defer: true) %>

View File

@ -0,0 +1,26 @@
<%
# encoding: utf-8
#
# Redmine plugin for Document Management System "Features"
#
# Karel Pičman <karel.picman@kontron.com>
#
# This file is part of Redmine DMSF plugin.
#
# Redmine DMSF plugin 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 3 of the License, or (at your option) any
# later version.
#
# Redmine DMSF plugin 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 Redmine DMSF plugin. If not, see
# <https://www.gnu.org/licenses/>.
%>
<%= stylesheet_link_tag('redmine_dmsf', plugin: :redmine_dmsf) %>
<%= stylesheet_link_tag('select2.min', plugin: :redmine_dmsf) %>
<%= javascript_include_tag('select2.min', plugin: :redmine_dmsf, defer: true) %>
<%= javascript_include_tag('redmine_dmsf', plugin: :redmine_dmsf, defer: true) %>
<%= javascript_include_tag('attachments_dmsf', plugin: :redmine_dmsf, defer: true) %>

View File

@ -65,8 +65,8 @@
<%= link_to_project folder.project %>
</td>
<td class="title">
<%= link_to h(folder.title), dmsf_folder_path(id: folder.project, folder_id: folder),
class: 'icon icon-folder' %>
<%= link_to sprite_icon('folder', h(folder.title)),
dmsf_folder_path(id: folder.project, folder_id: folder), class: 'icon icon-folder' %>
</td>
<td class="title">
<% if folder.dmsf_folder %>

View File

@ -80,8 +80,8 @@
<%= link_to_project folder.project %>
</td>
<td class="title">
<%= link_to h(folder.title), dmsf_folder_path(id: folder.project, folder_id: folder),
class: 'icon icon-folder' %>
<%= link_to sprite_icon('folder', h(folder.title)),
dmsf_folder_path(id: folder.project, folder_id: folder), class: 'icon icon-folder' %>
</td>
<td class="title">
<% if folder.dmsf_folder %>

View File

@ -0,0 +1,24 @@
/*
Redmine plugin for Document Management System "Features"
Karel Pičman <karel.picman@kontron.com>
This file is part of Redmine DMSF plugin.
Redmine DMSF plugin 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 3 of the License, or (at your option) any
later version.
Redmine DMSF plugin 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 Redmine DMSF plugin. If not, see
<https://www.gnu.org/licenses/>.
*/
/*
*= require select2.min
*= require redmine_dmsf
*= require attachments_dmsf
*/

View File

@ -0,0 +1,23 @@
/*
Redmine plugin for Document Management System "Features"
Karel Pičman <karel.picman@kontron.com>
This file is part of Redmine DMSF plugin.
Redmine DMSF plugin 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 3 of the License, or (at your option) any
later version.
Redmine DMSF plugin 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 Redmine DMSF plugin. If not, see
<https://www.gnu.org/licenses/>.
*/
/*
*= require redmine_dmsf
*= require select2.min
*/

View File

@ -75,23 +75,39 @@ div[id^="step-index-"] {
}
/* DMSF revision box */
h2.dmsf-header {
border: none !important;
}
#new_revision_form_content {
display: none;
}
form#new_revision_form {
margin: 0;
}
.dmsf-revision-box {
background-color: #f6f6f6;
margin-bottom: 16px;
}
.dmsf-revision-inner-box {
border: 1px solid #e4e4e4;
padding: 10px;
border-radius: 3px;
box-shadow: 0 1px 2px rgba(0,0,0,0.05);
}
div.dmsf-revision-inner-box .attribute {
padding-left: 180px;
padding: 0;
clear: left;
min-height: 1.8em;
border: none;
}
div.dmsf-revision-inner-box .attribute .label {
margin-left: 0 !important;
}
div.dmsf-revision-inner-box .attribute .label {
@ -110,6 +126,18 @@ div.dmsf-id-box {
padding-left: 10px;
}
div#dmsf_new_revision {
padding: 8px;
margin: 0px 0px 12px 0px;
background-color: rgb(249.3, 251.9, 255);
color: #505050;
line-height: 1.5em;
border: 1px solid #d0d7de;
word-wrap: break-word;
border-radius: 3px;
box-shadow: 0 1px 2px rgba(0,0,0,0.05);
}
.dmsf-log-header-box{
padding: 6px;
margin-bottom: 10px;
@ -129,6 +157,7 @@ div.dmsf-id-box {
.dmsf-widget-header {
font-weight: normal;
padding: 0 10px 0 10px;
background: #e9e9e9;
}
.dmsf-widget-header-text {
@ -152,67 +181,6 @@ div[id*="revision_access_"] {
max-width: 100%;
}
/* Command icons */
.dmsf-icon-link:not(:has(svg)) { background-image: url("../../../images/link.png"); }
/* File types */
.dmsf-icon-file{
display: inline-block;
height: 16px;
}
.dmsf-gray .icon-folder { background-image: url("../images/folder_gray.png"); }
.dmsf-system .icon-folder { background-image: url("../images/folder_system.png"); }
.icon-file.filetype-doc, .icon-file.filetype-docx { background-image: url("../images/filetypes/doc.png"); }
.icon-file.filetype-xls,
.icon-file.filetype-xlsx,
.icon-file.filetype-xlsm { background-image: url("../images/filetypes/xls.png"); }
.icon-file.filetype-ppt, .icon-file.filetype-pptx { background-image: url("../images/filetypes/ppt.png"); }
.icon-file.filetype-vsd, .icon-file.filetype-vsdx { background-image: url("../images/filetypes/vsd.png"); }
.icon-file.filetype-mpp { background-image: url("../images/filetypes/mpp.png"); }
.icon-file.filetype-odt { background-image: url("../images/filetypes/odt.png"); }
.icon-file.filetype-ods { background-image: url("../images/filetypes/ods.png"); }
.icon-file.filetype-ott { background-image: url("../images/filetypes/ott.png"); }
.icon-file.filetype-odp { background-image: url("../images/filetypes/odp.png"); }
.icon-file.filetype-odg { background-image: url("../images/filetypes/odg.png"); }
.dmsf-gray .icon-file.filetype-doc { background-image: url("../images/filetypes/doc_gray.png"); }
.dmsf-gray .icon-file.filetype-docx { background-image: url("../images/filetypes/doc_gray.png"); }
.dmsf-gray .icon-file.filetype-xls { background-image: url("../images/filetypes/xls_gray.png"); }
.dmsf-gray .icon-file.filetype-xlsx { background-image: url("../images/filetypes/xls_gray.png"); }
.dmsf-gray .icon-file.filetype-xlsm { background-image: url("../images/filetypes/xls_gray.png"); }
.dmsf-gray .icon-file.filetype-ppt { background-image: url("../images/filetypes/ppt_gray.png"); }
.dmsf-gray .icon-file.filetype-pptx { background-image: url("../images/filetypes/ppt_gray.png"); }
.dmsf-gray .icon-file.filetype-vsd { background-image: url("../images/filetypes/vsd_gray.png"); }
.dmsf-gray .icon-file.filetype-vsdx { background-image: url("../images/filetypes/vsd_gray.png"); }
.dmsf-gray .icon-file.filetype-mpp { background-image: url("../images/filetypes/mpp_gray.png"); }
.dmsf-gray .icon-file.filetype-odt { background-image: url("../images/filetypes/odt_gray.png"); }
.dmsf-gray .icon-file.filetype-ott { background-image: url("../images/filetypes/ott_gray.png"); }
.dmsf-gray .icon-file.filetype-ods { background-image: url("../images/filetypes/ods_gray.png"); }
.dmsf-gray .icon-file.filetype-odp { background-image: url("../images/filetypes/odp_gray.png"); }
.dmsf-gray .icon-file.filetype-odg { background-image: url("../images/filetypes/odg_gray.png"); }
.dmsf-gray .icon-file.text-x-c { background-image: url("../images/filetypes/c_gray.png"); }
.dmsf-gray .icon-file.text-x-csharp { background-image: url("../images/filetypes/csharp_gray.png"); }
.dmsf-gray .icon-file.text-x-java { background-image: url("../images/filetypes/java_gray.png"); }
.dmsf-gray .icon-file.text-x-javascript { background-image: url("../images/filetypes/js_gray.png"); }
.dmsf-gray .icon-file.text-x-php { background-image: url("../images/filetypes/php_gray.png"); }
.dmsf-gray .icon-file.text-x-ruby { background-image: url("../images/filetypes/ruby_gray.png"); }
.dmsf-gray .icon-file.text-xml { background-image: url("../images/filetypes/xml_gray.png"); }
.dmsf-gray .icon-file.text-css { background-image: url("../images/filetypes/css_gray.png"); }
.dmsf-gray .icon-file.text-html { background-image: url("../images/filetypes/html_gray.png"); }
.dmsf-gray .icon-file.image-gif { background-image: url("../images/filetypes/image_gray.png"); }
.dmsf-gray .icon-file.image-jpeg { background-image: url("../images/filetypes/image_gray.png"); }
.dmsf-gray .icon-file.image-png { background-image: url("../images/filetypes/image_gray.png"); }
.dmsf-gray .icon-file.image-tiff { background-image: url("../images/filetypes/image_gray.png"); }
.dmsf-gray .icon-file.application-pdf { background-image: url("../images/filetypes/pdf_gray.png"); }
.dmsf-gray .icon-file.application-zip { background-image: url("../images/filetypes/zip_gray.png"); }
.dmsf-gray .icon-file.application-x-gzip { background-image: url("../images/filetypes/zip_gray.png"); }
/* Activities */
.icon-dmsf-file-revision { background-image: url("../../../images/document.png"); }
/* Links */
.dmsf-gray,
.dmsf-gray a,
@ -237,19 +205,14 @@ svg.dmsf-system {
stroke: darkviolet;
}
/* Search results */
.icon-dmsf-file { background-image: url("../../../images/document.png"); }
/* DMSF tree view */
.dmsf-hidden { display: none; }
.dmsf-tree:not(.dmsf-child) span.dmsf-expander { cursor: pointer; }
.dmsf-tree.dmsf-expanded span.dmsf-expander {
background: url("../../../images/arrow_down.png") no-repeat 0 50%;
padding-left: 16px;
}
.dmsf-tree.dmsf-child span.dmsf-expander { padding-left: 16px; }
.dmsf-tree.dmsf-collapsed span.dmsf-expander {
background: url("../../../images/arrow_right.png") no-repeat 0 50%;
padding-left: 16px;
}
.dmsf-tree.idnt-1 td.dmsf-title { padding-left: 1.5em; }
@ -321,18 +284,9 @@ span.fileover {
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 0 50%;
}
#dmsf_attachments_fields .ajax-loading input.filename {
background: url("../../../images/loading.gif") no-repeat 0 50%;
}
#dmsf_attachments_fields div.ui-progressbar {
width: 100px;
height: 14px;
@ -351,7 +305,6 @@ span.fileover {
width: 250px;
color: #555;
background-color: inherit;
background: url("../../../images/link.png") no-repeat 1px 50%;
padding-left: 18px;
}
@ -360,7 +313,6 @@ span.fileover {
}
a.dmsf-scroll-down {
background: url("../../../images/arrow_down.png") no-repeat 5px 50%;
background-color: #759FCF;
text-decoration: none;
color: #FFFFFF;

View File

@ -0,0 +1,25 @@
# frozen_string_literal: true
# Redmine plugin for Document Management System "Features"
#
# Vít Jonáš <vit.jonas@gmail.com>, Daniel Munn <dan.munn@munnster.co.uk>, Karel Pičman <karel.picman@kontron.com>
#
# This file is part of Redmine DMSF plugin.
#
# Redmine DMSF plugin 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 3 of the License, or (at your option) any
# later version.
#
# Redmine DMSF plugin 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 Redmine DMSF plugin. If not, see
# <https://www.gnu.org/licenses/>.
Rails.application.configure do
asset_paths = EasyAssets.plugin_asset_paths('plugins/redmine_dmsf')
config.assets.paths.concat asset_paths
config.assets.precompile << 'easy_dmsf.js'
config.assets.precompile << 'easy_dmsf.css'
end

View File

@ -0,0 +1,173 @@
# frozen_string_literal: true
# Redmine plugin for Document Management System "Features"
#
# Vít Jonáš <vit.jonas@gmail.com>, Daniel Munn <dan.munn@munnster.co.uk>, Karel Pičman <karel.picman@kontron.com>
#
# This file is part of Redmine DMSF plugin.
#
# Redmine DMSF plugin 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 3 of the License, or (at your option) any
# later version.
#
# Redmine DMSF plugin 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 Redmine DMSF plugin. If not, see
# <https://www.gnu.org/licenses/>.
require 'redmine'
require 'zip'
require "#{File.dirname(__FILE__)}/../../lib/redmine_dmsf"
Rails.application.config.after_initialize do
require 'redmine_dmsf/preview'
options = {
'dmsf_max_file_download' => 0,
'dmsf_max_email_filesize' => 0,
'dmsf_storage_directory' => 'files/dmsf',
'dmsf_index_database' => File.expand_path('dmsf_index', Rails.root),
'dmsf_stemming_lang' => 'english',
'dmsf_stemming_strategy' => 'STEM_NONE',
'dmsf_webdav' => nil,
'dmsf_display_notified_recipients' => nil,
'dmsf_global_title_format' => '',
'dmsf_columns' => %w[title size modified version workflow author],
'dmsf_webdav_ignore' => '^(\._|\.DS_Store$|Thumbs.db$)',
'dmsf_webdav_disable_versioning' => '^\~\$|\.tmp$',
'dmsf_keep_documents_locked' => nil,
'dmsf_act_as_attachable' => nil,
'dmsf_documents_email_from' => '',
'dmsf_documents_email_reply_to' => '',
'dmsf_documents_email_links_only' => nil,
'dmsf_enable_cjk_ngrams' => nil,
'dmsf_webdav_use_project_names' => '1',
'dmsf_webdav_ignore_1b_file_for_authentication' => '1',
'dmsf_projects_as_subfolders' => nil,
'only_approval_zero_minor_version' => '0',
'dmsf_max_notification_receivers_info' => 10,
'office_bin' => 'libreoffice',
'dmsf_global_menu_disabled' => nil,
'dmsf_default_query' => nil,
'empty_minor_version_by_default' => nil,
'remove_original_documents_module' => nil,
'dmsf_webdav_authentication' => 'Digest',
'dmsf_really_delete_files' => '0'
}
Setting.define_setting 'plugin_redmine_dmsf', { 'default' => options, 'serialized' => true }
Redmine::Plugin.find(:redmine_dmsf).settings = { partial: 'settings/dmsf_settings' }
# Administration menu extension
Redmine::MenuManager.map :admin_menu do |menu|
menu.push :dmsf_approvalworkflows, :dmsf_workflows_path,
caption: :label_dmsf_workflow_plural,
icon: 'workflows',
html: { class: 'icon icon-workflows' },
if: proc { |_| User.current.admin? }
end
# Project menu extension
Redmine::MenuManager.map :project_menu do |menu|
menu.push :dmsf, { controller: 'dmsf', action: 'show' },
caption: :menu_dmsf,
before: :documents,
param: :id,
html: { class: 'icon icon-dmsf' }
end
# Main menu extension
Redmine::MenuManager.map :top_menu do |menu|
menu.push :dmsf, { controller: 'dmsf', action: 'index' },
caption: :menu_dmsf,
html: { class: 'icon-dmsf', category: :rest_extension_modules },
if: proc {
User.current.allowed_to?(:view_dmsf_folders, nil, global: true) &&
ActiveRecord::Base.connection.data_source_exists?('settings') &&
!RedmineDmsf.dmsf_global_menu_disabled?
}
end
Redmine::AccessControl.map do |map|
map.project_module :dmsf do |pmap|
pmap.permission :view_dmsf_file_revision_accesses, {}, read: true
pmap.permission :view_dmsf_file_revisions, {}, read: true
pmap.permission :view_dmsf_folders, { dmsf: %i[show index] }, read: true
pmap.permission :user_preferences, { dmsf_state: [:user_pref_save] }, require: :member
pmap.permission(:view_dmsf_files,
{ dmsf: %i[entries_operation entries_email download_email_entries add_email append_email
autocomplete_for_user],
dmsf_files: %i[show view thumbnail],
dmsf_workflows: [:log] },
read: true)
pmap.permission :email_documents,
{ dmsf_public_urls: [:create] }
pmap.permission :folder_manipulation,
{ dmsf: %i[new create delete edit save edit_root save_root lock unlock notify_activate
notify_deactivate restore drop copymove],
dmsf_folder_permissions: %i[new append autocomplete_for_user],
dmsf_context_menus: [:dmsf] }
pmap.permission :file_manipulation,
{ dmsf_files: %i[create_revision lock unlock delete_revision obsolete_revision
notify_activate notify_deactivate restore],
dmsf_upload: %i[upload_files upload commit_files commit delete_dmsf_attachment
delete_dmsf_link_attachment multi_upload],
dmsf_links: %i[new create destroy restore autocomplete_for_project autocomplete_for_folder],
dmsf_context_menus: [:dmsf] }
pmap.permission :file_delete,
{ dmsf: %i[trash delete_entries empty_trash],
dmsf_files: [:delete],
dmsf_trash_context_menus: [:trash] }
pmap.permission :force_file_unlock, {}
pmap.permission :file_approval,
{ dmsf_workflows: %i[action new_action autocomplete_for_user start assign assignment] }
pmap.permission :manage_workflows,
{ dmsf_workflows: %i[index new create destroy show new_step add_step remove_step
reorder_steps update update_step delete_step edit] }
pmap.permission :display_system_folders, {}, read: true
# Watchers
pmap.permission :view_dmsf_file_watchers, {}, read: true
pmap.permission :add_dmsf_file_watchers, { watchers: %i[new create append autocomplete_for_user] }
pmap.permission :delete_dmsf_file_watchers, { watchers: :destroy }
pmap.permission :view_dmsf_folder_watchers, {}, read: true
pmap.permission :add_dmsf_folder_watchers, { watchers: %i[new create append autocomplete_for_user] }
pmap.permission :delete_dmsf_folder_watchers, { watchers: :destroy }
pmap.permission :view_project_watchers, {}, read: true
pmap.permission :add_project_watchers, { watchers: %i[new create append autocomplete_for_user] }
pmap.permission :delete_project_watchers, { watchers: :destroy }
end
end
# Register panels for My page
EpmDmsfLockedDocuments.register_to_scope :user, plugin: :redmine_dmsf
EpmDmsfOpenApprovals.register_to_scope :user, plugin: :redmine_dmsf
EpmDmsfWatchedDocuments.register_to_scope :user, plugin: :redmine_dmsf
# DMSF WebDAV digest token
Token.add_action :dmsf_webdav_digest, max_instances: 1, validity_time: nil
end
Rails.application.configure do
# Rubyzip configuration
Zip.unicode_names = true
Rails.application.config.after_initialize do
# DMS custom fields
CustomFieldsHelper::CUSTOM_FIELDS_TABS << { name: 'DmsfFileRevisionCustomField', partial: 'custom_fields/index',
label: :dmsf }
# Searchable modules
Redmine::Search.map do |search|
search.register :dmsf_files
search.register :dmsf_folders
end
# Activities
Redmine::Activity.register :dmsf_file_revision_accesses, default: false
Redmine::Activity.register :dmsf_file_revisions
end
require "#{File.dirname(__FILE__)}/../../lib/redmine_dmsf/webdav/custom_middleware"
config.middleware.insert_before ActionDispatch::Cookies, RedmineDmsf::Webdav::CustomMiddleware
end

View File

@ -0,0 +1,24 @@
# frozen_string_literal: true
# Redmine plugin for Document Management System "Features"
#
# Vít Jonáš <vit.jonas@gmail.com>, Daniel Munn <dan.munn@munnster.co.uk>, Karel Pičman <karel.picman@kontron.com>
#
# This file is part of Redmine DMSF plugin.
#
# Redmine DMSF plugin 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 3 of the License, or (at your option) any
# later version.
#
# Redmine DMSF plugin 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 Redmine DMSF plugin. If not, see
# <https://www.gnu.org/licenses/>.
require 'redmine_dmsf/webdav/custom_middleware'
Rails.application.configure do
config.middleware.insert_before ActionDispatch::Cookies, RedmineDmsf::Webdav::CustomMiddleware
end

View File

@ -492,6 +492,8 @@ cs:
label_dmsf_upload_commit: Nahrát a potvrdit
notice_search_in_subfolders: Vyhledávání v podsložkách není rekurzivní. Pro rekurzivní vyhledávání běžte do nejvyšší úrovně.
warning_folder_unlockable: Složku nelze odemknout
redmine_dmsf: Redmine DMSF
easy_pages:
modules:

View File

@ -488,6 +488,8 @@ de:
notice_search_in_subfolders: Die Suche in Unterordnern ist nicht rekursiv. Für eine rekursive Suche gehen Sie auf die
oberste Ebene.
warning_folder_unlockable: Der Ordner kann nicht entsperrt werden
redmine_dmsf: Redmine DMSF
easy_pages:
modules:

View File

@ -491,6 +491,9 @@ en:
label_dmsf_upload_commit: Upload and commit
notice_search_in_subfolders: Searching in sub-folders is not recursive. For a recursive search go to the top level.
warning_folder_unlockable: The folder can't be unlocked
redmine_dmsf: Redmine DMSF
easy_pages:
modules:

View File

@ -488,6 +488,8 @@ es:
notice_webdav_digest_reset: Your DMS WebDAV digest was reset.
notice_search_in_subfolders: Searching in sub-folders is not recursive. For a recursive search go to the top level.
warning_folder_unlockable: The folder can't be unlocked
redmine_dmsf: Redmine DMSF
label_dmsf_commit: Commit
label_dmsf_upload_commit: Upload and commit

View File

@ -470,6 +470,8 @@ fa:
label_dmsf_upload_commit: Upload and commit
notice_search_in_subfolders: Searching in sub-folders is not recursive. For a recursive search go to the top level.
warning_folder_unlockable: The folder can't be unlocked
redmine_dmsf: Redmine DMSF
easy_pages:
modules:

View File

@ -491,6 +491,8 @@ fr:
label_dmsf_upload_commit: Upload and commit
notice_search_in_subfolders: Searching in sub-folders is not recursive. For a recursive search go to the top level.
warning_folder_unlockable: The folder can't be unlocked
redmine_dmsf: Redmine DMSF
easy_pages:
modules:

View File

@ -490,6 +490,8 @@ hu:
label_dmsf_upload_commit: Upload and commit
notice_search_in_subfolders: Searching in sub-folders is not recursive. For a recursive search go to the top level.
warning_folder_unlockable: The folder can't be unlocked
redmine_dmsf: Redmine DMSF
easy_pages:
modules:

View File

@ -491,6 +491,8 @@ it: # Italian strings thx 2 Matteo Arceci!
label_dmsf_upload_commit: Upload and commit
notice_search_in_subfolders: Searching in sub-folders is not recursive. For a recursive search go to the top level.
warning_folder_unlockable: The folder can't be unlocked
redmine_dmsf: Redmine DMSF
easy_pages:
modules:

View File

@ -492,6 +492,8 @@ ja:
label_dmsf_upload_commit: Upload and commit
notice_search_in_subfolders: Searching in sub-folders is not recursive. For a recursive search go to the top level.
warning_folder_unlockable: The folder can't be unlocked
redmine_dmsf: Redmine DMSF
easy_pages:
modules:

View File

@ -491,6 +491,8 @@ ko:
label_dmsf_upload_commit: Upload and commit
notice_search_in_subfolders: Searching in sub-folders is not recursive. For a recursive search go to the top level.
warning_folder_unlockable: The folder can't be unlocked
redmine_dmsf: Redmine DMSF
easy_pages:
modules:

View File

@ -491,6 +491,8 @@ nl:
label_dmsf_upload_commit: Upload and commit
notice_search_in_subfolders: Searching in sub-folders is not recursive. For a recursive search go to the top level.
warning_folder_unlockable: The folder can't be unlocked
redmine_dmsf: Redmine DMSF
easy_pages:
modules:

View File

@ -491,6 +491,8 @@ pl:
label_dmsf_upload_commit: Upload and commit
notice_search_in_subfolders: Searching in sub-folders is not recursive. For a recursive search go to the top level.
warning_folder_unlockable: The folder can't be unlocked
redmine_dmsf: Redmine DMSF
easy_pages:
modules:

View File

@ -491,6 +491,8 @@ pt-BR:
label_dmsf_upload_commit: Upload and commit
notice_search_in_subfolders: Searching in sub-folders is not recursive. For a recursive search go to the top level.
warning_folder_unlockable: The folder can't be unlocked
redmine_dmsf: Redmine DMSF
easy_pages:
modules:

View File

@ -491,6 +491,8 @@ sl:
label_dmsf_upload_commit: Upload and commit
notice_search_in_subfolders: Searching in sub-folders is not recursive. For a recursive search go to the top level.
warning_folder_unlockable: The folder can't be unlocked
redmine_dmsf: Redmine DMSF
easy_pages:
modules:

View File

@ -493,6 +493,8 @@ uk:
label_dmsf_upload_commit: Upload and commit
notice_search_in_subfolders: Searching in sub-folders is not recursive. For a recursive search go to the top level.
warning_folder_unlockable: The folder can't be unlocked
redmine_dmsf: Redmine DMSF
easy_pages:
modules:

View File

@ -490,6 +490,8 @@ zh-TW:
label_dmsf_upload_commit: Upload and commit
notice_search_in_subfolders: Searching in sub-folders is not recursive. For a recursive search go to the top level.
warning_folder_unlockable: The folder can't be unlocked
redmine_dmsf: Redmine DMSF
easy_pages:
modules:

View File

@ -491,6 +491,8 @@ zh:
label_dmsf_upload_commit: Upload and commit
notice_search_in_subfolders: Searching in sub-folders is not recursive. For a recursive search go to the top level.
warning_folder_unlockable: The folder can't be unlocked
redmine_dmsf: Redmine DMSF
easy_pages:
modules:

135
init.rb
View File

@ -17,23 +17,20 @@
# You should have received a copy of the GNU General Public License along with Redmine DMSF plugin. If not, see
# <https://www.gnu.org/licenses/>.
require 'redmine'
require 'zip'
require "#{File.dirname(__FILE__)}/lib/redmine_dmsf"
Redmine::Plugin.register :redmine_dmsf do
name 'DMSF'
url 'https://www.redmine.org/plugins/redmine_dmsf'
author_url 'https://github.com/danmunn/redmine_dmsf/graphs/contributors'
author 'Vít Jonáš / Daniel Munn / Karel Pičman'
description 'Document Management System Features'
version '4.1.4 devel'
version '4.1.3'
requires_redmine version_or_higher: '6.0.0'
webdav = if Redmine::Plugin.installed?('easy_hosting_services') && EasyHostingServices::EasyMultiTenancy.activated?
'1'
else
'0'
end
use_project_names = defined?(EasyExtensions) ? '1' : '0'
settings partial: 'settings/dmsf_settings',
default: {
'dmsf_max_file_download' => 0,
@ -42,7 +39,7 @@ Redmine::Plugin.register :redmine_dmsf do
'dmsf_index_database' => File.expand_path('dmsf_index', Rails.root),
'dmsf_stemming_lang' => 'english',
'dmsf_stemming_strategy' => 'STEM_NONE',
'dmsf_webdav' => webdav,
'dmsf_webdav' => '0',
'dmsf_display_notified_recipients' => '0',
'dmsf_global_title_format' => '',
'dmsf_columns' => %w[title size modified version workflow author],
@ -54,7 +51,7 @@ Redmine::Plugin.register :redmine_dmsf do
'dmsf_documents_email_reply_to' => '',
'dmsf_documents_email_links_only' => '0',
'dmsf_enable_cjk_ngrams' => '0',
'dmsf_webdav_use_project_names' => use_project_names,
'dmsf_webdav_use_project_names' => '0',
'dmsf_webdav_ignore_1b_file_for_authentication' => '1',
'dmsf_projects_as_subfolders' => '0',
'only_approval_zero_minor_version' => '0',
@ -69,4 +66,120 @@ Redmine::Plugin.register :redmine_dmsf do
}
end
require_relative 'after_init' unless defined?(EasyExtensions)
# Administration menu extension
Redmine::MenuManager.map :admin_menu do |menu|
menu.push :dmsf_approvalworkflows, :dmsf_workflows_path,
caption: :label_dmsf_workflow_plural,
icon: 'workflows',
html: { class: 'icon icon-workflows' },
if: proc { |_| User.current.admin? }
end
# Project menu extension
Redmine::MenuManager.map :project_menu do |menu|
menu.push :dmsf, { controller: 'dmsf', action: 'show' },
caption: :menu_dmsf,
before: :documents,
param: :id,
html: { class: 'icon icon-dmsf' }
# New menu extension
next if defined?(EasyExtensions)
menu.push :dmsf_file, { controller: 'dmsf_upload', action: 'multi_upload' },
caption: :label_dmsf_new_top_level_document, parent: :new_object
menu.push :dmsf_folder, { controller: 'dmsf', action: 'new' },
caption: :label_dmsf_new_top_level_folder,
parent: :new_object
end
# Main menu extension
Redmine::MenuManager.map :top_menu do |menu|
menu.push :dmsf, { controller: 'dmsf', action: 'index' },
caption: :menu_dmsf,
html: { class: 'icon-dmsf', category: :rest_extension_modules },
if: proc {
User.current.allowed_to?(:view_dmsf_folders, nil, global: true) &&
ActiveRecord::Base.connection.data_source_exists?('settings') &&
!RedmineDmsf.dmsf_global_menu_disabled?
}
end
Redmine::AccessControl.map do |map|
map.project_module :dmsf do |pmap|
pmap.permission :view_dmsf_file_revision_accesses, {}, read: true
pmap.permission :view_dmsf_file_revisions, {}, read: true
pmap.permission :view_dmsf_folders, { dmsf: %i[show index] }, read: true
pmap.permission :user_preferences, { dmsf_state: [:user_pref_save] }, require: :member
pmap.permission(:view_dmsf_files,
{
dmsf: %i[entries_operation entries_email download_email_entries add_email append_email
autocomplete_for_user],
dmsf_files: %i[show view thumbnail],
dmsf_workflows: [:log]
},
read: true)
pmap.permission :email_documents,
{ dmsf_public_urls: [:create] }
pmap.permission :folder_manipulation,
{
dmsf: %i[new create delete edit save edit_root save_root lock unlock notify_activate
notify_deactivate restore drop copymove],
dmsf_folder_permissions: %i[new append autocomplete_for_user],
dmsf_context_menus: [:dmsf]
}
pmap.permission :file_manipulation,
{
dmsf_files: %i[create_revision lock unlock delete_revision obsolete_revision notify_activate
notify_deactivate restore],
dmsf_upload: %i[upload_files upload commit_files commit delete_dmsf_attachment
delete_dmsf_link_attachment multi_upload],
dmsf_links: %i[new create destroy restore autocomplete_for_project autocomplete_for_folder],
dmsf_context_menus: [:dmsf]
}
pmap.permission :file_delete,
{
dmsf: %i[trash delete_entries empty_trash],
dmsf_files: [:delete],
dmsf_trash_context_menus: [:trash]
}
pmap.permission :force_file_unlock, {}
pmap.permission :file_approval,
{ dmsf_workflows: %i[action new_action autocomplete_for_user start assign assignment] }
pmap.permission :manage_workflows,
{
dmsf_workflows: %i[index new create destroy show new_step add_step remove_step reorder_steps
update update_step delete_step edit]
}
pmap.permission :display_system_folders, {}, read: true
# Watchers
pmap.permission :view_dmsf_file_watchers, {}, read: true
pmap.permission :add_dmsf_file_watchers, { watchers: %i[new create append autocomplete_for_user] }
pmap.permission :delete_dmsf_file_watchers, { watchers: :destroy }
pmap.permission :view_dmsf_folder_watchers, {}, read: true
pmap.permission :add_dmsf_folder_watchers, { watchers: %i[new create append autocomplete_for_user] }
pmap.permission :delete_dmsf_folder_watchers, { watchers: :destroy }
pmap.permission :view_project_watchers, {}, read: true
pmap.permission :add_project_watchers, { watchers: %i[new create append autocomplete_for_user] }
pmap.permission :delete_project_watchers, { watchers: :destroy }
end
end
# DMSF WebDAV digest token
Token.add_action :dmsf_webdav_digest, max_instances: 1, validity_time: nil
Rails.application.configure do
# Rubyzip configuration
Zip.unicode_names = true
# DMS custom fields
CustomFieldsHelper::CUSTOM_FIELDS_TABS << { name: 'DmsfFileRevisionCustomField', partial: 'custom_fields/index',
label: :dmsf }
# Searchable modules
Redmine::Search.map do |search|
search.register :dmsf_files
search.register :dmsf_folders
end
# Activities
Redmine::Activity.register :dmsf_file_revision_accesses, default: false
Redmine::Activity.register :dmsf_file_revisions
end

View File

@ -33,7 +33,7 @@ module Dav4rack
# main entry point, called by the Handler
def process
status = skip_authorization? || authenticate ? process_action || OK : HttpStatus::Unauthorized
status = skip_authorization? || authenticate? ? process_action || OK : HttpStatus::Unauthorized
rescue HttpStatus::Status => e
status = e
ensure
@ -335,7 +335,7 @@ module Dav4rack
# Perform authentication
#
# implement your authentication by overriding Resource#authenticate
def authenticate
def authenticate?
uname = nil
password = nil
if request.authorization?
@ -345,7 +345,7 @@ module Dav4rack
password = auth.credentials[1]
end
end
resource.authenticate uname, password
resource.authenticate? uname, password
end
def authentication_error_message

View File

@ -5,6 +5,11 @@ module Dav4rack
module HttpStatus
# Status
class Status < StandardError
delegate :code, to: :class
delegate :reason_phrase, to: :class
delegate :status_line, to: :class
delegate :to_i, to: :class
class << self
attr_accessor :code, :reason_phrase
alias to_i code
@ -13,22 +18,6 @@ module Dav4rack
"#{code} #{reason_phrase}"
end
end
def code
self.class.code
end
def reason_phrase
self.class.reason_phrase
end
def status_line
self.class.status_line
end
def to_i
self.class.to_i
end
end
STATUS_MESSAGES = {

View File

@ -102,7 +102,7 @@ module Dav4rack
# override to implement custom authentication
# should return true for successful authentication, false otherwise
def authenticate(_username, _password)
def authenticate?(_username, _password)
true
end

View File

@ -261,11 +261,6 @@ unless defined?(EasyPatchManager)
end
end
# A workaround for obsolete 'alias_method' usage in RedmineUp's plugins
after_easy_init do
require "#{File.dirname(__FILE__)}/redmine_dmsf/plugin"
end
# Load up classes that make up our WebDAV solution ontop of Dav4rack
after_easy_init do
require "#{File.dirname(__FILE__)}/dav4rack"

View File

@ -27,6 +27,8 @@ module RedmineDmsf
class Zip
attr_reader :dmsf_files
delegate :close, to: :@zip_file
def initialize
@temp_file = Tempfile.new([FILE_PREFIX, '.zip'], Rails.root.join('tmp'))
@zip_file = ::Zip::OutputStream.open(@temp_file)
@ -44,10 +46,6 @@ module RedmineDmsf
@temp_file.path
end
def close
@zip_file.close
end
def add_dmsf_file(dmsf_file, member = nil, root_path = nil, path = nil)
raise DmsfFileNotFoundError unless dmsf_file&.last_revision && File.exist?(dmsf_file.last_revision.disk_file)

View File

@ -29,11 +29,8 @@ module RedmineDmsf
return
end
"\n".html_safe + stylesheet_link_tag('redmine_dmsf', plugin: :redmine_dmsf) +
"\n".html_safe + stylesheet_link_tag('select2.min', plugin: :redmine_dmsf) +
"\n".html_safe + javascript_include_tag('select2.min', plugin: :redmine_dmsf, defer: true) +
"\n".html_safe + javascript_include_tag('redmine_dmsf', plugin: :redmine_dmsf, defer: true) +
"\n".html_safe + javascript_include_tag('attachments_dmsf', plugin: :redmine_dmsf, defer: true)
partial = "hooks/#{defined?(EasyExtensions) ? 'easy' : 'redmine'}_dmsf/view_layouts_base_html_head"
context[:controller].send :render_to_string, { partial: partial }
end
end
end

View File

@ -215,7 +215,7 @@ module RedmineDmsf
# Title, size
html << '<td>'
data = "#{dmsf_file.last_revision.detect_content_type}:#{h(dmsf_file.name)}:#{file_view_url}"
icon_name = icon_for_mime_type(Redmine::MimeType.css_class_of(item.filename))
icon_name = icon_for_mime_type(Redmine::MimeType.css_class_of(dmsf_file.name))
html << link_to(sprite_icon(icon_name, h(dmsf_file.title)),
file_view_url,
target: '_blank',

View File

@ -25,18 +25,17 @@ module RedmineDmsf
extend Redmine::Utils::Shell
include Redmine::I18n
OFFICE_BIN = (Setting.plugin_redmine_dmsf['office_bin'].presence || 'libreoffice').freeze
def self.office_available?
return @office_available if defined?(@office_available)
begin
`#{shell_quote OFFICE_BIN} --version`
office_bin = RedmineDmsf.office_bin.presence || 'libreoffice'
`#{shell_quote office_bin} --version`
@office_available = $CHILD_STATUS.success?
rescue StandardError
@office_available = false
end
Rails.logger.warn l(:note_dmsf_office_bin_not_available, value: OFFICE_BIN, locale: :en) unless @office_available
Rails.logger.warn l(:note_dmsf_office_bin_not_available, value: office_bin, locale: :en) unless @office_available
@office_available
end
@ -44,7 +43,8 @@ module RedmineDmsf
return target if File.exist?(target)
dir = File.dirname(target)
cmd = "#{shell_quote(OFFICE_BIN)} --convert-to pdf --headless --outdir #{shell_quote(dir)} #{shell_quote(source)}"
office_bin = RedmineDmsf.office_bin.presence || 'libreoffice'
cmd = "#{shell_quote(office_bin)} --convert-to pdf --headless --outdir #{shell_quote(dir)} #{shell_quote(source)}"
if system(cmd)
target
else

View File

@ -17,6 +17,8 @@
# You should have received a copy of the GNU General Public License along with Redmine DMSF plugin. If not, see
# <https://www.gnu.org/licenses/>.
require "#{File.dirname(__FILE__)}/dmsf_digest"
module RedmineDmsf
module Webdav
# DMSF controller
@ -33,7 +35,7 @@ module RedmineDmsf
def process
return super unless RedmineDmsf.dmsf_webdav_authentication == 'Digest'
status = skip_authorization? || authenticate ? process_action || OK : Dav4rack::HttpStatus::Unauthorized
status = skip_authorization? || authenticate? ? process_action || OK : Dav4rack::HttpStatus::Unauthorized
rescue Dav4rack::HttpStatus::Status => e
status = e
ensure
@ -49,14 +51,14 @@ module RedmineDmsf
end
end
def authenticate
def authenticate?
return super unless RedmineDmsf.dmsf_webdav_authentication == 'Digest'
auth_header = request.authorization.to_s
scheme = auth_header.split(' ', 2).first&.downcase
if scheme == 'digest'
Rails.logger.info 'Authentication: digest'
digest = Digest.new(request.authorization)
digest = DmsfDigest.new(request.authorization)
params = digest.params
username = params['username']
response = params['response']
@ -105,7 +107,7 @@ module RedmineDmsf
raise Unauthorized if User.current.anonymous?
Rails.logger.info "Current user: #{User.current}, User-Agent: #{request.user_agent}"
User.current
User.current && !User.current.anonymous?
end
end
end

View File

@ -20,7 +20,7 @@
module RedmineDmsf
module Webdav
# Replacement for Rack::Auth::Digest
class Digest
class DmsfDigest
def initialize(authorization)
@authorization = authorization
end

View File

@ -264,7 +264,7 @@ module RedmineDmsf
raise Locked if file.locked_for_user?
if dest.exist? && !dest.collection?
if dest.resource.file.last_revision.size.zero? || reuse_version_for_locked_file(dest.resource.file)
if dest.resource.file.last_revision.size.zero? || reuse_version_for_locked_file?(dest.resource.file)
# Last revision in the destination has zero size so reuse that revision
new_revision = dest.resource.file.last_revision
else
@ -390,7 +390,7 @@ module RedmineDmsf
entity = file || folder
return unless entity
refresh = args && (!args[:scope]) && (!args[:type])
refresh = args && !args[:scope] && !args[:type]
args ||= {}
args[:method] = @request.request_method.downcase
http_if = request.get_header('HTTP_IF')
@ -464,7 +464,7 @@ module RedmineDmsf
# logically assume is that the lock is being refreshed (office loves
# to do this for example, so we do a few checks, try to find the lock
# and ultimately extend it, otherwise we return Conflict for any failure
refresh = args && (!args[:scope]) && (!args[:type]) # Perhaps a lock refresh
refresh = args && !args[:scope] && !args[:type] # Perhaps a lock refresh
if refresh
http_if = request.get_header('HTTP_IF')
if http_if.blank?
@ -554,7 +554,7 @@ module RedmineDmsf
Rails.logger.info "Versioning disabled for #{basename}"
reuse_revision = true
end
reuse_revision = true if reuse_version_for_locked_file(file)
reuse_revision = true if reuse_version_for_locked_file?(file)
last_revision = file.last_revision
if last_revision.size.zero? || reuse_revision
new_revision = last_revision
@ -721,7 +721,7 @@ module RedmineDmsf
File.new disk_file
end
def reuse_version_for_locked_file(file)
def reuse_version_for_locked_file?(file)
locks = file.lock
locks.each do |lock|
next if lock.expired?

View File

@ -27,6 +27,27 @@ module RedmineDmsf
class ResourceProxy < Dav4rack::Resource
attr_reader :read_only
delegate :propstats, to: :@resource_c
delegate :set_property, to: :@resource_c
delegate :options, to: :@resource_c
delegate :lockdiscovery, to: :@resource_c
delegate :lockdiscovery_xml, to: :@resource_c
delegate :children, to: :@resource_c
delegate :collection?, to: :@resource_c
delegate :exist?, to: :@resource_c
delegate :creation_date, to: :@resource_c
delegate :last_modified, to: :@resource_c
delegate :etag, to: :@resource_c
delegate :content_type, to: :@resource_c
delegate :content_length, to: :@resource_c
delegate :get, to: :@resource_c
delegate :special_type, to: :@resource_c
delegate :name, to: :@resource_c
delegate :long_name, to: :@resource_c
delegate :get_property, to: :@resource_c
delegate :remove_property, to: :@resource_c
delegate :properties, to: :@resource_c
def initialize(path, request, response, options)
# Check the settings cache for each request
Setting.check_cache
@ -40,63 +61,15 @@ module RedmineDmsf
@read_only = RedmineDmsf.dmsf_webdav_strategy == 'WEBDAV_READ_ONLY'
end
def authenticate(username, password)
def authenticate?(username, password)
User.current = User.try_to_login(username, password)
User.current && !User.current.anonymous?
end
def options(request, response)
@resource_c.options request, response
end
def supports_locking?
!@read_only
end
def lockdiscovery
@resource_c.lockdiscovery
end
def lockdiscovery_xml
@resource_c.lockdiscovery_xml
end
def children
@resource_c.children
end
def collection?
@resource_c.collection?
end
def exist?
@resource_c.exist?
end
def creation_date
@resource_c.creation_date
end
def last_modified
@resource_c.last_modified
end
def etag
@resource_c.etag
end
def content_type
@resource_c.content_type
end
def content_length
@resource_c.content_length
end
def get(request, response)
@resource_c.get request, response
end
def put(request)
raise BadGateway if @read_only
@ -127,10 +100,6 @@ module RedmineDmsf
@resource_c.make_collection
end
def special_type
@resource_c.special_type
end
def lock(args)
raise BadGateway if @read_only
@ -147,38 +116,10 @@ module RedmineDmsf
@resource_c.unlock token
end
def name
@resource_c.name
end
def long_name
@resource_c.long_name
end
def resource
@resource_c
end
def get_property(element)
@resource_c.get_property element
end
def remove_property(element)
@resource_c.remove_property element
end
def properties
@resource_c.properties
end
def propstats(response, stats)
@resource_c.propstats response, stats
end
def set_property(element, value)
@resource_c.set_property element, value
end
# Adds the given xml namespace to namespaces and returns the prefix
def add_namespace(namespace, _prefix = '')
return if namespace.blank?

View File

@ -0,0 +1,54 @@
# frozen_string_literal: true
# Redmine plugin for Document Management System "Features"
#
# Karel Pičman <karel.picman@kontron.com>
#
# This file is part of Redmine DMSF plugin.
#
# Redmine DMSF plugin 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 3 of the License, or (at your option) any
# later version.
#
# Redmine DMSF plugin 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 Redmine DMSF plugin. If not, see
# <https://www.gnu.org/licenses/>.
module RedmineDmsf
module Patches
# AccessControl patch
# TODO: This is just a workaround to fix alias_method usage in Easy's plugins, which is in conflict with
# prepend and causes an infinite loop.
module AccessControlEasyPatch
##################################################################################################################
# Overridden methods
def self.included(base)
base.extend(ClassMethods)
base.class_eval do
class << self
alias_method_chain :available_project_modules, :easy
end
end
end
# Class methods
module ClassMethods
def available_project_modules_with_easy
# Removes the original Documents from project's modules (replaced with DMSF)
modules = available_project_modules_without_easy
modules.delete(:documents) if RedmineDmsf.remove_original_documents_module?
modules
end
end
end
end
end
# Apply the patch
if defined?(EasyPatchManager)
EasyPatchManager.register_other_patch 'Redmine::AccessControl', 'RedmineDmsf::Patches::AccessControlEasyPatch'
end

View File

@ -43,9 +43,4 @@ module RedmineDmsf
end
# Apply the patch
if defined?(EasyPatchManager)
EasyPatchManager.register_patch_to_be_first 'Redmine::Acts::Attachable::InstanceMethods',
'RedmineDmsf::Patches::AccessControlPatch', prepend: true, first: true
else
Redmine::AccessControl.prepend RedmineDmsf::Patches::AccessControlPatch
end
Redmine::AccessControl.prepend RedmineDmsf::Patches::AccessControlPatch unless defined?(EasyPatchManager)

View File

@ -38,8 +38,9 @@ module RedmineDmsf
'..', '..', '..', 'assets', 'javascripts', 'lang', "dmsf_button-#{lang}.js")
lang = 'en' unless File.exist?(path)
content_for :header_tags do
javascript_include_tag("lang/dmsf_button-#{lang}", plugin: 'redmine_dmsf') +
javascript_include_tag('dmsf_button', plugin: 'redmine_dmsf') +
plugin = defined?(EasyExtensions) ? nil : :redmine_dmsf
javascript_include_tag("lang/dmsf_button-#{lang}", plugin: plugin) +
javascript_include_tag('dmsf_button', plugin: plugin) +
javascript_tag("jsToolBar.prototype.dmsfList = #{@dmsf_macro_list.to_json};")
end
end

View File

@ -31,7 +31,7 @@ module RedmineDmsf
def get_image_filename(attrname)
if attrname =~ %r{/dmsf/files/(\d+)/}
file = DmsfFile.find_by(id: Regexp.last_match(1))
file&.last_revision ? file.last_revision.disk_file : nil
file&.last_revision&.disk_file
else
super
end

View File

@ -22,7 +22,6 @@ require File.expand_path('../../test_helper', __FILE__)
# DMSF controller
class DmsfControllerTest < RedmineDmsf::Test::TestCase
include Redmine::I18n
include Rails.application.routes.url_helpers
include DmsfHelper
fixtures :custom_fields, :custom_values, :dmsf_links, :dmsf_folder_permissions, :dmsf_locks,
@ -238,6 +237,9 @@ class DmsfControllerTest < RedmineDmsf::Test::TestCase
def test_show_webdav_disabled
post '/login', params: { username: 'jsmith', password: 'jsmith' }
# TODO: with_settings seems to be not working with Easy
return if defined?(EasyExtensions)
with_settings plugin_redmine_dmsf: { 'dmsf_webdav' => nil } do
get "/projects/#{@project1.id}/dmsf"
assert_response :success

View File

@ -44,6 +44,9 @@ class DmsfWebdavCustomMiddlewareTest < RedmineDmsf::Test::IntegrationTest
end
def test_webdav_not_enabled
# TODO: with_settings seems to be not working with Easy
return if defined?(EasyExtensions)
with_settings plugin_redmine_dmsf: { 'dmsf_webdav' => nil } do
process :options, '/dmsf/webdav'
assert_response :not_found

View File

@ -151,7 +151,7 @@ class DmsfWorkflowTest < RedmineDmsf::Test::UnitTest
wsa.author_id = @jsmith.id
assert wsa.save
# The workflow is finished
assert @wf1.try_finish(@revision1, @wfsac1, @jsmith.id)
assert @wf1.try_finish?(@revision1, @wfsac1, @jsmith.id)
@revision1.reload
assert_equal DmsfWorkflow::STATE_APPROVED, @revision1.workflow
end
@ -160,7 +160,7 @@ class DmsfWorkflowTest < RedmineDmsf::Test::UnitTest
# The forkflow is waiting for an approval
assert_equal DmsfWorkflow::STATE_WAITING_FOR_APPROVAL, @revision1.workflow
# The workflow is not finished
assert_not @wf1.try_finish(@revision1, @wfsac1, @jsmith.id)
assert_not @wf1.try_finish?(@revision1, @wfsac1, @jsmith.id)
@revision1.reload
assert_equal DmsfWorkflow::STATE_WAITING_FOR_APPROVAL, @revision1.workflow
end

View File

@ -26,16 +26,16 @@ class AttachablePatchTest < RedmineDmsf::Test::UnitTest
def setup
super
@issue1 = Issue.find 1
@issue2 = Issue.find 2
@issue5 = Issue.find 5
end
def test_has_attachmets
if defined?(EasyExtensions)
assert @issue1.has_attachments?
assert_not @issue2.has_attachments?
assert_not @issue5.has_attachments?
else
assert @issue1.dmsf_files.present?
assert @issue2.dmsf_files.blank?
assert @issue5.dmsf_files.blank?
end
end
end

View File

@ -21,19 +21,35 @@ require File.expand_path('../../../../test_helper', __FILE__)
# Macros tests
class DmsfMacrosTest < RedmineDmsf::Test::HelperTest
fixtures :dmsf_folders, :dmsf_files, :dmsf_file_revisions
# Mock view context for macros
class DmsfView
include ApplicationHelper
include ActionView::Helpers
include ActionDispatch::Routing
include ERB::Util
include Rails.application.routes.url_helpers
include ActionView::Helpers::UrlHelper
end
fixtures :dmsf_folders, :dmsf_files, :dmsf_file_revisions
# Cache the view context to avoid creating it for each macro call
def dmsf_view_context
@dmsf_view_context ||= DmsfView.new
end
# Hack to bypass missing methods to mocked view context
def respond_to_missing?(name, include_private)
dmsf_view_context.respond_to?(name) || super
end
def method_missing(method_name, ...)
dmsf_view_context.send(method_name.to_s, ...)
end
def setup
super
User.current = @jsmith
default_url_options[:host] = 'www.example.com'
Rails.application.routes.default_url_options[:host] = 'www.example.com'
@file1 = DmsfFile.find_by(id: 1)
@file6 = DmsfFile.find_by(id: 6) # video
@file7 = DmsfFile.find_by(id: 7) # image
@ -345,7 +361,9 @@ class DmsfMacrosTest < RedmineDmsf::Test::HelperTest
text = textilizable("{{dmsftn(#{@file7.id} #{@file7.id})}}")
url = static_dmsf_file_url(@file7, @file7.last_revision.name)
img = image_tag(url, alt: @file7.name, title: @file7.title, width: 'auto', height: 200)
link = link_to(img, url, target: '_blank',
link = link_to(img,
url,
target: '_blank',
rel: 'noopener',
title: h(@file7.last_revision.try(:tooltip)),
'data-downloadurl': 'image/gif:test.gif:http://www.example.com/dmsf/files/7/test.gif')
@ -379,7 +397,9 @@ class DmsfMacrosTest < RedmineDmsf::Test::HelperTest
height = '480'
text = textilizable("{{dmsftn(#{@file7.id}, height=#{height})}}")
img = image_tag(url, alt: @file7.name, title: @file7.title, width: 'auto', height: 480)
link = link_to(img, url, target: '_blank',
link = link_to(img,
url,
target: '_blank',
rel: 'noopener',
title: h(@file7.last_revision.try(:tooltip)),
'data-downloadurl': 'image/gif:test.gif:http://www.example.com/dmsf/files/7/test.gif')

View File

@ -18,6 +18,7 @@
# <https://www.gnu.org/licenses/>.
require File.expand_path('../../../../test_helper', __FILE__)
require File.expand_path('../../../../../lib/redmine_dmsf/plugin', __FILE__)
# Plugin tests
class DmsfPluginTest < RedmineDmsf::Test::HelperTest
@ -30,16 +31,20 @@ class DmsfPluginTest < RedmineDmsf::Test::HelperTest
end
def test_an_obsolete_plugin_present_no
return if defined?(EasyExtensions)
# No such plugin is present
assert_not RedmineDmsf::Plugin.an_obsolete_plugin_present?
end
def test_an_obsolete_plugin_present_yes
unless defined?(EasyExtensions)
# Create a fake redmine_checklists plugin
path = Rails.root.join('plugins/redmine_resources')
FileUtils.mkdir_p path
end
assert RedmineDmsf::Plugin.an_obsolete_plugin_present?
FileUtils.rm_rf path
FileUtils.rm_rf(path) unless defined?(EasyExtensions)
end
def test_lib_available?

View File

@ -18,6 +18,7 @@
# <https://www.gnu.org/licenses/>.
require File.expand_path('../../../../test_helper', __FILE__)
require File.expand_path('../../../../../lib/redmine_dmsf/dmsf_zip', __FILE__)
# Plugin tests
class DmsfZipTest < RedmineDmsf::Test::HelperTest

View File

@ -33,13 +33,13 @@ class UserPatchTest < RedmineDmsf::Test::UnitTest
assert_equal 0, DmsfFileRevision.where(dmsf_workflow_assigned_by_user_id: id).all.size
assert_equal 0, DmsfFileRevision.where(dmsf_workflow_started_by_user_id: id).all.size
assert_equal 0, DmsfFile.where(deleted_by_user_id: id).all.size
assert_equal 0, DmsfFolder.where(user_id: id).all.size
assert_equal 0, DmsfFolder.where(deleted_by_user_id: id).all.size
assert_equal 0, DmsfLink.where(user_id: id).all.size
assert_equal 0, DmsfLink.where(deleted_by_user_id: id).all.size
# TODO: Expected: 0, Actual: 1 in Easy extension
return if defined?(EasyExtensions)
assert_equal 0, DmsfFolder.where(user_id: id).all.size
assert_equal 0, DmsfLock.where(user_id: id).all.size
assert_equal 0, DmsfWorkflowStepAction.where(author_id: id).all.size
assert_equal 0, DmsfWorkflowStepAssignment.where(user_id: id).all.size