#1340 Rails 6
This commit is contained in:
parent
e941eff160
commit
a66cec3e3f
@ -789,7 +789,7 @@ IMPORTANT
|
||||
1.4.1: *2012-06-15*
|
||||
-------------------
|
||||
|
||||
* New: DAV4Rack requirement added (Gemfile makes reference to github repository for latest release).
|
||||
* New: Dav4rack requirement added (Gemfile makes reference to github repository for latest release).
|
||||
* New: Webdav functionality included, additional administrative settings added
|
||||
* Bug: #2 - extended xapian search fixed with Rails 3 compatible code.
|
||||
|
||||
|
||||
1
Gemfile
1
Gemfile
@ -30,7 +30,6 @@ source 'https://rubygems.org' do
|
||||
gem 'simple_enum'
|
||||
end
|
||||
unless %w(easyproject easy_gantt).any? { |plugin| Dir.exist?(File.expand_path("../../#{plugin}", __FILE__)) }
|
||||
gem 'redmine_extensions', '~> 0.3.9'
|
||||
group :test do
|
||||
gem 'rails-controller-testing'
|
||||
end
|
||||
|
||||
12
README.md
12
README.md
@ -9,7 +9,7 @@ Redmine DMSF is Document Management System Features plugin for Redmine issue tra
|
||||
|
||||
Redmine DMSF now comes bundled with Webdav functionality: if switched on within plugin settings this will be accessible from /dmsf/webdav.
|
||||
|
||||
Webdav functionality is provided through DAV4Rack library.
|
||||
Webdav functionality is provided through Dav4rack library.
|
||||
|
||||
Initial development was for Kontron AG R&D department and it is released as open source thanks to their generosity.
|
||||
Project home: <https://code.google.com/p/redmine-dmsf/>
|
||||
@ -295,6 +295,16 @@ You can either clone the master branch or download the latest zipped version. Be
|
||||
rake redmine:dmsf_maintenance RAILS_ENV="production"
|
||||
rake redmine:dmsf_maintenance dry_run=1 RAILS_ENV="production"
|
||||
|
||||
### WebDAV
|
||||
|
||||
In order to enable WebDAV module, it is necessary to put the following code into yor `config/additional_environment.rb`
|
||||
|
||||
```ruby
|
||||
# Redmine DMSF's WebDAV
|
||||
require File.dirname(__FILE__) + '/plugins/redmine_dmsf/lib/redmine_dmsf/webdav/custom_middleware'
|
||||
config.middleware.insert_before ActionDispatch::Cookies, RedmineDmsf::Webdav::CustomMiddleware
|
||||
```
|
||||
|
||||
### Installation in a sub-uri
|
||||
|
||||
In order to documents and folders are available via WebDAV in case that the Redmine is configured to be run in a sub-uri
|
||||
|
||||
@ -21,10 +21,10 @@
|
||||
# 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 'zip'
|
||||
require_dependency File.dirname(__FILE__) + '/lib/redmine_dmsf.rb'
|
||||
require 'zip'
|
||||
require File.dirname(__FILE__) + '/lib/redmine_dmsf'
|
||||
|
||||
ActiveSupport::Dependencies.autoload_paths << File.join(File.dirname(__FILE__), 'app', 'validators')
|
||||
#ActiveSupport::Dependencies.autoload_paths << File.join(File.dirname(__FILE__), 'app', 'validators')
|
||||
|
||||
def dmsf_init
|
||||
# Administration menu extension
|
||||
@ -124,7 +124,13 @@ else
|
||||
dmsf_init
|
||||
end
|
||||
|
||||
RedmineExtensions::Reloader.to_prepare do
|
||||
if Redmine::Plugin.installed?(:easy_extensions)
|
||||
_class_ = RedmineExtensions
|
||||
else
|
||||
_class_ = ActiveSupport
|
||||
end
|
||||
|
||||
_class_::Reloader.to_prepare do
|
||||
# Rubyzip configuration
|
||||
Zip.unicode_names = true
|
||||
|
||||
@ -144,6 +150,3 @@ RedmineExtensions::Reloader.to_prepare do
|
||||
# Redmine::Search.available_search_types.delete('documents')
|
||||
# Redmine::AccessControl.available_project_modules.delete(:documents)
|
||||
end
|
||||
|
||||
# WebDAV
|
||||
Rails.configuration.middleware.insert_before ActionDispatch::Cookies, RedmineDmsf::Webdav::CustomMiddleware
|
||||
|
||||
@ -23,6 +23,7 @@ class DmsfContextMenusController < ApplicationController
|
||||
|
||||
helper :context_menus
|
||||
helper :watchers
|
||||
helper :dmsf
|
||||
|
||||
before_action :find_folder
|
||||
before_action :find_dmsf_file
|
||||
|
||||
@ -191,9 +191,9 @@ class DmsfController < ApplicationController
|
||||
else
|
||||
download_entries(selected_folders, selected_files)
|
||||
end
|
||||
rescue FileNotFound
|
||||
rescue RedmineDmsf::Errors::DmsfFileNotFoundError
|
||||
render_404
|
||||
rescue DmsfAccessError
|
||||
rescue RedmineDmsf::Errors::DmsfAccessError
|
||||
render_403
|
||||
rescue StandardError => e
|
||||
flash[:error] = e.message
|
||||
@ -494,14 +494,14 @@ class DmsfController < ApplicationController
|
||||
end
|
||||
|
||||
def email_entries(selected_folders, selected_files)
|
||||
raise DmsfAccessError unless User.current.allowed_to?(:email_documents, @project)
|
||||
raise RedmineDmsf::Errors::DmsfAccessError unless User.current.allowed_to?(:email_documents, @project)
|
||||
zip = Zip.new
|
||||
zip_entries(zip, selected_folders, selected_files)
|
||||
zipped_content = zip.finish
|
||||
|
||||
max_filesize = Setting.plugin_redmine_dmsf['dmsf_max_email_filesize'].to_f
|
||||
if max_filesize > 0 && File.size(zipped_content) > max_filesize * 1048576
|
||||
raise EmailMaxFileSize
|
||||
raise RedmineDmsf::Errors::DmsfEmailMaxFileSizeError
|
||||
end
|
||||
|
||||
zip.files.each do |f|
|
||||
@ -556,22 +556,22 @@ class DmsfController < ApplicationController
|
||||
if folder
|
||||
zip.add_folder(folder, member, (folder.dmsf_folder.dmsf_path_str if folder.dmsf_folder))
|
||||
else
|
||||
raise FileNotFound
|
||||
raise RedmineDmsf::Errors::DmsfFileNotFoundError
|
||||
end
|
||||
end
|
||||
selected_files.each do |selected_file_id|
|
||||
file = DmsfFile.visible.find_by(id: selected_file_id)
|
||||
unless file && file.last_revision && File.exist?(file.last_revision.disk_file)
|
||||
raise FileNotFound
|
||||
raise RedmineDmsf::Errors::DmsfFileNotFoundError
|
||||
end
|
||||
unless (file.project == @project) || User.current.allowed_to?(:view_dmsf_files, file.project)
|
||||
raise DmsfAccessError
|
||||
raise RedmineDmsf::Errors::DmsfAccessError
|
||||
end
|
||||
zip.add_file(file, member, (file.dmsf_folder.dmsf_path_str if file.dmsf_folder))
|
||||
end
|
||||
max_files = Setting.plugin_redmine_dmsf['dmsf_max_file_download'].to_i
|
||||
if max_files > 0 && zip.files.length > max_files
|
||||
raise ZipMaxFilesError
|
||||
raise RedmineDmsf::Errors::DmsfZipMaxFilesError
|
||||
end
|
||||
zip
|
||||
end
|
||||
@ -585,7 +585,7 @@ class DmsfController < ApplicationController
|
||||
flash[:error] = folder.errors.full_messages.to_sentence
|
||||
end
|
||||
else
|
||||
raise FileNotFound
|
||||
raise RedmineDmsf::Errors::DmsfFileNotFoundError
|
||||
end
|
||||
end
|
||||
# Files
|
||||
@ -596,7 +596,7 @@ class DmsfController < ApplicationController
|
||||
flash[:error] = file.errors.full_messages.to_sentence
|
||||
end
|
||||
else
|
||||
raise FileNotFound
|
||||
raise RedmineDmsf::Errors::DmsfFileNotFoundError
|
||||
end
|
||||
end
|
||||
# Links
|
||||
@ -607,7 +607,7 @@ class DmsfController < ApplicationController
|
||||
flash[:error] = link.errors.full_messages.to_sentence
|
||||
end
|
||||
else
|
||||
raise FileNotFound
|
||||
raise RedmineDmsf::Errors::DmsfFileNotFoundError
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -615,7 +615,7 @@ class DmsfController < ApplicationController
|
||||
def delete_entries(selected_folders, selected_files, selected_dir_links, selected_file_links, selected_url_links, commit)
|
||||
# Folders
|
||||
selected_folders.each do |id|
|
||||
raise DmsfAccessError unless User.current.allowed_to?(:folder_manipulation, @project)
|
||||
raise RedmineDmsf::Errors::DmsfAccessError unless User.current.allowed_to?(:folder_manipulation, @project)
|
||||
folder = DmsfFolder.find_by(id: id)
|
||||
if folder
|
||||
unless folder.delete commit
|
||||
@ -623,14 +623,14 @@ class DmsfController < ApplicationController
|
||||
return
|
||||
end
|
||||
elsif !commit
|
||||
raise FileNotFound
|
||||
raise RedmineDmsf::Errors::DmsfFileNotFoundError
|
||||
end
|
||||
end
|
||||
# Files
|
||||
deleted_files = []
|
||||
not_deleted_files = []
|
||||
selected_files.each do |id|
|
||||
raise DmsfAccessError unless User.current.allowed_to?(:file_delete, @project)
|
||||
raise RedmineDmsf::Errors::DmsfAccessError unless User.current.allowed_to?(:file_delete, @project)
|
||||
file = DmsfFile.find_by(id: id)
|
||||
if file
|
||||
if file.delete(commit)
|
||||
@ -639,7 +639,7 @@ class DmsfController < ApplicationController
|
||||
not_deleted_files << file
|
||||
end
|
||||
elsif !commit
|
||||
raise FileNotFound
|
||||
raise RedmineDmsf::Errors::DmsfFileNotFoundError
|
||||
end
|
||||
end
|
||||
# Activities
|
||||
@ -663,12 +663,12 @@ class DmsfController < ApplicationController
|
||||
end
|
||||
# Links
|
||||
selected_dir_links.each do |id|
|
||||
raise DmsfAccessError unless User.current.allowed_to?(:folder_manipulation, @project)
|
||||
raise RedmineDmsf::Errors::DmsfAccessError unless User.current.allowed_to?(:folder_manipulation, @project)
|
||||
link = DmsfLink.find_by(id: id)
|
||||
link.delete commit if link
|
||||
end
|
||||
(selected_file_links + selected_url_links).each do |id|
|
||||
raise DmsfAccessError unless User.current.allowed_to?(:file_delete, @project)
|
||||
raise RedmineDmsf::Errors::DmsfAccessError unless User.current.allowed_to?(:file_delete, @project)
|
||||
link = DmsfLink.find_by(id: id)
|
||||
link.delete commit if link
|
||||
end
|
||||
|
||||
@ -54,7 +54,7 @@ class DmsfFilesController < ApplicationController
|
||||
@revision = @file.last_revision
|
||||
else
|
||||
@revision = DmsfFileRevision.find(params[:download].to_i)
|
||||
raise DmsfAccessError if @revision.dmsf_file != @file
|
||||
raise RedmineDmsf::Errors::DmsfAccessError if @revision.dmsf_file != @file
|
||||
end
|
||||
check_project @revision.dmsf_file
|
||||
raise ActionController::MissingFile if @file.deleted?
|
||||
@ -70,7 +70,7 @@ class DmsfFilesController < ApplicationController
|
||||
filename: filename_for_content_disposition(@revision.formatted_name(member)),
|
||||
type: @revision.detect_content_type,
|
||||
disposition: params[:disposition].present? ? params[:disposition] : @revision.dmsf_file.disposition
|
||||
rescue DmsfAccessError => e
|
||||
rescue RedmineDmsf::Errors::DmsfAccessError => e
|
||||
Rails.logger.error e.message
|
||||
render_403
|
||||
rescue => e
|
||||
@ -357,7 +357,7 @@ class DmsfFilesController < ApplicationController
|
||||
|
||||
def check_project(entry)
|
||||
if entry && entry.project != @project
|
||||
raise DmsfAccessError, l(:error_entry_project_does_not_match_current_project)
|
||||
raise RedmineDmsf::Errors::DmsfAccessError, l(:error_entry_project_does_not_match_current_project)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@ -31,6 +31,8 @@ class DmsfFilesCopyController < ApplicationController
|
||||
|
||||
accept_api_auth :copy, :move
|
||||
|
||||
helper :dmsf
|
||||
|
||||
def new
|
||||
member = Member.find_by(project_id: @project.id, user_id: User.current.id)
|
||||
@fast_links = member && member.dmsf_fast_links
|
||||
@ -90,11 +92,11 @@ private
|
||||
def find_file
|
||||
raise ActiveRecord::RecordNotFound unless DmsfFile.where(id: params[:id]).exists?
|
||||
@file = DmsfFile.visible.find params[:id]
|
||||
raise DmsfAccessError if @file.locked_for_user?
|
||||
raise RedmineDmsf::Errors::DmsfAccessError if @file.locked_for_user?
|
||||
@project = @file.project
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
rescue DmsfAccessError
|
||||
rescue RedmineDmsf::Errors::DmsfAccessError
|
||||
render_403
|
||||
end
|
||||
|
||||
@ -126,9 +128,9 @@ private
|
||||
end
|
||||
if (@target_folder && (@target_folder.locked_for_user? || !DmsfFolder.permissions?(@target_folder,
|
||||
false))) || !@target_project.allows_to?(:file_manipulation)
|
||||
raise DmsfAccessError
|
||||
raise RedmineDmsf::Errors::DmsfAccessError
|
||||
end
|
||||
rescue DmsfAccessError
|
||||
rescue RedmineDmsf::Errors::DmsfAccessError
|
||||
render_403
|
||||
end
|
||||
|
||||
|
||||
@ -26,6 +26,8 @@ class DmsfFolderPermissionsController < ApplicationController
|
||||
before_action :authorize
|
||||
before_action :permissions
|
||||
|
||||
helper :dmsf
|
||||
|
||||
def permissions
|
||||
render_403 unless DmsfFolder.permissions?(@dmsf_folder)
|
||||
true
|
||||
@ -63,7 +65,7 @@ class DmsfFolderPermissionsController < ApplicationController
|
||||
|
||||
def find_project
|
||||
@project = Project.visible.find_by_param(params[:project_id])
|
||||
rescue DmsfAccessError
|
||||
rescue RedmineDmsf::Errors::DmsfAccessError
|
||||
render_403
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
@ -71,7 +73,7 @@ class DmsfFolderPermissionsController < ApplicationController
|
||||
|
||||
def find_folder
|
||||
@dmsf_folder = DmsfFolder.visible.find_by!(id: params[:dmsf_folder_id])
|
||||
rescue DmsfAccessError
|
||||
rescue RedmineDmsf::Errors::DmsfAccessError
|
||||
render_403
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
|
||||
@ -31,6 +31,8 @@ class DmsfFoldersCopyController < ApplicationController
|
||||
|
||||
accept_api_auth :copy, :move
|
||||
|
||||
helper :dmsf
|
||||
|
||||
def new
|
||||
member = Member.find_by(project_id: @project.id, user_id: User.current.id)
|
||||
@fast_links = member && member.dmsf_fast_links
|
||||
@ -91,11 +93,11 @@ class DmsfFoldersCopyController < ApplicationController
|
||||
def find_folder
|
||||
raise ActiveRecord::RecordNotFound unless DmsfFolder.where(id: params[:id]).exists?
|
||||
@folder = DmsfFolder.visible.find params[:id]
|
||||
raise DmsfAccessError if @folder.locked_for_user?
|
||||
raise RedmineDmsf::Errors::DmsfAccessError if @folder.locked_for_user?
|
||||
@project = @folder.project
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
rescue DmsfAccessError
|
||||
rescue RedmineDmsf::Errors::DmsfAccessError
|
||||
render_403
|
||||
end
|
||||
|
||||
@ -124,9 +126,9 @@ class DmsfFoldersCopyController < ApplicationController
|
||||
end
|
||||
if (@target_folder && (@target_folder.locked_for_user? || !DmsfFolder.permissions?(@target_folder, false))) ||
|
||||
!@target_project.allows_to?(:folder_manipulation)
|
||||
raise DmsfAccessError
|
||||
raise RedmineDmsf::Errors::DmsfAccessError
|
||||
end
|
||||
rescue DmsfAccessError
|
||||
rescue RedmineDmsf::Errors::DmsfAccessError
|
||||
render_403
|
||||
end
|
||||
|
||||
|
||||
@ -33,6 +33,8 @@ class DmsfLinksController < ApplicationController
|
||||
|
||||
accept_api_auth :create
|
||||
|
||||
helper :dmsf
|
||||
|
||||
def permissions
|
||||
if @dmsf_link
|
||||
render_403 unless DmsfFolder.permissions?(@dmsf_link.dmsf_folder)
|
||||
|
||||
@ -30,8 +30,9 @@ class DmsfUploadController < ApplicationController
|
||||
before_action :find_folder, except: [:upload, :commit, :delete_dmsf_attachment, :delete_dmsf_link_attachment]
|
||||
before_action :permissions, except: [:upload, :commit, :delete_dmsf_attachment, :delete_dmsf_link_attachment]
|
||||
|
||||
helper :all
|
||||
helper :custom_fields
|
||||
helper :dmsf_workflows
|
||||
helper :dmsf
|
||||
|
||||
accept_api_auth :upload, :commit
|
||||
|
||||
@ -140,7 +141,7 @@ class DmsfUploadController < ApplicationController
|
||||
|
||||
def find_folder
|
||||
@folder = DmsfFolder.visible.find(params[:folder_id]) if params.keys.include?('folder_id')
|
||||
rescue DmsfAccessError
|
||||
rescue RedmineDmsf::Errors::DmsfAccessError
|
||||
render_403
|
||||
end
|
||||
|
||||
|
||||
@ -32,6 +32,8 @@ class DmsfWorkflowsController < ApplicationController
|
||||
|
||||
layout :workflows_layout
|
||||
|
||||
helper :dmsf
|
||||
|
||||
def permissions
|
||||
revision = DmsfFileRevision.find_by(id: params[:dmsf_file_revision_id]) if params[:dmsf_file_revision_id].present?
|
||||
if revision
|
||||
@ -68,7 +70,7 @@ class DmsfWorkflowsController < ApplicationController
|
||||
if revision.dmsf_file
|
||||
begin
|
||||
revision.dmsf_file.unlock!(true) unless Setting.plugin_redmine_dmsf['dmsf_keep_documents_locked']
|
||||
rescue DmsfLockError => e
|
||||
rescue RedmineDmsf::Errors::DmsfLockError => e
|
||||
flash[:info] = e.message
|
||||
end
|
||||
end
|
||||
@ -213,7 +215,7 @@ class DmsfWorkflowsController < ApplicationController
|
||||
if file
|
||||
begin
|
||||
file.lock!
|
||||
rescue DmsfLockError => e
|
||||
rescue RedmineDmsf::Errors::DmsfLockError => e
|
||||
Rails.logger.warn e.message
|
||||
end
|
||||
flash[:notice] = l(:notice_successful_update)
|
||||
|
||||
@ -27,6 +27,14 @@ require 'csv'
|
||||
module DmsfHelper
|
||||
include Redmine::I18n
|
||||
|
||||
unless Redmine::Plugin.installed?(:easy_extensions)
|
||||
|
||||
def late_javascript_tag(content_or_options_with_block = nil, html_options = {}, &block)
|
||||
javascript_tag content_or_options_with_block, html_options
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def self.temp_dir
|
||||
if Setting.plugin_redmine_dmsf['dmsf_tmpdir'].present?
|
||||
tmpdir = Pathname.new(Setting.plugin_redmine_dmsf['dmsf_tmpdir'])
|
||||
|
||||
@ -137,7 +137,7 @@ module DmsfUploadHelper
|
||||
wf.notify_users project, new_revision, controller
|
||||
begin
|
||||
file.lock!
|
||||
rescue DmsfLockError => e
|
||||
rescue RedmineDmsf::Errors::DmsfLockError => e
|
||||
Rails.logger.warn e.message
|
||||
end
|
||||
else
|
||||
|
||||
@ -184,7 +184,7 @@ class DmsfFileRevision < ActiveRecord::Base
|
||||
end
|
||||
|
||||
def new_storage_filename
|
||||
raise DmsfAccessError, 'File id is not set' unless dmsf_file&.id
|
||||
raise RedmineDmsf::Errors::DmsfAccessError, 'File id is not set' unless dmsf_file&.id
|
||||
filename = DmsfHelper.sanitize_filename(name)
|
||||
timestamp = DateTime.current.strftime('%y%m%d%H%M%S')
|
||||
while File.exist?(storage_base_path.join("#{timestamp}_#{dmsf_file.id}_#{filename}"))
|
||||
|
||||
@ -20,6 +20,8 @@
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
|
||||
require 'query'
|
||||
|
||||
class DmsfModifiedQueryColumn < QueryColumn
|
||||
|
||||
def value_object(object)
|
||||
|
||||
@ -19,26 +19,33 @@
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
if defined?(EasyExtensions)
|
||||
module EasyPageModules
|
||||
module EasyDms
|
||||
|
||||
class EpmDmsfLockedDocuments < EasyPageModule
|
||||
|
||||
def category_name
|
||||
@category_name ||= 'easy_dms'
|
||||
unless defined?(EasyExtensions)
|
||||
class EasyPageModule
|
||||
end
|
||||
end
|
||||
|
||||
def get_show_data(settings, user, page_context = {})
|
||||
{}
|
||||
end
|
||||
class EpmDmsfLockedDocuments < EasyPageModule
|
||||
|
||||
def get_edit_data(settings, user, page_context = {})
|
||||
{}
|
||||
end
|
||||
def category_name
|
||||
@category_name ||= 'easy_dms'
|
||||
end
|
||||
|
||||
def get_show_data(settings, user, page_context = {})
|
||||
{}
|
||||
end
|
||||
|
||||
def get_edit_data(settings, user, page_context = {})
|
||||
{}
|
||||
end
|
||||
|
||||
def registered_in_plugin
|
||||
'redmine_dmsf'
|
||||
end
|
||||
|
||||
def registered_in_plugin
|
||||
'redmine_dmsf'
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
@ -19,22 +19,29 @@
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
if defined?(EasyExtensions)
|
||||
module EasyPageModules
|
||||
module EasyDms
|
||||
|
||||
class EpmDmsfOpenApprovals < EasyPageModule
|
||||
|
||||
def category_name
|
||||
@category_name ||= 'easy_dms'
|
||||
unless defined?(EasyExtensions)
|
||||
class EasyPageModule
|
||||
end
|
||||
end
|
||||
|
||||
def get_show_data(settings, user, page_context = {})
|
||||
{}
|
||||
end
|
||||
class EpmDmsfOpenApprovals < EasyPageModule
|
||||
|
||||
def category_name
|
||||
@category_name ||= 'easy_dms'
|
||||
end
|
||||
|
||||
def get_show_data(settings, user, page_context = {})
|
||||
{}
|
||||
end
|
||||
|
||||
def registered_in_plugin
|
||||
'redmine_dmsf'
|
||||
end
|
||||
|
||||
def registered_in_plugin
|
||||
'redmine_dmsf'
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
@ -1,15 +1,38 @@
|
||||
# encoding: utf-8
|
||||
# frozen_string_literal: true
|
||||
#
|
||||
# Redmine plugin for Document Management System "Features"
|
||||
#
|
||||
# Copyright © 2011 Vít Jonáš <vit.jonas@gmail.com>
|
||||
# Copyright © 2012 Daniel Munn <dan.munn@munnster.co.uk>
|
||||
# Copyright © 2011-22 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 'time'
|
||||
require 'uri'
|
||||
require 'nokogiri'
|
||||
require 'ox'
|
||||
|
||||
require 'rack'
|
||||
require 'dav4rack/utils'
|
||||
require 'dav4rack/http_status'
|
||||
require 'dav4rack/resource'
|
||||
require 'dav4rack/handler'
|
||||
require 'dav4rack/controller'
|
||||
require File.dirname(__FILE__) + '/dav4rack/utils'
|
||||
require File.dirname(__FILE__) + '/dav4rack/http_status'
|
||||
require File.dirname(__FILE__) + '/dav4rack/resource'
|
||||
require File.dirname(__FILE__) + '/dav4rack/handler'
|
||||
require File.dirname(__FILE__) + '/dav4rack/controller'
|
||||
|
||||
module DAV4Rack
|
||||
module Dav4rack
|
||||
IS_18 = RUBY_VERSION[0,3] == '1.8'
|
||||
end
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
Current DAV4Rack license:
|
||||
Current Dav4rack license:
|
||||
|
||||
Copyright (c) 2010 Chris Roberts <chrisroberts.code@gmail.com>
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# DAV4Rack - Web Authoring for Rack[](https://travis-ci.org/planio-gmbh/dav4rack)
|
||||
# Dav4rack - Web Authoring for Rack[](https://travis-ci.org/planio-gmbh/dav4rack)
|
||||
|
||||
DAV4Rack is a framework for providing WebDAV via Rack allowing content
|
||||
Dav4rack is a framework for providing WebDAV via Rack allowing content
|
||||
authoring over HTTP. It is based off the [original RackDAV
|
||||
framework](http://github.com/georgi/rack_dav) adding some useful new features:
|
||||
|
||||
@ -21,7 +21,7 @@ are just here to use the library, enjoy!
|
||||
|
||||
## About this fork
|
||||
|
||||
This is the [Planio](https://plan.io/redmine-hosting) fork of DAV4Rack. The
|
||||
This is the [Planio](https://plan.io/redmine-hosting) fork of Dav4rack. The
|
||||
master branch includes improvements and fixes done by @djgraham and
|
||||
@tim-vandecasteele in their respective forks on Github.
|
||||
|
||||
@ -30,7 +30,7 @@ plugin, as well as improvements done by ourselves during development of an
|
||||
upcoming redmine document management plugin.
|
||||
|
||||
Several core APIs were changed in the process so it will not be a straight
|
||||
upgrade for applications that were developed with DAV4Rack 0.3 (the last
|
||||
upgrade for applications that were developed with Dav4rack 0.3 (the last
|
||||
released Gem version).
|
||||
|
||||
## Install
|
||||
@ -51,7 +51,7 @@ This will give you the last officially released version, which is *very* old.
|
||||
|
||||
## Documentation
|
||||
|
||||
- [DAV4Rack documentation](http://chrisroberts.github.com/dav4rack)
|
||||
- [Dav4rack documentation](http://chrisroberts.github.com/dav4rack)
|
||||
|
||||
## Quickstart
|
||||
|
||||
@ -71,7 +71,7 @@ basic authentication which is used for an example. To enable it:
|
||||
|
||||
## Rack Handler
|
||||
|
||||
Using DAV4Rack within a rack application is pretty simple. A very slim
|
||||
Using Dav4rack within a rack application is pretty simple. A very slim
|
||||
rackup script would look something like this:
|
||||
|
||||
|
||||
@ -80,15 +80,15 @@ rackup script would look something like this:
|
||||
require 'dav4rack'
|
||||
|
||||
use Rack::CommonLogger
|
||||
run DAV4Rack::Handler.new(root: '/path/to/public/fileshare')
|
||||
run Dav4rack::Handler.new(root: '/path/to/public/fileshare')
|
||||
```
|
||||
|
||||
This will use the included FileResource and set the share path. However,
|
||||
DAV4Rack has some nifty little extras that can be enabled in the rackup script.
|
||||
Dav4rack has some nifty little extras that can be enabled in the rackup script.
|
||||
First, an example of how to use a custom resource:
|
||||
|
||||
```ruby
|
||||
run DAV4Rack::Handler.new(resource_class: CustomResource,
|
||||
run Dav4rack::Handler.new(resource_class: CustomResource,
|
||||
custom: 'options',
|
||||
passed: 'to resource')
|
||||
```
|
||||
@ -106,14 +106,14 @@ specific directory: `/webdav/share/`
|
||||
|
||||
app = Rack::Builder.new{
|
||||
map '/webdav/share/' do
|
||||
run DAV4Rack::Handler.new(root: '/path/to/public/fileshare')
|
||||
run Dav4rack::Handler.new(root: '/path/to/public/fileshare')
|
||||
end
|
||||
}.to_app
|
||||
run app
|
||||
```
|
||||
|
||||
Aside from the `Builder#map` block, notice the new option passed to the Handler's
|
||||
initialization, `:root_uri_path`. When DAV4Rack receives a request, it will
|
||||
initialization, `:root_uri_path`. When Dav4rack receives a request, it will
|
||||
automatically convert the request to the proper path and pass it to the
|
||||
resource.
|
||||
|
||||
@ -130,13 +130,13 @@ with the last example but this time include the interceptor:
|
||||
use Rack::CommonLogger
|
||||
app = Rack::Builder.new{
|
||||
map '/webdav/share/' do
|
||||
run DAV4Rack::Handler.new(root: '/path/to/public/fileshare')
|
||||
run Dav4rack::Handler.new(root: '/path/to/public/fileshare')
|
||||
end
|
||||
map '/webdav/share2/' do
|
||||
run DAV4Rack::Handler.new(resource_class: CustomResource)
|
||||
run Dav4rack::Handler.new(resource_class: CustomResource)
|
||||
end
|
||||
map '/' do
|
||||
use DAV4Rack::Interceptor, mappings: {
|
||||
use Dav4rack::Interceptor, mappings: {
|
||||
'/webdav/share/' => {resource_class: FileResource, custom: 'option'},
|
||||
'/webdav/share2/' => {resource_class: CustomResource}
|
||||
}
|
||||
@ -155,7 +155,7 @@ a virtual file system view to the provided mapped paths. Once the actual
|
||||
resources have been reached, authentication will be enforced based on the
|
||||
requirements defined by the individual resource. Also note in the root map you
|
||||
can see we are running a Rails application. This is how you can easily enable
|
||||
DAV4Rack with your Rails application.
|
||||
Dav4rack with your Rails application.
|
||||
|
||||
|
||||
## Custom Middleware
|
||||
@ -175,7 +175,7 @@ class CustomMiddleware
|
||||
|
||||
@dav_app = Rack::Builder.new{
|
||||
map '/dav/' do
|
||||
run DAV4Rack::Handler.new(resource_class: CustomResource)
|
||||
run Dav4rack::Handler.new(resource_class: CustomResource)
|
||||
end
|
||||
|
||||
map '/other/dav' do
|
||||
@ -217,22 +217,22 @@ Rails.configuration.middleware.insert_before ActionDispatch::Cookies, CustomMidd
|
||||
|
||||
## Logging
|
||||
|
||||
DAV4Rack provides some simple logging in a Rails style format (simply for
|
||||
Dav4rack provides some simple logging in a Rails style format (simply for
|
||||
consistency) so the output should look somewhat familiar.
|
||||
|
||||
DAV4Rack::Handler.new(resource_class: CustomResource, log_to: '/my/log/file')
|
||||
Dav4rack::Handler.new(resource_class: CustomResource, log_to: '/my/log/file')
|
||||
|
||||
You can even specify the level of logging:
|
||||
|
||||
DAV4Rack::Handler.new(resource_class: CustomResource, log_to: ['/my/log/file', Logger::DEBUG])
|
||||
Dav4rack::Handler.new(resource_class: CustomResource, log_to: ['/my/log/file', Logger::DEBUG])
|
||||
|
||||
In order to use the Rails logger, just specify `log_to: Rails.logger`.
|
||||
|
||||
## Custom Resources
|
||||
|
||||
Creating your own resource is easy. Simply inherit the DAV4Rack::Resource
|
||||
Creating your own resource is easy. Simply inherit the Dav4rack::Resource
|
||||
class, and start redefining all the methods you want to customize. The
|
||||
DAV4Rack::Resource class only has implementations for methods that can be
|
||||
Dav4rack::Resource class only has implementations for methods that can be
|
||||
provided extremely generically. This means that most things will require at
|
||||
least some sort of implementation. However, because the Resource is defined so
|
||||
generically, and the Controller simply passes the request on to the Resource,
|
||||
@ -242,7 +242,7 @@ it is easy to create fully virtualized resources.
|
||||
|
||||
There are some helpers worth mentioning that make things a little easier.
|
||||
|
||||
First of all, take note that the `request` object will be an instance of `DAV4Rack::Request`, which extends `Rack::Request` with some useful helpers.
|
||||
First of all, take note that the `request` object will be an instance of `Dav4rack::Request`, which extends `Rack::Request` with some useful helpers.
|
||||
|
||||
### Redirects and sending remote files
|
||||
|
||||
@ -251,11 +251,11 @@ will accept and properly use a 302 redirect for a GET request. Most clients do
|
||||
not properly support this, which can be a real pain when working with
|
||||
virtualized files that may be located some where else, like S3. To deal with
|
||||
those clients that don't support redirects, a helper has been provided so
|
||||
resources don't have to deal with proxying themselves. The DAV4Rack::RemoteFile
|
||||
resources don't have to deal with proxying themselves. The Dav4rack::RemoteFile
|
||||
is a modified Rack::File that can do some interesting things. First, lets look
|
||||
at its most basic use:
|
||||
|
||||
class MyResource < DAV4Rack::Resource
|
||||
class MyResource < Dav4rack::Resource
|
||||
def setup
|
||||
@item = method_to_fill_this_properly
|
||||
end
|
||||
@ -264,7 +264,7 @@ at its most basic use:
|
||||
if(request.client_allows_redirect?)
|
||||
response.redirect item[:url]
|
||||
else
|
||||
response.body = DAV4Rack::RemoteFile.new(item[:url], :size => content_length, :mime_type => content_type)
|
||||
response.body = Dav4rack::RemoteFile.new(item[:url], :size => content_length, :mime_type => content_type)
|
||||
OK
|
||||
end
|
||||
end
|
||||
@ -274,7 +274,7 @@ This is a simple proxy. When Rack receives the RemoteFile, it will pull a chunk
|
||||
sends it to the user over and over again until the EOF is reached. This much the same method that Rack::File uses but instead we are pulling
|
||||
from a socket rather than an actual file. Now, instead of proxying these files from a remote server every time, lets cache them:
|
||||
|
||||
response.body = DAV4Rack::RemoteFile.new(item[:url], :size => content_length, :mime_type => content_type, :cache_directory => '/tmp')
|
||||
response.body = Dav4rack::RemoteFile.new(item[:url], :size => content_length, :mime_type => content_type, :cache_directory => '/tmp')
|
||||
|
||||
Providing the `:cache_directory` will let RemoteFile cache the items locally,
|
||||
and then search for them on subsequent requests before heading out to the
|
||||
@ -283,7 +283,7 @@ and last modified time. It is important to note that for services like S3, the
|
||||
path will often change, making this cache pretty worthless. To combat this, we
|
||||
can provide a reference to use instead:
|
||||
|
||||
response.body = DAV4Rack::RemoteFile.new(item[:url], :size => content_length, :mime_type => content_type, :cache_directory => '/tmp', :cache_ref => item[:static_url])
|
||||
response.body = Dav4rack::RemoteFile.new(item[:url], :size => content_length, :mime_type => content_type, :cache_directory => '/tmp', :cache_ref => item[:static_url])
|
||||
|
||||
These methods will work just fine, but it would be really nice to just let
|
||||
someone else deal with the proxying and let the process get back to dealing
|
||||
@ -320,16 +320,16 @@ applicable to other servers. First, a simplified NGINX server block:
|
||||
|
||||
With this in place, the parameters for the RemoteFile change slightly:
|
||||
|
||||
response.body = DAV4Rack::RemoteFile.new(item[:url], :size => content_length, :mime_type => content_type, :sendfile => true)
|
||||
response.body = Dav4rack::RemoteFile.new(item[:url], :size => content_length, :mime_type => content_type, :sendfile => true)
|
||||
|
||||
The RemoteFile will automatically take care of building out the correct path and sending the proper headers. If the X-Accel-Remote-Mapping header
|
||||
is not available, you can simply pass the value:
|
||||
|
||||
response.body = DAV4Rack::RemoteFile.new(item[:url], :size => content_length, :mime_type => content_type, :sendfile => true, :sendfile_prefix => 'webdav_redirect')
|
||||
response.body = Dav4rack::RemoteFile.new(item[:url], :size => content_length, :mime_type => content_type, :sendfile => true, :sendfile_prefix => 'webdav_redirect')
|
||||
|
||||
And if you don't have the X-Sendfile-Type header set, you can fix that by changing the value of :sendfile:
|
||||
|
||||
response.body = DAV4Rack::RemoteFile.new(item[:url], :size => content_length, :mime_type => content_type, :sendfile => 'X-Accel-Redirect', :sendfile_prefix => 'webdav_redirect')
|
||||
response.body = Dav4rack::RemoteFile.new(item[:url], :size => content_length, :mime_type => content_type, :sendfile => 'X-Accel-Redirect', :sendfile_prefix => 'webdav_redirect')
|
||||
|
||||
And if you have none of the above because your server hasn't been configured for sendfile support, you're out of luck until it's configured.
|
||||
|
||||
@ -359,7 +359,7 @@ to be called by the Controller, or anyone else, should be scoped protected or pr
|
||||
|
||||
Callbacks can be called before or after a method call. For example:
|
||||
|
||||
class MyResource < DAV4Rack::Resource
|
||||
class MyResource < Dav4rack::Resource
|
||||
before do |resource, method_name|
|
||||
resource.send(:my_authentication_method)
|
||||
end
|
||||
@ -379,7 +379,7 @@ In this example MyResource#my_authentication_method will be called before any pu
|
||||
line will be printed to STDOUT. Running callbacks before/after every method call is a bit much in most cases, so callbacks can be applied to specific
|
||||
methods:
|
||||
|
||||
class MyResource < DAV4Rack::Resource
|
||||
class MyResource < Dav4rack::Resource
|
||||
before_get do |resource|
|
||||
puts "#{Time.now} -> Received GET request from resource: #{resource}"
|
||||
end
|
||||
@ -390,7 +390,7 @@ provided to callbacks. The method name is only provided to the generic before/af
|
||||
|
||||
Something very handy for dealing with the mess of files OS X leaves on the system:
|
||||
|
||||
class MyResource < DAV4Rack::Resource
|
||||
class MyResource < Dav4rack::Resource
|
||||
after_unlock do |resource|
|
||||
resource.delete if resource.name[0,1] == '.'
|
||||
end
|
||||
@ -400,7 +400,7 @@ Because OS X implements locking correctly, we can wait until it releases the loc
|
||||
|
||||
Callbacks are called in the order they are defined, so you can easily build callbacks off each other. Like this example:
|
||||
|
||||
class MyResource < DAV4Rack::Resource
|
||||
class MyResource < Dav4rack::Resource
|
||||
before do |resource, method_name|
|
||||
resource.DAV_authenticate unless resource.user.is_a?(User)
|
||||
raise Unauthorized unless resource.user.is_a?(User)
|
||||
@ -419,7 +419,7 @@ Something special to notice in the last example is the DAV_ prefix on authentica
|
||||
any callbacks being applied to the given method. This allows us to provide a public method that the callback can access on the resource
|
||||
without getting stuck in a loop.
|
||||
|
||||
## Software using DAV4Rack!
|
||||
## Software using Dav4rack!
|
||||
|
||||
* {meishi}[https://github.com/inferiorhumanorgans/meishi] - Lightweight CardDAV implementation in Rails
|
||||
* {dav4rack_ext}[https://github.com/schmurfy/dav4rack_ext] - CardDAV extension. (CalDAV planned)
|
||||
|
||||
@ -1,21 +1,21 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'uri'
|
||||
require 'dav4rack/destination_header'
|
||||
require 'dav4rack/request'
|
||||
require 'dav4rack/xml_elements'
|
||||
require 'dav4rack/xml_response'
|
||||
require File.dirname(__FILE__) + '/uri'
|
||||
require File.dirname(__FILE__) + '/destination_header'
|
||||
require File.dirname(__FILE__) + '/request'
|
||||
require File.dirname(__FILE__) + '/xml_elements'
|
||||
require File.dirname(__FILE__) + '/xml_response'
|
||||
|
||||
module DAV4Rack
|
||||
module Dav4rack
|
||||
|
||||
class Controller
|
||||
include DAV4Rack::HTTPStatus
|
||||
include DAV4Rack::Utils
|
||||
include Dav4rack::HttpStatus
|
||||
include Dav4rack::Utils
|
||||
|
||||
attr_reader :request, :response, :resource
|
||||
|
||||
|
||||
# request:: DAV4Rack::Request
|
||||
# request:: Dav4rack::Request
|
||||
# response:: Rack::Response
|
||||
# options:: Options hash
|
||||
# Create a new Controller.
|
||||
@ -45,9 +45,9 @@ module DAV4Rack
|
||||
if skip_authorization? || authenticate
|
||||
status = process_action || OK
|
||||
else
|
||||
status = HTTPStatus::Unauthorized
|
||||
status = HttpStatus::Unauthorized
|
||||
end
|
||||
rescue HTTPStatus::Status => e
|
||||
rescue HttpStatus::Status => e
|
||||
status = e
|
||||
ensure
|
||||
if status
|
||||
@ -63,7 +63,7 @@ module DAV4Rack
|
||||
private
|
||||
|
||||
# delegates to the handler method matching this requests http method.
|
||||
# must return an HTTPStatus. If nil / false, the resulting status will be
|
||||
# must return an HttpStatus. If nil / false, the resulting status will be
|
||||
# 200/OK
|
||||
def process_action
|
||||
send request.request_method.downcase
|
||||
@ -175,7 +175,9 @@ module DAV4Rack
|
||||
if request.content_length.to_i > 0
|
||||
return UnsupportedMediaType
|
||||
end
|
||||
return MethodNotAllowed if resource.exist?
|
||||
if resource.exist?
|
||||
return MethodNotAllowed
|
||||
end
|
||||
|
||||
resource.lock_check if resource.supports_locking?
|
||||
status = resource.make_collection
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
require 'addressable/uri'
|
||||
|
||||
module DAV4Rack
|
||||
module Dav4rack
|
||||
class DestinationHeader
|
||||
|
||||
attr_reader :host, :path, :path_info
|
||||
|
||||
# uri is expected to be a DAV4Rack::Uri instance
|
||||
# uri is expected to be a Dav4rack::Uri instance
|
||||
def initialize(uri)
|
||||
@host = uri.host
|
||||
@path = uri.path
|
||||
@ -18,9 +18,9 @@ module DAV4Rack
|
||||
# host must be the same, but path must differ
|
||||
def validate(host: nil, resource_path: nil)
|
||||
if host and self.host and self.host != host
|
||||
DAV4Rack::HTTPStatus::BadGateway
|
||||
Dav4rack::HttpStatus::BadGateway
|
||||
elsif resource_path and self.path_info == resource_path
|
||||
DAV4Rack::HTTPStatus::Forbidden
|
||||
Dav4rack::HttpStatus::Forbidden
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@ -2,8 +2,8 @@ require 'time'
|
||||
require 'rack/utils'
|
||||
require 'rack/mime'
|
||||
|
||||
module DAV4Rack
|
||||
# DAV4Rack::File simply allows us to use Rack::File but with the
|
||||
module Dav4rack
|
||||
# Dav4rack::File simply allows us to use Rack::File but with the
|
||||
# specific location we deem appropriate
|
||||
#
|
||||
# FIXME is that used anywhere?
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
require 'pstore'
|
||||
|
||||
module DAV4Rack
|
||||
module Dav4rack
|
||||
class FileResourceLock
|
||||
attr_accessor :path
|
||||
attr_accessor :token
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'dav4rack/logger'
|
||||
require File.dirname(__FILE__) + '/logger'
|
||||
|
||||
module DAV4Rack
|
||||
module Dav4rack
|
||||
class Handler
|
||||
include DAV4Rack::HTTPStatus
|
||||
include Dav4rack::HttpStatus
|
||||
|
||||
# Options:
|
||||
#
|
||||
@ -63,7 +63,7 @@ module DAV4Rack
|
||||
|
||||
|
||||
def setup_request(env)
|
||||
::DAV4Rack::Request.new env, @options
|
||||
::Dav4rack::Request.new env, @options
|
||||
end
|
||||
|
||||
|
||||
@ -72,7 +72,7 @@ module DAV4Rack
|
||||
end
|
||||
|
||||
def controller_class
|
||||
@options[:controller_class] || ::DAV4Rack::Controller
|
||||
@options[:controller_class] || ::Dav4rack::Controller
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module DAV4Rack
|
||||
module Dav4rack
|
||||
|
||||
module HTTPStatus
|
||||
module HttpStatus
|
||||
|
||||
class Status < Exception
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
require 'dav4rack/interceptor_resource'
|
||||
module DAV4Rack
|
||||
require File.dirname(__FILE__) + '/interceptor_resource'
|
||||
module Dav4rack
|
||||
class Interceptor
|
||||
def initialize(app, args={})
|
||||
@roots = args[:mappings].keys
|
||||
@ -14,7 +14,7 @@ module DAV4Rack
|
||||
method = env['REQUEST_METHOD'].upcase
|
||||
app = nil
|
||||
if(@roots.detect{|x| path =~ /^#{Regexp.escape(x.downcase)}\/?/}.nil? && @intercept_methods.include?(method))
|
||||
app = DAV4Rack::Handler.new(:resource_class => InterceptorResource, :mappings => @args[:mappings], :log_to => @args[:log_to])
|
||||
app = Dav4rack::Handler.new(:resource_class => InterceptorResource, :mappings => @args[:mappings], :log_to => @args[:log_to])
|
||||
end
|
||||
app ? app.call(env) : @app.call(env)
|
||||
end
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
require 'digest/sha1'
|
||||
|
||||
module DAV4Rack
|
||||
module Dav4rack
|
||||
|
||||
class InterceptorResource < Resource
|
||||
attr_reader :path, :options
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
module DAV4Rack
|
||||
module Dav4rack
|
||||
class Lock
|
||||
|
||||
def initialize(args={})
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
require 'dav4rack/lock'
|
||||
module DAV4Rack
|
||||
require File.dirname(__FILE__) + '/lock'
|
||||
|
||||
module Dav4rack
|
||||
class LockStore
|
||||
class << self
|
||||
def create
|
||||
@ -58,4 +59,4 @@ module DAV4Rack
|
||||
end
|
||||
end
|
||||
|
||||
DAV4Rack::LockStore.create
|
||||
Dav4rack::LockStore.create
|
||||
@ -1,6 +1,6 @@
|
||||
require 'logger'
|
||||
|
||||
module DAV4Rack
|
||||
module Dav4rack
|
||||
# This is a simple wrapper for the Logger class. It allows easy access
|
||||
# to log messages from the library.
|
||||
class Logger
|
||||
|
||||
@ -3,7 +3,7 @@ require 'uri'
|
||||
require 'digest/sha1'
|
||||
require 'rack/file'
|
||||
|
||||
module DAV4Rack
|
||||
module Dav4rack
|
||||
|
||||
#FIXME unused?
|
||||
class RemoteFile < Rack::File
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'uri'
|
||||
require File.dirname(__FILE__) + '/uri'
|
||||
require 'addressable/uri'
|
||||
require 'dav4rack/logger'
|
||||
require 'dav4rack/uri'
|
||||
require File.dirname(__FILE__) + '/logger'
|
||||
require File.dirname(__FILE__) + '/uri'
|
||||
|
||||
module DAV4Rack
|
||||
module Dav4rack
|
||||
class Request < Rack::Request
|
||||
|
||||
# Root URI path for the resource
|
||||
@ -89,7 +89,7 @@ module DAV4Rack
|
||||
# Destination header
|
||||
def destination
|
||||
@destination ||= if h = get_header('HTTP_DESTINATION')
|
||||
DestinationHeader.new DAV4Rack::Uri.new(h, script_name: script_name)
|
||||
DestinationHeader.new Dav4rack::Uri.new(h, script_name: script_name)
|
||||
end
|
||||
end
|
||||
|
||||
@ -131,7 +131,7 @@ module DAV4Rack
|
||||
# returns the given path, but with the leading script_name removed. Will
|
||||
# return nil if the path does not begin with the script_name
|
||||
def path_info_for(full_path, script_name: self.script_name)
|
||||
uri = DAV4Rack::Uri.new full_path, script_name: script_name
|
||||
uri = Dav4rack::Uri.new full_path, script_name: script_name
|
||||
return uri.path_info
|
||||
end
|
||||
|
||||
@ -195,8 +195,8 @@ module DAV4Rack
|
||||
config.strict
|
||||
} if body
|
||||
rescue
|
||||
DAV4Rack::Logger.error $!.message
|
||||
raise ::DAV4Rack::HTTPStatus::BadRequest
|
||||
Dav4rack::Logger.error $!.message
|
||||
raise ::Dav4rack::HttpStatus::BadRequest
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'uuidtools'
|
||||
require 'dav4rack/lock_store'
|
||||
require 'dav4rack/xml_elements'
|
||||
require File.dirname(__FILE__) + '/lock_store'
|
||||
require File.dirname(__FILE__) + '/xml_elements'
|
||||
|
||||
module DAV4Rack
|
||||
module Dav4rack
|
||||
|
||||
class LockFailure < RuntimeError
|
||||
attr_reader :path_status
|
||||
@ -19,8 +19,8 @@ module DAV4Rack
|
||||
end
|
||||
|
||||
class Resource
|
||||
include DAV4Rack::Utils
|
||||
include DAV4Rack::XmlElements
|
||||
include Dav4rack::Utils
|
||||
include Dav4rack::XmlElements
|
||||
|
||||
attr_reader :path, :request,
|
||||
:response, :propstat_relative_path, :root_xml_attributes, :namespaces
|
||||
@ -54,7 +54,7 @@ module DAV4Rack
|
||||
|
||||
end
|
||||
|
||||
include DAV4Rack::HTTPStatus
|
||||
include Dav4rack::HttpStatus
|
||||
|
||||
# path:: Internal resource path (unescaped PATH_INFO)
|
||||
# request:: Rack::Request
|
||||
@ -288,17 +288,17 @@ module DAV4Rack
|
||||
end
|
||||
begin
|
||||
lock_check(args[:type])
|
||||
rescue DAV4Rack::LockFailure => lock_failure
|
||||
rescue Dav4rack::LockFailure => lock_failure
|
||||
lock.destroy
|
||||
raise lock_failure
|
||||
rescue HTTPStatus::Status => status
|
||||
rescue HttpStatus::Status => status
|
||||
status
|
||||
end
|
||||
[lock.remaining_timeout, lock.token]
|
||||
end
|
||||
|
||||
# lock_scope:: scope of lock
|
||||
# Check if resource is locked. Raise DAV4Rack::LockFailure if locks are in place.
|
||||
# Check if resource is locked. Raise Dav4rack::LockFailure if locks are in place.
|
||||
def lock_check(lock_scope=nil)
|
||||
return unless @lock_class
|
||||
if(@lock_class.explicitly_locked?(@path))
|
||||
@ -306,7 +306,7 @@ module DAV4Rack
|
||||
elsif(@lock_class.implicitly_locked?(@path))
|
||||
if(lock_scope.to_s == 'exclusive')
|
||||
locks = @lock_class.implicit_locks(@path)
|
||||
failure = DAV4Rack::LockFailure.new("Failed to lock: #{@path}")
|
||||
failure = Dav4rack::LockFailure.new("Failed to lock: #{@path}")
|
||||
locks.each do |lock|
|
||||
failure.add_failure(@path, Locked)
|
||||
end
|
||||
|
||||
@ -3,377 +3,379 @@
|
||||
|
||||
require 'pstore'
|
||||
#require 'webrick/httputils'
|
||||
require 'dav4rack/file_resource_lock'
|
||||
require 'dav4rack/security_utils'
|
||||
require File.dirname(__FILE__) + '/../file_resource_lock'
|
||||
require File.dirname(__FILE__) + '/../security_utils'
|
||||
|
||||
module DAV4Rack
|
||||
module Dav4rack
|
||||
module Resources
|
||||
|
||||
class FileResource < Resource
|
||||
class FileResource < Resource
|
||||
|
||||
#include WEBrick::HTTPUtils
|
||||
include DAV4Rack::Utils
|
||||
#include WEBrick::HTTPUtils
|
||||
include Dav4rack::Utils
|
||||
|
||||
# If this is a collection, return the child resources.
|
||||
def children
|
||||
Dir[file_path + '/*'].map do |path|
|
||||
child ::File.basename(path)
|
||||
end
|
||||
end
|
||||
|
||||
# Is this resource a collection?
|
||||
def collection?
|
||||
::File.directory?(file_path)
|
||||
end
|
||||
|
||||
# Does this recource exist?
|
||||
def exist?
|
||||
::File.exist?(file_path)
|
||||
end
|
||||
|
||||
# Return the creation time.
|
||||
def creation_date
|
||||
stat.ctime
|
||||
end
|
||||
|
||||
# Return the time of last modification.
|
||||
def last_modified
|
||||
stat.mtime
|
||||
end
|
||||
|
||||
# Set the time of last modification.
|
||||
def last_modified=(time)
|
||||
::File.utime(Time.now, time, file_path)
|
||||
end
|
||||
|
||||
# Return an Etag, an unique hash value for this resource.
|
||||
def etag
|
||||
sprintf('%x-%x-%x', stat.ino, stat.size, stat.mtime.to_i)
|
||||
end
|
||||
|
||||
# Return the mime type of this resource.
|
||||
def content_type
|
||||
if stat.directory?
|
||||
"text/html"
|
||||
else
|
||||
mime_type(file_path, DefaultMimeTypes)
|
||||
end
|
||||
end
|
||||
|
||||
# Return the size in bytes for this resource.
|
||||
def content_length
|
||||
stat.size
|
||||
end
|
||||
|
||||
# HTTP GET request.
|
||||
#
|
||||
# Write the content of the resource to the response.body.
|
||||
def get(request, response)
|
||||
return NotFound unless exist?
|
||||
if stat.directory?
|
||||
response.body = "".dup
|
||||
Rack::Directory.new(root).call(request.env)[2].each do |line|
|
||||
response.body << line
|
||||
# If this is a collection, return the child resources.
|
||||
def children
|
||||
Dir[file_path + '/*'].map do |path|
|
||||
child ::File.basename(path)
|
||||
end
|
||||
response['Content-Length'] = response.body.bytesize.to_s
|
||||
OK
|
||||
else
|
||||
status, headers, body = Rack::File.new(root).call(request.env)
|
||||
headers.each do |k, v|
|
||||
response[k] = v
|
||||
end
|
||||
response.body = body
|
||||
StatusClasses[status]
|
||||
end
|
||||
end
|
||||
|
||||
# HTTP PUT request.
|
||||
#
|
||||
# Save the content of the request.body.
|
||||
def put(request, response)
|
||||
write(request.body)
|
||||
Created
|
||||
end
|
||||
|
||||
# HTTP POST request.
|
||||
#
|
||||
# Usually forbidden.
|
||||
def post(request, response)
|
||||
raise HTTPStatus::Forbidden
|
||||
end
|
||||
|
||||
# HTTP DELETE request.
|
||||
#
|
||||
# Delete this resource.
|
||||
def delete
|
||||
if stat.directory?
|
||||
FileUtils.rm_rf(file_path)
|
||||
else
|
||||
::File.unlink(file_path)
|
||||
end
|
||||
::File.unlink(prop_path) if ::File.exist?(prop_path)
|
||||
@_prop_hash = nil
|
||||
NoContent
|
||||
end
|
||||
|
||||
# HTTP COPY request.
|
||||
#
|
||||
# Copy this resource to given destination path.
|
||||
def copy(dest_path, overwrite, depth = nil)
|
||||
|
||||
is_new = true
|
||||
|
||||
dest = new_for_path dest_path
|
||||
unless dest.parent.exist? and dest.parent.collection?
|
||||
return Conflict
|
||||
end
|
||||
|
||||
if dest.exist?
|
||||
if overwrite
|
||||
FileUtils.rm_r dest.file_path, secure: true
|
||||
# Is this resource a collection?
|
||||
def collection?
|
||||
::File.directory?(file_path)
|
||||
end
|
||||
|
||||
# Does this recource exist?
|
||||
def exist?
|
||||
::File.exist?(file_path)
|
||||
end
|
||||
|
||||
# Return the creation time.
|
||||
def creation_date
|
||||
stat.ctime
|
||||
end
|
||||
|
||||
# Return the time of last modification.
|
||||
def last_modified
|
||||
stat.mtime
|
||||
end
|
||||
|
||||
# Set the time of last modification.
|
||||
def last_modified=(time)
|
||||
::File.utime(Time.now, time, file_path)
|
||||
end
|
||||
|
||||
# Return an Etag, an unique hash value for this resource.
|
||||
def etag
|
||||
sprintf('%x-%x-%x', stat.ino, stat.size, stat.mtime.to_i)
|
||||
end
|
||||
|
||||
# Return the mime type of this resource.
|
||||
def content_type
|
||||
if stat.directory?
|
||||
"text/html"
|
||||
else
|
||||
return PreconditionFailed
|
||||
mime_type(file_path, DefaultMimeTypes)
|
||||
end
|
||||
is_new = false
|
||||
end
|
||||
|
||||
if collection?
|
||||
|
||||
if request.depth == 0
|
||||
Dir.mkdir dest.file_path
|
||||
else
|
||||
FileUtils.cp_r(file_path, dest.file_path)
|
||||
end
|
||||
|
||||
else
|
||||
|
||||
FileUtils.cp(file_path, dest.file_path.sub(/\/$/, ''))
|
||||
FileUtils.cp(prop_path, dest.prop_path) if ::File.exist? prop_path
|
||||
|
||||
# Return the size in bytes for this resource.
|
||||
def content_length
|
||||
stat.size
|
||||
end
|
||||
|
||||
is_new ? Created : NoContent
|
||||
end
|
||||
|
||||
# HTTP MOVE request.
|
||||
#
|
||||
# Move this resource to given destination resource.
|
||||
def move(*args)
|
||||
result = copy(*args)
|
||||
delete if [Created, NoContent].include?(result)
|
||||
result
|
||||
end
|
||||
|
||||
# HTTP MKCOL request.
|
||||
#
|
||||
# Create this resource as collection.
|
||||
def make_collection
|
||||
if(request.body.read.to_s == '')
|
||||
if(::File.directory?(file_path))
|
||||
MethodNotAllowed
|
||||
# HTTP GET request.
|
||||
#
|
||||
# Write the content of the resource to the response.body.
|
||||
def get(request, response)
|
||||
return NotFound unless exist?
|
||||
if stat.directory?
|
||||
response.body = "".dup
|
||||
Rack::Directory.new(root).call(request.env)[2].each do |line|
|
||||
response.body << line
|
||||
end
|
||||
response['Content-Length'] = response.body.bytesize.to_s
|
||||
OK
|
||||
else
|
||||
if(::File.directory?(::File.dirname(file_path)) && !::File.exist?(file_path))
|
||||
Dir.mkdir(file_path)
|
||||
Created
|
||||
status, headers, body = Rack::File.new(root).call(request.env)
|
||||
headers.each do |k, v|
|
||||
response[k] = v
|
||||
end
|
||||
response.body = body
|
||||
StatusClasses[status]
|
||||
end
|
||||
end
|
||||
|
||||
# HTTP PUT request.
|
||||
#
|
||||
# Save the content of the request.body.
|
||||
def put(request, response)
|
||||
write(request.body)
|
||||
Created
|
||||
end
|
||||
|
||||
# HTTP POST request.
|
||||
#
|
||||
# Usually forbidden.
|
||||
def post(request, response)
|
||||
raise HttpStatus::Forbidden
|
||||
end
|
||||
|
||||
# HTTP DELETE request.
|
||||
#
|
||||
# Delete this resource.
|
||||
def delete
|
||||
if stat.directory?
|
||||
FileUtils.rm_rf(file_path)
|
||||
else
|
||||
::File.unlink(file_path)
|
||||
end
|
||||
::File.unlink(prop_path) if ::File.exist?(prop_path)
|
||||
@_prop_hash = nil
|
||||
NoContent
|
||||
end
|
||||
|
||||
# HTTP COPY request.
|
||||
#
|
||||
# Copy this resource to given destination path.
|
||||
def copy(dest_path, overwrite, depth = nil)
|
||||
|
||||
is_new = true
|
||||
|
||||
dest = new_for_path dest_path
|
||||
unless dest.parent.exist? and dest.parent.collection?
|
||||
return Conflict
|
||||
end
|
||||
|
||||
if dest.exist?
|
||||
if overwrite
|
||||
FileUtils.rm_r dest.file_path, secure: true
|
||||
else
|
||||
Conflict
|
||||
return PreconditionFailed
|
||||
end
|
||||
is_new = false
|
||||
end
|
||||
|
||||
if collection?
|
||||
|
||||
if request.depth == 0
|
||||
Dir.mkdir dest.file_path
|
||||
else
|
||||
FileUtils.cp_r(file_path, dest.file_path)
|
||||
end
|
||||
|
||||
else
|
||||
|
||||
FileUtils.cp(file_path, dest.file_path.sub(/\/$/, ''))
|
||||
FileUtils.cp(prop_path, dest.prop_path) if ::File.exist? prop_path
|
||||
|
||||
end
|
||||
|
||||
is_new ? Created : NoContent
|
||||
end
|
||||
|
||||
# HTTP MOVE request.
|
||||
#
|
||||
# Move this resource to given destination resource.
|
||||
def move(*args)
|
||||
result = copy(*args)
|
||||
delete if [Created, NoContent].include?(result)
|
||||
result
|
||||
end
|
||||
|
||||
# HTTP MKCOL request.
|
||||
#
|
||||
# Create this resource as collection.
|
||||
def make_collection
|
||||
if(request.body.read.to_s == '')
|
||||
if(::File.directory?(file_path))
|
||||
MethodNotAllowed
|
||||
else
|
||||
if(::File.directory?(::File.dirname(file_path)) && !::File.exist?(file_path))
|
||||
Dir.mkdir(file_path)
|
||||
Created
|
||||
else
|
||||
Conflict
|
||||
end
|
||||
end
|
||||
else
|
||||
UnsupportedMediaType
|
||||
end
|
||||
end
|
||||
|
||||
# Write to this resource from given IO.
|
||||
def write(io)
|
||||
tempfile = "#{file_path}.#{Process.pid}.#{object_id}"
|
||||
open(tempfile, "wb") do |file|
|
||||
while part = io.read(8192)
|
||||
file << part
|
||||
end
|
||||
end
|
||||
else
|
||||
UnsupportedMediaType
|
||||
::File.rename(tempfile, file_path)
|
||||
ensure
|
||||
::File.unlink(tempfile) rescue nil
|
||||
end
|
||||
end
|
||||
|
||||
# Write to this resource from given IO.
|
||||
def write(io)
|
||||
tempfile = "#{file_path}.#{Process.pid}.#{object_id}"
|
||||
open(tempfile, "wb") do |file|
|
||||
while part = io.read(8192)
|
||||
file << part
|
||||
# name:: String - Property name
|
||||
# Returns the value of the given property
|
||||
def get_property(name)
|
||||
if name[:ns_href] == DAV_NAMESPACE
|
||||
super
|
||||
else
|
||||
custom_props(name)
|
||||
end
|
||||
end
|
||||
::File.rename(tempfile, file_path)
|
||||
ensure
|
||||
::File.unlink(tempfile) rescue nil
|
||||
end
|
||||
|
||||
# name:: String - Property name
|
||||
# Returns the value of the given property
|
||||
def get_property(name)
|
||||
if name[:ns_href] == DAV_NAMESPACE
|
||||
super
|
||||
else
|
||||
custom_props(name)
|
||||
end
|
||||
end
|
||||
|
||||
# name:: String - Property name
|
||||
# value:: New value
|
||||
# Set the property to the given value
|
||||
def set_property(name, value)
|
||||
# let Resource handle DAV properties
|
||||
if name[:ns_href] == DAV_NAMESPACE
|
||||
super
|
||||
else
|
||||
set_custom_props name, value
|
||||
end
|
||||
end
|
||||
|
||||
def remove_property(element)
|
||||
prop_hash.transaction do
|
||||
prop_hash.delete(to_element_key(element))
|
||||
prop_hash.commit
|
||||
end
|
||||
val = prop_hash.transaction{ prop_hash[to_element_key(element)] }
|
||||
end
|
||||
|
||||
def lock(args)
|
||||
unless(parent_exists?)
|
||||
Conflict
|
||||
else
|
||||
lock_check(args[:type])
|
||||
lock = FileResourceLock.explicit_locks(@path, root, :scope => args[:scope], :kind => args[:type], :user => @user)
|
||||
unless(lock)
|
||||
token = UUIDTools::UUID.random_create.to_s
|
||||
lock = FileResourceLock.generate(@path, @user, token, root)
|
||||
lock.scope = args[:scope]
|
||||
lock.kind = args[:type]
|
||||
lock.owner = args[:owner]
|
||||
lock.depth = args[:depth]
|
||||
if(args[:timeout])
|
||||
lock.timeout = args[:timeout] <= @max_timeout && args[:timeout] > 0 ? args[:timeout] : @max_timeout
|
||||
else
|
||||
lock.timeout = @default_timeout
|
||||
end
|
||||
lock.save
|
||||
# name:: String - Property name
|
||||
# value:: New value
|
||||
# Set the property to the given value
|
||||
def set_property(name, value)
|
||||
# let Resource handle DAV properties
|
||||
if name[:ns_href] == DAV_NAMESPACE
|
||||
super
|
||||
else
|
||||
set_custom_props name, value
|
||||
end
|
||||
begin
|
||||
lock_check(args[:type])
|
||||
rescue DAV4Rack::LockFailure => lock_failure
|
||||
lock.destroy
|
||||
raise lock_failure
|
||||
rescue HTTPStatus::Status => status
|
||||
status
|
||||
end
|
||||
[lock.remaining_timeout, lock.token]
|
||||
end
|
||||
end
|
||||
|
||||
def unlock(token)
|
||||
token = token.slice(1, token.length - 2)
|
||||
if(token.nil? || token.empty?)
|
||||
BadRequest
|
||||
else
|
||||
lock = FileResourceLock.find_by_token(token, root)
|
||||
if(lock.nil? || lock.user != @user)
|
||||
Forbidden
|
||||
elsif(lock.path !~ /^#{Regexp.escape(@path)}.*$/)
|
||||
def remove_property(element)
|
||||
prop_hash.transaction do
|
||||
prop_hash.delete(to_element_key(element))
|
||||
prop_hash.commit
|
||||
end
|
||||
val = prop_hash.transaction{ prop_hash[to_element_key(element)] }
|
||||
end
|
||||
|
||||
def lock(args)
|
||||
unless(parent_exists?)
|
||||
Conflict
|
||||
else
|
||||
lock.destroy
|
||||
NoContent
|
||||
lock_check(args[:type])
|
||||
lock = FileResourceLock.explicit_locks(@path, root, :scope => args[:scope], :kind => args[:type], :user => @user)
|
||||
unless(lock)
|
||||
token = UUIDTools::UUID.random_create.to_s
|
||||
lock = FileResourceLock.generate(@path, @user, token, root)
|
||||
lock.scope = args[:scope]
|
||||
lock.kind = args[:type]
|
||||
lock.owner = args[:owner]
|
||||
lock.depth = args[:depth]
|
||||
if(args[:timeout])
|
||||
lock.timeout = args[:timeout] <= @max_timeout && args[:timeout] > 0 ? args[:timeout] : @max_timeout
|
||||
else
|
||||
lock.timeout = @default_timeout
|
||||
end
|
||||
lock.save
|
||||
end
|
||||
begin
|
||||
lock_check(args[:type])
|
||||
rescue Dav4rack::LockFailure => lock_failure
|
||||
lock.destroy
|
||||
raise lock_failure
|
||||
rescue HttpStatus::Status => status
|
||||
status
|
||||
end
|
||||
[lock.remaining_timeout, lock.token]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def authenticate(user, pass)
|
||||
if(@options[:username])
|
||||
# This comparison uses & so that it doesn't short circuit and
|
||||
# uses `variable_size_secure_compare` so that length information
|
||||
# isn't leaked.
|
||||
SecurityUtils.variable_size_secure_compare(
|
||||
user, @options[:username]
|
||||
) &
|
||||
SecurityUtils.variable_size_secure_compare(
|
||||
pass, @options[:password]
|
||||
)
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def file_path
|
||||
::File.expand_path ::File.join(root, path)
|
||||
end
|
||||
|
||||
def prop_path
|
||||
path = ::File.join(store_directory, "#{::File.join(::File.dirname(file_path), ::File.basename(file_path)).gsub('/', '_')}.pstore")
|
||||
unless(::File.directory?(::File.dirname(path)))
|
||||
FileUtils.mkdir_p(::File.dirname(path))
|
||||
end
|
||||
path
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def lock_check(lock_type=nil)
|
||||
if(FileResourceLock.explicitly_locked?(@path, root))
|
||||
raise Locked if lock_type && lock_type == 'exclusive'
|
||||
#raise Locked if FileResourceLock.explicit_locks(@path, root).find(:all, :conditions => ["scope = 'exclusive' AND user_id != ?", @user.id]).size > 0
|
||||
elsif(FileResourceLock.implicitly_locked?(@path, root))
|
||||
if(lock_type.to_s == 'exclusive')
|
||||
locks = FileResourceLock.implicit_locks(@path)
|
||||
failure = DAV4Rack::LockFailure.new("Failed to lock: #{@path}")
|
||||
locks.each do |lock|
|
||||
failure.add_failure(@path, Locked)
|
||||
end
|
||||
raise failure
|
||||
def unlock(token)
|
||||
token = token.slice(1, token.length - 2)
|
||||
if(token.nil? || token.empty?)
|
||||
BadRequest
|
||||
else
|
||||
locks = FileResourceLock.implict_locks(@path).find(:all, :conditions => ["scope = 'exclusive' AND user_id != ?", @user.id])
|
||||
if(locks.size > 0)
|
||||
failure = LockFailure.new("Failed to lock: #{@path}")
|
||||
lock = FileResourceLock.find_by_token(token, root)
|
||||
if(lock.nil? || lock.user != @user)
|
||||
Forbidden
|
||||
elsif(lock.path !~ /^#{Regexp.escape(@path)}.*$/)
|
||||
Conflict
|
||||
else
|
||||
lock.destroy
|
||||
NoContent
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def authenticate(user, pass)
|
||||
if(@options[:username])
|
||||
# This comparison uses & so that it doesn't short circuit and
|
||||
# uses `variable_size_secure_compare` so that length information
|
||||
# isn't leaked.
|
||||
SecurityUtils.variable_size_secure_compare(
|
||||
user, @options[:username]
|
||||
) &
|
||||
SecurityUtils.variable_size_secure_compare(
|
||||
pass, @options[:password]
|
||||
)
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def file_path
|
||||
::File.expand_path ::File.join(root, path)
|
||||
end
|
||||
|
||||
def prop_path
|
||||
path = ::File.join(store_directory, "#{::File.join(::File.dirname(file_path), ::File.basename(file_path)).gsub('/', '_')}.pstore")
|
||||
unless(::File.directory?(::File.dirname(path)))
|
||||
FileUtils.mkdir_p(::File.dirname(path))
|
||||
end
|
||||
path
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def lock_check(lock_type=nil)
|
||||
if(FileResourceLock.explicitly_locked?(@path, root))
|
||||
raise Locked if lock_type && lock_type == 'exclusive'
|
||||
#raise Locked if FileResourceLock.explicit_locks(@path, root).find(:all, :conditions => ["scope = 'exclusive' AND user_id != ?", @user.id]).size > 0
|
||||
elsif(FileResourceLock.implicitly_locked?(@path, root))
|
||||
if(lock_type.to_s == 'exclusive')
|
||||
locks = FileResourceLock.implicit_locks(@path)
|
||||
failure = Dav4rack::LockFailure.new("Failed to lock: #{@path}")
|
||||
locks.each do |lock|
|
||||
failure.add_failure(@path, Locked)
|
||||
end
|
||||
raise failure
|
||||
else
|
||||
locks = FileResourceLock.implict_locks(@path).find(:all, :conditions => ["scope = 'exclusive' AND user_id != ?", @user.id])
|
||||
if(locks.size > 0)
|
||||
failure = LockFailure.new("Failed to lock: #{@path}")
|
||||
locks.each do |lock|
|
||||
failure.add_failure(@path, Locked)
|
||||
end
|
||||
raise failure
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def set_custom_props(element, val)
|
||||
prop_hash.transaction do
|
||||
prop_hash[to_element_key(element)] = val
|
||||
prop_hash.commit
|
||||
def set_custom_props(element, val)
|
||||
prop_hash.transaction do
|
||||
prop_hash[to_element_key(element)] = val
|
||||
prop_hash.commit
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def custom_props(element)
|
||||
val = prop_hash.transaction(true) do
|
||||
prop_hash[to_element_key(element)]
|
||||
def custom_props(element)
|
||||
val = prop_hash.transaction(true) do
|
||||
prop_hash[to_element_key(element)]
|
||||
end
|
||||
val || NotFound
|
||||
end
|
||||
val || NotFound
|
||||
end
|
||||
|
||||
def store_directory
|
||||
path = ::File.join(root, '.attrib_store')
|
||||
unless(::File.directory?(::File.dirname(path)))
|
||||
FileUtils.mkdir_p(::File.dirname(path))
|
||||
def store_directory
|
||||
path = ::File.join(root, '.attrib_store')
|
||||
unless(::File.directory?(::File.dirname(path)))
|
||||
FileUtils.mkdir_p(::File.dirname(path))
|
||||
end
|
||||
path
|
||||
end
|
||||
path
|
||||
end
|
||||
|
||||
def lock_path
|
||||
path = ::File.join(store_directory, 'locks.pstore')
|
||||
unless(::File.directory?(::File.dirname(path)))
|
||||
FileUtils.mkdir_p(::File.dirname(path))
|
||||
def lock_path
|
||||
path = ::File.join(store_directory, 'locks.pstore')
|
||||
unless(::File.directory?(::File.dirname(path)))
|
||||
FileUtils.mkdir_p(::File.dirname(path))
|
||||
end
|
||||
path
|
||||
end
|
||||
path
|
||||
end
|
||||
|
||||
def prop_hash
|
||||
@_prop_hash ||= IS_18 ? PStore.new(prop_path) : PStore.new(prop_path, true)
|
||||
end
|
||||
def prop_hash
|
||||
@_prop_hash ||= IS_18 ? PStore.new(prop_path) : PStore.new(prop_path, true)
|
||||
end
|
||||
|
||||
def root
|
||||
@options[:root]
|
||||
end
|
||||
def root
|
||||
@options[:root]
|
||||
end
|
||||
|
||||
def stat
|
||||
@stat ||= ::File.stat(file_path)
|
||||
end
|
||||
|
||||
def stat
|
||||
@stat ||= ::File.stat(file_path)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
require 'digest'
|
||||
|
||||
module DAV4Rack
|
||||
module Dav4rack
|
||||
|
||||
# Implements secure string comparison methods.
|
||||
# Taken straight from ActiveSupport
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
require 'addressable/uri'
|
||||
|
||||
module DAV4Rack
|
||||
module Dav4rack
|
||||
|
||||
# adds a bit of parsing logic around a header URI or path value
|
||||
class Uri
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
require 'ostruct'
|
||||
|
||||
module DAV4Rack
|
||||
module Dav4rack
|
||||
|
||||
# Simple wrapper for formatted elements
|
||||
class DAVElement < OpenStruct
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
module DAV4Rack
|
||||
module Dav4rack
|
||||
class Version
|
||||
|
||||
attr_reader :major, :minor, :tiny
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module DAV4Rack
|
||||
module Dav4rack
|
||||
module XmlElements
|
||||
|
||||
DAV_NAMESPACE = 'DAV:'
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module DAV4Rack
|
||||
module Dav4rack
|
||||
class XmlResponse
|
||||
include XmlElements
|
||||
|
||||
|
||||
@ -26,56 +26,55 @@ DMSF_MAX_NOTIFICATION_RECEIVERS_INFO = 10
|
||||
# DMSF libraries
|
||||
|
||||
# Validators
|
||||
require_dependency File.dirname(__FILE__) + '/../app/validators/dmsf_file_name_validator'
|
||||
require_dependency File.dirname(__FILE__) + '/../app/validators/dmsf_max_file_size_validator'
|
||||
require_dependency File.dirname(__FILE__) + '/../app/validators/dmsf_workflow_name_validator'
|
||||
require_dependency File.dirname(__FILE__) + '/../app/validators/dmsf_url_validator'
|
||||
require_dependency File.dirname(__FILE__) + '/../app/validators/dmsf_folder_parent_validator'
|
||||
require File.dirname(__FILE__) + '/../app/validators/dmsf_file_name_validator'
|
||||
require File.dirname(__FILE__) + '/../app/validators/dmsf_max_file_size_validator'
|
||||
require File.dirname(__FILE__) + '/../app/validators/dmsf_workflow_name_validator'
|
||||
require File.dirname(__FILE__) + '/../app/validators/dmsf_url_validator'
|
||||
require File.dirname(__FILE__) + '/../app/validators/dmsf_folder_parent_validator'
|
||||
|
||||
# Plugin's patches
|
||||
require 'redmine_dmsf/patches/projects_helper_patch'
|
||||
require 'redmine_dmsf/patches/project_patch'
|
||||
require 'redmine_dmsf/patches/user_preference_patch'
|
||||
require 'redmine_dmsf/patches/user_patch'
|
||||
require 'redmine_dmsf/patches/issue_patch'
|
||||
require 'redmine_dmsf/patches/role_patch'
|
||||
require 'redmine_dmsf/patches/queries_controller_patch'
|
||||
require 'redmine_dmsf/patches/notifiable_patch'
|
||||
require File.dirname(__FILE__) + '/redmine_dmsf/patches/projects_helper_patch'
|
||||
require File.dirname(__FILE__) + '/redmine_dmsf/patches/project_patch'
|
||||
require File.dirname(__FILE__) + '/redmine_dmsf/patches/user_preference_patch'
|
||||
require File.dirname(__FILE__) + '/redmine_dmsf/patches/user_patch'
|
||||
require File.dirname(__FILE__) + '/redmine_dmsf/patches/issue_patch'
|
||||
require File.dirname(__FILE__) + '/redmine_dmsf/patches/role_patch'
|
||||
require File.dirname(__FILE__) + '/redmine_dmsf/patches/queries_controller_patch'
|
||||
require File.dirname(__FILE__) + '/redmine_dmsf/patches/notifiable_patch'
|
||||
|
||||
if defined?(EasyExtensions)
|
||||
require 'redmine_dmsf/patches/easy_crm_case_patch'
|
||||
require 'redmine_dmsf/patches/attachable_patch'
|
||||
require 'redmine_dmsf/patches/easy_crm_cases_controller_patch.rb'
|
||||
require File.dirname(__FILE__) + '/redmine_dmsf/patches/easy_crm_case_patch'
|
||||
require File.dirname(__FILE__) + '/redmine_dmsf/patches/attachable_patch'
|
||||
require File.dirname(__FILE__) + '/redmine_dmsf/patches/easy_crm_cases_controller_patch.rb'
|
||||
end
|
||||
|
||||
# Load up classes that make up our WebDAV solution ontop of DAV4Rack
|
||||
require 'dav4rack'
|
||||
require 'redmine_dmsf/webdav/custom_middleware'
|
||||
require 'redmine_dmsf/webdav/base_resource'
|
||||
require 'redmine_dmsf/webdav/dmsf_resource'
|
||||
require 'redmine_dmsf/webdav/index_resource'
|
||||
require 'redmine_dmsf/webdav/project_resource'
|
||||
require 'redmine_dmsf/webdav/resource_proxy'
|
||||
# Load up classes that make up our WebDAV solution ontop of Dav4rack
|
||||
require File.dirname(__FILE__) + '/dav4rack'
|
||||
require File.dirname(__FILE__) + '/redmine_dmsf/webdav/custom_middleware'
|
||||
require File.dirname(__FILE__) + '/redmine_dmsf/webdav/base_resource'
|
||||
require File.dirname(__FILE__) + '/redmine_dmsf/webdav/dmsf_resource'
|
||||
require File.dirname(__FILE__) + '/redmine_dmsf/webdav/index_resource'
|
||||
require File.dirname(__FILE__) + '/redmine_dmsf/webdav/project_resource'
|
||||
require File.dirname(__FILE__) + '/redmine_dmsf/webdav/resource_proxy'
|
||||
|
||||
# Errors
|
||||
require 'redmine_dmsf/errors/dmsf_access_error'
|
||||
require 'redmine_dmsf/errors/dmsf_content_error'
|
||||
require 'redmine_dmsf/errors/dmsf_email_max_file_error'
|
||||
require 'redmine_dmsf/errors/dmsf_file_not_found_error'
|
||||
require 'redmine_dmsf/errors/dmsf_lock_error'
|
||||
require 'redmine_dmsf/errors/dmsf_zip_max_file_error'
|
||||
require File.dirname(__FILE__) + '/redmine_dmsf/errors/dmsf_access_error'
|
||||
require File.dirname(__FILE__) + '/redmine_dmsf/errors/dmsf_email_max_file_size_error'
|
||||
require File.dirname(__FILE__) + '/redmine_dmsf/errors/dmsf_file_not_found_error'
|
||||
require File.dirname(__FILE__) + '/redmine_dmsf/errors/dmsf_lock_error'
|
||||
require File.dirname(__FILE__) + '/redmine_dmsf/errors/dmsf_zip_max_files_error'
|
||||
|
||||
# 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/views/issue_view_hooks'
|
||||
require 'redmine_dmsf/hooks/views/custom_field_view_hooks'
|
||||
require 'redmine_dmsf/hooks/views/search_view_hooks'
|
||||
require 'redmine_dmsf/hooks/helpers/issues_helper_hooks'
|
||||
require 'redmine_dmsf/hooks/helpers/search_helper_hooks'
|
||||
require 'redmine_dmsf/hooks/helpers/project_helper_hooks'
|
||||
require File.dirname(__FILE__) + '/redmine_dmsf/hooks/controllers/search_controller_hooks'
|
||||
require File.dirname(__FILE__) + '/redmine_dmsf/hooks/controllers/issues_controller_hooks'
|
||||
require File.dirname(__FILE__) + '/redmine_dmsf/hooks/views/view_projects_form_hook'
|
||||
require File.dirname(__FILE__) + '/redmine_dmsf/hooks/views/base_view_hooks'
|
||||
require File.dirname(__FILE__) + '/redmine_dmsf/hooks/views/issue_view_hooks'
|
||||
require File.dirname(__FILE__) + '/redmine_dmsf/hooks/views/custom_field_view_hooks'
|
||||
require File.dirname(__FILE__) + '/redmine_dmsf/hooks/views/search_view_hooks'
|
||||
require File.dirname(__FILE__) + '/redmine_dmsf/hooks/helpers/issues_helper_hooks'
|
||||
require File.dirname(__FILE__) + '/redmine_dmsf/hooks/helpers/search_helper_hooks'
|
||||
require File.dirname(__FILE__) + '/redmine_dmsf/hooks/helpers/project_helper_hooks'
|
||||
|
||||
# Macros
|
||||
require 'redmine_dmsf/macros'
|
||||
require File.dirname(__FILE__) + '/redmine_dmsf/macros'
|
||||
@ -48,7 +48,7 @@ module RedmineDmsf
|
||||
def add_file(file, member, root_path = nil)
|
||||
unless @files.include?(file)
|
||||
unless file && file.last_revision && File.exist?(file.last_revision.disk_file)
|
||||
raise FileNotFound
|
||||
raise RedmineDmsf::Errors::DmsfFileNotFoundError
|
||||
end
|
||||
string_path = file.dmsf_folder.nil? ? '' : (file.dmsf_folder.dmsf_path_str + File::SEPARATOR)
|
||||
string_path = string_path[(root_path.length + 1) .. string_path.length] if root_path
|
||||
|
||||
@ -20,6 +20,12 @@
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
class DmsfAccessError < StandardError
|
||||
module RedmineDmsf
|
||||
module Errors
|
||||
|
||||
class DmsfAccessError < StandardError
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,25 +0,0 @@
|
||||
# encoding: utf-8
|
||||
# frozen_string_literal: true
|
||||
#
|
||||
# Redmine plugin for Document Management System "Features"
|
||||
#
|
||||
# Copyright © 2011 Vít Jonáš <vit.jonas@gmail.com>
|
||||
# Copyright © 2011-22 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 DmsfContentError < StandardError
|
||||
|
||||
end
|
||||
@ -19,14 +19,20 @@
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
class ZipMaxFilesError < StandardError
|
||||
include Redmine::I18n
|
||||
|
||||
def initialize(message = nil)
|
||||
if message.present?
|
||||
super message
|
||||
else
|
||||
super l(:error_max_files_exceeded, number: Setting.plugin_redmine_dmsf['dmsf_max_file_download'])
|
||||
module RedmineDmsf
|
||||
module Errors
|
||||
|
||||
class DmsfEmailMaxFileSizeError < StandardError
|
||||
include Redmine::I18n
|
||||
|
||||
def initialize(message = nil)
|
||||
if message.present?
|
||||
super message
|
||||
else
|
||||
super l(:error_max_email_filesize_exceeded, number: Setting.plugin_redmine_dmsf['dmsf_max_email_filesize'])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@ -19,6 +19,12 @@
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
class FileNotFound < StandardError
|
||||
|
||||
module RedmineDmsf
|
||||
module Errors
|
||||
|
||||
class DmsfFileNotFoundError < StandardError
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@ -20,8 +20,14 @@
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
class DmsfLockError < StandardError
|
||||
module RedmineDmsf
|
||||
module Errors
|
||||
|
||||
class DmsfLockError < StandardError
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
@ -19,14 +19,21 @@
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
class EmailMaxFileSize < StandardError
|
||||
include Redmine::I18n
|
||||
|
||||
def initialize(message = nil)
|
||||
if message.present?
|
||||
super message
|
||||
else
|
||||
super l(:error_max_email_filesize_exceeded, number: Setting.plugin_redmine_dmsf['dmsf_max_email_filesize'])
|
||||
module RedmineDmsf
|
||||
module Errors
|
||||
|
||||
class DmsfZipMaxFilesError < StandardError
|
||||
include Redmine::I18n
|
||||
|
||||
def initialize(message = nil)
|
||||
if message.present?
|
||||
super message
|
||||
else
|
||||
super l(:error_max_files_exceeded, number: Setting.plugin_redmine_dmsf['dmsf_max_file_download'])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@ -21,168 +21,169 @@
|
||||
|
||||
module RedmineDmsf
|
||||
module Hooks
|
||||
include Redmine::Hook
|
||||
module Controllers
|
||||
|
||||
class ControllerIssuesHook < RedmineDmsf::Hooks::Listener
|
||||
class IssuesControllerHooks < Redmine::Hook::Listener
|
||||
|
||||
def controller_issues_new_before_save(context={})
|
||||
controller_issues_before_save(context)
|
||||
end
|
||||
def controller_issues_new_before_save(context={})
|
||||
controller_issues_before_save(context)
|
||||
end
|
||||
|
||||
def controller_issues_new_after_save(context={})
|
||||
controller_issues_after_save(context)
|
||||
# Copy documents from the source issue
|
||||
if context.is_a?(Hash)
|
||||
issue = context[:issue]
|
||||
params = context[:params]
|
||||
copied_from = Issue.find_by(id: params[:copy_from]) if params[:copy_from].present?
|
||||
# Save documents
|
||||
if copied_from
|
||||
copied_from.dmsf_files.each do |dmsf_file|
|
||||
dmsf_file.copy_to(issue.project, issue.system_folder(true))
|
||||
def controller_issues_new_after_save(context={})
|
||||
controller_issues_after_save(context)
|
||||
# Copy documents from the source issue
|
||||
if context.is_a?(Hash)
|
||||
issue = context[:issue]
|
||||
params = context[:params]
|
||||
copied_from = Issue.find_by(id: params[:copy_from]) if params[:copy_from].present?
|
||||
# Save documents
|
||||
if copied_from
|
||||
copied_from.dmsf_files.each do |dmsf_file|
|
||||
dmsf_file.copy_to(issue.project, issue.system_folder(true))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def controller_issues_edit_before_save(context={})
|
||||
controller_issues_before_save(context)
|
||||
end
|
||||
|
||||
def controller_issues_edit_after_save(context={})
|
||||
controller_issues_after_save(context, true)
|
||||
end
|
||||
|
||||
def controller_issues_bulk_edit_before_save(context={})
|
||||
controller_issues_before_save(context)
|
||||
end
|
||||
|
||||
# Unfortunately this hook is missing in Redmine. It's called in Easy Redmine only.
|
||||
def controller_issues_bulk_edit_after_save(context={})
|
||||
controller_issues_after_save(context, true)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def controller_issues_before_save(context)
|
||||
if context.is_a?(Hash)
|
||||
issue = context[:issue]
|
||||
@new_object = issue.new_record?
|
||||
params = context[:params]
|
||||
# Save upload preferences DMS/Attachments
|
||||
User.current.pref.update_attribute :dmsf_attachments_upload_choice, params[:dmsf_attachments_upload_choice]
|
||||
# Save attachments
|
||||
issue.save_dmsf_attachments(params[:dmsf_attachments])
|
||||
issue.save_dmsf_links(params[:dmsf_links])
|
||||
issue.save_dmsf_attachments_wfs(params[:dmsf_attachments_wfs], params[:dmsf_attachments])
|
||||
issue.save_dmsf_links_wfs(params[:dmsf_links_wfs])
|
||||
def controller_issues_edit_before_save(context={})
|
||||
controller_issues_before_save(context)
|
||||
end
|
||||
end
|
||||
|
||||
def controller_issues_after_save(context, edit = false)
|
||||
if context.is_a?(Hash)
|
||||
issue = context[:issue]
|
||||
params = context[:params]
|
||||
controller = context[:controller]
|
||||
if edit && ((params[:issue] && params[:issue][:project_id].present?) || context[:new_project])
|
||||
project_id = context[:new_project] ? context[:new_project].id : params[:issue][:project_id].to_i
|
||||
old_project_id = context[:project] ? context[:project].id : project_id
|
||||
# Sync the title with the issue's subject
|
||||
old_system_folder = issue.system_folder(false, old_project_id)
|
||||
if old_system_folder
|
||||
old_system_folder.title = "#{issue.id} - #{DmsfFolder::get_valid_title(issue.subject)}"
|
||||
unless old_system_folder.save
|
||||
controller.flash[:error] = old_system_folder.errors.full_messages.to_sentence
|
||||
Rails.logger.error old_system_folder.errors.full_messages.to_sentence
|
||||
end
|
||||
end
|
||||
# Move documents, links and folders if needed
|
||||
if project_id != old_project_id
|
||||
def controller_issues_edit_after_save(context={})
|
||||
controller_issues_after_save(context, true)
|
||||
end
|
||||
|
||||
def controller_issues_bulk_edit_before_save(context={})
|
||||
controller_issues_before_save(context)
|
||||
end
|
||||
|
||||
# Unfortunately this hook is missing in Redmine. It's called in Easy Redmine only.
|
||||
def controller_issues_bulk_edit_after_save(context={})
|
||||
controller_issues_after_save(context, true)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def controller_issues_before_save(context)
|
||||
if context.is_a?(Hash)
|
||||
issue = context[:issue]
|
||||
@new_object = issue.new_record?
|
||||
params = context[:params]
|
||||
# Save upload preferences DMS/Attachments
|
||||
User.current.pref.update_attribute :dmsf_attachments_upload_choice, params[:dmsf_attachments_upload_choice]
|
||||
# Save attachments
|
||||
issue.save_dmsf_attachments(params[:dmsf_attachments])
|
||||
issue.save_dmsf_links(params[:dmsf_links])
|
||||
issue.save_dmsf_attachments_wfs(params[:dmsf_attachments_wfs], params[:dmsf_attachments])
|
||||
issue.save_dmsf_links_wfs(params[:dmsf_links_wfs])
|
||||
end
|
||||
end
|
||||
|
||||
def controller_issues_after_save(context, edit = false)
|
||||
if context.is_a?(Hash)
|
||||
issue = context[:issue]
|
||||
params = context[:params]
|
||||
controller = context[:controller]
|
||||
if edit && ((params[:issue] && params[:issue][:project_id].present?) || context[:new_project])
|
||||
project_id = context[:new_project] ? context[:new_project].id : params[:issue][:project_id].to_i
|
||||
old_project_id = context[:project] ? context[:project].id : project_id
|
||||
# Sync the title with the issue's subject
|
||||
old_system_folder = issue.system_folder(false, old_project_id)
|
||||
if old_system_folder
|
||||
new_main_system_folder = issue.main_system_folder(true)
|
||||
if new_main_system_folder
|
||||
old_system_folder.dmsf_folder_id = new_main_system_folder.id
|
||||
old_system_folder.project_id = project_id
|
||||
unless old_system_folder.save
|
||||
controller.flash[:error] = old_system_folder.errors.full_messages.to_sentence
|
||||
Rails.logger.error old_system_folder.errors.full_messages.to_sentence
|
||||
end
|
||||
issue.dmsf_files.each do |dmsf_file|
|
||||
dmsf_file.project_id = project_id
|
||||
unless dmsf_file.save
|
||||
controller.flash[:error] = dmsf_file.errors.full_messages.to_sentence
|
||||
Rails.logger.error dmsf_file.errors.full_messages.to_sentence
|
||||
old_system_folder.title = "#{issue.id} - #{DmsfFolder::get_valid_title(issue.subject)}"
|
||||
unless old_system_folder.save
|
||||
controller.flash[:error] = old_system_folder.errors.full_messages.to_sentence
|
||||
Rails.logger.error old_system_folder.errors.full_messages.to_sentence
|
||||
end
|
||||
end
|
||||
# Move documents, links and folders if needed
|
||||
if project_id != old_project_id
|
||||
if old_system_folder
|
||||
new_main_system_folder = issue.main_system_folder(true)
|
||||
if new_main_system_folder
|
||||
old_system_folder.dmsf_folder_id = new_main_system_folder.id
|
||||
old_system_folder.project_id = project_id
|
||||
unless old_system_folder.save
|
||||
controller.flash[:error] = old_system_folder.errors.full_messages.to_sentence
|
||||
Rails.logger.error old_system_folder.errors.full_messages.to_sentence
|
||||
end
|
||||
end
|
||||
issue.dmsf_links.each do | dmsf_link|
|
||||
dmsf_link.project_id = project_id
|
||||
unless dmsf_link.save
|
||||
controller.flash[:error] = dmsf_link.errors.full_messages.to_sentence
|
||||
Rails.logger.error dmsf_link.errors.full_messages.to_sentence
|
||||
issue.dmsf_files.each do |dmsf_file|
|
||||
dmsf_file.project_id = project_id
|
||||
unless dmsf_file.save
|
||||
controller.flash[:error] = dmsf_file.errors.full_messages.to_sentence
|
||||
Rails.logger.error dmsf_file.errors.full_messages.to_sentence
|
||||
end
|
||||
end
|
||||
issue.dmsf_links.each do | dmsf_link|
|
||||
dmsf_link.project_id = project_id
|
||||
unless dmsf_link.save
|
||||
controller.flash[:error] = dmsf_link.errors.full_messages.to_sentence
|
||||
Rails.logger.error dmsf_link.errors.full_messages.to_sentence
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
# Attach DMS documents
|
||||
uploaded_files = params[:dmsf_attachments]
|
||||
if uploaded_files
|
||||
system_folder = issue.system_folder(true)
|
||||
uploaded_files.each do |key, uploaded_file|
|
||||
upload = DmsfUpload.create_from_uploaded_attachment(issue.project, system_folder, uploaded_file)
|
||||
if upload
|
||||
uploaded_file[:disk_filename] = upload.disk_filename
|
||||
uploaded_file[:name] = upload.name
|
||||
uploaded_file[:title] = upload.title
|
||||
uploaded_file[:version] = 1
|
||||
uploaded_file[:size] = upload.size
|
||||
uploaded_file[:mime_type] = upload.mime_type
|
||||
uploaded_file[:tempfile_path] = upload.tempfile_path
|
||||
uploaded_file[:digest] = upload.digest
|
||||
if params[:dmsf_attachments_wfs].present? && params[:dmsf_attachments_wfs][key].present?
|
||||
uploaded_file[:workflow_id] = params[:dmsf_attachments_wfs][key].to_i
|
||||
# Attach DMS documents
|
||||
uploaded_files = params[:dmsf_attachments]
|
||||
if uploaded_files
|
||||
system_folder = issue.system_folder(true)
|
||||
uploaded_files.each do |key, uploaded_file|
|
||||
upload = DmsfUpload.create_from_uploaded_attachment(issue.project, system_folder, uploaded_file)
|
||||
if upload
|
||||
uploaded_file[:disk_filename] = upload.disk_filename
|
||||
uploaded_file[:name] = upload.name
|
||||
uploaded_file[:title] = upload.title
|
||||
uploaded_file[:version] = 1
|
||||
uploaded_file[:size] = upload.size
|
||||
uploaded_file[:mime_type] = upload.mime_type
|
||||
uploaded_file[:tempfile_path] = upload.tempfile_path
|
||||
uploaded_file[:digest] = upload.digest
|
||||
if params[:dmsf_attachments_wfs].present? && params[:dmsf_attachments_wfs][key].present?
|
||||
uploaded_file[:workflow_id] = params[:dmsf_attachments_wfs][key].to_i
|
||||
end
|
||||
end
|
||||
end
|
||||
DmsfUploadHelper.commit_files_internal uploaded_files, issue.project, system_folder,
|
||||
context[:controller], @new_object, issue
|
||||
end
|
||||
DmsfUploadHelper.commit_files_internal uploaded_files, issue.project, system_folder,
|
||||
context[:controller], @new_object, issue
|
||||
end
|
||||
# Attach DMS links
|
||||
issue.saved_dmsf_links.each do |l|
|
||||
file = l.target_file
|
||||
revision = file.last_revision
|
||||
system_folder = issue.system_folder(true)
|
||||
if system_folder
|
||||
l.project_id = system_folder.project_id
|
||||
l.dmsf_folder_id = system_folder.id
|
||||
if l.save && (!@new_object)
|
||||
issue.dmsf_file_added file
|
||||
end
|
||||
wf = issue.saved_dmsf_links_wfs[l.id]
|
||||
if wf
|
||||
# Assign the workflow
|
||||
revision.set_workflow wf.id, 'assign'
|
||||
revision.assign_workflow wf.id
|
||||
# Start the workflow
|
||||
revision.set_workflow wf.id, 'start'
|
||||
if revision.save
|
||||
wf.notify_users issue.project, revision, context[:controller]
|
||||
begin
|
||||
file.lock!
|
||||
rescue DmsfLockError => e
|
||||
Rails.logger.warn e.message
|
||||
# Attach DMS links
|
||||
issue.saved_dmsf_links.each do |l|
|
||||
file = l.target_file
|
||||
revision = file.last_revision
|
||||
system_folder = issue.system_folder(true)
|
||||
if system_folder
|
||||
l.project_id = system_folder.project_id
|
||||
l.dmsf_folder_id = system_folder.id
|
||||
if l.save && (!@new_object)
|
||||
issue.dmsf_file_added file
|
||||
end
|
||||
wf = issue.saved_dmsf_links_wfs[l.id]
|
||||
if wf
|
||||
# Assign the workflow
|
||||
revision.set_workflow wf.id, 'assign'
|
||||
revision.assign_workflow wf.id
|
||||
# Start the workflow
|
||||
revision.set_workflow wf.id, 'start'
|
||||
if revision.save
|
||||
wf.notify_users issue.project, revision, context[:controller]
|
||||
begin
|
||||
file.lock!
|
||||
rescue RedmineDmsf::Errors::DmsfLockError => e
|
||||
Rails.logger.warn e.message
|
||||
end
|
||||
else
|
||||
Rails.logger.error l(:error_workflow_assign)
|
||||
end
|
||||
else
|
||||
Rails.logger.error l(:error_workflow_assign)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@ -21,23 +21,24 @@
|
||||
|
||||
module RedmineDmsf
|
||||
module Hooks
|
||||
include Redmine::Hook
|
||||
module Controllers
|
||||
|
||||
class ControllerSearchHook < RedmineDmsf::Hooks::Listener
|
||||
include Rails.application.routes.url_helpers
|
||||
|
||||
def controller_search_quick_jump(context={})
|
||||
if context.is_a?(Hash)
|
||||
question = context[:question]
|
||||
if question.present?
|
||||
if question.match(/^D(\d+)$/) && DmsfFile.visible.where(id: $1).exists?
|
||||
return dmsf_file_path(id: $1)
|
||||
class SearchControllerHooks < Redmine::Hook::Listener
|
||||
include Rails.application.routes.url_helpers
|
||||
|
||||
def controller_search_quick_jump(context={})
|
||||
if context.is_a?(Hash)
|
||||
question = context[:question]
|
||||
if question.present?
|
||||
if question.match(/^D(\d+)$/) && DmsfFile.visible.where(id: $1).exists?
|
||||
return dmsf_file_path(id: $1)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@ -21,22 +21,23 @@
|
||||
|
||||
module RedmineDmsf
|
||||
module Hooks
|
||||
include Redmine::Hook
|
||||
module Helpers
|
||||
|
||||
class HelperIssuesHook < RedmineDmsf::Hooks::Listener
|
||||
class IssuesHelperHooks < Redmine::Hook::Listener
|
||||
|
||||
def helper_issues_show_detail_after_setting(context)
|
||||
if context.is_a?(Hash)
|
||||
detail = context[:detail]
|
||||
case detail.property
|
||||
when 'dmsf_file'
|
||||
detail.prop_key = l(:label_document)
|
||||
detail.property = 'attachment'
|
||||
def helper_issues_show_detail_after_setting(context)
|
||||
if context.is_a?(Hash)
|
||||
detail = context[:detail]
|
||||
case detail.property
|
||||
when 'dmsf_file'
|
||||
detail.prop_key = l(:label_document)
|
||||
detail.property = 'attachment'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@ -21,21 +21,22 @@
|
||||
|
||||
module RedmineDmsf
|
||||
module Hooks
|
||||
include Redmine::Hook
|
||||
module Helpers
|
||||
|
||||
class HelperProjectsHook < RedmineDmsf::Hooks::Listener
|
||||
class ProjectHelperHooks < Redmine::Hook::Listener
|
||||
|
||||
def helper_project_settings_tabs(context)
|
||||
dmsf_tabs = [
|
||||
{ name: 'dmsf', action: { controller: 'dmsf_state', action: 'user_pref_save' },
|
||||
partial: 'dmsf_state/user_pref', label: :menu_dmsf },
|
||||
{ name: 'dmsf_workflow', action: { controller: 'dmsf_workflows', action: 'index' },
|
||||
partial: 'dmsf_workflows/main', label: :label_dmsf_workflow_plural }
|
||||
]
|
||||
context[:tabs].concat(dmsf_tabs.select { |dmsf_tab| User.current.allowed_to?(dmsf_tab[:action], context[:project]) })
|
||||
end
|
||||
|
||||
def helper_project_settings_tabs(context)
|
||||
dmsf_tabs = [
|
||||
{ name: 'dmsf', action: { controller: 'dmsf_state', action: 'user_pref_save' },
|
||||
partial: 'dmsf_state/user_pref', label: :menu_dmsf },
|
||||
{ name: 'dmsf_workflow', action: { controller: 'dmsf_workflows', action: 'index' },
|
||||
partial: 'dmsf_workflows/main', label: :label_dmsf_workflow_plural }
|
||||
]
|
||||
context[:tabs].concat(dmsf_tabs.select { |dmsf_tab| User.current.allowed_to?(dmsf_tab[:action], context[:project]) })
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@ -21,31 +21,32 @@
|
||||
|
||||
module RedmineDmsf
|
||||
module Hooks
|
||||
include Redmine::Hook
|
||||
module Helpers
|
||||
|
||||
class SearchHelperHook < RedmineDmsf::Hooks::Listener
|
||||
class SearchHelperHooks < Redmine::Hook::Listener
|
||||
|
||||
def helper_easy_extensions_search_helper_patch(context={})
|
||||
case context[:entity].event_type
|
||||
when 'dmsf-file', 'dmsf-folder'
|
||||
str = context[:controller].send(:render_to_string, partial: 'search/container',
|
||||
locals: { object: context[:entity] })
|
||||
if str
|
||||
html = +'<p class=\"file-detail-container\"><span><strong>'
|
||||
if context[:entity].dmsf_folder_id
|
||||
html << context[:entity].class.human_attribute_name(:folder)
|
||||
else
|
||||
html << context[:entity].class.human_attribute_name(:project)
|
||||
def helper_easy_extensions_search_helper_patch(context={})
|
||||
case context[:entity].event_type
|
||||
when 'dmsf-file', 'dmsf-folder'
|
||||
str = context[:controller].send(:render_to_string, partial: 'search/container',
|
||||
locals: { object: context[:entity] })
|
||||
if str
|
||||
html = +'<p class=\"file-detail-container\"><span><strong>'
|
||||
if context[:entity].dmsf_folder_id
|
||||
html << context[:entity].class.human_attribute_name(:folder)
|
||||
else
|
||||
html << context[:entity].class.human_attribute_name(:project)
|
||||
end
|
||||
html << ':</strong>'
|
||||
html << str
|
||||
html << '</span></p>'
|
||||
context[:additional_result] << html
|
||||
end
|
||||
html << ':</strong>'
|
||||
html << str
|
||||
html << '</span></p>'
|
||||
context[:additional_result] << html
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@ -20,22 +20,24 @@
|
||||
|
||||
module RedmineDmsf
|
||||
module Hooks
|
||||
include Redmine::Hook
|
||||
module Views
|
||||
|
||||
class DmsfViewListener < Redmine::Hook::ViewListener
|
||||
|
||||
def view_layouts_base_html_head(context={})
|
||||
return unless /^(Dmsf|Projects|Issues|Queries|EasyCrmCases|MyController|SettingsController)/.match?(
|
||||
context[:controller].class.name)
|
||||
meta = "\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, defer: true) +
|
||||
"\n".html_safe + javascript_include_tag('redmine_dmsf.js', plugin: :redmine_dmsf, defer: true) +
|
||||
"\n".html_safe + javascript_include_tag('attachments_dmsf.js', plugin: :redmine_dmsf, defer: true)
|
||||
if defined?(EasyExtensions)
|
||||
meta = meta + "\n".html_safe + stylesheet_link_tag('easy_extensions.css', plugin: :redmine_dmsf)
|
||||
class BaseViewHooks < Redmine::Hook::ViewListener
|
||||
|
||||
def view_layouts_base_html_head(context={})
|
||||
return unless /^(Dmsf|Projects|Issues|Queries|EasyCrmCases|MyController|SettingsController)/.match?(
|
||||
context[:controller].class.name)
|
||||
meta = "\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, defer: true) +
|
||||
"\n".html_safe + javascript_include_tag('redmine_dmsf.js', plugin: :redmine_dmsf, defer: true) +
|
||||
"\n".html_safe + javascript_include_tag('attachments_dmsf.js', plugin: :redmine_dmsf, defer: true)
|
||||
if defined?(EasyExtensions)
|
||||
meta = meta + "\n".html_safe + stylesheet_link_tag('easy_extensions.css', plugin: :redmine_dmsf)
|
||||
end
|
||||
meta
|
||||
end
|
||||
meta
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@ -20,25 +20,27 @@
|
||||
|
||||
module RedmineDmsf
|
||||
module Hooks
|
||||
include Redmine::Hook
|
||||
module Views
|
||||
|
||||
class DmsfViewListener < Redmine::Hook::ViewListener
|
||||
class CustomFieldViewHooks < Redmine::Hook::ViewListener
|
||||
|
||||
def view_custom_fields_form_dmsf_file_revision_custom_field(context={})
|
||||
html = ''
|
||||
if context.is_a?(Hash) && context[:form]
|
||||
# Add the inheritable option
|
||||
f = context[:form]
|
||||
html = "<p>#{f.check_box(:dmsf_not_inheritable)}</p>"
|
||||
# Add is filter option
|
||||
if context[:custom_field]
|
||||
custom_field = context[:custom_field]
|
||||
if custom_field.format.is_filter_supported
|
||||
html << "<p>#{f.check_box(:is_filter)}</p>"
|
||||
def view_custom_fields_form_dmsf_file_revision_custom_field(context={})
|
||||
html = ''
|
||||
if context.is_a?(Hash) && context[:form]
|
||||
# Add the inheritable option
|
||||
f = context[:form]
|
||||
html = "<p>#{f.check_box(:dmsf_not_inheritable)}</p>"
|
||||
# Add is filter option
|
||||
if context[:custom_field]
|
||||
custom_field = context[:custom_field]
|
||||
if custom_field.format.is_filter_supported
|
||||
html << "<p>#{f.check_box(:is_filter)}</p>"
|
||||
end
|
||||
end
|
||||
end
|
||||
html.html_safe
|
||||
end
|
||||
html.html_safe
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@ -20,272 +20,274 @@
|
||||
|
||||
module RedmineDmsf
|
||||
module Hooks
|
||||
include Redmine::Hook
|
||||
module Views
|
||||
|
||||
class DmsfViewListener < Redmine::Hook::ViewListener
|
||||
class IssueViewHooks < Redmine::Hook::ViewListener
|
||||
|
||||
def view_issues_form_details_bottom(context={})
|
||||
return if defined?(EasyExtensions)
|
||||
context[:container] = context[:issue]
|
||||
attach_documents_form(context)
|
||||
end
|
||||
def view_issues_form_details_bottom(context={})
|
||||
return if defined?(EasyExtensions)
|
||||
context[:container] = context[:issue]
|
||||
attach_documents_form(context)
|
||||
end
|
||||
|
||||
def view_attachments_form_top(context={})
|
||||
html = ''
|
||||
container = context[:container]
|
||||
description = defined?(EasyExtensions) && EasySetting.value('attachment_description')
|
||||
# Radio buttons
|
||||
if allowed_to_attach_documents(container) && allowed_to_attach_attachments(container)
|
||||
html << (description ? '<p>' : '<div>')
|
||||
classes = +'inline'
|
||||
html << "<label class=\"#{classes}\">"
|
||||
html << radio_button_tag('dmsf_attachments_upload_choice', 'Attachments',
|
||||
User.current.pref.dmsf_attachments_upload_choice == 'Attachments',
|
||||
onchange: "$('.attachments-container:not(.dmsf-uploader)').show(); $('.dmsf-uploader').parent().hide(); return false;")
|
||||
html << l(:label_basic_attachments)
|
||||
html << '</label>'
|
||||
unless container && container.new_record?
|
||||
classes << ' dmsf_attachments_label'
|
||||
def view_attachments_form_top(context={})
|
||||
html = ''
|
||||
container = context[:container]
|
||||
description = defined?(EasyExtensions) && EasySetting.value('attachment_description')
|
||||
# Radio buttons
|
||||
if allowed_to_attach_documents(container) && allowed_to_attach_attachments(container)
|
||||
html << (description ? '<p>' : '<div>')
|
||||
classes = +'inline'
|
||||
html << "<label class=\"#{classes}\">"
|
||||
html << radio_button_tag('dmsf_attachments_upload_choice', 'Attachments',
|
||||
User.current.pref.dmsf_attachments_upload_choice == 'Attachments',
|
||||
onchange: "$('.attachments-container:not(.dmsf-uploader)').show(); $('.dmsf-uploader').parent().hide(); return false;")
|
||||
html << l(:label_basic_attachments)
|
||||
html << '</label>'
|
||||
unless container && container.new_record?
|
||||
classes << ' dmsf_attachments_label'
|
||||
end
|
||||
html << "<label class=\"#{classes}\">"
|
||||
html << radio_button_tag('dmsf_attachments_upload_choice', 'DMSF',
|
||||
User.current.pref.dmsf_attachments_upload_choice == 'DMSF',
|
||||
onchange: "$('.attachments-container:not(.dmsf-uploader)').hide(); $('.dmsf-uploader').parent().show(); return false;")
|
||||
html << l(:label_dmsf_attachments)
|
||||
html << '</label>'
|
||||
html << (description ? '</p>' : '</div>')
|
||||
if User.current.pref.dmsf_attachments_upload_choice == 'DMSF'
|
||||
html << context[:hook_caller].late_javascript_tag("$('.attachments-container:not(.dmsf-uploader)').hide();")
|
||||
end
|
||||
html << "<label class=\"#{classes}\">"
|
||||
html << radio_button_tag('dmsf_attachments_upload_choice', 'DMSF',
|
||||
User.current.pref.dmsf_attachments_upload_choice == 'DMSF',
|
||||
onchange: "$('.attachments-container:not(.dmsf-uploader)').hide(); $('.dmsf-uploader').parent().show(); return false;")
|
||||
html << l(:label_dmsf_attachments)
|
||||
html << '</label>'
|
||||
html << (description ? '</p>' : '</div>')
|
||||
if User.current.pref.dmsf_attachments_upload_choice == 'DMSF'
|
||||
end
|
||||
# Upload form
|
||||
if allowed_to_attach_documents(container)
|
||||
html << attach_documents_form(context, false, description)
|
||||
end
|
||||
unless allowed_to_attach_attachments(container)
|
||||
html << context[:hook_caller].late_javascript_tag("$('.attachments-container:not(.dmsf-uploader)').hide();")
|
||||
end
|
||||
end
|
||||
# Upload form
|
||||
if allowed_to_attach_documents(container)
|
||||
html << attach_documents_form(context, false, description)
|
||||
end
|
||||
unless allowed_to_attach_attachments(container)
|
||||
html << context[:hook_caller].late_javascript_tag("$('.attachments-container:not(.dmsf-uploader)').hide();")
|
||||
end
|
||||
html.html_safe
|
||||
end
|
||||
|
||||
def view_issues_show_description_bottom(context={})
|
||||
return if defined?(EasyExtensions)
|
||||
show_attached_documents(context[:issue], context[:controller])
|
||||
end
|
||||
|
||||
def view_issues_show_attachments_table_bottom(context={})
|
||||
unless context[:options][:only_mails].present?
|
||||
show_attached_documents(context[:container], context[:controller], context[:attachments])
|
||||
end
|
||||
end
|
||||
|
||||
def view_issues_dms_attachments(context={})
|
||||
'yes' if get_links(context[:container]).any?
|
||||
end
|
||||
|
||||
def view_issues_show_thumbnails(context={})
|
||||
unless context[:options][:only_mails].present?
|
||||
show_thumbnails(context[:container], context[:controller])
|
||||
end
|
||||
end
|
||||
|
||||
def view_issues_dms_thumbnails(context={})
|
||||
unless context[:options][:only_mails].present?
|
||||
links = get_links(context[:container])
|
||||
if links.present? && Setting.thumbnails_enabled?
|
||||
images = links.map{ |x| x[0] }.select(&:image?)
|
||||
return 'yes' if images.any?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def view_issues_edit_notes_bottom_style(context={})
|
||||
if ((User.current.pref.dmsf_attachments_upload_choice == 'Attachments') ||
|
||||
!allowed_to_attach_documents(context[:container])) && allowed_to_attach_attachments(context[:container])
|
||||
''
|
||||
else
|
||||
'display: none'
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def allowed_to_attach_documents(container)
|
||||
container && container.respond_to?(:saved_dmsf_attachments) && container.project &&
|
||||
User.current.allowed_to?(:file_manipulation, container.project) &&
|
||||
Setting.plugin_redmine_dmsf['dmsf_act_as_attachable'] &&
|
||||
(container.project.dmsf_act_as_attachable == Project::ATTACHABLE_DMS_AND_ATTACHMENTS)
|
||||
end
|
||||
|
||||
def allowed_to_attach_attachments(container)
|
||||
unless defined?(EasyExtensions)
|
||||
return true
|
||||
end
|
||||
if allowed_to_attach_documents(container) && (!container.project.module_enabled?(:documents))
|
||||
return false
|
||||
end
|
||||
true
|
||||
end
|
||||
|
||||
def get_links(container)
|
||||
links = []
|
||||
if defined?(container.dmsf_files) && User.current.allowed_to?(:view_dmsf_files, container.project) &&
|
||||
Setting.plugin_redmine_dmsf['dmsf_act_as_attachable'] &&
|
||||
(container.project.dmsf_act_as_attachable == Project::ATTACHABLE_DMS_AND_ATTACHMENTS)
|
||||
container.dmsf_files.each do |dmsf_file|
|
||||
if dmsf_file.last_revision
|
||||
links << [dmsf_file, nil, dmsf_file.created_at]
|
||||
end
|
||||
end
|
||||
container.dmsf_links.each do |dmsf_link|
|
||||
dmsf_file = dmsf_link.target_file
|
||||
if dmsf_file && dmsf_file.last_revision
|
||||
links << [dmsf_file, dmsf_link, dmsf_link.created_at]
|
||||
end
|
||||
end
|
||||
# Sort by 'create_at'
|
||||
links.sort!{ |x, y| x[2] <=> y[2] }
|
||||
end
|
||||
links
|
||||
end
|
||||
|
||||
def show_thumbnails(container, controller)
|
||||
links = get_links(container)
|
||||
if links.present?
|
||||
html = controller.send(:render_to_string,
|
||||
{ partial: 'dmsf_files/thumbnails',
|
||||
locals: { links: links, thumbnails: Setting.thumbnails_enabled?, link_to: false } })
|
||||
html.html_safe
|
||||
end
|
||||
end
|
||||
|
||||
def attach_documents_form(context, label=true, description=true)
|
||||
if context.is_a?(Hash) && context[:container]
|
||||
# Add Dmsf upload form
|
||||
container = context[:container]
|
||||
if allowed_to_attach_documents(container)
|
||||
html = (description ? '<p' : '<div')
|
||||
html << " style=\"#{(User.current.pref.dmsf_attachments_upload_choice == 'Attachments') && allowed_to_attach_attachments(container) ? 'display: none;' : ''}\">"
|
||||
if label
|
||||
html << "<label>#{l(:label_document_plural)}</label>"
|
||||
html << "<span class=\"attachments-container dmsf-uploader\">"
|
||||
else
|
||||
html << "<span class=\"attachments-container dmsf-uploader\" style=\"border: 2px dashed #dfccaf; background: none;\">"
|
||||
def view_issues_show_description_bottom(context={})
|
||||
return if defined?(EasyExtensions)
|
||||
show_attached_documents(context[:issue], context[:controller])
|
||||
end
|
||||
|
||||
def view_issues_show_attachments_table_bottom(context={})
|
||||
unless context[:options][:only_mails].present?
|
||||
show_attached_documents(context[:container], context[:controller], context[:attachments])
|
||||
end
|
||||
end
|
||||
|
||||
def view_issues_dms_attachments(context={})
|
||||
'yes' if get_links(context[:container]).any?
|
||||
end
|
||||
|
||||
def view_issues_show_thumbnails(context={})
|
||||
unless context[:options][:only_mails].present?
|
||||
show_thumbnails(context[:container], context[:controller])
|
||||
end
|
||||
end
|
||||
|
||||
def view_issues_dms_thumbnails(context={})
|
||||
unless context[:options][:only_mails].present?
|
||||
links = get_links(context[:container])
|
||||
if links.present? && Setting.thumbnails_enabled?
|
||||
images = links.map{ |x| x[0] }.select(&:image?)
|
||||
return 'yes' if images.any?
|
||||
end
|
||||
html << context[:controller].send(:render_to_string,
|
||||
{ partial: 'dmsf_upload/form',
|
||||
locals: { container: container, multiple: true, description: description, awf: true }})
|
||||
html << '</span>'
|
||||
html << (description ? '</p>' : '</div>')
|
||||
end
|
||||
end
|
||||
|
||||
def view_issues_edit_notes_bottom_style(context={})
|
||||
if ((User.current.pref.dmsf_attachments_upload_choice == 'Attachments') ||
|
||||
!allowed_to_attach_documents(context[:container])) && allowed_to_attach_attachments(context[:container])
|
||||
''
|
||||
else
|
||||
'display: none'
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def allowed_to_attach_documents(container)
|
||||
container && container.respond_to?(:saved_dmsf_attachments) && container.project &&
|
||||
User.current.allowed_to?(:file_manipulation, container.project) &&
|
||||
Setting.plugin_redmine_dmsf['dmsf_act_as_attachable'] &&
|
||||
(container.project.dmsf_act_as_attachable == Project::ATTACHABLE_DMS_AND_ATTACHMENTS)
|
||||
end
|
||||
|
||||
def allowed_to_attach_attachments(container)
|
||||
unless defined?(EasyExtensions)
|
||||
return true
|
||||
end
|
||||
if allowed_to_attach_documents(container) && (!container.project.module_enabled?(:documents))
|
||||
return false
|
||||
end
|
||||
true
|
||||
end
|
||||
|
||||
def get_links(container)
|
||||
links = []
|
||||
if defined?(container.dmsf_files) && User.current.allowed_to?(:view_dmsf_files, container.project) &&
|
||||
Setting.plugin_redmine_dmsf['dmsf_act_as_attachable'] &&
|
||||
(container.project.dmsf_act_as_attachable == Project::ATTACHABLE_DMS_AND_ATTACHMENTS)
|
||||
container.dmsf_files.each do |dmsf_file|
|
||||
if dmsf_file.last_revision
|
||||
links << [dmsf_file, nil, dmsf_file.created_at]
|
||||
end
|
||||
end
|
||||
container.dmsf_links.each do |dmsf_link|
|
||||
dmsf_file = dmsf_link.target_file
|
||||
if dmsf_file && dmsf_file.last_revision
|
||||
links << [dmsf_file, dmsf_link, dmsf_link.created_at]
|
||||
end
|
||||
end
|
||||
# Sort by 'create_at'
|
||||
links.sort!{ |x, y| x[2] <=> y[2] }
|
||||
end
|
||||
links
|
||||
end
|
||||
|
||||
def show_thumbnails(container, controller)
|
||||
links = get_links(container)
|
||||
if links.present?
|
||||
html = controller.send(:render_to_string,
|
||||
{ partial: 'dmsf_files/thumbnails',
|
||||
locals: { links: links, thumbnails: Setting.thumbnails_enabled?, link_to: false } })
|
||||
html.html_safe
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def show_attached_documents(container, controller, attachments=nil)
|
||||
# Add list of attached documents
|
||||
links = get_links(container)
|
||||
if links.present?
|
||||
if defined?(EasyExtensions)
|
||||
attachment_rows(links, container, controller)
|
||||
else
|
||||
controller.send :render_to_string,
|
||||
{ partial: 'dmsf_files/links', locals: { links: links, thumbnails: Setting.thumbnails_enabled?}}
|
||||
def attach_documents_form(context, label=true, description=true)
|
||||
if context.is_a?(Hash) && context[:container]
|
||||
# Add Dmsf upload form
|
||||
container = context[:container]
|
||||
if allowed_to_attach_documents(container)
|
||||
html = (description ? '<p' : '<div')
|
||||
html << " style=\"#{(User.current.pref.dmsf_attachments_upload_choice == 'Attachments') && allowed_to_attach_attachments(container) ? 'display: none;' : ''}\">"
|
||||
if label
|
||||
html << "<label>#{l(:label_document_plural)}</label>"
|
||||
html << "<span class=\"attachments-container dmsf-uploader\">"
|
||||
else
|
||||
html << "<span class=\"attachments-container dmsf-uploader\" style=\"border: 2px dashed #dfccaf; background: none;\">"
|
||||
end
|
||||
html << context[:controller].send(:render_to_string,
|
||||
{ partial: 'dmsf_upload/form',
|
||||
locals: { container: container, multiple: true, description: description, awf: true }})
|
||||
html << '</span>'
|
||||
html << (description ? '</p>' : '</div>')
|
||||
html.html_safe
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def attachment_rows(links, issue, controller)
|
||||
if links.any?
|
||||
html = +"<tbody><tr><th colspan=\"4\">#{l(:label_dmsf_attachments)} (#{links.count})</th></tr>"
|
||||
links.each do |dmsf_file, link, create_at|
|
||||
html << attachment_row(dmsf_file, link, issue, controller)
|
||||
def show_attached_documents(container, controller, attachments=nil)
|
||||
# Add list of attached documents
|
||||
links = get_links(container)
|
||||
if links.present?
|
||||
if defined?(EasyExtensions)
|
||||
attachment_rows(links, container, controller)
|
||||
else
|
||||
controller.send :render_to_string,
|
||||
{ partial: 'dmsf_files/links', locals: { links: links, thumbnails: Setting.thumbnails_enabled?}}
|
||||
end
|
||||
end
|
||||
html << '</tbody>'
|
||||
html.html_safe
|
||||
end
|
||||
end
|
||||
|
||||
def attachment_row(dmsf_file, link, issue, controller)
|
||||
if link
|
||||
html = +'<tr class="dmsf-gray">'
|
||||
else
|
||||
html = +'<tr>'
|
||||
end
|
||||
# Checkbox
|
||||
show_checkboxes = true #options[:show_checkboxes].nil? ? true : options[:show_checkboxes]
|
||||
if show_checkboxes
|
||||
html << '<td></td>'
|
||||
end
|
||||
file_view_url = url_for({ controller: :dmsf_files, action: 'view', id: dmsf_file})
|
||||
# Title, size
|
||||
html << '<td>'
|
||||
html << 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}")
|
||||
html << "<span class=\"size\">(#{number_to_human_size(dmsf_file.last_revision.size)})</span>"
|
||||
html << " - #{h(dmsf_file.description)}" unless dmsf_file.description.blank?
|
||||
html << '</td>'
|
||||
# Author, updated at
|
||||
html << '<td>'
|
||||
html << "<span class=\"author\">#{h(dmsf_file.last_revision.user)}, #{format_time(dmsf_file.last_revision.updated_at)}</span>"
|
||||
html << '</td>'
|
||||
# Command icons
|
||||
html << '<td class="fast-icons easy-query-additional-ending-buttons hide-when-print">'
|
||||
# Details
|
||||
if User.current.allowed_to? :file_manipulation, dmsf_file.project
|
||||
html << link_to('', dmsf_file_path(id: dmsf_file),
|
||||
title: l(:link_details, title: h(dmsf_file.last_revision.title)),
|
||||
class: 'icon icon-edit')
|
||||
else
|
||||
html << '<span class="icon"></span>'
|
||||
end
|
||||
# Email
|
||||
html << link_to('', entries_operations_dmsf_path(id: dmsf_file.project,
|
||||
email_entries: 'email', files: [dmsf_file.id]), method: :post,
|
||||
title: l(:heading_send_documents_by_email), class: 'icon icon-email-disabled')
|
||||
# Lock
|
||||
if !dmsf_file.locked?
|
||||
html << link_to('', lock_dmsf_files_path(id: dmsf_file),
|
||||
title: l(:title_lock_file), class: 'icon icon-lock')
|
||||
elsif dmsf_file.unlockable? && (!dmsf_file.locked_for_user? || User.current.allowed_to?(:force_file_unlock, dmsf_file.project))
|
||||
html << link_to('', unlock_dmsf_files_path(id: dmsf_file),
|
||||
title: dmsf_file.get_locked_title, class: 'icon icon-unlock')
|
||||
else
|
||||
html << "<span class=\"icon icon-unlock\" title=\"#{dmsf_file.get_locked_title}\"></span>"
|
||||
end
|
||||
if dmsf_file.locked?
|
||||
html << '<span class="icon"></span>' * 2
|
||||
else
|
||||
# Notifications
|
||||
if dmsf_file.notification
|
||||
html << link_to('', notify_deactivate_dmsf_files_path(id: dmsf_file),
|
||||
title: l(:title_notifications_active_deactivate), class: 'icon icon-email')
|
||||
else
|
||||
html << link_to('', notify_activate_dmsf_files_path(id: dmsf_file),
|
||||
title: l(:title_notifications_not_active_activate), class: 'icon icon-email-add')
|
||||
end
|
||||
# Delete
|
||||
if issue.attributes_editable? &&
|
||||
((link && User.current.allowed_to?(:file_manipulation, dmsf_file.project)) ||
|
||||
(!link && User.current.allowed_to?(:file_delete, dmsf_file.project)))
|
||||
html << link_to('',
|
||||
link ? dmsf_link_path(link, commit: 'yes') : dmsf_file_path(id: dmsf_file, commit: 'yes'),
|
||||
data: { confirm: l(:text_are_you_sure) }, method: :delete, title: l(:button_delete),
|
||||
class: 'icon icon-del')
|
||||
def attachment_rows(links, issue, controller)
|
||||
if links.any?
|
||||
html = +"<tbody><tr><th colspan=\"4\">#{l(:label_dmsf_attachments)} (#{links.count})</th></tr>"
|
||||
links.each do |dmsf_file, link, create_at|
|
||||
html << attachment_row(dmsf_file, link, issue, controller)
|
||||
end
|
||||
html << '</tbody>'
|
||||
html.html_safe
|
||||
end
|
||||
end
|
||||
# Approval workflow
|
||||
wf = DmsfWorkflow.find_by(id: dmsf_file.last_revision.dmsf_workflow_id) if dmsf_file.last_revision.dmsf_workflow_id
|
||||
html << controller.send(:render_to_string, { partial: 'dmsf_workflows/approval_workflow_button',
|
||||
locals: { file: dmsf_file,
|
||||
file_approval_allowed: User.current.allowed_to?(:file_approval, dmsf_file.project),
|
||||
workflows_available: DmsfWorkflow.where(['project_id = ? OR project_id IS NULL', dmsf_file.project.id]).exists?,
|
||||
project: dmsf_file.project, wf: wf, dmsf_link_id: nil }})
|
||||
html << '</td>'
|
||||
html << '</tr>'
|
||||
html
|
||||
|
||||
def attachment_row(dmsf_file, link, issue, controller)
|
||||
if link
|
||||
html = +'<tr class="dmsf-gray">'
|
||||
else
|
||||
html = +'<tr>'
|
||||
end
|
||||
# Checkbox
|
||||
show_checkboxes = true #options[:show_checkboxes].nil? ? true : options[:show_checkboxes]
|
||||
if show_checkboxes
|
||||
html << '<td></td>'
|
||||
end
|
||||
file_view_url = url_for({ controller: :dmsf_files, action: 'view', id: dmsf_file})
|
||||
# Title, size
|
||||
html << '<td>'
|
||||
html << 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}")
|
||||
html << "<span class=\"size\">(#{number_to_human_size(dmsf_file.last_revision.size)})</span>"
|
||||
html << " - #{h(dmsf_file.description)}" unless dmsf_file.description.blank?
|
||||
html << '</td>'
|
||||
# Author, updated at
|
||||
html << '<td>'
|
||||
html << "<span class=\"author\">#{h(dmsf_file.last_revision.user)}, #{format_time(dmsf_file.last_revision.updated_at)}</span>"
|
||||
html << '</td>'
|
||||
# Command icons
|
||||
html << '<td class="fast-icons easy-query-additional-ending-buttons hide-when-print">'
|
||||
# Details
|
||||
if User.current.allowed_to? :file_manipulation, dmsf_file.project
|
||||
html << link_to('', dmsf_file_path(id: dmsf_file),
|
||||
title: l(:link_details, title: h(dmsf_file.last_revision.title)),
|
||||
class: 'icon icon-edit')
|
||||
else
|
||||
html << '<span class="icon"></span>'
|
||||
end
|
||||
# Email
|
||||
html << link_to('', entries_operations_dmsf_path(id: dmsf_file.project,
|
||||
email_entries: 'email', files: [dmsf_file.id]), method: :post,
|
||||
title: l(:heading_send_documents_by_email), class: 'icon icon-email-disabled')
|
||||
# Lock
|
||||
if !dmsf_file.locked?
|
||||
html << link_to('', lock_dmsf_files_path(id: dmsf_file),
|
||||
title: l(:title_lock_file), class: 'icon icon-lock')
|
||||
elsif dmsf_file.unlockable? && (!dmsf_file.locked_for_user? || User.current.allowed_to?(:force_file_unlock, dmsf_file.project))
|
||||
html << link_to('', unlock_dmsf_files_path(id: dmsf_file),
|
||||
title: dmsf_file.get_locked_title, class: 'icon icon-unlock')
|
||||
else
|
||||
html << "<span class=\"icon icon-unlock\" title=\"#{dmsf_file.get_locked_title}\"></span>"
|
||||
end
|
||||
if dmsf_file.locked?
|
||||
html << '<span class="icon"></span>' * 2
|
||||
else
|
||||
# Notifications
|
||||
if dmsf_file.notification
|
||||
html << link_to('', notify_deactivate_dmsf_files_path(id: dmsf_file),
|
||||
title: l(:title_notifications_active_deactivate), class: 'icon icon-email')
|
||||
else
|
||||
html << link_to('', notify_activate_dmsf_files_path(id: dmsf_file),
|
||||
title: l(:title_notifications_not_active_activate), class: 'icon icon-email-add')
|
||||
end
|
||||
# Delete
|
||||
if issue.attributes_editable? &&
|
||||
((link && User.current.allowed_to?(:file_manipulation, dmsf_file.project)) ||
|
||||
(!link && User.current.allowed_to?(:file_delete, dmsf_file.project)))
|
||||
html << link_to('',
|
||||
link ? dmsf_link_path(link, commit: 'yes') : dmsf_file_path(id: dmsf_file, commit: 'yes'),
|
||||
data: { confirm: l(:text_are_you_sure) }, method: :delete, title: l(:button_delete),
|
||||
class: 'icon icon-del')
|
||||
end
|
||||
end
|
||||
# Approval workflow
|
||||
wf = DmsfWorkflow.find_by(id: dmsf_file.last_revision.dmsf_workflow_id) if dmsf_file.last_revision.dmsf_workflow_id
|
||||
html << controller.send(:render_to_string, { partial: 'dmsf_workflows/approval_workflow_button',
|
||||
locals: { file: dmsf_file,
|
||||
file_approval_allowed: User.current.allowed_to?(:file_approval, dmsf_file.project),
|
||||
workflows_available: DmsfWorkflow.where(['project_id = ? OR project_id IS NULL', dmsf_file.project.id]).exists?,
|
||||
project: dmsf_file.project, wf: wf, dmsf_link_id: nil }})
|
||||
html << '</td>'
|
||||
html << '</tr>'
|
||||
html
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@ -18,17 +18,20 @@
|
||||
|
||||
module RedmineDmsf
|
||||
module Hooks
|
||||
module Views
|
||||
|
||||
class ViewSearchFormHook < Redmine::Hook::ViewListener
|
||||
class SearchViewHooks < Redmine::Hook::ViewListener
|
||||
|
||||
def view_search_index_container(context={})
|
||||
if context[:object].is_a?(DmsfFile) || context[:object].is_a?(DmsfFolder)
|
||||
str = context[:controller].send(:render_to_string, partial: 'search/container',
|
||||
locals: { object: context[:object] })
|
||||
if str
|
||||
" #{str} /"
|
||||
def view_search_index_container(context={})
|
||||
if context[:object].is_a?(DmsfFile) || context[:object].is_a?(DmsfFolder)
|
||||
str = context[:controller].send(:render_to_string, partial: 'search/container',
|
||||
locals: { object: context[:object] })
|
||||
if str
|
||||
" #{str} /"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@ -18,14 +18,18 @@
|
||||
|
||||
module RedmineDmsf
|
||||
module Hooks
|
||||
class ViewProjectsFormHook < Redmine::Hook::ViewListener
|
||||
include Redmine::I18n
|
||||
module Views
|
||||
|
||||
class ViewProjectsFormHook < Redmine::Hook::ViewListener
|
||||
include Redmine::I18n
|
||||
|
||||
def view_projects_form(context={})
|
||||
context[:controller].send :render_to_string, {
|
||||
partial: 'hooks/redmine_dmsf/view_projects_form',
|
||||
locals: context
|
||||
}
|
||||
end
|
||||
|
||||
def view_projects_form(context={})
|
||||
context[:controller].send :render_to_string, {
|
||||
partial: 'hooks/redmine_dmsf/view_projects_form',
|
||||
locals: context
|
||||
}
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@ -47,22 +47,22 @@ module RedmineDmsf
|
||||
def lock!(scope = :scope_exclusive, type = :type_write, expire = nil, owner = nil)
|
||||
# Raise a lock error if entity is locked, but its not at resource level
|
||||
existing = lock(false)
|
||||
raise DmsfLockError.new(l(:error_resource_or_parent_locked)) if self.locked? && existing.empty?
|
||||
raise RedmineDmsf::Errors::RedmineDmsf::Errors::DmsfLockError.new(l(:error_resource_or_parent_locked)) if self.locked? && existing.empty?
|
||||
unless existing.empty?
|
||||
if (existing[0].lock_scope == :scope_shared) && (scope == :scope_shared)
|
||||
# RFC states if an item is exclusively locked and another lock is attempted we reject
|
||||
# if the item is shared locked however, we can always add another lock to it
|
||||
if self.dmsf_folder.locked?
|
||||
raise DmsfLockError.new(l(:error_parent_locked))
|
||||
raise RedmineDmsf::Errors::RedmineDmsf::Errors::DmsfLockError.new(l(:error_parent_locked))
|
||||
else
|
||||
existing.each do |l|
|
||||
if (l.user.id == User.current.id) && (owner.nil? || (owner.downcase == l.owner&.downcase))
|
||||
raise DmsfLockError.new(l(:error_resource_locked))
|
||||
raise RedmineDmsf::Errors::RedmineDmsf::Errors::DmsfLockError.new(l(:error_resource_locked))
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
raise DmsfLockError.new(l(:error_lock_exclusively)) if scope == :scope_exclusive
|
||||
raise RedmineDmsf::Errors::RedmineDmsf::Errors::DmsfLockError.new(l(:error_lock_exclusively)) if scope == :scope_exclusive
|
||||
end
|
||||
end
|
||||
l = DmsfLock.new
|
||||
@ -113,16 +113,16 @@ module RedmineDmsf
|
||||
end
|
||||
|
||||
def unlock!(force_file_unlock_allowed = false, owner = nil)
|
||||
raise DmsfLockError.new(l(:warning_file_not_locked)) unless self.locked?
|
||||
raise RedmineDmsf::Errors::RedmineDmsf::Errors::DmsfLockError.new(l(:warning_file_not_locked)) unless self.locked?
|
||||
existing = self.lock(true)
|
||||
destroyed = false
|
||||
# If its empty its a folder that's locked (not root)
|
||||
if existing.empty? || (!self.dmsf_folder.nil? && self.dmsf_folder.locked?)
|
||||
raise DmsfLockError.new(l(:error_unlock_parent_locked))
|
||||
raise RedmineDmsf::Errors::RedmineDmsf::Errors::DmsfLockError.new(l(:error_unlock_parent_locked))
|
||||
else
|
||||
# If entity is locked to you, you aren't the lock originator (or named in a shared lock) so deny action
|
||||
# Unless of course you have the rights to force an unlock
|
||||
raise DmsfLockError.new(l(:error_only_user_that_locked_file_can_unlock_it)) if (
|
||||
raise RedmineDmsf::Errors::RedmineDmsf::Errors::DmsfLockError.new(l(:error_only_user_that_locked_file_can_unlock_it)) if (
|
||||
self.locked_for_user? && !User.current.allowed_to?(:force_file_unlock, self.project) && !force_file_unlock_allowed)
|
||||
# Now we need to determine lock type and do the needful
|
||||
if (existing.count == 1) && (existing[0].lock_scope == :exclusive)
|
||||
|
||||
@ -18,241 +18,248 @@
|
||||
# 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.
|
||||
#
|
||||
|
||||
Redmine::WikiFormatting::Macros.register do
|
||||
module RedmineDmsf
|
||||
module Macros
|
||||
|
||||
# dmsf - link to a document
|
||||
desc "Wiki link to DMSF file:\n\n" +
|
||||
"{{dmsf(file_id [, title [, revision_id]])}}\n\n" +
|
||||
"_file_id_ / _revision_id_ can be found in the link for file/revision download."
|
||||
macro :dmsf do |obj, args|
|
||||
raise ArgumentError if args.length < 1 # Requires file id
|
||||
file = DmsfFile.visible.find args[0]
|
||||
if args[2].blank?
|
||||
revision = file.last_revision
|
||||
else
|
||||
revision = DmsfFileRevision.find args[2]
|
||||
if revision.dmsf_file != file
|
||||
raise ActiveRecord::RecordNotFound
|
||||
end
|
||||
end
|
||||
unless User.current&.allowed_to?(:view_dmsf_files, file.project, { id: file.id })
|
||||
raise l(:notice_not_authorized)
|
||||
end
|
||||
title = args[1].present? ? args[1] : file.title
|
||||
title.gsub! /\A"|"\z/, '' # Remove apostrophes
|
||||
title.gsub! /\A'|'\z/, ''
|
||||
title = file.title if title.empty?
|
||||
url = view_dmsf_file_path(id: file.id, download: args[2])
|
||||
link_to h(title), url, target: '_blank', title: h(revision.tooltip),
|
||||
'data-downloadurl' => "#{file.last_revision.detect_content_type}:#{h(file.name)}:#{url}"
|
||||
end
|
||||
Redmine::WikiFormatting::Macros.register do
|
||||
|
||||
# dmsff - link to a folder
|
||||
desc "Wiki link to DMSF folder:\n\n" +
|
||||
"{{dmsff([folder_id [, title]])}}\n\n" +
|
||||
"_folder_id_ can be found in the link for folder opening. Without arguments return link to main folder 'Documents'"
|
||||
macro :dmsff do |obj, args|
|
||||
if args.length < 1
|
||||
if User.current.allowed_to?(:view_dmsf_folders, @project) && @project.module_enabled?(:dmsf)
|
||||
return link_to l(:link_documents), dmsf_folder_url(@project)
|
||||
else
|
||||
raise l(:notice_not_authorized)
|
||||
end
|
||||
else
|
||||
folder = DmsfFolder.visible.find args[0]
|
||||
if User.current&.allowed_to?(:view_dmsf_folders, folder.project)
|
||||
title = args[1] ? args[1] : folder.title
|
||||
title.gsub! /\A"|"\z/, '' # Remove leading and trailing apostrophe
|
||||
# dmsf - link to a document
|
||||
desc "Wiki link to DMSF file:\n\n" +
|
||||
"{{dmsf(file_id [, title [, revision_id]])}}\n\n" +
|
||||
"_file_id_ / _revision_id_ can be found in the link for file/revision download."
|
||||
macro :dmsf do |obj, args|
|
||||
raise ArgumentError if args.length < 1 # Requires file id
|
||||
file = DmsfFile.visible.find args[0]
|
||||
if args[2].blank?
|
||||
revision = file.last_revision
|
||||
else
|
||||
revision = DmsfFileRevision.find args[2]
|
||||
if revision.dmsf_file != file
|
||||
raise ActiveRecord::RecordNotFound
|
||||
end
|
||||
end
|
||||
unless User.current&.allowed_to?(:view_dmsf_files, file.project, { id: file.id })
|
||||
raise l(:notice_not_authorized)
|
||||
end
|
||||
title = args[1].present? ? args[1] : file.title
|
||||
title.gsub! /\A"|"\z/, '' # Remove apostrophes
|
||||
title.gsub! /\A'|'\z/, ''
|
||||
title = folder.title if title.empty?
|
||||
link_to h(title), dmsf_folder_url(folder.project, folder_id: folder)
|
||||
else
|
||||
raise l(:notice_not_authorized)
|
||||
title = file.title if title.empty?
|
||||
url = view_dmsf_file_path(id: file.id, download: args[2])
|
||||
link_to h(title), url, target: '_blank', title: h(revision.tooltip),
|
||||
'data-downloadurl' => "#{file.last_revision.detect_content_type}:#{h(file.name)}:#{url}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# dmsfd - link to the document's details
|
||||
desc "Wiki link to DMSF document details:\n\n" +
|
||||
"{{dmsfd(document_id [, title])}}\n\n" +
|
||||
"_document_id_ can be found in the document's details."
|
||||
macro :dmsfd do |obj, args|
|
||||
raise ArgumentError if args.length < 1 # Requires file id
|
||||
file = DmsfFile.visible.find args[0]
|
||||
if User.current&.allowed_to?(:view_dmsf_files, file.project)
|
||||
title = args[1].present? ? args[1] : file.title
|
||||
title.gsub! /\A"|"\z/, '' # Remove leading and trailing apostrophe
|
||||
title.gsub! /\A'|'\z/, ''
|
||||
link_to h(title), dmsf_file_path(id: file)
|
||||
else
|
||||
raise l(:notice_not_authorized)
|
||||
end
|
||||
end
|
||||
# dmsff - link to a folder
|
||||
desc "Wiki link to DMSF folder:\n\n" +
|
||||
"{{dmsff([folder_id [, title]])}}\n\n" +
|
||||
"_folder_id_ can be found in the link for folder opening. Without arguments return link to main folder 'Documents'"
|
||||
macro :dmsff do |obj, args|
|
||||
if args.length < 1
|
||||
if User.current.allowed_to?(:view_dmsf_folders, @project) && @project.module_enabled?(:dmsf)
|
||||
return link_to l(:link_documents), dmsf_folder_url(@project)
|
||||
else
|
||||
raise l(:notice_not_authorized)
|
||||
end
|
||||
else
|
||||
folder = DmsfFolder.visible.find args[0]
|
||||
if User.current&.allowed_to?(:view_dmsf_folders, folder.project)
|
||||
title = args[1] ? args[1] : folder.title
|
||||
title.gsub! /\A"|"\z/, '' # Remove leading and trailing apostrophe
|
||||
title.gsub! /\A'|'\z/, ''
|
||||
title = folder.title if title.empty?
|
||||
link_to h(title), dmsf_folder_url(folder.project, folder_id: folder)
|
||||
else
|
||||
raise l(:notice_not_authorized)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# dmsfdesc - text referring to the document's description
|
||||
desc "Text referring to DMSF document description:\n\n" +
|
||||
"{{dmsfdesc(document_id)}}\n\n" +
|
||||
"_document_id_ can be found in the document's details."
|
||||
macro :dmsfdesc do |obj, args|
|
||||
raise ArgumentError if args.length < 1 # Requires file id
|
||||
file = DmsfFile.visible.find args[0]
|
||||
if User.current&.allowed_to?(:view_dmsf_files, file.project)
|
||||
textilizable file.description
|
||||
else
|
||||
raise l(:notice_not_authorized)
|
||||
end
|
||||
end
|
||||
# dmsfd - link to the document's details
|
||||
desc "Wiki link to DMSF document details:\n\n" +
|
||||
"{{dmsfd(document_id [, title])}}\n\n" +
|
||||
"_document_id_ can be found in the document's details."
|
||||
macro :dmsfd do |obj, args|
|
||||
raise ArgumentError if args.length < 1 # Requires file id
|
||||
file = DmsfFile.visible.find args[0]
|
||||
if User.current&.allowed_to?(:view_dmsf_files, file.project)
|
||||
title = args[1].present? ? args[1] : file.title
|
||||
title.gsub! /\A"|"\z/, '' # Remove leading and trailing apostrophe
|
||||
title.gsub! /\A'|'\z/, ''
|
||||
link_to h(title), dmsf_file_path(id: file)
|
||||
else
|
||||
raise l(:notice_not_authorized)
|
||||
end
|
||||
end
|
||||
|
||||
# dmsfversion - text referring to the document's version
|
||||
desc "Text referring to DMSF document version:\n\n" +
|
||||
"{{dmsfversion(document_id)}}\n\n" +
|
||||
"_document_id_ can be found in the document's details."
|
||||
macro :dmsfversion do |obj, args|
|
||||
raise ArgumentError if args.length < 1 # Requires file id
|
||||
file = DmsfFile.visible.find args[0]
|
||||
if User.current&.allowed_to?(:view_dmsf_files, file.project)
|
||||
textilizable file.version
|
||||
else
|
||||
raise l(:notice_not_authorized)
|
||||
end
|
||||
end
|
||||
# dmsfdesc - text referring to the document's description
|
||||
desc "Text referring to DMSF document description:\n\n" +
|
||||
"{{dmsfdesc(document_id)}}\n\n" +
|
||||
"_document_id_ can be found in the document's details."
|
||||
macro :dmsfdesc do |obj, args|
|
||||
raise ArgumentError if args.length < 1 # Requires file id
|
||||
file = DmsfFile.visible.find args[0]
|
||||
if User.current&.allowed_to?(:view_dmsf_files, file.project)
|
||||
textilizable file.description
|
||||
else
|
||||
raise l(:notice_not_authorized)
|
||||
end
|
||||
end
|
||||
|
||||
# dmsflastupdate - text referring to the document's last update date
|
||||
desc "Text referring to DMSF document last update date:\n\n" +
|
||||
"{{dmsflastupdate(document_id)}}\n\n" +
|
||||
"_document_id_ can be found in the document's details."
|
||||
macro :dmsflastupdate do |obj, args|
|
||||
raise ArgumentError if args.length < 1 # Requires file id
|
||||
file = DmsfFile.visible.find args[0]
|
||||
if User.current&.allowed_to?(:view_dmsf_files, file.project)
|
||||
textilizable format_time(file.last_revision.updated_at)
|
||||
else
|
||||
raise l(:notice_not_authorized)
|
||||
end
|
||||
end
|
||||
# dmsfversion - text referring to the document's version
|
||||
desc "Text referring to DMSF document version:\n\n" +
|
||||
"{{dmsfversion(document_id)}}\n\n" +
|
||||
"_document_id_ can be found in the document's details."
|
||||
macro :dmsfversion do |obj, args|
|
||||
raise ArgumentError if args.length < 1 # Requires file id
|
||||
file = DmsfFile.visible.find args[0]
|
||||
if User.current&.allowed_to?(:view_dmsf_files, file.project)
|
||||
textilizable file.version
|
||||
else
|
||||
raise l(:notice_not_authorized)
|
||||
end
|
||||
end
|
||||
|
||||
# dmsft - link to the document's content preview
|
||||
desc "Text referring to DMSF text document content:\n\n" +
|
||||
"{{dmsft(file_id, lines_count)}}\n\n" +
|
||||
"_file_id_ can be found in the document's details. _lines_count_ indicates quantity of lines to show."
|
||||
macro :dmsft do |obj, args|
|
||||
raise ArgumentError if args.length < 2 # Requires file id and lines number
|
||||
file = DmsfFile.visible.find args[0]
|
||||
if User.current&.allowed_to?(:view_dmsf_files, file.project)
|
||||
file.preview(args[1]).gsub("\n", '<br/>').html_safe
|
||||
else
|
||||
raise l(:notice_not_authorized)
|
||||
end
|
||||
end
|
||||
# dmsflastupdate - text referring to the document's last update date
|
||||
desc "Text referring to DMSF document last update date:\n\n" +
|
||||
"{{dmsflastupdate(document_id)}}\n\n" +
|
||||
"_document_id_ can be found in the document's details."
|
||||
macro :dmsflastupdate do |obj, args|
|
||||
raise ArgumentError if args.length < 1 # Requires file id
|
||||
file = DmsfFile.visible.find args[0]
|
||||
if User.current&.allowed_to?(:view_dmsf_files, file.project)
|
||||
textilizable format_time(file.last_revision.updated_at)
|
||||
else
|
||||
raise l(:notice_not_authorized)
|
||||
end
|
||||
end
|
||||
|
||||
# dmsf_image - link to an image
|
||||
desc "Wiki DMSF image:\n\n" +
|
||||
"{{dmsf_image(file_id)}}\n" +
|
||||
"{{dmsf_image(file_id, size=50%)}} -- with size 50%\n" +
|
||||
"{{dmsf_image(file_id, size=300)}} -- with size 300\n" +
|
||||
"{{dmsf_image(file_id, height=300)}} -- with height (auto width)\n" +
|
||||
"{{dmsf_image(file_id, width=300)}} -- with width (auto height)\n" +
|
||||
"{{dmsf_image(file_id, size=640x480)}} -- with size 640x480"
|
||||
macro :dmsf_image do |obj, args|
|
||||
raise ArgumentError if args.length < 1 # Requires file id
|
||||
args, options = extract_macro_options(args, :size, :width, :height, :title)
|
||||
size = options[:size]
|
||||
width = options[:width]
|
||||
height = options[:height]
|
||||
file = DmsfFile.visible.find args[0]
|
||||
unless User.current&.allowed_to?(:view_dmsf_files, file.project)
|
||||
raise l(:notice_not_authorized)
|
||||
end
|
||||
raise 'Not supported image format' unless file.image?
|
||||
url = view_dmsf_file_path(file)
|
||||
if size&.include?('%')
|
||||
image_tag url, alt: file.title, width: size, height: size
|
||||
elsif height
|
||||
image_tag url, alt: file.title, width: 'auto', height: height
|
||||
elsif width
|
||||
image_tag url, alt: file.title, width: width, height: 'auto'
|
||||
else
|
||||
image_tag url, alt: file.title, size: size
|
||||
end
|
||||
end
|
||||
# dmsft - link to the document's content preview
|
||||
desc "Text referring to DMSF text document content:\n\n" +
|
||||
"{{dmsft(file_id, lines_count)}}\n\n" +
|
||||
"_file_id_ can be found in the document's details. _lines_count_ indicates quantity of lines to show."
|
||||
macro :dmsft do |obj, args|
|
||||
raise ArgumentError if args.length < 2 # Requires file id and lines number
|
||||
file = DmsfFile.visible.find args[0]
|
||||
if User.current&.allowed_to?(:view_dmsf_files, file.project)
|
||||
file.preview(args[1]).gsub("\n", '<br/>').html_safe
|
||||
else
|
||||
raise l(:notice_not_authorized)
|
||||
end
|
||||
end
|
||||
|
||||
# dmsf_video - link to a video
|
||||
desc "Wiki DMSF video:\n\n" +
|
||||
"{{dmsf_video(file_id)}}\n" +
|
||||
"{{dmsf_video(file_id, size=50%)}} -- with size 50%\n" +
|
||||
"{{dmsf_video(file_id, size=300)}} -- with size 300x300\n" +
|
||||
"{{dmsf_video(file_id, height=300)}} -- with height (auto width)\n" +
|
||||
"{{dmsf_video(file_id, width=300)}} -- with width (auto height)\n" +
|
||||
"{{dmsf_video(file_id, size=640x480)}} -- with size 640x480"
|
||||
macro :dmsf_video do |obj, args|
|
||||
raise ArgumentError if args.length < 1 # Requires file id
|
||||
args, options = extract_macro_options(args, :size, :width, :height, :title)
|
||||
size = options[:size]
|
||||
width = options[:width]
|
||||
height = options[:height]
|
||||
file = DmsfFile.visible.find args[0]
|
||||
unless User.current&.allowed_to?(:view_dmsf_files, file.project)
|
||||
raise l(:notice_not_authorized)
|
||||
end
|
||||
raise 'Not supported video format' unless file.video?
|
||||
url = view_dmsf_file_path(file)
|
||||
if size&.include?('%')
|
||||
video_tag url, controls: true, alt: file.title, width: size, height: size
|
||||
elsif height
|
||||
video_tag url, controls: true, alt: file.title, width: 'auto', height: height
|
||||
elsif width
|
||||
video_tag url, controls: true, alt: file.title, width: width, height: 'auto'
|
||||
else
|
||||
video_tag url, controls: true, alt: file.title, size: size
|
||||
end
|
||||
end
|
||||
# dmsf_image - link to an image
|
||||
desc "Wiki DMSF image:\n\n" +
|
||||
"{{dmsf_image(file_id)}}\n" +
|
||||
"{{dmsf_image(file_id, size=50%)}} -- with size 50%\n" +
|
||||
"{{dmsf_image(file_id, size=300)}} -- with size 300\n" +
|
||||
"{{dmsf_image(file_id, height=300)}} -- with height (auto width)\n" +
|
||||
"{{dmsf_image(file_id, width=300)}} -- with width (auto height)\n" +
|
||||
"{{dmsf_image(file_id, size=640x480)}} -- with size 640x480"
|
||||
macro :dmsf_image do |obj, args|
|
||||
raise ArgumentError if args.length < 1 # Requires file id
|
||||
args, options = extract_macro_options(args, :size, :width, :height, :title)
|
||||
size = options[:size]
|
||||
width = options[:width]
|
||||
height = options[:height]
|
||||
file = DmsfFile.visible.find args[0]
|
||||
unless User.current&.allowed_to?(:view_dmsf_files, file.project)
|
||||
raise l(:notice_not_authorized)
|
||||
end
|
||||
raise 'Not supported image format' unless file.image?
|
||||
url = view_dmsf_file_path(file)
|
||||
if size&.include?('%')
|
||||
image_tag url, alt: file.title, width: size, height: size
|
||||
elsif height
|
||||
image_tag url, alt: file.title, width: 'auto', height: height
|
||||
elsif width
|
||||
image_tag url, alt: file.title, width: width, height: 'auto'
|
||||
else
|
||||
image_tag url, alt: file.title, size: size
|
||||
end
|
||||
end
|
||||
|
||||
# dmsftn - link to an image thumbnail
|
||||
desc "Wiki DMSF thumbnail:\n\n" +
|
||||
"{{dmsftn(file_id)}} -- with default height 200 (auto width)\n" +
|
||||
"{{dmsftn(file_id, size=300)}} -- with size 300x300\n" +
|
||||
"{{dmsftn(file_id, height=300)}} -- with height (auto width)\n" +
|
||||
"{{dmsftn(file_id, width=300)}} -- with width (auto height)\n" +
|
||||
"{{dmsftn(file_id, size=640x480)}} -- with size 640x480"
|
||||
macro :dmsftn do |obj, args|
|
||||
raise ArgumentError if args.length < 1 # Requires file id
|
||||
args, options = extract_macro_options(args, :size, :width, :height, :title)
|
||||
size = options[:size]
|
||||
width = options[:width]
|
||||
height = options[:height]
|
||||
file = DmsfFile.visible.find args[0]
|
||||
unless User.current&.allowed_to?(:view_dmsf_files, file.project)
|
||||
raise l(:notice_not_authorized)
|
||||
end
|
||||
raise 'Not supported image format' unless file.image?
|
||||
url = view_dmsf_file_path(file)
|
||||
if size
|
||||
img = image_tag(url, alt: file.title, size: size)
|
||||
elsif height
|
||||
img = image_tag(url, alt: file.title, width: 'auto', height: height)
|
||||
elsif width
|
||||
img = image_tag(url, alt: file.title, width: width, height: 'auto')
|
||||
else
|
||||
img = image_tag(url, alt: file.title, width: 'auto', height: 200)
|
||||
end
|
||||
link_to img, url, target: '_blank', title: h(file.last_revision.try(:tooltip)),
|
||||
'data-downloadurl' => "#{file.last_revision.detect_content_type}:#{h(file.name)}:#{url}"
|
||||
end
|
||||
# dmsf_video - link to a video
|
||||
desc "Wiki DMSF video:\n\n" +
|
||||
"{{dmsf_video(file_id)}}\n" +
|
||||
"{{dmsf_video(file_id, size=50%)}} -- with size 50%\n" +
|
||||
"{{dmsf_video(file_id, size=300)}} -- with size 300x300\n" +
|
||||
"{{dmsf_video(file_id, height=300)}} -- with height (auto width)\n" +
|
||||
"{{dmsf_video(file_id, width=300)}} -- with width (auto height)\n" +
|
||||
"{{dmsf_video(file_id, size=640x480)}} -- with size 640x480"
|
||||
macro :dmsf_video do |obj, args|
|
||||
raise ArgumentError if args.length < 1 # Requires file id
|
||||
args, options = extract_macro_options(args, :size, :width, :height, :title)
|
||||
size = options[:size]
|
||||
width = options[:width]
|
||||
height = options[:height]
|
||||
file = DmsfFile.visible.find args[0]
|
||||
unless User.current&.allowed_to?(:view_dmsf_files, file.project)
|
||||
raise l(:notice_not_authorized)
|
||||
end
|
||||
raise 'Not supported video format' unless file.video?
|
||||
url = view_dmsf_file_path(file)
|
||||
if size&.include?('%')
|
||||
video_tag url, controls: true, alt: file.title, width: size, height: size
|
||||
elsif height
|
||||
video_tag url, controls: true, alt: file.title, width: 'auto', height: height
|
||||
elsif width
|
||||
video_tag url, controls: true, alt: file.title, width: width, height: 'auto'
|
||||
else
|
||||
video_tag url, controls: true, alt: file.title, size: size
|
||||
end
|
||||
end
|
||||
|
||||
# dmsfw - link to a document's approval workflow status
|
||||
desc "Text referring to DMSF document's approval workflow status:\n\n" +
|
||||
"{{dmsfw(file_id)}}\n\n" +
|
||||
"_file_id_ can be found in the document's details."
|
||||
macro :dmsfw do |obj, args|
|
||||
raise ArgumentError if args.length < 1 # Requires file id
|
||||
file = DmsfFile.visible.find args[0]
|
||||
if User.current&.allowed_to?(:view_dmsf_files, file.project)
|
||||
raise ActiveRecord::RecordNotFound unless file.last_revision
|
||||
file.last_revision.workflow_str(false)
|
||||
else
|
||||
raise l(:notice_not_authorized)
|
||||
end
|
||||
end
|
||||
# dmsftn - link to an image thumbnail
|
||||
desc "Wiki DMSF thumbnail:\n\n" +
|
||||
"{{dmsftn(file_id)}} -- with default height 200 (auto width)\n" +
|
||||
"{{dmsftn(file_id, size=300)}} -- with size 300x300\n" +
|
||||
"{{dmsftn(file_id, height=300)}} -- with height (auto width)\n" +
|
||||
"{{dmsftn(file_id, width=300)}} -- with width (auto height)\n" +
|
||||
"{{dmsftn(file_id, size=640x480)}} -- with size 640x480"
|
||||
macro :dmsftn do |obj, args|
|
||||
raise ArgumentError if args.length < 1 # Requires file id
|
||||
args, options = extract_macro_options(args, :size, :width, :height, :title)
|
||||
size = options[:size]
|
||||
width = options[:width]
|
||||
height = options[:height]
|
||||
file = DmsfFile.visible.find args[0]
|
||||
unless User.current&.allowed_to?(:view_dmsf_files, file.project)
|
||||
raise l(:notice_not_authorized)
|
||||
end
|
||||
raise 'Not supported image format' unless file.image?
|
||||
url = view_dmsf_file_path(file)
|
||||
if size
|
||||
img = image_tag(url, alt: file.title, size: size)
|
||||
elsif height
|
||||
img = image_tag(url, alt: file.title, width: 'auto', height: height)
|
||||
elsif width
|
||||
img = image_tag(url, alt: file.title, width: width, height: 'auto')
|
||||
else
|
||||
img = image_tag(url, alt: file.title, width: 'auto', height: 200)
|
||||
end
|
||||
link_to img, url, target: '_blank', title: h(file.last_revision.try(:tooltip)),
|
||||
'data-downloadurl' => "#{file.last_revision.detect_content_type}:#{h(file.name)}:#{url}"
|
||||
end
|
||||
|
||||
# dmsfw - link to a document's approval workflow status
|
||||
desc "Text referring to DMSF document's approval workflow status:\n\n" +
|
||||
"{{dmsfw(file_id)}}\n\n" +
|
||||
"_file_id_ can be found in the document's details."
|
||||
macro :dmsfw do |obj, args|
|
||||
raise ArgumentError if args.length < 1 # Requires file id
|
||||
file = DmsfFile.visible.find args[0]
|
||||
if User.current&.allowed_to?(:view_dmsf_files, file.project)
|
||||
raise ActiveRecord::RecordNotFound unless file.last_revision
|
||||
file.last_revision.workflow_str(false)
|
||||
else
|
||||
raise l(:notice_not_authorized)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@ -35,5 +35,10 @@ module RedmineDmsf
|
||||
end
|
||||
end
|
||||
|
||||
RedmineExtensions::PatchManager.register_patch_to_be_first 'Redmine::Acts::Attachable::InstanceMethods',
|
||||
'RedmineDmsf::Patches::AttachablePatch', prepend: true, first: true
|
||||
# Apply the patch
|
||||
if Redmine::Plugin.installed?(:easy_extensions)
|
||||
RedmineExtensions::PatchManager.register_patch_to_be_first 'Redmine::Acts::Attachable::InstanceMethods',
|
||||
'RedmineDmsf::Patches::AttachablePatch', prepend: true, first: true
|
||||
else
|
||||
Redmine::Acts::Attachable.prepend RedmineDmsf::Patches::AttachablePatch
|
||||
end
|
||||
@ -170,5 +170,8 @@ module RedmineDmsf
|
||||
end
|
||||
end
|
||||
|
||||
RedmineExtensions::PatchManager.register_model_patch 'EasyCrmCase',
|
||||
'RedmineDmsf::Patches::EasyCrmCasePatch', if: proc { Redmine::Plugin.installed?(:easy_crm) }
|
||||
# Apply the patch
|
||||
if Redmine::Plugin.installed?(:easy_extensions)
|
||||
RedmineExtensions::PatchManager.register_model_patch 'EasyCrmCase',
|
||||
'RedmineDmsf::Patches::EasyCrmCasePatch', if: proc { Redmine::Plugin.installed?(:easy_crm) }
|
||||
end
|
||||
@ -75,7 +75,7 @@ module RedmineDmsf
|
||||
wf.notify_users(easy_crm_case.project, revision, self)
|
||||
begin
|
||||
file.lock!
|
||||
rescue DmsfLockError => e
|
||||
rescue RedmineDmsf::Errors::DmsfLockError => e
|
||||
Rails.logger.warn e.message
|
||||
end
|
||||
else
|
||||
@ -118,6 +118,9 @@ module RedmineDmsf
|
||||
end
|
||||
end
|
||||
|
||||
RedmineExtensions::PatchManager.register_controller_patch 'EasyCrmCasesController',
|
||||
'RedmineDmsf::Patches::EasyCrmCasesControllerPatch', prepend: true,
|
||||
if: proc { Redmine::Plugin.installed?(:easy_crm) }
|
||||
# Apply the patch
|
||||
if Redmine::Plugin.installed?(:easy_extensions)
|
||||
RedmineExtensions::PatchManager.register_controller_patch 'EasyCrmCasesController',
|
||||
'RedmineDmsf::Patches::EasyCrmCasesControllerPatch', prepend: true,
|
||||
if: proc { Redmine::Plugin.installed?(:easy_crm) }
|
||||
end
|
||||
|
||||
@ -174,5 +174,9 @@ module RedmineDmsf
|
||||
end
|
||||
|
||||
# Apply patch
|
||||
RedmineExtensions::PatchManager.register_model_patch 'Issue',
|
||||
'RedmineDmsf::Patches::IssuePatch'
|
||||
if Redmine::Plugin.installed?(:easy_extensions)
|
||||
RedmineExtensions::PatchManager.register_model_patch 'Issue',
|
||||
'RedmineDmsf::Patches::IssuePatch'
|
||||
else
|
||||
Issue.prepend RedmineDmsf::Patches::IssuePatch
|
||||
end
|
||||
@ -49,5 +49,10 @@ module RedmineDmsf
|
||||
end
|
||||
end
|
||||
|
||||
RedmineExtensions::PatchManager.register_patch_to_be_first 'Redmine::Notifiable',
|
||||
'RedmineDmsf::Patches::NotifiablePatch', prepend: true, first: true
|
||||
# Apply the patch
|
||||
if Redmine::Plugin.installed?(:easy_extensions)
|
||||
RedmineExtensions::PatchManager.register_patch_to_be_first 'Redmine::Notifiable',
|
||||
'RedmineDmsf::Patches::NotifiablePatch', prepend: true, first: true
|
||||
else
|
||||
Redmine::Notifiable.prepend RedmineDmsf::Patches::NotifiablePatch
|
||||
end
|
||||
@ -138,5 +138,10 @@ module RedmineDmsf
|
||||
end
|
||||
end
|
||||
|
||||
RedmineExtensions::PatchManager.register_model_patch 'Project',
|
||||
'RedmineDmsf::Patches::ProjectPatch', prepend: true
|
||||
# Apply the patch
|
||||
if Redmine::Plugin.installed?(:easy_extensions)
|
||||
RedmineExtensions::PatchManager.register_model_patch 'Project',
|
||||
'RedmineDmsf::Patches::ProjectPatch', prepend: true
|
||||
else
|
||||
Project.prepend RedmineDmsf::Patches::ProjectPatch
|
||||
end
|
||||
@ -23,7 +23,7 @@
|
||||
|
||||
module RedmineDmsf
|
||||
module Patches
|
||||
module ProjectHelperPatch
|
||||
module ProjectsHelperPatch
|
||||
|
||||
##################################################################################################################
|
||||
# Overridden methods
|
||||
@ -44,6 +44,7 @@ module RedmineDmsf
|
||||
end
|
||||
end
|
||||
|
||||
# Apply the patch
|
||||
unless Redmine::Plugin.installed?(:easy_extensions)
|
||||
ProjectsController.send :helper, RedmineDmsf::Patches::ProjectHelperPatch
|
||||
ProjectsController.send :helper, RedmineDmsf::Patches::ProjectsHelperPatch
|
||||
end
|
||||
@ -40,5 +40,10 @@ module RedmineDmsf
|
||||
end
|
||||
end
|
||||
|
||||
RedmineExtensions::PatchManager.register_controller_patch 'QueriesController',
|
||||
'RedmineDmsf::Patches::QueriesControllerPatch', prepend: true
|
||||
# Apply the patch
|
||||
if Redmine::Plugin.installed?(:easy_extensions)
|
||||
RedmineExtensions::PatchManager.register_controller_patch 'QueriesController',
|
||||
'RedmineDmsf::Patches::QueriesControllerPatch', prepend: true
|
||||
else
|
||||
QueriesController.prepend RedmineDmsf::Patches::QueriesControllerPatch
|
||||
end
|
||||
@ -42,6 +42,10 @@ module RedmineDmsf
|
||||
end
|
||||
end
|
||||
|
||||
# Apply patch
|
||||
RedmineExtensions::PatchManager.register_model_patch 'Role',
|
||||
'RedmineDmsf::Patches::RolePatch', prepend: true
|
||||
# Apply the patch
|
||||
if Redmine::Plugin.installed?(:easy_extensions)
|
||||
RedmineExtensions::PatchManager.register_model_patch 'Role',
|
||||
'RedmineDmsf::Patches::RolePatch', prepend: true
|
||||
else
|
||||
Role.prepend RedmineDmsf::Patches::RolePatch
|
||||
end
|
||||
@ -26,12 +26,14 @@ module RedmineDmsf
|
||||
##################################################################################################################
|
||||
# New methods
|
||||
|
||||
def self.included(base)
|
||||
def self.prepended(base)
|
||||
base.class_eval do
|
||||
before_destroy :remove_dmsf_references, prepend: true
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def remove_dmsf_references
|
||||
return if self.id.nil?
|
||||
substitute = User.anonymous
|
||||
@ -59,6 +61,9 @@ module RedmineDmsf
|
||||
end
|
||||
end
|
||||
|
||||
# Apply patch
|
||||
RedmineExtensions::PatchManager.register_model_patch 'User',
|
||||
'RedmineDmsf::Patches::UserPatch'
|
||||
# Apply the patch
|
||||
if Redmine::Plugin.installed?(:easy_extensions)
|
||||
RedmineExtensions::PatchManager.register_model_patch 'UserPatch', 'RedmineDmsf::Patches::UserPatch'
|
||||
else
|
||||
User.prepend RedmineDmsf::Patches::UserPatch
|
||||
end
|
||||
@ -21,7 +21,7 @@
|
||||
|
||||
module RedmineDmsf
|
||||
module Patches
|
||||
module UserPreference
|
||||
module UserPreferencePatch
|
||||
|
||||
##################################################################################################################
|
||||
# New methods
|
||||
@ -44,6 +44,10 @@ module RedmineDmsf
|
||||
end
|
||||
end
|
||||
|
||||
# Apply patch
|
||||
RedmineExtensions::PatchManager.register_model_patch 'UserPreference',
|
||||
'RedmineDmsf::Patches::UserPreference'
|
||||
# Apply the patch
|
||||
if Redmine::Plugin.installed?(:easy_extensions)
|
||||
RedmineExtensions::PatchManager.register_model_patch 'UserPreference',
|
||||
'RedmineDmsf::Patches::UserPreferencePatch'
|
||||
else
|
||||
UserPreference.prepend RedmineDmsf::Patches::UserPreferencePatch
|
||||
end
|
||||
@ -20,13 +20,13 @@
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
require 'dav4rack'
|
||||
require File.dirname(__FILE__) + '/../../dav4rack'
|
||||
require 'addressable/uri'
|
||||
|
||||
module RedmineDmsf
|
||||
module Webdav
|
||||
|
||||
class BaseResource < DAV4Rack::Resource
|
||||
class BaseResource < Dav4rack::Resource
|
||||
include Redmine::I18n
|
||||
include ActionView::Helpers::NumberHelper
|
||||
|
||||
@ -245,7 +245,10 @@ module RedmineDmsf
|
||||
else
|
||||
@file = DmsfFile.find_file_by_name(@project, @folder, pinfo.first)
|
||||
@folder = nil
|
||||
raise Conflict unless (pinfo.length < 2 || @file)
|
||||
unless (pinfo.length < 2 || @file)
|
||||
Rails.logger.error "Resource not found: #{@path}"
|
||||
raise Conflict
|
||||
end
|
||||
break # We're at the end
|
||||
end
|
||||
end
|
||||
|
||||
@ -19,7 +19,7 @@
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
require 'dav4rack'
|
||||
require File.dirname(__FILE__) + '/../../dav4rack'
|
||||
|
||||
module RedmineDmsf
|
||||
module Webdav
|
||||
@ -31,7 +31,7 @@ module RedmineDmsf
|
||||
path = '/dmsf/webdav'
|
||||
@dav_app = Rack::Builder.new{
|
||||
map path do
|
||||
run DAV4Rack::Handler.new(
|
||||
run Dav4rack::Handler.new(
|
||||
root_uri_path: path,
|
||||
resource_class: RedmineDmsf::Webdav::ResourceProxy,
|
||||
log_to: Rails.logger,
|
||||
|
||||
@ -421,7 +421,7 @@ module RedmineDmsf
|
||||
# Lock
|
||||
def lock(args)
|
||||
unless parent&.exist?
|
||||
e = DAV4Rack::LockFailure.new
|
||||
e = Dav4rack::LockFailure.new
|
||||
e.add_failure @path, Conflict
|
||||
raise e
|
||||
end
|
||||
@ -431,7 +431,7 @@ module RedmineDmsf
|
||||
lock_check args
|
||||
entity = file || folder
|
||||
unless entity
|
||||
e = DAV4Rack::LockFailure.new
|
||||
e = Dav4rack::LockFailure.new
|
||||
e.add_failure @path, MethodNotAllowed
|
||||
raise e
|
||||
end
|
||||
@ -444,7 +444,7 @@ module RedmineDmsf
|
||||
if refresh
|
||||
http_if = request.get_header('HTTP_IF')
|
||||
if http_if.blank?
|
||||
e = DAV4Rack::LockFailure.new
|
||||
e = Dav4rack::LockFailure.new
|
||||
e.add_failure @path, Conflict
|
||||
raise e
|
||||
end
|
||||
@ -453,7 +453,7 @@ module RedmineDmsf
|
||||
l = DmsfLock.find_by(uuid: $1)
|
||||
end
|
||||
unless l
|
||||
e = DAV4Rack::LockFailure.new
|
||||
e = Dav4rack::LockFailure.new
|
||||
e.add_failure @path, Conflict
|
||||
raise e
|
||||
end
|
||||
@ -468,8 +468,8 @@ module RedmineDmsf
|
||||
l = entity.lock!(scope, type, Time.current + 1.weeks, args[:owner])
|
||||
@response['Lock-Token'] = l.uuid
|
||||
[1.week.to_i, l.uuid]
|
||||
rescue DmsfLockError => exception
|
||||
e = DAV4Rack::LockFailure.new(exception.message)
|
||||
rescue RedmineDmsf::Errors::DmsfLockError => exception
|
||||
e = Dav4rack::LockFailure.new(exception.message)
|
||||
e.add_failure @path, Conflict
|
||||
raise e
|
||||
end
|
||||
@ -680,7 +680,7 @@ module RedmineDmsf
|
||||
# Prepare file for download using Rack functionality:
|
||||
# Download (see RedmineDmsf::Webdav::Download) extends Rack::File to allow single-file
|
||||
# implementation of service for request, which allows for us to pipe a single file through
|
||||
# also best-utilising DAV4Rack's implementation.
|
||||
# also best-utilising Dav4rack's implementation.
|
||||
def download
|
||||
raise NotFound unless file&.last_revision
|
||||
disk_file = file.last_revision.disk_file
|
||||
|
||||
@ -110,7 +110,7 @@ module RedmineDmsf
|
||||
end
|
||||
|
||||
def lock(args)
|
||||
e = DAV4Rack::LockFailure.new
|
||||
e = Dav4rack::LockFailure.new
|
||||
e.add_failure @path, MethodNotAllowed
|
||||
raise e
|
||||
end
|
||||
|
||||
@ -20,7 +20,7 @@
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
require 'dav4rack'
|
||||
require File.dirname(__FILE__) + '/../../dav4rack'
|
||||
|
||||
module RedmineDmsf
|
||||
module Webdav
|
||||
@ -30,7 +30,7 @@ module RedmineDmsf
|
||||
# This is more of a factory approach of an object, class determines which class to
|
||||
# instantiate based on pathing information, it then populates @resource_c with this
|
||||
# object, and proxies calls made against class to it.
|
||||
class ResourceProxy < DAV4Rack::Resource
|
||||
class ResourceProxy < Dav4rack::Resource
|
||||
|
||||
attr_reader :read_only
|
||||
|
||||
|
||||
@ -35,8 +35,11 @@ namespace :redmine do
|
||||
prj = Project.new
|
||||
prj.identifier = 'dmsf_test_project'
|
||||
prj.name = 'DMFS Test Project'
|
||||
prj.description = 'A temporary project for Litmus tests'
|
||||
prj.enable_module! :dmsf
|
||||
prj.save
|
||||
unless prj.save
|
||||
Rails.logger.error prj.errors.full_messages.to_sentence
|
||||
end
|
||||
# Settings
|
||||
Setting.rest_api_enabled = true
|
||||
# Plugin's settings
|
||||
|
||||
@ -84,18 +84,18 @@ bundle exec rake redmine:plugins:test:units NAME=redmine_dmsf RAILS_ENV=test
|
||||
bundle exec rake redmine:plugins:test:functionals NAME=redmine_dmsf RAILS_ENV=test
|
||||
bundle exec rake redmine:plugins:test:integration NAME=redmine_dmsf RAILS_ENV=test
|
||||
# Macros
|
||||
ruby plugin/redmine_dmsf/test/unit/lib/redmine_dmsf/dmsf_macros_test.rb RAILS_ENV=test
|
||||
ruby plugins/redmine_dmsf/test/unit/lib/redmine_dmsf/dmsf_macros_test.rb RAILS_ENV=test
|
||||
# Helpers
|
||||
ruby plugin/redmine_dmsf/test/helpers/dmsf_helper_test.rb RAILS_ENV=test
|
||||
ruby plugin/redmine_dmsf/test/helpers/dmsf_queries_helper_test.rb RAILS_ENV=test
|
||||
ruby plugins/redmine_dmsf/test/helpers/dmsf_helper_test.rb RAILS_ENV=test
|
||||
ruby plugins/redmine_dmsf/test/helpers/dmsf_queries_helper_test.rb RAILS_ENV=test
|
||||
|
||||
# Litmus
|
||||
|
||||
# Prepare Redmine's environment for WebDAV testing
|
||||
RAILS_ENV=test bundle exec rake redmine:dmsf_webdav_test_on
|
||||
|
||||
# Run Webrick server
|
||||
bundle exec rails server webrick -e test -d
|
||||
# Run an integrated Rails' server
|
||||
bundle exec rails server -e test -d
|
||||
|
||||
# Run Litmus tests (Omit 'http' tests due to 'timeout waiting for interim response' and locks due to complex bogus conditional)
|
||||
TESTS="basic copymove props" litmus http://localhost:3000/dmsf/webdav/dmsf_test_project admin admin
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
# encoding: utf-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2019 Jean-Philippe Lang
|
||||
#
|
||||
# Redmine plugin for Document Management System "Features"
|
||||
#
|
||||
# Copyright © 2011-22 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,13 +26,18 @@ class DmsfHelperTest < RedmineDmsf::Test::HelperTest
|
||||
|
||||
fixtures :dmsf_folders
|
||||
|
||||
def setup
|
||||
super
|
||||
@folder1 = DmsfFolder.find 1
|
||||
end
|
||||
|
||||
def test_webdav_url
|
||||
base_url = ["#{Setting.protocol}:/", Setting.host_name, 'dmsf', 'webdav'].join('/')
|
||||
assert_equal "#{base_url}/", webdav_url(nil, nil)
|
||||
assert_equal "#{base_url}/ecookbook/", webdav_url(@project1, nil)
|
||||
assert_equal "#{base_url}/ecookbook/folder1/", webdav_url(@project1, @folder1)
|
||||
assert_equal "#{base_url}/%5Becookbook%5D/", webdav_url(@project1, nil)
|
||||
assert_equal "#{base_url}/%5Becookbook%5D/folder1/", webdav_url(@project1, @folder1)
|
||||
with_settings plugin_redmine_dmsf: {'dmsf_webdav_use_project_names' => '1'} do
|
||||
assert_equal "#{base_url}/eCookbook 1/", webdav_url(@project1, nil)
|
||||
assert_equal "#{base_url}/%5BeCookbook%201%5D/", webdav_url(@project1, nil)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
# encoding: utf-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2019 Jean-Philippe Lang
|
||||
#
|
||||
# Redmine plugin for Document Management System "Features"
|
||||
#
|
||||
# Copyright © 2011-22 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
|
||||
|
||||
@ -69,13 +69,13 @@ class DmsfLockTest < RedmineDmsf::Test::UnitTest
|
||||
@folder7.lock!
|
||||
User.current = nil
|
||||
assert_no_difference('@folder7.lock.count') do
|
||||
assert_raise DmsfLockError do
|
||||
assert_raise RedmineDmsf::Errors::RedmineDmsf::Errors::DmsfLockError do
|
||||
@folder7.unlock!
|
||||
end
|
||||
end
|
||||
User.current = @jsmith
|
||||
assert_no_difference('@folder7.lock.count') do
|
||||
assert_raise DmsfLockError do
|
||||
assert_raise RedmineDmsf::Errors::RedmineDmsf::Errors::DmsfLockError do
|
||||
@folder7.unlock!
|
||||
end
|
||||
end
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user