This commit is contained in:
Karel.Picman 2022-03-15 14:33:04 +01:00
parent e941eff160
commit a66cec3e3f
84 changed files with 1516 additions and 1351 deletions

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -23,6 +23,7 @@ class DmsfContextMenusController < ApplicationController
helper :context_menus
helper :watchers
helper :dmsf
before_action :find_folder
before_action :find_dmsf_file

View 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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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'])

View File

@ -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

View File

@ -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}"))

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -1,4 +1,4 @@
Current DAV4Rack license:
Current Dav4rack license:
Copyright (c) 2010 Chris Roberts <chrisroberts.code@gmail.com>

View File

@ -1,6 +1,6 @@
# DAV4Rack - Web Authoring for Rack[![Build Status](https://travis-ci.org/planio-gmbh/dav4rack.svg?branch=master)](https://travis-ci.org/planio-gmbh/dav4rack)
# Dav4rack - Web Authoring for Rack[![Build Status](https://travis-ci.org/planio-gmbh/dav4rack.svg?branch=master)](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)

View File

@ -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

View File

@ -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

View File

@ -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?

View File

@ -1,6 +1,6 @@
require 'pstore'
module DAV4Rack
module Dav4rack
class FileResourceLock
attr_accessor :path
attr_accessor :token

View File

@ -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

View File

@ -1,8 +1,8 @@
# frozen_string_literal: true
module DAV4Rack
module Dav4rack
module HTTPStatus
module HttpStatus
class Status < Exception

View File

@ -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

View File

@ -1,6 +1,6 @@
require 'digest/sha1'
module DAV4Rack
module Dav4rack
class InterceptorResource < Resource
attr_reader :path, :options

View File

@ -1,4 +1,4 @@
module DAV4Rack
module Dav4rack
class Lock
def initialize(args={})

View File

@ -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

View File

@ -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

View File

@ -3,7 +3,7 @@ require 'uri'
require 'digest/sha1'
require 'rack/file'
module DAV4Rack
module Dav4rack
#FIXME unused?
class RemoteFile < Rack::File

View 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

View File

@ -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

View File

@ -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

View File

@ -1,6 +1,6 @@
require 'digest'
module DAV4Rack
module Dav4rack
# Implements secure string comparison methods.
# Taken straight from ActiveSupport

View File

@ -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

View File

@ -2,7 +2,7 @@
require 'ostruct'
module DAV4Rack
module Dav4rack
# Simple wrapper for formatted elements
class DAVElement < OpenStruct

View File

@ -1,4 +1,4 @@
module DAV4Rack
module Dav4rack
class Version
attr_reader :major, :minor, :tiny

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
module DAV4Rack
module Dav4rack
module XmlElements
DAV_NAMESPACE = 'DAV:'

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
module DAV4Rack
module Dav4rack
class XmlResponse
include XmlElements

View File

@ -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'

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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

View 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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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