From c5d279e266b0985c980d9dd21c01ecfd4e55baa6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Pi=C4=8Dman?= Date: Wed, 8 Apr 2015 10:04:46 +0200 Subject: [PATCH 01/52] Control DMSF via REST API #78 --- app/controllers/dmsf_controller.rb | 23 +++--- app/controllers/dmsf_upload_controller.rb | 81 +++++++++++++++++----- app/models/dmsf_upload.rb | 8 +-- app/views/dmsf/show.api.rsb | 30 ++++++++ app/views/dmsf_files/show.api.rsb | 6 +- app/views/dmsf_upload/commit.api.rsb | 3 + app/views/dmsf_upload/upload.api.rsb | 3 + config/routes.rb | 2 + extra/{ => api}/api_client.rb | 19 +++-- extra/api/api_client.sh | 42 +++++++++++ extra/api/cat.gif | Bin 0 -> 9982 bytes extra/api/file.xml | 12 ++++ extra/api/folder.xml | 6 ++ init.rb | 4 +- lib/redmine_dmsf/webdav/base_resource.rb | 2 +- lib/tasks/dmsf_convert_documents.rake | 13 +--- 16 files changed, 198 insertions(+), 56 deletions(-) create mode 100644 app/views/dmsf/show.api.rsb create mode 100644 app/views/dmsf_upload/commit.api.rsb create mode 100644 app/views/dmsf_upload/upload.api.rsb rename extra/{ => api}/api_client.rb (79%) create mode 100644 extra/api/api_client.sh create mode 100644 extra/api/cat.gif create mode 100644 extra/api/file.xml create mode 100644 extra/api/folder.xml diff --git a/app/controllers/dmsf_controller.rb b/app/controllers/dmsf_controller.rb index 65e355fe..59a45a57 100644 --- a/app/controllers/dmsf_controller.rb +++ b/app/controllers/dmsf_controller.rb @@ -31,6 +31,8 @@ class DmsfController < ApplicationController before_filter :authorize before_filter :find_folder, :except => [:new, :create, :edit_root, :save_root] before_filter :find_parent, :only => [:new, :create] + + accept_api_auth :show, :create helper :all @@ -134,7 +136,14 @@ class DmsfController < ApplicationController @trash_enabled = DmsfFolder.deleted.where(:project_id => @project.id).any? || DmsfFile.deleted.where(:project_id => @project.id).any? || DmsfLink.deleted.where(:project_id => @project.id).any? - end + + respond_to do |format| + format.html { + render :layout => !request.xhr? + } + format.api + end + end def trash @folder_manipulation_allowed = User.current.allowed_to? :folder_manipulation, @project @@ -249,13 +258,11 @@ class DmsfController < ApplicationController render :action => 'edit' end - def create - if (Rails::VERSION::MAJOR > 3) - @folder = DmsfFolder.new( - params.require(:dmsf_folder).permit(:title, :description, :dmsf_folder_id)) - else - @folder = DmsfFolder.new(params[:dmsf_folder]) - end + def create + @folder = DmsfFolder.new + @folder.title = params[:dmsf_folder][:title] + @folder.description = params[:dmsf_folder][:description] + @folder.dmsf_folder_id = params[:dmsf_folder][:dmsf_folder_id] @folder.project = @project @folder.user = User.current diff --git a/app/controllers/dmsf_upload_controller.rb b/app/controllers/dmsf_upload_controller.rb index 0c531d1e..1371bbf7 100644 --- a/app/controllers/dmsf_upload_controller.rb +++ b/app/controllers/dmsf_upload_controller.rb @@ -26,10 +26,12 @@ class DmsfUploadController < ApplicationController before_filter :find_project before_filter :authorize - before_filter :find_folder, :except => [:upload_file] + before_filter :find_folder, :except => [:upload_file, :upload] helper :all helper :dmsf_workflows + + accept_api_auth :upload, :commit def upload_files uploaded_files = params[:attachments] @@ -62,11 +64,7 @@ class DmsfUploadController < ApplicationController return end @disk_filename = DmsfHelper.temp_filename(@tempfile.original_filename) - File.open("#{DmsfHelper.temp_dir}/#{@disk_filename}", 'wb') do |f| - while (buffer = @tempfile.read(8192)) - f.write(buffer) - end - end + FileUtils.mv(@tempfile.path, "#{DmsfHelper.temp_dir}/#{@disk_filename}") if File.size("#{DmsfHelper.temp_dir}/#{@disk_filename}") <= 0 begin File.delete("#{DmsfHelper.temp_dir}/#{@disk_filename}") @@ -85,12 +83,55 @@ class DmsfUploadController < ApplicationController end end + # REST API document upload + def upload + unless request.content_type == 'application/octet-stream' + render :nothing => true, :status => 406 + return + end + + @attachment = Attachment.new(:file => request.raw_post) + @attachment.author = User.current + @attachment.filename = params[:filename].presence || Redmine::Utils.random_hex(16) + saved = @attachment.save + + respond_to do |format| + format.js + format.api { + if saved + render :action => 'upload', :status => :created + else + render_validation_errors(@attachment) + end + } + end + end + def commit_files if (Rails::VERSION::MAJOR > 3) commited_files = params.require(:commited_files) else commited_files = params[:commited_files] end + commit_files_internal commited_files + end + + # REST API file commit + def commit + uploaded_files = params[:attachments] + if uploaded_files && uploaded_files.is_a?(Hash) + # standard file input uploads + uploaded_files.each_value do |uploaded_file| + upload = DmsfUpload.create_from_uploaded_attachment(@project, @folder, uploaded_file) + uploaded_file[:disk_filename] = upload.disk_filename + end + end + commit_files_internal uploaded_files + end + + private + + def commit_files_internal(commited_files) if commited_files && commited_files.is_a?(Hash) files = [] failed_uploads = [] @@ -146,12 +187,12 @@ class DmsfUploadController < ApplicationController new_revision.mime_type = Redmine::MimeType.of(new_revision.name) new_revision.size = File.size(commited_disk_filepath) - file_upload = File.new(commited_disk_filepath, 'rb') - unless file_upload - failed_uploads.push(commited_file) - flash[:error] = l(:error_file_commit_require_uploaded_file) - next - end +# file_upload = File.new(commited_disk_filepath, 'rb') +# unless file_upload +# failed_uploads.push(commited_file) +# flash[:error] = l(:error_file_commit_require_uploaded_file) +# next +# end if file.locked? begin @@ -180,9 +221,10 @@ class DmsfUploadController < ApplicationController if new_revision.save new_revision.assign_workflow(commited_file[:dmsf_workflow_id]) - new_revision.copy_file_content(file_upload) - file_upload.close - File.delete(commited_disk_filepath) + #new_revision.copy_file_content(file_upload) + #file_upload.close + #File.delete(commited_disk_filepath) + FileUtils.mv(commited_disk_filepath, new_revision.disk_file) file.set_last_revision new_revision files.push(file) else @@ -213,10 +255,13 @@ class DmsfUploadController < ApplicationController flash[:warning] = l(:warning_some_files_were_not_commited, :files => failed_uploads.map{|u| u['name']}.join(', ')) end end - redirect_to dmsf_folder_path(:id => @project, :folder_id => @folder) + respond_to do |format| + format.js + format.api { render_validation_errors(failed_uploads) } + format.html { redirect_to dmsf_folder_path(:id => @project, :folder_id => @folder) } + end + end - - private def log_activity(file, action) Rails.logger.info "#{Time.now.strftime('%Y-%m-%d %H:%M:%S')} #{User.current.login}@#{request.remote_ip}/#{request.env['HTTP_X_FORWARDED_FOR']}: #{action} dmsf://#{file.project.identifier}/#{file.id}/#{file.last_revision.id}" diff --git a/app/models/dmsf_upload.rb b/app/models/dmsf_upload.rb index 41fc5145..31ae5566 100644 --- a/app/models/dmsf_upload.rb +++ b/app/models/dmsf_upload.rb @@ -45,13 +45,7 @@ class DmsfUpload :original_filename => a.filename, :comment => uploaded_file[:description] } - File.open(a.diskfile, 'rb') do |fr| - File.open("#{DmsfHelper.temp_dir}/#{uploaded[:disk_filename]}", 'wb') do |fw| - while (buffer = fr.read(8192)) - fw.write(buffer) - end - end - end + FileUtils.mv(a.diskfile, "#{DmsfHelper.temp_dir}/#{uploaded[:disk_filename]}") a.destroy DmsfUpload.new(project, folder, uploaded) end diff --git a/app/views/dmsf/show.api.rsb b/app/views/dmsf/show.api.rsb new file mode 100644 index 00000000..a1d33e73 --- /dev/null +++ b/app/views/dmsf/show.api.rsb @@ -0,0 +1,30 @@ +api.array :dmsf_folders, api_meta(:total_count => @subfolders.size) do + @subfolders.each do |folder| + api.folder do + api.id folder.id + api.title folder.title + end + end +end + +api.array :dmsf_files, api_meta(:total_count => @files.size) do + @files.each do |file| + api.file do + api.id file.id + api.name file.name + end + end +end + +api.array :dmsf_links, api_meta(:total_count => @dir_links.size + @file_links.size + @url_links.size) do + (@dir_links + @file_links + @url_links).each do |link| + api.link do + api.id link.id + api.name link.name + api.target_type link.target_type + api.target_id link.target_id + api.target_project_id link.target_project_id + api.external_url link.external_url if link.external_url.present? + end + end +end \ No newline at end of file diff --git a/app/views/dmsf_files/show.api.rsb b/app/views/dmsf_files/show.api.rsb index 8a66d0f3..4f265db2 100644 --- a/app/views/dmsf_files/show.api.rsb +++ b/app/views/dmsf_files/show.api.rsb @@ -1,8 +1,8 @@ api.file do api.id @file.id - api.name @file.name + api.name @file.name api.project_id @file.project_id - api.dmsf_folder_id @file.folder ? @file.dmsf_folder_id : -1 - api.version "#{@file.last_revision.major_version}.#{@file.last_revision.minor_version}" + api.dmsf_folder_id @file.folder if @file.dmsf_folder_id + api.version "#{@file.last_revision.major_version}.#{@file.last_revision.minor_version}" if @file.last_revision api.content_url url_for(:controller => :dmsf_files, :action => 'show', :download => '', :id => @file, :only_path => false) end \ No newline at end of file diff --git a/app/views/dmsf_upload/commit.api.rsb b/app/views/dmsf_upload/commit.api.rsb new file mode 100644 index 00000000..f1481c7d --- /dev/null +++ b/app/views/dmsf_upload/commit.api.rsb @@ -0,0 +1,3 @@ +api.commit do + puts 'OK' +end diff --git a/app/views/dmsf_upload/upload.api.rsb b/app/views/dmsf_upload/upload.api.rsb new file mode 100644 index 00000000..0a399ec1 --- /dev/null +++ b/app/views/dmsf_upload/upload.api.rsb @@ -0,0 +1,3 @@ +api.upload do + api.token @attachment.token +end diff --git a/config/routes.rb b/config/routes.rb index 0852c9ab..9569f325 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -58,7 +58,9 @@ RedmineApp::Application.routes.draw do post '/projects/:id/dmsf/upload/files', :controller => 'dmsf_upload', :action => 'upload_files' post '/projects/:id/dmsf/upload/file', :controller => 'dmsf_upload', :action => 'upload_file' + post '/projects/:id/dmsf/upload', :controller => 'dmsf_upload', :action => 'upload' post '/projects/:id/dmsf/upload/commit', :controller => 'dmsf_upload', :action => 'commit_files' + post '/projects/:id/dmsf/commit', :controller => 'dmsf_upload', :action => 'commit' # # dmsf_files controller diff --git a/extra/api_client.rb b/extra/api/api_client.rb similarity index 79% rename from extra/api_client.rb rename to extra/api/api_client.rb index 98de7da1..cb9554b9 100644 --- a/extra/api_client.rb +++ b/extra/api/api_client.rb @@ -1,3 +1,5 @@ +# encoding: utf-8 +# # Redmine plugin for Document Management System "Features" # # Copyright (C) 2011-15 Karel Pičman @@ -19,22 +21,25 @@ require 'rubygems' require 'active_resource' +# Simple REST API client in Ruby +# usage: ruby api_client.rb [login] [password] + # Dmsf file class DmsfFile < ActiveResource::Base self.site = 'http://localhost:3000/' - self.user = 'kpicman' - self.password = 'Kontron2014+' + self.user = ARGV[0] + self.password = ARGV[1] end -# Retrieving a file -file = DmsfFile.find 17200 +# 2. Get a document +FILE_ID = 17216 +file = DmsfFile.find FILE_ID if file puts file.id puts file.name puts file.version - puts file.project_id - puts file.dmsf_folder_id + puts file.project_id puts file.content_url else - puts 'No file with id = 1 found' + puts "No file with id = #{FILE_ID} found" end \ No newline at end of file diff --git a/extra/api/api_client.sh b/extra/api/api_client.sh new file mode 100644 index 00000000..43e5f79b --- /dev/null +++ b/extra/api/api_client.sh @@ -0,0 +1,42 @@ +# encoding: utf-8 +# +# Redmine plugin for Document Management System "Features" +# +# Copyright (C) 2011-15 Karel Pičman +# +# 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. + +#!/bin/bash + +# Authentication as input parameters either as login + password or the API key +# -u ${1}:${2} +# -H "X-Redmine-API-Key: ${1}" + +# BOTH XML and JSON formats are supported. +# Just replace .xml with .json + +# 1. List of documents in a given folder or the root folder +curl -v -H "Content-Type: application/xml" -X GET -u ${1}:${2} http://localhost:3000//projects/12/dmsf.xml +#curl -v -H "Content-Type: application/xml" -X GET -u ${1}:${2} http://localhost:3000//projects/12/dmsf.xml?folder_id=5155 + +# 2. Get a document +#curl -v -H "Content-Type: application/xml" -X GET -u ${1}:${2} http://localhost:3000/dmsf/files/17216.xml + +# 3. Create a folder +#curl -v -H "Content-Type: application/xml" -X POST --data "@folder.xml" -u ${1}:${2} http://localhost:3000//projects/12/dmsf/create.xml + +# 4. Upload a document into a given folder or the root folder +#curl --data-binary "@cat.gif" -H "Content-Type: application/octet-stream" -X POST -u ${1}:${2} http://localhost:3000//projects/12/dmsf/upload.xml?filename=cat.gif +#curl -v -H "Content-Type: application/xml" -X POST --data "@file.xml" -u ${1}:${2} http://localhost:3000//projects/12/dmsf/commit.xml \ No newline at end of file diff --git a/extra/api/cat.gif b/extra/api/cat.gif new file mode 100644 index 0000000000000000000000000000000000000000..0329e659baf657fe9b0564f0d61fd97d58fbf8f5 GIT binary patch literal 9982 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 + + + cat.gif + cat.gif + cat.gif + REST API + From API + + 15843.c49f68ff81b552d315927df2e27df506 + + diff --git a/extra/api/folder.xml b/extra/api/folder.xml new file mode 100644 index 00000000..fd0bef9c --- /dev/null +++ b/extra/api/folder.xml @@ -0,0 +1,6 @@ + + + rest_api + A folder created via REST API + + diff --git a/init.rb b/init.rb index eb332460..5097a868 100644 --- a/init.rb +++ b/init.rb @@ -28,7 +28,7 @@ Redmine::Plugin.register :redmine_dmsf do name 'DMSF' author 'Vit Jonas / Daniel Munn / Karel Picman' description 'Document Management System Features' - version '1.5.1 stable' + version '1.5.2 devel' url 'http://www.redmine.org/plugins/dmsf' author_url 'https://github.com/danmunn/redmine_dmsf/graphs/contributors' @@ -74,7 +74,7 @@ Redmine::Plugin.register :redmine_dmsf do {:dmsf => [:new, :create, :delete, :edit, :save, :edit_root, :save_root, :lock, :unlock, :notify_activate, :notify_deactivate, :restore]} permission :file_manipulation, {:dmsf_files => [:create_revision, :lock, :unlock, :delete_revision, :notify_activate, :notify_deactivate, :restore], - :dmsf_upload => [:upload_files, :upload_file, :commit_files], + :dmsf_upload => [:upload_files, :upload_file, :upload, :commit_files, :commit], :dmsf_links => [:new, :create, :destroy, :restore] } permission :file_delete, diff --git a/lib/redmine_dmsf/webdav/base_resource.rb b/lib/redmine_dmsf/webdav/base_resource.rb index 7b5bf7ae..b91a9786 100644 --- a/lib/redmine_dmsf/webdav/base_resource.rb +++ b/lib/redmine_dmsf/webdav/base_resource.rb @@ -78,7 +78,7 @@ module RedmineDmsf '-', '', '', - ] + entities if parent? + ] + entities if parent @response.body << index_page % [ path.empty? ? '/' : path, path.empty? ? '/' : path , entities ] end diff --git a/lib/tasks/dmsf_convert_documents.rake b/lib/tasks/dmsf_convert_documents.rake index 82e0321f..7df67efd 100644 --- a/lib/tasks/dmsf_convert_documents.rake +++ b/lib/tasks/dmsf_convert_documents.rake @@ -141,19 +141,12 @@ class DmsfConvertDocuments revision.comment = 'Converted from documents' revision.mime_type = attachment.content_type - revision.disk_filename = revision.new_storage_filename - attachment_file = File.open(attachment.diskfile, 'rb') + revision.disk_filename = revision.new_storage_filename unless dry - File.open(revision.disk_file, 'wb') do |f| - while (buffer = attachment_file.read(8192)) - f.write(buffer) - end - end + FileUtils.cp(attachment.diskfile, revision.disk_file) revision.size = File.size(revision.disk_file) - end - - attachment_file.close + end if dry puts "Dry check revision: #{revision.title}" From d92ad665e620d73ae3f6913f1e1205d1262c1a73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Pi=C4=8Dman?= Date: Wed, 8 Apr 2015 10:10:32 +0200 Subject: [PATCH 02/52] Control DMSF via REST API #78 --- app/controllers/dmsf_upload_controller.rb | 12 +----------- app/views/dmsf_upload/commit.api.rsb | 3 --- 2 files changed, 1 insertion(+), 14 deletions(-) delete mode 100644 app/views/dmsf_upload/commit.api.rsb diff --git a/app/controllers/dmsf_upload_controller.rb b/app/controllers/dmsf_upload_controller.rb index 1371bbf7..d7bb62d3 100644 --- a/app/controllers/dmsf_upload_controller.rb +++ b/app/controllers/dmsf_upload_controller.rb @@ -187,13 +187,6 @@ class DmsfUploadController < ApplicationController new_revision.mime_type = Redmine::MimeType.of(new_revision.name) new_revision.size = File.size(commited_disk_filepath) -# file_upload = File.new(commited_disk_filepath, 'rb') -# unless file_upload -# failed_uploads.push(commited_file) -# flash[:error] = l(:error_file_commit_require_uploaded_file) -# next -# end - if file.locked? begin file.unlock! @@ -220,10 +213,7 @@ class DmsfUploadController < ApplicationController end if new_revision.save - new_revision.assign_workflow(commited_file[:dmsf_workflow_id]) - #new_revision.copy_file_content(file_upload) - #file_upload.close - #File.delete(commited_disk_filepath) + new_revision.assign_workflow(commited_file[:dmsf_workflow_id]) FileUtils.mv(commited_disk_filepath, new_revision.disk_file) file.set_last_revision new_revision files.push(file) diff --git a/app/views/dmsf_upload/commit.api.rsb b/app/views/dmsf_upload/commit.api.rsb deleted file mode 100644 index f1481c7d..00000000 --- a/app/views/dmsf_upload/commit.api.rsb +++ /dev/null @@ -1,3 +0,0 @@ -api.commit do - puts 'OK' -end From d0cef0bc4473bd8ac9c84fd73f73bd58444bf83c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Pi=C4=8Dman?= Date: Wed, 8 Apr 2015 11:00:34 +0200 Subject: [PATCH 03/52] Number of downloads #374 --- app/controllers/dmsf_files_controller.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/controllers/dmsf_files_controller.rb b/app/controllers/dmsf_files_controller.rb index 613d874c..c9a843ee 100644 --- a/app/controllers/dmsf_files_controller.rb +++ b/app/controllers/dmsf_files_controller.rb @@ -71,7 +71,8 @@ class DmsfFilesController < ApplicationController access = DmsfFileRevisionAccess.new access.user = User.current access.revision = @revision - access.action = DmsfFileRevisionAccess::DownloadAction + access.action = DmsfFileRevisionAccess::DownloadAction + access.save! send_file(@revision.disk_file, :filename => filename_for_content_disposition(@revision.name), :type => @revision.detect_content_type, From d61998f5916cf158517fd8e7cdd5d7eadd13ce6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Pi=C4=8Dman?= Date: Wed, 8 Apr 2015 12:22:00 +0200 Subject: [PATCH 04/52] Control DMSF via REST API #78 --- app/controllers/dmsf_controller.rb | 23 ++++++++++++++++------- extra/api/api_client.rb | 6 +++--- extra/api/api_client.sh | 11 ++++++----- 3 files changed, 25 insertions(+), 15 deletions(-) diff --git a/app/controllers/dmsf_controller.rb b/app/controllers/dmsf_controller.rb index 59a45a57..a6f68434 100644 --- a/app/controllers/dmsf_controller.rb +++ b/app/controllers/dmsf_controller.rb @@ -273,13 +273,22 @@ class DmsfController < ApplicationController end end - if @folder.save - flash[:notice] = l(:notice_folder_created) - redirect_to dmsf_folder_path(:id => @project, :folder_id => @folder) - else - @pathfolder = @parent - render :action => 'edit' - end + saved = @folder.save + + respond_to do |format| + format.js + format.api { render_validation_errors(@folder) } + format.html { + if saved + flash[:notice] = l(:notice_folder_created) + redirect_to dmsf_folder_path(:id => @project, :folder_id => @folder) + else + @pathfolder = @parent + render :action => 'edit' + end + } + end + end def edit diff --git a/extra/api/api_client.rb b/extra/api/api_client.rb index cb9554b9..cde89f69 100644 --- a/extra/api/api_client.rb +++ b/extra/api/api_client.rb @@ -26,13 +26,13 @@ require 'active_resource' # Dmsf file class DmsfFile < ActiveResource::Base - self.site = 'http://localhost:3000/' + self.site = 'https://localhost:3000/' self.user = ARGV[0] self.password = ARGV[1] end # 2. Get a document -FILE_ID = 17216 +FILE_ID = 41532 file = DmsfFile.find FILE_ID if file puts file.id @@ -42,4 +42,4 @@ if file puts file.content_url else puts "No file with id = #{FILE_ID} found" -end \ No newline at end of file +end diff --git a/extra/api/api_client.sh b/extra/api/api_client.sh index 43e5f79b..bb7a112b 100644 --- a/extra/api/api_client.sh +++ b/extra/api/api_client.sh @@ -28,15 +28,16 @@ # Just replace .xml with .json # 1. List of documents in a given folder or the root folder -curl -v -H "Content-Type: application/xml" -X GET -u ${1}:${2} http://localhost:3000//projects/12/dmsf.xml -#curl -v -H "Content-Type: application/xml" -X GET -u ${1}:${2} http://localhost:3000//projects/12/dmsf.xml?folder_id=5155 +curl -v -H "Content-Type: application/xml" -X GET -u ${1}:${2} http://localhost:3000/projects/12/dmsf.xml +#curl -v -H "Content-Type: application/xml" -X GET -u ${1}:${2} http://localhost:3000/projects/12/dmsf.xml?folder_id=5155 # 2. Get a document #curl -v -H "Content-Type: application/xml" -X GET -u ${1}:${2} http://localhost:3000/dmsf/files/17216.xml +#curl -v -H "Content-Type: application/octet-stream" -X GET -u ${1}:${2} http://localhost:3000/dmsf/files/41532/download > file.txt # 3. Create a folder -#curl -v -H "Content-Type: application/xml" -X POST --data "@folder.xml" -u ${1}:${2} http://localhost:3000//projects/12/dmsf/create.xml +#curl -v -H "Content-Type: application/xml" -X POST --data "@folder.xml" -u ${1}:${2} http://localhost:3000/projects/12/dmsf/create.xml # 4. Upload a document into a given folder or the root folder -#curl --data-binary "@cat.gif" -H "Content-Type: application/octet-stream" -X POST -u ${1}:${2} http://localhost:3000//projects/12/dmsf/upload.xml?filename=cat.gif -#curl -v -H "Content-Type: application/xml" -X POST --data "@file.xml" -u ${1}:${2} http://localhost:3000//projects/12/dmsf/commit.xml \ No newline at end of file +#curl --data-binary "@cat.gif" -H "Content-Type: application/octet-stream" -X POST -u ${1}:${2} http://localhost:3000/projects/12/dmsf/upload.xml?filename=cat.gif +#curl -v -H "Content-Type: application/xml" -X POST --data "@file.xml" -u ${1}:${2} http://localhost:3000/projects/12/dmsf/commit.xml \ No newline at end of file From 03cf2db8379f5b734cf9200cfe042464c97c9a6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Pi=C4=8Dman?= Date: Wed, 8 Apr 2015 12:34:57 +0200 Subject: [PATCH 05/52] Control DMSF via REST API #78 --- app/controllers/dmsf_controller.rb | 6 +++++- app/views/dmsf/create.api.rsb | 4 ++++ app/views/dmsf_files/show.api.rsb | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 app/views/dmsf/create.api.rsb diff --git a/app/controllers/dmsf_controller.rb b/app/controllers/dmsf_controller.rb index a6f68434..d7376b59 100644 --- a/app/controllers/dmsf_controller.rb +++ b/app/controllers/dmsf_controller.rb @@ -277,7 +277,11 @@ class DmsfController < ApplicationController respond_to do |format| format.js - format.api { render_validation_errors(@folder) } + format.api { + unless saved + render_validation_errors(@folder) + end + } format.html { if saved flash[:notice] = l(:notice_folder_created) diff --git a/app/views/dmsf/create.api.rsb b/app/views/dmsf/create.api.rsb new file mode 100644 index 00000000..94caf8cc --- /dev/null +++ b/app/views/dmsf/create.api.rsb @@ -0,0 +1,4 @@ +api.dmsf_folder do + api.id @folder.id + api.title @folder.title +end \ No newline at end of file diff --git a/app/views/dmsf_files/show.api.rsb b/app/views/dmsf_files/show.api.rsb index 4f265db2..e846a86d 100644 --- a/app/views/dmsf_files/show.api.rsb +++ b/app/views/dmsf_files/show.api.rsb @@ -1,4 +1,4 @@ -api.file do +api.dmsf_file do api.id @file.id api.name @file.name api.project_id @file.project_id From db06ee468f43be16361ebbc82f13d17452a9856e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Pi=C4=8Dman?= Date: Wed, 8 Apr 2015 14:01:34 +0200 Subject: [PATCH 06/52] Control DMSF via REST API #78 --- app/controllers/dmsf_upload_controller.rb | 24 +++++++++++++---------- app/views/dmsf_upload/commit.api.rsb | 8 ++++++++ extra/api/file.xml | 5 +++-- 3 files changed, 25 insertions(+), 12 deletions(-) create mode 100644 app/views/dmsf_upload/commit.api.rsb diff --git a/app/controllers/dmsf_upload_controller.rb b/app/controllers/dmsf_upload_controller.rb index d7bb62d3..d88a5562 100644 --- a/app/controllers/dmsf_upload_controller.rb +++ b/app/controllers/dmsf_upload_controller.rb @@ -26,7 +26,7 @@ class DmsfUploadController < ApplicationController before_filter :find_project before_filter :authorize - before_filter :find_folder, :except => [:upload_file, :upload] + before_filter :find_folder, :except => [:upload_file, :upload, :commit] helper :all helper :dmsf_workflows @@ -118,9 +118,11 @@ class DmsfUploadController < ApplicationController # REST API file commit def commit - uploaded_files = params[:attachments] - if uploaded_files && uploaded_files.is_a?(Hash) + attachments = params[:attachments] + if attachments && attachments.is_a?(Hash) + @folder = DmsfFolder.visible.find_by_id attachments[:folder_id].to_i if attachments[:folder_id].present? # standard file input uploads + uploaded_files = attachments.select { |key, value| key == 'uploaded_file'} uploaded_files.each_value do |uploaded_file| upload = DmsfUpload.create_from_uploaded_attachment(@project, @folder, uploaded_file) uploaded_file[:disk_filename] = upload.disk_filename @@ -133,7 +135,7 @@ class DmsfUploadController < ApplicationController def commit_files_internal(commited_files) if commited_files && commited_files.is_a?(Hash) - files = [] + @files = [] failed_uploads = [] commited_files.each_value do |commited_file| name = commited_file[:name] @@ -216,18 +218,18 @@ class DmsfUploadController < ApplicationController new_revision.assign_workflow(commited_file[:dmsf_workflow_id]) FileUtils.mv(commited_disk_filepath, new_revision.disk_file) file.set_last_revision new_revision - files.push(file) + @files.push(file) else failed_uploads.push(commited_file) end end - unless files.empty? - files.each { |file| log_activity(file, 'uploaded') if file } + unless @files.empty? + @files.each { |file| log_activity(file, 'uploaded') if file } if (@folder && @folder.notification?) || (!@folder && @project.dmsf_notification?) begin - recipients = DmsfMailer.get_notify_users(@project, files) + recipients = DmsfMailer.get_notify_users(@project, @files) recipients.each do |u| - DmsfMailer.files_updated(u, @project, files).deliver + DmsfMailer.files_updated(u, @project, @files).deliver end if Setting.plugin_redmine_dmsf[:dmsf_display_notified_recipients] == '1' unless recipients.empty? @@ -247,7 +249,9 @@ class DmsfUploadController < ApplicationController end respond_to do |format| format.js - format.api { render_validation_errors(failed_uploads) } + format.api { + render_validation_errors(failed_uploads) unless failed_uploads.empty? + } format.html { redirect_to dmsf_folder_path(:id => @project, :folder_id => @folder) } end diff --git a/app/views/dmsf_upload/commit.api.rsb b/app/views/dmsf_upload/commit.api.rsb new file mode 100644 index 00000000..e5e85426 --- /dev/null +++ b/app/views/dmsf_upload/commit.api.rsb @@ -0,0 +1,8 @@ +api.array :dmsf_files, api_meta(:total_count => @files.size) do + @files.each do |file| + api.file do + api.id file.id + api.name file.name + end + end +end \ No newline at end of file diff --git a/extra/api/file.xml b/extra/api/file.xml index 289c3586..f1c19385 100644 --- a/extra/api/file.xml +++ b/extra/api/file.xml @@ -1,5 +1,6 @@ - + + 6118 cat.gif cat.gif @@ -7,6 +8,6 @@ REST API From API - 15843.c49f68ff81b552d315927df2e27df506 + 15838.c49f68ff81b552d315927df2e27df506 From 4660fdefb82e72b48f1cfdc59c8d11e3e047b36a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Pi=C4=8Dman?= Date: Tue, 14 Apr 2015 12:48:45 +0200 Subject: [PATCH 07/52] Can access WebDAV when redmine is located under sub-URI #377 --- README.md | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3cf3e286..f420087d 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ Redmine DMSF Plugin =================== -The current version of Redmine DMSF is **1.5.1** [![Build Status](https://api.travis-ci.org/danmunn/redmine_dmsf.png)](https://travis-ci.org/danmunn/redmine_dmsf) +The current version of Redmine DMSF is **1.5.2** [![Build Status](https://api.travis-ci.org/danmunn/redmine_dmsf.png)](https://travis-ci.org/danmunn/redmine_dmsf) Redmine DMSF is Document Management System Features plugin for Redmine issue tracking system; It is aimed to replace current Redmine's Documents module. @@ -42,7 +42,7 @@ Features Dependencies ------------ - * Redmine 2.5.0 or higher (Not yet fully compatible with Redmine 3.x but it should work in general) + * Redmine 2.5.0 or higher ### Fulltext search (optional) @@ -143,6 +143,18 @@ Before installing ensure that the Redmine instance is stopped. 1. In case of upgrade BACKUP YOUR DATABASE first 2. Put redmine_dmsf plugin directory into plugins +3. If you install Redmine in a sub-uri, it's necessary to modify two hard-coded paths in order to webdav is working: + a) /config/routes.rb + mount DAV4Rack::Handler.new( + -- :root_uri_path => '/dmsf/webdav', + ++ :root_uri_path => '/sub-uri/dmsf/webdav', + :resource_class => RedmineDmsf::Webdav::ResourceProxy, + :controller_class => RedmineDmsf::Webdav::Controller + ), :at => "/dmsf/webdav" + b) lib/redmine_dmsf/webdav/no_parse.rb + Rails.configuration.middleware.insert_before(ActionDispatch::ParamsParser, + -- RedmineDmsf::NoParse, :urls => ['/dmsf/webdav']) + ++ RedmineDmsf::NoParse, :urls => ['/sub-uri/dmsf/webdav']) 3. Initialize/Update database: `rake redmine:plugins:migrate RAILS_ENV="production"` 4. The access rights must be set for web server, example: `chown -R www-data:www-data plugins/redmine_dmsf` 5. Restart web server From 8999f9db92079db847de079c1299ece8c88840f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Pi=C4=8Dman?= Date: Tue, 14 Apr 2015 12:51:38 +0200 Subject: [PATCH 08/52] Can access WebDAV when redmine is located under sub-URI #377 --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index f420087d..69c8dcad 100644 --- a/README.md +++ b/README.md @@ -155,12 +155,12 @@ Before installing ensure that the Redmine instance is stopped. Rails.configuration.middleware.insert_before(ActionDispatch::ParamsParser, -- RedmineDmsf::NoParse, :urls => ['/dmsf/webdav']) ++ RedmineDmsf::NoParse, :urls => ['/sub-uri/dmsf/webdav']) -3. Initialize/Update database: `rake redmine:plugins:migrate RAILS_ENV="production"` -4. The access rights must be set for web server, example: `chown -R www-data:www-data plugins/redmine_dmsf` -5. Restart web server -6. You should configure plugin via Redmine interface: Administration -> Plugins -> DMSF -> Configure -7. Assign DMSF permissions to appropriate roles -8. There are two rake tasks: +4. Initialize/Update database: `rake redmine:plugins:migrate RAILS_ENV="production"` +5. The access rights must be set for web server, example: `chown -R www-data:www-data plugins/redmine_dmsf` +6. Restart web server +7. You should configure plugin via Redmine interface: Administration -> Plugins -> DMSF -> Configure +8. Assign DMSF permissions to appropriate roles +9. There are two rake tasks: a) To convert documents from the standard Redmine document module Available options: * project => id or identifier of project (defaults to all projects) From 8139ca2df28018cc9bcd9984d2936ffe708ba1f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Pi=C4=8Dman?= Date: Tue, 14 Apr 2015 12:55:34 +0200 Subject: [PATCH 09/52] Code snippets style --- README.md | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 69c8dcad..7811abec 100644 --- a/README.md +++ b/README.md @@ -144,33 +144,48 @@ Before installing ensure that the Redmine instance is stopped. 1. In case of upgrade BACKUP YOUR DATABASE first 2. Put redmine_dmsf plugin directory into plugins 3. If you install Redmine in a sub-uri, it's necessary to modify two hard-coded paths in order to webdav is working: + a) /config/routes.rb + + ```ruby mount DAV4Rack::Handler.new( -- :root_uri_path => '/dmsf/webdav', ++ :root_uri_path => '/sub-uri/dmsf/webdav', :resource_class => RedmineDmsf::Webdav::ResourceProxy, :controller_class => RedmineDmsf::Webdav::Controller ), :at => "/dmsf/webdav" + ``` + b) lib/redmine_dmsf/webdav/no_parse.rb + + ```ruby Rails.configuration.middleware.insert_before(ActionDispatch::ParamsParser, -- RedmineDmsf::NoParse, :urls => ['/dmsf/webdav']) ++ RedmineDmsf::NoParse, :urls => ['/sub-uri/dmsf/webdav']) + ``` + 4. Initialize/Update database: `rake redmine:plugins:migrate RAILS_ENV="production"` 5. The access rights must be set for web server, example: `chown -R www-data:www-data plugins/redmine_dmsf` 6. Restart web server 7. You should configure plugin via Redmine interface: Administration -> Plugins -> DMSF -> Configure 8. Assign DMSF permissions to appropriate roles 9. There are two rake tasks: + a) To convert documents from the standard Redmine document module + Available options: + * project => id or identifier of project (defaults to all projects) * dry => true or false (default false) to perform just check without any conversion * invalid=replace => to perform document title invalid characters replacement for '-' + Example: - rake redmine:dmsf_convert_documents project=test RAILS_ENV="production" + ```rake redmine:dmsf_convert_documents project=test RAILS_ENV="production"``` + b) To alert all users who are expected to do an approval in the current approval steps + Example: - rake redmine:dmsf_alert_approvals RAILS_ENV="production" + ```rake redmine:dmsf_alert_approvals RAILS_ENV="production"``` ### Fulltext search (optional) If you want to use fulltext search features, you must setup file content indexing. @@ -184,7 +199,7 @@ This command must be run on regular basis (e.g. from cron) Example of cron job (once per hour at 8th minute): - `8 * * * * root /usr/bin/ruby redmine_dmsf/extra/xapian_indexer.rb -f` + ```8 * * * * root /usr/bin/ruby redmine_dmsf/extra/xapian_indexer.rb -f``` See redmine_dmsf/extra/xapian_indexer.rb for help. From 32bda708fa4ca25f8f2f58a987e985a4188e360d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Pi=C4=8Dman?= Date: Tue, 14 Apr 2015 12:57:30 +0200 Subject: [PATCH 10/52] Code snippets style --- README.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 7811abec..fdadd11c 100644 --- a/README.md +++ b/README.md @@ -180,12 +180,16 @@ Before installing ensure that the Redmine instance is stopped. * invalid=replace => to perform document title invalid characters replacement for '-' Example: - ```rake redmine:dmsf_convert_documents project=test RAILS_ENV="production"``` + ``` + rake redmine:dmsf_convert_documents project=test RAILS_ENV="production" + ``` b) To alert all users who are expected to do an approval in the current approval steps Example: - ```rake redmine:dmsf_alert_approvals RAILS_ENV="production"``` + ``` + rake redmine:dmsf_alert_approvals RAILS_ENV="production" + ``` ### Fulltext search (optional) If you want to use fulltext search features, you must setup file content indexing. @@ -199,7 +203,9 @@ This command must be run on regular basis (e.g. from cron) Example of cron job (once per hour at 8th minute): - ```8 * * * * root /usr/bin/ruby redmine_dmsf/extra/xapian_indexer.rb -f``` + ``` + 8 * * * * root /usr/bin/ruby redmine_dmsf/extra/xapian_indexer.rb -f + ``` See redmine_dmsf/extra/xapian_indexer.rb for help. From 0fe51cda8cc4905344ce989afcde66ec80bc9432 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Pi=C4=8Dman?= Date: Tue, 14 Apr 2015 13:01:20 +0200 Subject: [PATCH 11/52] Code snippets style --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index fdadd11c..49ed852a 100644 --- a/README.md +++ b/README.md @@ -180,14 +180,16 @@ Before installing ensure that the Redmine instance is stopped. * invalid=replace => to perform document title invalid characters replacement for '-' Example: - ``` + + ```ruby rake redmine:dmsf_convert_documents project=test RAILS_ENV="production" ``` b) To alert all users who are expected to do an approval in the current approval steps Example: - ``` + + ```ruby rake redmine:dmsf_alert_approvals RAILS_ENV="production" ``` @@ -203,7 +205,7 @@ This command must be run on regular basis (e.g. from cron) Example of cron job (once per hour at 8th minute): - ``` + ```bash 8 * * * * root /usr/bin/ruby redmine_dmsf/extra/xapian_indexer.rb -f ``` From de47a7ef365e572727b3b4759a906847a6b91baf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Pi=C4=8Dman?= Date: Tue, 14 Apr 2015 13:03:52 +0200 Subject: [PATCH 12/52] Code snippets style --- README.md | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 49ed852a..24cc6609 100644 --- a/README.md +++ b/README.md @@ -180,18 +180,14 @@ Before installing ensure that the Redmine instance is stopped. * invalid=replace => to perform document title invalid characters replacement for '-' Example: - - ```ruby + rake redmine:dmsf_convert_documents project=test RAILS_ENV="production" - ``` b) To alert all users who are expected to do an approval in the current approval steps Example: - - ```ruby - rake redmine:dmsf_alert_approvals RAILS_ENV="production" - ``` + + rake redmine:dmsf_alert_approvals RAILS_ENV="production" ### Fulltext search (optional) If you want to use fulltext search features, you must setup file content indexing. @@ -204,10 +200,8 @@ It is necessary to index DMSF files with omega before searching attempts to rece This command must be run on regular basis (e.g. from cron) Example of cron job (once per hour at 8th minute): - - ```bash + 8 * * * * root /usr/bin/ruby redmine_dmsf/extra/xapian_indexer.rb -f - ``` See redmine_dmsf/extra/xapian_indexer.rb for help. From d6500cb861fb56b77f70b74efb110c0f88bafafe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Pi=C4=8Dman?= Date: Tue, 14 Apr 2015 14:44:43 +0200 Subject: [PATCH 13/52] Links to deleted documents #376 --- README.md | 11 ++++---- app/controllers/dmsf_controller.rb | 18 +++++++++---- app/controllers/dmsf_files_controller.rb | 10 +++---- app/views/dmsf/_file_trash.html.erb | 11 +++----- init.rb | 34 +++++++++++------------- 5 files changed, 43 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index 24cc6609..358f8484 100644 --- a/README.md +++ b/README.md @@ -120,11 +120,12 @@ In the file /public/help//wiki_syntax_detailed.html, aft
  • DMSF:
      -
    • {{dmsf(17)}} (link to file with id 17)
    • -
    • {{dmsf(17,File)}} (link to file with id 17 with link text "File")
    • -
    • {{dmsf(17,File,10)}} (link to file with id 17 with link text "File" and link pointing to revision 10)
    • -
    • {{dmsff(5)}} (link to folder with id 5)
    • -
    • {{dmsff(5,Folder)}} (link to folder with id 5 with link text "Folder")
    • +
    • {{dmsf(17)}} (a link to the file with id 17)
    • +
    • {{dmsf(17, File)}} (a link to the file with id 17 with the link text "File")
    • +
    • {{dmsf(17, File, 10)}} (a link to the file with id 17 with the link text "File" and the link pointing to the revision 10)
    • +
    • {{dmsfd(17)}} (a link to the description of the file with id 17)
    • +
    • {{dmsff(5)}} (a link to the folder with id 5)
    • +
    • {{dmsff(5, Folder)}} (a link to the folder with id 5 with the link text "Folder")
    The DMSF file/revision id can be found in the link for file/revision download from within Redmine.
    The DMSF folder id can be found in the link when opening folders within Redmine. diff --git a/app/controllers/dmsf_controller.rb b/app/controllers/dmsf_controller.rb index d7376b59..24497ec3 100644 --- a/app/controllers/dmsf_controller.rb +++ b/app/controllers/dmsf_controller.rb @@ -36,7 +36,7 @@ class DmsfController < ApplicationController helper :all - def show + def show @folder_manipulation_allowed = User.current.allowed_to?(:folder_manipulation, @project) @file_manipulation_allowed = User.current.allowed_to?(:file_manipulation, @project) @file_delete_allowed = User.current.allowed_to?(:file_delete, @project) @@ -120,6 +120,12 @@ class DmsfController < ApplicationController end @locked_for_user = false else + + if @folder.deleted + render_404 + return + end + @subfolders = @folder.subfolders.visible @files = @folder.files.visible @dir_links = @folder.folder_links.visible @@ -607,12 +613,16 @@ class DmsfController < ApplicationController @folder = DmsfFolder.find params[:folder_id] if params[:folder_id].present? rescue DmsfAccessError render_403 + rescue ActiveRecord::RecordNotFound + render_404 end def find_parent @parent = DmsfFolder.visible.find params[:parent_id] if params[:parent_id].present? rescue DmsfAccessError render_403 + rescue ActiveRecord::RecordNotFound + render_404 end def copy_folder(folder) @@ -628,7 +638,5 @@ class DmsfController < ApplicationController :to, :zipped_content, :email, :cc, :subject, :zipped_content => [], :files => []) end - - - -end + +end \ No newline at end of file diff --git a/app/controllers/dmsf_files_controller.rb b/app/controllers/dmsf_files_controller.rb index c9a843ee..43a11600 100644 --- a/app/controllers/dmsf_files_controller.rb +++ b/app/controllers/dmsf_files_controller.rb @@ -37,6 +37,7 @@ class DmsfFilesController < ApplicationController @revision = @file.last_revision check_project(@revision.file) begin + raise ActionController::MissingFile if @file.deleted log_activity('downloaded') access = DmsfFileRevisionAccess.new access.user = User.current @@ -47,8 +48,7 @@ class DmsfFilesController < ApplicationController :filename => filename_for_content_disposition(@revision.name), :type => @revision.detect_content_type, :disposition => 'inline') - rescue ActionController::MissingFile => e - logger.error e.message + rescue ActionController::MissingFile render_404 end end @@ -67,6 +67,7 @@ class DmsfFilesController < ApplicationController end check_project(@revision.file) begin + raise ActionController::MissingFile if @revision.file.deleted log_activity('downloaded') access = DmsfFileRevisionAccess.new access.user = User.current @@ -77,8 +78,7 @@ class DmsfFilesController < ApplicationController :filename => filename_for_content_disposition(@revision.name), :type => @revision.detect_content_type, :disposition => 'attachment') - rescue ActionController::MissingFile => e - logger.error e.message + rescue ActionController::MissingFile render_404 end return @@ -303,7 +303,7 @@ class DmsfFilesController < ApplicationController end def find_revision - @revision = DmsfFileRevision.visible.find(params[:id]) + @revision = DmsfFileRevision.visible.find params[:id] @file = @revision.file @project = @file.project rescue ActiveRecord::RecordNotFound diff --git a/app/views/dmsf/_file_trash.html.erb b/app/views/dmsf/_file_trash.html.erb index 7023f0b2..b00d59cc 100644 --- a/app/views/dmsf/_file_trash.html.erb +++ b/app/views/dmsf/_file_trash.html.erb @@ -20,13 +20,10 @@ <%= check_box_tag(name, id, false, :title => l(:title_check_for_restore_or_delete)) %> - - <% file_download_url = url_for({:only_path => false, :controller => :dmsf_files, :action => 'show', :id => file, :download => ''}) %> - <%= link_to(h(title), - file_download_url, - :class => "icon icon-file #{DmsfHelper.filetype_css(file.name)}", - :title => l(:title_title_version_version_download, :title => h(file.title), :version => file.version), - 'data-downloadurl' => "#{file.last_revision.detect_content_type}:#{h(file.name)}:#{file_download_url}") %> + + <%= content_tag(:span, h(title), + :title => h(title), + :class => "icon icon-file #{DmsfHelper.filetype_css(file.name)}") %>
    <%= h(link ? link.path : file.display_name) %>
    <%= number_to_human_size(file.last_revision.size) %> diff --git a/init.rb b/init.rb index 5097a868..bffd57bc 100644 --- a/init.rb +++ b/init.rb @@ -89,19 +89,18 @@ Redmine::Plugin.register :redmine_dmsf do # Administration menu extension Redmine::MenuManager.map :admin_menu do |menu| - menu.push :approvalworkflows, {:controller => 'dmsf_workflows', :action => 'index'}, :caption => :label_dmsf_workflow_plural + menu.push :approvalworkflows, {:controller => 'dmsf_workflows', :action => 'index'}, :caption => :label_dmsf_workflow_plural end Redmine::WikiFormatting::Macros.register do desc "Wiki link to DMSF file:\n\n" + "!{{dmsf(file_id [, title [, revision_id]])}}\n\n" + - "_file_id_ / _revision_id_ can be found in link for file/revision download." + "_file_id_ / _revision_id_ can be found in the link for file/revision download." macro :dmsf do |obj, args| - return nil if args.length < 1 # require file id - entry_id = args[0].strip - entry = DmsfFile.find(entry_id) - if entry && !entry.deleted && User.current && User.current.allowed_to?(:view_dmsf_files, entry.project) + return nil if args.length < 1 # require file id + entry = DmsfFile.visible.find_by_id args[0].strip + if entry && User.current && User.current.allowed_to?(:view_dmsf_files, entry.project) title = args[1] ? args[1] : entry.title revision = args[2] ? args[2] : '' return link_to h(title), download_revision_path(entry, revision, :only_path => false) @@ -113,14 +112,13 @@ Redmine::Plugin.register :redmine_dmsf do Redmine::WikiFormatting::Macros.register do desc "Wiki link to DMSF folder:\n\n" + "!{{dmsff(folder_id [, title])}}\n\n" + - "_folder_id_ may be missing. _folder_id_ can be found in link for folder opening." + "_folder_id_ may be missing. _folder_id_ can be found in the link for folder opening." macro :dmsff do |obj, args| if args.length < 1 - return link_to l(:link_documents), :controller => 'dmsf', :action => 'show', :id => @project, :only_path => false - else - entry_id = args[0].strip - entry = DmsfFolder.find(entry_id) + return link_to l(:link_documents), dmsf_folder_path(@project, :only_path => false) + else + entry = DmsfFolder.visible.find_by_id args[0].strip if entry && User.current && User.current.allowed_to?(:view_dmsf_folders, entry.project) title = args[1] ? args[1] : entry.title return link_to h(title), dmsf_folder_path(entry.project, :folder_id => entry, :only_path => false) @@ -132,16 +130,14 @@ Redmine::Plugin.register :redmine_dmsf do Redmine::WikiFormatting::Macros.register do desc "Wiki link to DMSF document description:\n\n" + - "{{dmsfd(file_id [, title])}}\n\n" + - "_file_id_ / _revision_id_ can be found in link for file/revision download." + "{{dmsfd(file_id)}}\n\n" + + "_file_id_ can be found in the link for file/revision download." macro :dmsfd do |obj, args| - return nil if args.length < 1 # require file id - entry_id = args[0].strip - entry = DmsfFile.find(entry_id) - if entry && !entry.deleted && User.current && User.current.allowed_to?(:view_dmsf_files, entry.project) - title = args[1] ? args[1] : entry.title - return link_to h(title), dmsf_file_path(entry, :only_path => false) + return nil if args.length < 1 # require file id + entry = DmsfFile.visible.find_by_id args[0].strip + if entry && User.current && User.current.allowed_to?(:view_dmsf_files, entry.project) + return entry.description end nil end From e15c56657d1bae7c0f617d77100e5a5662931c53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Pi=C4=8Dman?= Date: Wed, 15 Apr 2015 10:04:51 +0200 Subject: [PATCH 14/52] ! in macro's description --- init.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/init.rb b/init.rb index bffd57bc..06a3d260 100644 --- a/init.rb +++ b/init.rb @@ -94,7 +94,7 @@ Redmine::Plugin.register :redmine_dmsf do Redmine::WikiFormatting::Macros.register do desc "Wiki link to DMSF file:\n\n" + - "!{{dmsf(file_id [, title [, revision_id]])}}\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| @@ -111,7 +111,7 @@ Redmine::Plugin.register :redmine_dmsf do Redmine::WikiFormatting::Macros.register do desc "Wiki link to DMSF folder:\n\n" + - "!{{dmsff(folder_id [, title])}}\n\n" + + "{{dmsff(folder_id [, title])}}\n\n" + "_folder_id_ may be missing. _folder_id_ can be found in the link for folder opening." macro :dmsff do |obj, args| @@ -141,6 +141,7 @@ Redmine::Plugin.register :redmine_dmsf do end nil end + end # Rubyzip configuration From 5dfde34127f4c15f1499f4b9f38b87263d5dea77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Pi=C4=8Dman?= Date: Wed, 15 Apr 2015 10:20:14 +0200 Subject: [PATCH 15/52] Can access WebDAV when redmine is located under sub-URI #377 --- README.md | 33 ++++++----------------------- config/routes.rb | 2 +- lib/redmine_dmsf/webdav/no_parse.rb | 5 +++-- 3 files changed, 10 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 358f8484..550dc66f 100644 --- a/README.md +++ b/README.md @@ -144,33 +144,12 @@ Before installing ensure that the Redmine instance is stopped. 1. In case of upgrade BACKUP YOUR DATABASE first 2. Put redmine_dmsf plugin directory into plugins -3. If you install Redmine in a sub-uri, it's necessary to modify two hard-coded paths in order to webdav is working: - - a) /config/routes.rb - - ```ruby - mount DAV4Rack::Handler.new( - -- :root_uri_path => '/dmsf/webdav', - ++ :root_uri_path => '/sub-uri/dmsf/webdav', - :resource_class => RedmineDmsf::Webdav::ResourceProxy, - :controller_class => RedmineDmsf::Webdav::Controller - ), :at => "/dmsf/webdav" - ``` - - b) lib/redmine_dmsf/webdav/no_parse.rb - - ```ruby - Rails.configuration.middleware.insert_before(ActionDispatch::ParamsParser, - -- RedmineDmsf::NoParse, :urls => ['/dmsf/webdav']) - ++ RedmineDmsf::NoParse, :urls => ['/sub-uri/dmsf/webdav']) - ``` - -4. Initialize/Update database: `rake redmine:plugins:migrate RAILS_ENV="production"` -5. The access rights must be set for web server, example: `chown -R www-data:www-data plugins/redmine_dmsf` -6. Restart web server -7. You should configure plugin via Redmine interface: Administration -> Plugins -> DMSF -> Configure -8. Assign DMSF permissions to appropriate roles -9. There are two rake tasks: +3. Initialize/Update database: `rake redmine:plugins:migrate RAILS_ENV="production"` +4. The access rights must be set for web server, example: `chown -R www-data:www-data plugins/redmine_dmsf` +5. Restart web server +6. You should configure plugin via Redmine interface: Administration -> Plugins -> DMSF -> Configure +7. Assign DMSF permissions to appropriate roles +8. There are two rake tasks: a) To convert documents from the standard Redmine document module diff --git a/config/routes.rb b/config/routes.rb index 9569f325..1b827724 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -111,7 +111,7 @@ RedmineApp::Application.routes.draw do # DAV4Rack implementation of Webdav [note: if changing path you'll need to update lib/redmine_dmsf/webdav/no_parse.rb also] # /dmsf/webdav mount DAV4Rack::Handler.new( - :root_uri_path => "/dmsf/webdav", + :root_uri_path => "#{Redmine::Utils::relative_url_root}/dmsf/webdav", :resource_class => RedmineDmsf::Webdav::ResourceProxy, :controller_class => RedmineDmsf::Webdav::Controller ), :at => "/dmsf/webdav" diff --git a/lib/redmine_dmsf/webdav/no_parse.rb b/lib/redmine_dmsf/webdav/no_parse.rb index ee8625bc..bef4080c 100644 --- a/lib/redmine_dmsf/webdav/no_parse.rb +++ b/lib/redmine_dmsf/webdav/no_parse.rb @@ -46,5 +46,6 @@ end # This should probably be configurable somehow or better have the module hunt for the correct pathing # automatically without the need to add a "/dmsf/webdav" configuration to it, as if the route is changed # the functonality of this patch will effectively break. -Rails.configuration.middleware.insert_before(ActionDispatch::ParamsParser, - RedmineDmsf::NoParse, :urls => ['/dmsf/webdav']) \ No newline at end of file +Rails.configuration.middleware.insert_before( + ActionDispatch::ParamsParser, + RedmineDmsf::NoParse, :urls => ["#{Redmine::Utils::relative_url_root}/dmsf/webdav"]) \ No newline at end of file From bcfa79883398873bc781cf46a19fb81ba7453270 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Pi=C4=8Dman?= Date: Tue, 21 Apr 2015 09:37:50 +0200 Subject: [PATCH 16/52] REST API - list of document produces invalid XML #382 --- app/views/dmsf/show.api.rsb | 44 +++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/app/views/dmsf/show.api.rsb b/app/views/dmsf/show.api.rsb index a1d33e73..542a39f4 100644 --- a/app/views/dmsf/show.api.rsb +++ b/app/views/dmsf/show.api.rsb @@ -1,30 +1,32 @@ -api.array :dmsf_folders, api_meta(:total_count => @subfolders.size) do - @subfolders.each do |folder| - api.folder do - api.id folder.id - api.title folder.title +api.dmsf do + api.array :dmsf_folders, api_meta(:total_count => @subfolders.size) do + @subfolders.each do |folder| + api.folder do + api.id folder.id + api.title folder.title + end end end -end -api.array :dmsf_files, api_meta(:total_count => @files.size) do - @files.each do |file| - api.file do - api.id file.id - api.name file.name + api.array :dmsf_files, api_meta(:total_count => @files.size) do + @files.each do |file| + api.file do + api.id file.id + api.name file.name + end end end -end -api.array :dmsf_links, api_meta(:total_count => @dir_links.size + @file_links.size + @url_links.size) do - (@dir_links + @file_links + @url_links).each do |link| - api.link do - api.id link.id - api.name link.name - api.target_type link.target_type - api.target_id link.target_id - api.target_project_id link.target_project_id - api.external_url link.external_url if link.external_url.present? + api.array :dmsf_links, api_meta(:total_count => @dir_links.size + @file_links.size + @url_links.size) do + (@dir_links + @file_links + @url_links).each do |link| + api.link do + api.id link.id + api.name link.name + api.target_type link.target_type + api.target_id link.target_id + api.target_project_id link.target_project_id + api.external_url link.external_url if link.external_url.present? + end end end end \ No newline at end of file From 89c9b746b1ea9778be95f6d0a2ee62fdf43e99b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Pi=C4=8Dman?= Date: Wed, 22 Apr 2015 09:35:05 +0200 Subject: [PATCH 17/52] Missleading number of entities in documents folder #383 --- app/models/dmsf_folder.rb | 9 +++++++++ app/views/dmsf/_dir.html.erb | 6 ++++-- app/views/dmsf/_dir_trash.html.erb | 4 +++- app/views/dmsf/show.html.erb | 2 +- app/views/dmsf/trash.html.erb | 4 +++- config/locales/cs.yml | 2 +- config/locales/de.yml | 2 +- config/locales/en.yml | 2 +- config/locales/es.yml | 2 +- config/locales/fr.yml | 2 +- config/locales/ja.yml | 2 +- config/locales/pl.yml | 2 +- config/locales/ru.yml | 2 +- config/locales/sl.yml | 2 +- config/locales/zh-TW.yml | 2 +- config/locales/zh.yml | 2 +- 16 files changed, 31 insertions(+), 16 deletions(-) diff --git a/app/models/dmsf_folder.rb b/app/models/dmsf_folder.rb index d2b032ca..f87d38c8 100644 --- a/app/models/dmsf_folder.rb +++ b/app/models/dmsf_folder.rb @@ -343,6 +343,15 @@ class DmsfFolder < ActiveRecord::Base end last_update end + + # Number of items in the folder + def items + subfolders.visible.count + + files.visible.count + + folder_links.visible.count + + file_links.visible.count + + url_links.visible.count + end private diff --git a/app/views/dmsf/_dir.html.erb b/app/views/dmsf/_dir.html.erb index 574e3b6c..fe8c073f 100644 --- a/app/views/dmsf/_dir.html.erb +++ b/app/views/dmsf/_dir.html.erb @@ -1,7 +1,9 @@ <%#= +# encoding: utf-8 +# # Redmine plugin for Document Management System "Features" # -# Copyright (C) 2011-14 Karel Pičman +# Copyright (C) 2011-15 Karel Pičman # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -30,7 +32,7 @@ <% if link %>
    <%= link.path %>
    <% else %> -
    [<%= subfolder.files.visible.count + subfolder.file_links.visible.count %>]
    +
    [<%= subfolder.items %>]
    <% end %> diff --git a/app/views/dmsf/_dir_trash.html.erb b/app/views/dmsf/_dir_trash.html.erb index 31f6a452..9159d9b4 100644 --- a/app/views/dmsf/_dir_trash.html.erb +++ b/app/views/dmsf/_dir_trash.html.erb @@ -1,4 +1,6 @@ <%#= +# encoding: utf-8 +# # Redmine plugin for Document Management System "Features" # # Copyright (C) 2011-15 Karel Pičman @@ -27,7 +29,7 @@ <% if link %>
    <%= link.path %>
    <% else %> -
    [<%= subfolder.files.visible.count + subfolder.file_links.visible.count %>]
    +
    [<%= subfolder.items %>]
    <% end %> diff --git a/app/views/dmsf/show.html.erb b/app/views/dmsf/show.html.erb index 2adadf82..2187c514 100644 --- a/app/views/dmsf/show.html.erb +++ b/app/views/dmsf/show.html.erb @@ -263,7 +263,7 @@ jQuery('div.controls').prependTo(jQuery('#browser_wrapper div.fg-toolbar')[0]); }, 'fnInfoCallback': function( oSettings, iStart, iEnd, iMax, iTotal, sPre ) { - return "<%= l(:label_number_of_folders)%>: <%= @subfolders.count + @dir_links.count %>, <%= l(:label_number_of_documents)%>: <%= @files.count + @file_links.count %>"; + return "<%= l(:label_number_of_folders)%>: <%= @subfolders.count + @dir_links.count %>, <%= l(:label_number_of_documents)%>: <%= @files.count + @file_links.count + @url_links.count %>"; } }); diff --git a/app/views/dmsf/trash.html.erb b/app/views/dmsf/trash.html.erb index 8478b707..e7d8a42c 100644 --- a/app/views/dmsf/trash.html.erb +++ b/app/views/dmsf/trash.html.erb @@ -1,4 +1,6 @@ <%#= +# encoding: utf-8 +# # Redmine plugin for Document Management System "Features" # # Copyright (C) 2011-15 Karel Pičman @@ -184,7 +186,7 @@ jQuery('div.controls').prependTo(jQuery('#browser_wrapper div.fg-toolbar')[0]); }, 'fnInfoCallback': function( oSettings, iStart, iEnd, iMax, iTotal, sPre ) { - return "<%= l(:label_number_of_folders)%>: <%= @subfolders.count + @dir_links.count %>, <%= l(:label_number_of_documents)%>: <%= @files.count + @file_links.count %>"; + return "<%= l(:label_number_of_folders)%>: <%= @subfolders.count + @dir_links.count %>, <%= l(:label_number_of_documents)%>: <%= @files.count + @file_links.count + @url_links.count %>"; } }); diff --git a/config/locales/cs.yml b/config/locales/cs.yml index cc12a50e..6f703555 100644 --- a/config/locales/cs.yml +++ b/config/locales/cs.yml @@ -166,7 +166,7 @@ cs: notice_entries_deleted: Položky smazány warning_some_entries_were_not_deleted: "Některé položky nebyly smazány: %{entries}" title_delete_checked: Smaž vybrané - title_number_of_files_in_directory: Počet souborů ve složce + title_items: položek title_filename_for_download: Název Zip archívu ke stažení label_number_of_folders: Složky label_number_of_documents: Dokumenty diff --git a/config/locales/de.yml b/config/locales/de.yml index 422750cb..708375a7 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -164,7 +164,7 @@ de: notice_entries_deleted: Einträge löschen warning_some_entries_were_not_deleted: "Enige Einträge wurden nicht gelöscht: %{entries}" title_delete_checked: Löschen ausgewählt - title_number_of_files_in_directory: Dateianzahl in dem Ordner + title_items: items title_filename_for_download: Dateiname beim Herunterladen oder in ZIP-Archiv verwenden label_number_of_folders: Order label_number_of_documents: Dokumente diff --git a/config/locales/en.yml b/config/locales/en.yml index 4ec072d1..ef0098cc 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -164,7 +164,7 @@ en: notice_entries_deleted: Entries deleted warning_some_entries_were_not_deleted: "Some entries weren't deleted: %{entries}" title_delete_checked: Delete checked - title_number_of_files_in_directory: Number of files in directory + title_items: items title_filename_for_download: Filename used for download or in Zip archive label_number_of_folders: Folders label_number_of_documents: Documents diff --git a/config/locales/es.yml b/config/locales/es.yml index b5e2f353..a649b72e 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -166,7 +166,7 @@ es: notice_entries_deleted: Entradas eliminadas warning_some_entries_were_not_deleted: "Algunas entradas no fueron eliminadas: %{entries}" title_delete_checked: Eliminación chequeada - title_number_of_files_in_directory: Número de archivos en directorio + title_items: items title_filename_for_download: Nombre de archivo utilizado para descargar o crear zip label_number_of_folders: Directorios label_number_of_documents: Documentos diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 1e551f06..aa5318b8 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -163,7 +163,7 @@ fr: notice_entries_deleted: Elément(s) supprimé(s) warning_some_entries_were_not_deleted: "Certains éléments n'ont pas été supprimés : %{entries}" title_delete_checked: Supprimer les éléments sélectionnés - title_number_of_files_in_directory: Nombre de fichiers dans le dossier + title_items: items title_filename_for_download: "Nom du fichier à utiliser lors du téléchargement ou de l'archive ZIP" label_number_of_folders: Dossiers label_number_of_documents: Fichiers diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 244304f2..5ce5eb39 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -166,7 +166,7 @@ ja: notice_entries_deleted: エントリーを削除しました warning_some_entries_were_not_deleted: "いくつかのエントリーは削除されませんでした: %{entries}" title_delete_checked: チェックしたものを削除します - title_number_of_files_in_directory: フォルダ内のファイル数 + title_items: items title_filename_for_download: ファイル名はダウンロードまたは Zip アーカイブに使われます label_number_of_folders: フォルダ label_number_of_documents: 文書 diff --git a/config/locales/pl.yml b/config/locales/pl.yml index b3b81f4e..744814d3 100644 --- a/config/locales/pl.yml +++ b/config/locales/pl.yml @@ -167,7 +167,7 @@ pl: notice_entries_deleted: Wpisy usunięte warning_some_entries_were_not_deleted: "Niektóre wpisy nie zostały usunięte: %{entries}" title_delete_checked: Usuń zaznaczone - title_number_of_files_in_directory: Liczba plików w folderze + title_items: items title_filename_for_download: Nazwa pliku używana do pobierania lub tworzenai archiwum Zip label_number_of_folders: Foldery label_number_of_documents: Dokumenty diff --git a/config/locales/ru.yml b/config/locales/ru.yml index 7cc0a83b..2d315615 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -166,7 +166,7 @@ ru: notice_entries_deleted: Файлы удалены warning_some_entries_were_not_deleted: "Некоторые файлы не были удалены: %{entries}" title_delete_checked: Удалить выбранные документы - title_number_of_files_in_directory: Количество файлов в директории + title_items: items title_filename_for_download: Имя файла для скачиваемого архива label_number_of_folders: Папок label_number_of_documents: Документов diff --git a/config/locales/sl.yml b/config/locales/sl.yml index 31f40f3a..4c341e09 100644 --- a/config/locales/sl.yml +++ b/config/locales/sl.yml @@ -166,7 +166,7 @@ sl: notice_entries_deleted: Izbrane enote izbrisane warning_some_entries_were_not_deleted: "Nekatere enote niso izbrisane: %{entries}" title_delete_checked: Izbriši izbrano - title_number_of_files_in_directory: Število datotek v mapi + title_items: items title_filename_for_download: Naziv datoteke za prenos dol ali Zip arhiva label_number_of_folders: Mapa label_number_of_documents: Dokumentacija diff --git a/config/locales/zh-TW.yml b/config/locales/zh-TW.yml index 06922d62..f12c1796 100644 --- a/config/locales/zh-TW.yml +++ b/config/locales/zh-TW.yml @@ -167,7 +167,7 @@ zh-TW: notice_entries_deleted: 項目己刪除 warning_some_entries_were_not_deleted: "部份項目無法刪除: %{entries}" title_delete_checked: 刪除選取項目 - title_number_of_files_in_directory: 資料夾的檔案數量 + title_items: items title_filename_for_download: 下載時的檔名,或是ZIP的檔案名稱。 label_number_of_folders: 資料夾 label_number_of_documents: 文件檔案 diff --git a/config/locales/zh.yml b/config/locales/zh.yml index 38e4cd59..f64e4892 100644 --- a/config/locales/zh.yml +++ b/config/locales/zh.yml @@ -166,7 +166,7 @@ zh: notice_entries_deleted: 条目已删除 warning_some_entries_were_not_deleted: "某些条目未被删除: %{entries}" title_delete_checked: 删除选中 - title_number_of_files_in_directory: 目录总文件个数 + title_items: items title_filename_for_download: 用于下载或zip归档的文件名 label_number_of_folders: Folders label_number_of_documents: Documents From 34ba0624cfc38844c26807855f61ea5c15c83378 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Pi=C4=8Dman?= Date: Wed, 22 Apr 2015 10:05:12 +0200 Subject: [PATCH 18/52] Revision view, delete revision bug #378 --- app/views/dmsf_files/show.html.erb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/views/dmsf_files/show.html.erb b/app/views/dmsf_files/show.html.erb index e9c0b6f0..a82445c1 100644 --- a/app/views/dmsf_files/show.html.erb +++ b/app/views/dmsf_files/show.html.erb @@ -1,4 +1,6 @@ <%#= +# encoding: utf-8 +# # Redmine plugin for Document Management System "Features" # # Copyright (C) 2011 Vít Jonáš @@ -83,7 +85,7 @@ delete_revision_path(revision), :data => {:confirm => l(:text_are_you_sure)}, :title => l(:title_delete_revision), - :class => 'icon icon-dmsf-rev-delete' if @file_delete_allowed %> + :class => 'icon icon-dmsf-rev-delete' if @file_delete_allowed && (@file.revisions.visible.count > 1) %> <%= l(:info_revision, :rev => revision.id) %> <%= (revision.source_revision.nil? ? l(:label_created) : l(:label_changed)).downcase %> From 28aac0314ca82284c7debc2a6087523433b94005 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Pi=C4=8Dman?= Date: Fri, 24 Apr 2015 10:15:05 +0200 Subject: [PATCH 19/52] Internal Error 500 when dmsf page is accessed #380 --- app/views/dmsf/show.html.erb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/views/dmsf/show.html.erb b/app/views/dmsf/show.html.erb index 2187c514..ac20e7ae 100644 --- a/app/views/dmsf/show.html.erb +++ b/app/views/dmsf/show.html.erb @@ -153,7 +153,11 @@ :title => subfolder.title }) %> <% end %> - <% @dir_links.each do |link| %> + <% @dir_links.each do |link| %> + <% unless (link.target_project && link.target_folder) %> + <% Rails.logger.error "Error: dmsf_link id #{link.id} has no target!" %> + <% next %> + <% end %> <%= render(:partial => 'dir', :locals => { From 266676d9a9ec0d60a74d922e28f34094f1297da9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Pi=C4=8Dman?= Date: Fri, 24 Apr 2015 13:32:01 +0200 Subject: [PATCH 20/52] Internal Error 500 when dmsf page is accessed #380 --- app/controllers/dmsf_controller.rb | 15 +++++-------- app/controllers/dmsf_links_controller.rb | 11 +++++----- app/controllers/dmsf_upload_controller.rb | 17 ++++----------- app/models/dmsf_link.rb | 14 ++++++------ app/views/dmsf/_dir.html.erb | 26 +++++++++++++---------- app/views/dmsf/_file.html.erb | 2 ++ app/views/dmsf/show.html.erb | 2 +- app/views/dmsf_files/show.html.erb | 2 +- 8 files changed, 39 insertions(+), 50 deletions(-) diff --git a/app/controllers/dmsf_controller.rb b/app/controllers/dmsf_controller.rb index 24497ec3..b91025ff 100644 --- a/app/controllers/dmsf_controller.rb +++ b/app/controllers/dmsf_controller.rb @@ -240,20 +240,15 @@ class DmsfController < ApplicationController end end - def entries_email - if (Rails::VERSION::MAJOR > 3) - @email_params = e_params - else - @email_params = params[:email] - end - if @email_params[:to].strip.blank? + def entries_email + if params[:email][:to].strip.blank? flash.now[:error] = l(:error_email_to_must_be_entered) render :action => 'email_entries' return end - DmsfMailer.send_documents(@project, User.current, @email_params).deliver - File.delete(@email_params['zipped_content']) - flash[:notice] = l(:notice_email_sent, @email_params['to']) + DmsfMailer.send_documents(@project, User.current, params[:email]).deliver + File.delete(params[:email][:zipped_content]) + flash[:notice] = l(:notice_email_sent, params[:email][:to]) redirect_to dmsf_folder_path(:id => @project, :folder_id => @folder) end diff --git a/app/controllers/dmsf_links_controller.rb b/app/controllers/dmsf_links_controller.rb index c960128b..a49e34f2 100644 --- a/app/controllers/dmsf_links_controller.rb +++ b/app/controllers/dmsf_links_controller.rb @@ -1,3 +1,5 @@ +# encoding: utf-8 +# # Redmine plugin for Document Management System "Features" # # Copyright (C) 2011-15 Karel Pičman @@ -24,12 +26,9 @@ class DmsfLinksController < ApplicationController before_filter :find_link_project before_filter :authorize - def new - if (Rails::VERSION::MAJOR > 3) - @dmsf_link = DmsfLink.new(l_params) - else - @dmsf_link = DmsfLink.new(:project_id => params[:project_id]) - end + def new + @dmsf_link = DmsfLink.new + @dmsf_link.project_id = params[:project_id] if params[:dmsf_link].present? # Reload diff --git a/app/controllers/dmsf_upload_controller.rb b/app/controllers/dmsf_upload_controller.rb index d88a5562..af1b9638 100644 --- a/app/controllers/dmsf_upload_controller.rb +++ b/app/controllers/dmsf_upload_controller.rb @@ -53,12 +53,8 @@ class DmsfUploadController < ApplicationController end # async single file upload handling - def upload_file - if (Rails::VERSION::MAJOR > 3) - @tempfile = params.require(:file) - else - @tempfile = params[:file] - end + def upload_file + @tempfile = params[:file] unless @tempfile.original_filename render_404 return @@ -107,13 +103,8 @@ class DmsfUploadController < ApplicationController end end - def commit_files - if (Rails::VERSION::MAJOR > 3) - commited_files = params.require(:commited_files) - else - commited_files = params[:commited_files] - end - commit_files_internal commited_files + def commit_files + commit_files_internal params[:commited_files] end # REST API file commit diff --git a/app/models/dmsf_link.rb b/app/models/dmsf_link.rb index d34d76fc..f21a5864 100644 --- a/app/models/dmsf_link.rb +++ b/app/models/dmsf_link.rb @@ -95,15 +95,13 @@ class DmsfLink < ActiveRecord::Base end def path - if self.target_type == DmsfFile.model_name - file = self.target_file - path = file.dmsf_path.map { |element| element.is_a?(DmsfFile) ? element.name : element.title }.join('/') if file - else - folder = self.target_folder - path = folder.dmsf_path_str if folder + if self.target_type == DmsfFile.model_name + path = self.target_file.dmsf_path.map { |element| element.is_a?(DmsfFile) ? element.name : element.title }.join('/') if self.target_file + else + path = self.target_folder ? self.target_folder.dmsf_path_str : '' end - path.insert(0, "#{self.target_project.name}:") if self.project_id != self.target_project_id && path - if path.length > 50 + path.insert(0, "#{self.target_project.name}:") if self.project_id != self.target_project_id + if path && path.length > 50 return "#{path[0, 25]}...#{path[-25, 25]}" end path diff --git a/app/views/dmsf/_dir.html.erb b/app/views/dmsf/_dir.html.erb index fe8c073f..4ccf038e 100644 --- a/app/views/dmsf/_dir.html.erb +++ b/app/views/dmsf/_dir.html.erb @@ -20,8 +20,8 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. %> -<% locked_for_user = subfolder.locked_for_user? %> -<% locked = subfolder.locked? %> +<% locked_for_user = subfolder && subfolder.locked_for_user? %> +<% locked = subfolder && subfolder.locked? %> <%= check_box_tag(name, id, false, :title => l(:title_check_for_zip_download_or_email)) %> @@ -36,7 +36,7 @@ <% end %> -<%= format_time(subfolder.modified) %> +<%= format_time(subfolder.modified) if subfolder %> <% if locked_for_user %> <% if subfolder.lock.reverse[0].user %> <%= link_to('', @@ -54,20 +54,24 @@ -<%= h(subfolder.user) %> +<%= h(subfolder.user) if subfolder %> <% if @folder_manipulation_allowed %>
    <% unless locked %> <%= link_to('', edit_dmsf_path(:id => project, :folder_id => subfolder), - :title => l(:link_edit, :title => h(subfolder.title)), + :title => l(:link_edit, :title => subfolder ? h(subfolder.title) : project.name), :class => 'icon icon-edit') %> - <%= link_to('', - lock_dmsf_path(:id => project, :folder_id => subfolder), - :title => l(:title_lock_file), - :class => 'icon icon-dmsf-lock') %> - <% if subfolder.notification %> + <% if subfolder %> + <%= link_to('', + lock_dmsf_path(:id => project, :folder_id => subfolder), + :title => l(:title_lock_file), + :class => 'icon icon-dmsf-lock') %> + <% else %> + + <% end %> + <% if (subfolder && subfolder.notification) || (!subfolder && project.dmsf_notification) %> <%= link_to('', notify_deactivate_dmsf_path(:id => project, :folder_id => subfolder), :title => l(:title_notifications_active_deactivate), @@ -106,5 +110,5 @@ 0 0 -<%= subfolder.updated_at.to_i %> +<%= subfolder.updated_at.to_i if subfolder %> 0 \ No newline at end of file diff --git a/app/views/dmsf/_file.html.erb b/app/views/dmsf/_file.html.erb index 3e201646..2d9e0058 100644 --- a/app/views/dmsf/_file.html.erb +++ b/app/views/dmsf/_file.html.erb @@ -1,4 +1,6 @@ <%#= +# encode: utf-8 +# # Redmine plugin for Document Management System "Features" # # Copyright (C) 2011-15 Karel Pičman diff --git a/app/views/dmsf/show.html.erb b/app/views/dmsf/show.html.erb index ac20e7ae..4e76ba60 100644 --- a/app/views/dmsf/show.html.erb +++ b/app/views/dmsf/show.html.erb @@ -154,7 +154,7 @@ <% end %> <% @dir_links.each do |link| %> - <% unless (link.target_project && link.target_folder) %> + <% unless link.target_project %> <% Rails.logger.error "Error: dmsf_link id #{link.id} has no target!" %> <% next %> <% end %> diff --git a/app/views/dmsf_files/show.html.erb b/app/views/dmsf_files/show.html.erb index a82445c1..aec16f88 100644 --- a/app/views/dmsf_files/show.html.erb +++ b/app/views/dmsf_files/show.html.erb @@ -90,7 +90,7 @@ <%= l(:info_revision, :rev => revision.id) %> <%= (revision.source_revision.nil? ? l(:label_created) : l(:label_changed)).downcase %> <%= l(:info_changed_by_user, :changed => format_time(revision.updated_at)) %> - <%= link_to(revision.user.name, user_path(revision.user)) %> + <%= link_to(revision.user.name, user_path(revision.user)) if revision.user %>
    From 48d83d571f400045874ce93a979deb4715e8a242 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Pi=C4=8Dman?= Date: Fri, 24 Apr 2015 14:43:02 +0200 Subject: [PATCH 21/52] internal 500 error : 1.5.1 stable with redmine 3.0.1 when search in dmsf enabled project #373 --- app/models/dmsf_file.rb | 6 +++--- app/models/dmsf_folder.rb | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/models/dmsf_file.rb b/app/models/dmsf_file.rb index f6ac3dd9..d6ece1f6 100644 --- a/app/models/dmsf_file.rb +++ b/app/models/dmsf_file.rb @@ -320,11 +320,11 @@ class DmsfFile < ActiveRecord::Base results = [] results_count = 0 - includes(:project, :revisions). + joins(:project, :revisions). where(project_conditions.join(' AND ') + " AND #{DmsfFile.table_name}.deleted = :false", {:false => false}).scoping do where(find_options[:conditions]).order(find_options[:order]).scoping do - results_count = count(:all) - results = find(:all, limit_options) + results_count = count(:all) + results = where(limit_options) end end diff --git a/app/models/dmsf_folder.rb b/app/models/dmsf_folder.rb index f87d38c8..3093699e 100644 --- a/app/models/dmsf_folder.rb +++ b/app/models/dmsf_folder.rb @@ -309,13 +309,13 @@ class DmsfFolder < ActiveRecord::Base results = [] results_count = 0 - includes(:project). + joins(:project). where(project_conditions.join(' AND ')).scoping do where(find_options[:conditions]).order(find_options[:order]).scoping do results_count = count(:all) - results = find(:all, limit_options) + results = where(limit_options) end - end + end [results, results_count] end From 89b95f0c722f82a4d5c91425764613056c702a8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Pi=C4=8Dman?= Date: Fri, 24 Apr 2015 14:46:15 +0200 Subject: [PATCH 22/52] internal 500 error : 1.5.1 stable with redmine 3.0.1 when search in dmsf enabled project #373 --- app/models/dmsf_file.rb | 2 +- app/models/dmsf_folder.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/dmsf_file.rb b/app/models/dmsf_file.rb index d6ece1f6..d4336258 100644 --- a/app/models/dmsf_file.rb +++ b/app/models/dmsf_file.rb @@ -323,7 +323,7 @@ class DmsfFile < ActiveRecord::Base joins(:project, :revisions). where(project_conditions.join(' AND ') + " AND #{DmsfFile.table_name}.deleted = :false", {:false => false}).scoping do where(find_options[:conditions]).order(find_options[:order]).scoping do - results_count = count(:all) + results_count = count results = where(limit_options) end end diff --git a/app/models/dmsf_folder.rb b/app/models/dmsf_folder.rb index 3093699e..b0a73eff 100644 --- a/app/models/dmsf_folder.rb +++ b/app/models/dmsf_folder.rb @@ -312,7 +312,7 @@ class DmsfFolder < ActiveRecord::Base joins(:project). where(project_conditions.join(' AND ')).scoping do where(find_options[:conditions]).order(find_options[:order]).scoping do - results_count = count(:all) + results_count = count results = where(limit_options) end end From 85395480b5e50a115fd4be0db45f01722afba477 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Pi=C4=8Dman?= Date: Fri, 24 Apr 2015 16:01:09 +0200 Subject: [PATCH 23/52] Error when trying to uninstall DMSF #384 --- .../20120822100401_create_dmsf_workflows.rb | 4 ++- ...120822100402_create_dmsf_workflow_steps.rb | 4 ++- ...3_create_dmsf_workflow_step_assignments.rb | 4 ++- ...00404_create_dmsf_workflow_step_actions.rb | 4 ++- db/migrate/20130819013955_update_projects.rb | 4 ++- .../20131108141401_add_index_to_dmsf_files.rb | 4 ++- ...0131108141402_add_index_to_dmsf_folders.rb | 4 ++- ...3141401_add_index_to_dmsf_file_revision.rb | 4 ++- .../20131113141402_add_index_to_dmsf_lock.rb | 4 ++- .../20131113141403_create_dmsf_links.rb | 4 ++- db/migrate/20140314132501_notifications_on.rb | 6 +++-- db/migrate/20140519133201_trash_bin.rb | 4 ++- ...41015132701_remove_folder_from_revision.rb | 4 ++- ...0141205143001_remove_uniqueness_from_wf.rb | 10 +++----- .../20150120152101_notifications_nullable.rb | 2 +- db/migrate/20150130052716_add_external.rb | 25 ++++++++++++++++--- .../20150202010301_add_user_to_links.rb | 20 +++++++++++++++ 17 files changed, 86 insertions(+), 25 deletions(-) diff --git a/db/migrate/20120822100401_create_dmsf_workflows.rb b/db/migrate/20120822100401_create_dmsf_workflows.rb index 8bc9de19..bd35ae63 100644 --- a/db/migrate/20120822100401_create_dmsf_workflows.rb +++ b/db/migrate/20120822100401_create_dmsf_workflows.rb @@ -1,6 +1,8 @@ +# encoding: utf-8 +# # Redmine plugin for Document Management System "Features" # -# Copyright (C) 2013 Karel Picman +# Copyright (C) 2011-15 Karel Pičman # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff --git a/db/migrate/20120822100402_create_dmsf_workflow_steps.rb b/db/migrate/20120822100402_create_dmsf_workflow_steps.rb index d02c29dc..b44c92a6 100644 --- a/db/migrate/20120822100402_create_dmsf_workflow_steps.rb +++ b/db/migrate/20120822100402_create_dmsf_workflow_steps.rb @@ -1,6 +1,8 @@ +# encoding: utf-8 +# # Redmine plugin for Document Management System "Features" # -# Copyright (C) 2013 Karel Picman +# Copyright (C) 2011-15 Karel Pičman # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff --git a/db/migrate/20120822100403_create_dmsf_workflow_step_assignments.rb b/db/migrate/20120822100403_create_dmsf_workflow_step_assignments.rb index c0f4bd06..f51b37c1 100644 --- a/db/migrate/20120822100403_create_dmsf_workflow_step_assignments.rb +++ b/db/migrate/20120822100403_create_dmsf_workflow_step_assignments.rb @@ -1,6 +1,8 @@ +# encoding: utf-8 +# # Redmine plugin for Document Management System "Features" # -# Copyright (C) 2013 Karel Picman +# Copyright (C) 2011-15 Karel Pičman # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff --git a/db/migrate/20120822100404_create_dmsf_workflow_step_actions.rb b/db/migrate/20120822100404_create_dmsf_workflow_step_actions.rb index 7a076a5e..a4243d4d 100644 --- a/db/migrate/20120822100404_create_dmsf_workflow_step_actions.rb +++ b/db/migrate/20120822100404_create_dmsf_workflow_step_actions.rb @@ -1,6 +1,8 @@ +# encoding: utf-8 +# # Redmine plugin for Document Management System "Features" # -# Copyright (C) 2013 Karel Picman +# Copyright (C) 2011-15 Karel Pičman # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff --git a/db/migrate/20130819013955_update_projects.rb b/db/migrate/20130819013955_update_projects.rb index 58901ac2..f4f5be61 100644 --- a/db/migrate/20130819013955_update_projects.rb +++ b/db/migrate/20130819013955_update_projects.rb @@ -1,6 +1,8 @@ +# encoding: utf-8 +# # Redmine plugin for Document Management System "Features" # -# Copyright (C) 2013 Karel Picman +# Copyright (C) 2011-15 Karel Pičman # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff --git a/db/migrate/20131108141401_add_index_to_dmsf_files.rb b/db/migrate/20131108141401_add_index_to_dmsf_files.rb index 512715e1..d9958433 100644 --- a/db/migrate/20131108141401_add_index_to_dmsf_files.rb +++ b/db/migrate/20131108141401_add_index_to_dmsf_files.rb @@ -1,6 +1,8 @@ +# encoding: utf-8 +# # Redmine plugin for Document Management System "Features" # -# Copyright (C) 2013 Karel Picman +# Copyright (C) 2011-15 Karel Pičman # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff --git a/db/migrate/20131108141402_add_index_to_dmsf_folders.rb b/db/migrate/20131108141402_add_index_to_dmsf_folders.rb index 991c1aa5..8ed8b912 100644 --- a/db/migrate/20131108141402_add_index_to_dmsf_folders.rb +++ b/db/migrate/20131108141402_add_index_to_dmsf_folders.rb @@ -1,6 +1,8 @@ +# encoding: utf-8 +# # Redmine plugin for Document Management System "Features" # -# Copyright (C) 2013 Karel Picman +# Copyright (C) 2011-15 Karel Pičman # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff --git a/db/migrate/20131113141401_add_index_to_dmsf_file_revision.rb b/db/migrate/20131113141401_add_index_to_dmsf_file_revision.rb index f0746052..e8e0b029 100644 --- a/db/migrate/20131113141401_add_index_to_dmsf_file_revision.rb +++ b/db/migrate/20131113141401_add_index_to_dmsf_file_revision.rb @@ -1,6 +1,8 @@ +# encoding: utf-8 +# # Redmine plugin for Document Management System "Features" # -# Copyright (C) 2013 Karel Picman +# Copyright (C) 2011-15 Karel Pičman # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff --git a/db/migrate/20131113141402_add_index_to_dmsf_lock.rb b/db/migrate/20131113141402_add_index_to_dmsf_lock.rb index 9e70a488..fd598a8f 100644 --- a/db/migrate/20131113141402_add_index_to_dmsf_lock.rb +++ b/db/migrate/20131113141402_add_index_to_dmsf_lock.rb @@ -1,6 +1,8 @@ +# encoding: utf-8 +# # Redmine plugin for Document Management System "Features" # -# Copyright (C) 2013 Karel Picman +# Copyright (C) 2011-15 Karel Pičman # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff --git a/db/migrate/20131113141403_create_dmsf_links.rb b/db/migrate/20131113141403_create_dmsf_links.rb index 901a2834..2fcc9e8e 100644 --- a/db/migrate/20131113141403_create_dmsf_links.rb +++ b/db/migrate/20131113141403_create_dmsf_links.rb @@ -1,6 +1,8 @@ +# encoding: utf-8 +# # Redmine plugin for Document Management System "Features" # -# Copyright (C) 2014 Karel Pičman +# Copyright (C) 2011-15 Karel Pičman # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff --git a/db/migrate/20140314132501_notifications_on.rb b/db/migrate/20140314132501_notifications_on.rb index 2ba3338b..5e0a2d28 100644 --- a/db/migrate/20140314132501_notifications_on.rb +++ b/db/migrate/20140314132501_notifications_on.rb @@ -1,6 +1,8 @@ +# encoding: utf-8 +# # Redmine plugin for Document Management System "Features" # -# Copyright (C) 2014 Karel Pičman +# Copyright (C) 2011-15 Karel Pičman # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -25,6 +27,6 @@ class NotificationsOn < ActiveRecord::Migration def down change_column :projects, :dmsf_notification, :boolean, :default => false, :null => true - change_column :dmsf_folders, :notification, :boolean, :default => false, :null => false + change_column :dmsf_folders, :notification, :boolean, :default => false end end \ No newline at end of file diff --git a/db/migrate/20140519133201_trash_bin.rb b/db/migrate/20140519133201_trash_bin.rb index 109e9706..32ba217f 100644 --- a/db/migrate/20140519133201_trash_bin.rb +++ b/db/migrate/20140519133201_trash_bin.rb @@ -1,6 +1,8 @@ +# encoding: utf-8 +# # Redmine plugin for Document Management System "Features" # -# Copyright (C) 2011-14 Karel Pičman +# Copyright (C) 2011-15 Karel Pičman # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff --git a/db/migrate/20141015132701_remove_folder_from_revision.rb b/db/migrate/20141015132701_remove_folder_from_revision.rb index 3f3bc787..007fdd43 100644 --- a/db/migrate/20141015132701_remove_folder_from_revision.rb +++ b/db/migrate/20141015132701_remove_folder_from_revision.rb @@ -1,6 +1,8 @@ +# encoding: utf-8 +# # Redmine plugin for Document Management System "Features" # -# Copyright (C) 2011-14 Karel Pičman +# Copyright (C) 2011-15 Karel Pičman # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff --git a/db/migrate/20141205143001_remove_uniqueness_from_wf.rb b/db/migrate/20141205143001_remove_uniqueness_from_wf.rb index 2431efa9..886c7a4a 100644 --- a/db/migrate/20141205143001_remove_uniqueness_from_wf.rb +++ b/db/migrate/20141205143001_remove_uniqueness_from_wf.rb @@ -1,6 +1,8 @@ +# encoding: utf-8 +# # Redmine plugin for Document Management System "Features" # -# Copyright (C) 2011-14 Karel Pičman +# Copyright (C) 2011-15 Karel Pičman # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -19,9 +21,5 @@ class RemoveUniquenessFromWf < ActiveRecord::Migration def up remove_index :dmsf_workflows, :name - end - - def down - add_index :dmsf_workflows, [:name], :unique => true - end + end end \ No newline at end of file diff --git a/db/migrate/20150120152101_notifications_nullable.rb b/db/migrate/20150120152101_notifications_nullable.rb index 5155b23c..ff835e78 100644 --- a/db/migrate/20150120152101_notifications_nullable.rb +++ b/db/migrate/20150120152101_notifications_nullable.rb @@ -2,7 +2,7 @@ # # Redmine plugin for Document Management System "Features" # -# Copyright (C) 2011-15 Karel Pičman +# Copyright (C) 2011-15 Karel Pičman # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff --git a/db/migrate/20150130052716_add_external.rb b/db/migrate/20150130052716_add_external.rb index 8c083df7..27f826f4 100644 --- a/db/migrate/20150130052716_add_external.rb +++ b/db/migrate/20150130052716_add_external.rb @@ -1,13 +1,30 @@ +# encoding: utf-8 +# +# Redmine plugin for Document Management System "Features" +# +# Copyright (C) 2011-15 Karel Pičman +# +# 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 AddExternal < ActiveRecord::Migration def up change_column :dmsf_links, :target_id, :integer, :null => true - add_column :dmsf_links, :external_url, :string, :null => true end - def down - change_column :dmsf_links, :target_id, :integer, :null => false - + def down remove_column :dmsf_links, :external_url end end diff --git a/db/migrate/20150202010301_add_user_to_links.rb b/db/migrate/20150202010301_add_user_to_links.rb index 55ed3672..43d8e383 100644 --- a/db/migrate/20150202010301_add_user_to_links.rb +++ b/db/migrate/20150202010301_add_user_to_links.rb @@ -1,3 +1,23 @@ +# encoding: utf-8 +# +# Redmine plugin for Document Management System "Features" +# +# Copyright (C) 2011-15 Karel Pičman +# +# 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 AddUserToLinks < ActiveRecord::Migration def up add_column :dmsf_links, :user_id, :integer From 9610ec9efe3134cd42482fccd8e9e550cb1ba79e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Pi=C4=8Dman?= Date: Wed, 29 Apr 2015 14:56:02 +0200 Subject: [PATCH 24/52] Wrong sorting by Modified column #387 --- app/views/dmsf/_dir.html.erb | 2 +- app/views/dmsf/_dir_trash.html.erb | 4 ++-- app/views/dmsf/_url.html.erb | 8 +++++--- app/views/dmsf/_url_trash.html.erb | 6 ++++-- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/app/views/dmsf/_dir.html.erb b/app/views/dmsf/_dir.html.erb index 4ccf038e..dbce36b0 100644 --- a/app/views/dmsf/_dir.html.erb +++ b/app/views/dmsf/_dir.html.erb @@ -110,5 +110,5 @@ 0 0 -<%= subfolder.updated_at.to_i if subfolder %> +<%= subfolder.modified.to_i if subfolder %> 0 \ No newline at end of file diff --git a/app/views/dmsf/_dir_trash.html.erb b/app/views/dmsf/_dir_trash.html.erb index 9159d9b4..4f311976 100644 --- a/app/views/dmsf/_dir_trash.html.erb +++ b/app/views/dmsf/_dir_trash.html.erb @@ -34,7 +34,7 @@ - <%= format_time(subfolder.updated_at) %> + <%= format_time(subfolder.modified) if subfolder %> @@ -56,5 +56,5 @@ 0 0 -<%= subfolder.updated_at.to_i %> +<%= subfolder.modified.to_i %> 0 \ No newline at end of file diff --git a/app/views/dmsf/_url.html.erb b/app/views/dmsf/_url.html.erb index 0b4712b5..15699e0b 100644 --- a/app/views/dmsf/_url.html.erb +++ b/app/views/dmsf/_url.html.erb @@ -1,7 +1,9 @@ <%#= +# encoding: utf-8 +# # Redmine plugin for Document Management System "Features" # -# Copyright (C) 2011-14 Karel Pičman +# Copyright (C) 2011-15 Karel Pičman # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -55,5 +57,5 @@ 1 - - +link.updated_at.to_i + \ No newline at end of file diff --git a/app/views/dmsf/_url_trash.html.erb b/app/views/dmsf/_url_trash.html.erb index b8ff5a6b..be7ebe7e 100644 --- a/app/views/dmsf/_url_trash.html.erb +++ b/app/views/dmsf/_url_trash.html.erb @@ -1,4 +1,6 @@ <%#= +# encoding: utf-8 +# # Redmine plugin for Document Management System "Features" # # Copyright (C) 2011-15 Karel Pičman @@ -50,5 +52,5 @@ 1 - - +link.updated_at.to_i + \ No newline at end of file From 49290ea3d4c7c158b7ce50155fe832cbf460c22b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Pi=C4=8Dman?= Date: Wed, 29 Apr 2015 14:57:35 +0200 Subject: [PATCH 25/52] Wrong sorting by Modified column #387 --- app/views/dmsf/_dir_trash.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/dmsf/_dir_trash.html.erb b/app/views/dmsf/_dir_trash.html.erb index 4f311976..e708653b 100644 --- a/app/views/dmsf/_dir_trash.html.erb +++ b/app/views/dmsf/_dir_trash.html.erb @@ -56,5 +56,5 @@ 0 0 -<%= subfolder.modified.to_i %> +<%= subfolder.modified.to_i if subfolder %> 0 \ No newline at end of file From a9188a2a5531ad39b1708bcbd4e9640c1aba1bfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Pi=C4=8Dman?= Date: Mon, 4 May 2015 08:15:46 +0200 Subject: [PATCH 26/52] Error while trying to move a file #375 --- lib/redmine_dmsf/webdav/dmsf_resource.rb | 42 +++++++++--------------- 1 file changed, 16 insertions(+), 26 deletions(-) diff --git a/lib/redmine_dmsf/webdav/dmsf_resource.rb b/lib/redmine_dmsf/webdav/dmsf_resource.rb index 0d3488b1..8a024cf6 100644 --- a/lib/redmine_dmsf/webdav/dmsf_resource.rb +++ b/lib/redmine_dmsf/webdav/dmsf_resource.rb @@ -252,55 +252,42 @@ module RedmineDmsf # Behavioural differences between collection and single entity # Todo: Support overwrite between both types of entity, and implement better checking def move(dest, overwrite) - # All of this should carry accrross the ResourceProxy frontend, we ensure this to - # prevent unexpected errors - if dest.is_a?(ResourceProxy) - resource = dest.resource - else - resource = dest - end - + # prevent unexpected errors + resource = dest.is_a?(ResourceProxy) ? dest.resource : dest + return PreconditionFailed if !resource.is_a?(DmsfResource) || resource.project.nil? || resource.project.id == 0 - + parent = resource.parent + if (collection?) - #At the moment we don't support cross project destinations return MethodNotImplemented unless project.id == resource.project.id raise Forbidden unless User.current.admin? || User.current.allowed_to?(:folder_manipulation, project) #Current object is a folder, so now we need to figure out information about Destination - if(dest.exist?) then - + if dest.exist? MethodNotAllowed - else - if(parent.projectless_path == '/') #Project root folder.dmsf_folder_id = nil else return PreconditionFailed unless parent.exist? && parent.folder? - folder.dmsf_folder_id = parent.folder.id + folder.dmsf_folder_id = parent.folder.id end folder.title = resource.basename folder.save ? Created : PreconditionFailed - end else raise Forbidden unless User.current.admin? || User.current.allowed_to?(:folder_manipulation, project) || - User.current.allowed_to?(:folder_manipulation, resource.project) + User.current.allowed_to?(:folder_manipulation, resource.project) - if(dest.exist?) then - - methodNotAllowed - + if dest.exist? + methodNotAllowed # Files cannot be merged at this point, until a decision is made on how to merge them - # ideally, we would merge revision history for both, ensuring the origin file wins with latest revision. - + # ideally, we would merge revision history for both, ensuring the origin file wins with latest revision. else - if(parent.projectless_path == '/') #Project root f = nil else @@ -311,8 +298,11 @@ module RedmineDmsf return InternalServerError unless file.move_to(resource.project, f) # Update Revision and names of file [We can link to old physical resource, as it's not changed] - file.last_revision.name = resource.basename if file.last_revision - file.name = resource.basename + if file.last_revision + file.last_revision.name = resource.basename + file.last_revision.title = DmsfFileRevision.filename_to_title(resource.basename) + end + file.name = resource.basename # Save Changes (file.last_revision.save! && file.save!) ? Created : PreconditionFailed From ac8f0693e0b98fbd77157127f076d9d514bc6693 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Pi=C4=8Dman?= Date: Tue, 5 May 2015 10:03:45 +0200 Subject: [PATCH 27/52] file_path = false DEPRECATED --- init.rb | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/init.rb b/init.rb index 06a3d260..7d6cd202 100644 --- a/init.rb +++ b/init.rb @@ -101,9 +101,8 @@ Redmine::Plugin.register :redmine_dmsf do return nil if args.length < 1 # require file id entry = DmsfFile.visible.find_by_id args[0].strip if entry && User.current && User.current.allowed_to?(:view_dmsf_files, entry.project) - title = args[1] ? args[1] : entry.title - revision = args[2] ? args[2] : '' - return link_to h(title), download_revision_path(entry, revision, :only_path => false) + title = args[1] ? args[1] : entry.title + return link_to h(title), dmsf_file_url(entry, :download => args[2]) end nil end @@ -116,12 +115,12 @@ Redmine::Plugin.register :redmine_dmsf do macro :dmsff do |obj, args| if args.length < 1 - return link_to l(:link_documents), dmsf_folder_path(@project, :only_path => false) + return link_to l(:link_documents), dmsf_folder_url(@project) else entry = DmsfFolder.visible.find_by_id args[0].strip if entry && User.current && User.current.allowed_to?(:view_dmsf_folders, entry.project) title = args[1] ? args[1] : entry.title - return link_to h(title), dmsf_folder_path(entry.project, :folder_id => entry, :only_path => false) + return link_to h(title), dmsf_folder_url(entry.project, :folder_id => entry) end end nil @@ -151,4 +150,4 @@ end Redmine::Search.map do |search| search.register :dmsf_files search.register :dmsf_folders -end +end \ No newline at end of file From cc365c9af46c8032de1e622d23e53a3bf392f660 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Pi=C4=8Dman?= Date: Thu, 14 May 2015 16:53:33 +0200 Subject: [PATCH 28/52] xapian not indexing files/project #388 --- app/models/dmsf_file.rb | 62 ++++++++++++++--------------- app/models/dmsf_folder.rb | 84 ++++++++++----------------------------- 2 files changed, 52 insertions(+), 94 deletions(-) diff --git a/app/models/dmsf_file.rb b/app/models/dmsf_file.rb index d4336258..c56161b8 100644 --- a/app/models/dmsf_file.rb +++ b/app/models/dmsf_file.rb @@ -29,11 +29,8 @@ end class DmsfFile < ActiveRecord::Base unloadable - - include RedmineDmsf::Lockable - - attr_accessor :event_description - attr_accessible :project + + include RedmineDmsf::Lockable belongs_to :project belongs_to :folder, :class_name => 'DmsfFolder', :foreign_key => 'dmsf_folder_id' @@ -89,6 +86,10 @@ class DmsfFile < ActiveRecord::Base :url => Proc.new {|o| {:controller => 'dmsf_files', :action => 'show', :id => o}}, :datetime => Proc.new {|o| o.updated_at }, :author => Proc.new {|o| o.last_revision.user } + + acts_as_searchable :columns => ["#{table_name}.name", "#{DmsfFileRevision}.title", "#{DmsfFileRevision}.description"], + :project_key => 'project_id', + :date_column => "#{table_name}.updated_at" before_create :default_values def default_values @@ -177,7 +178,7 @@ class DmsfFile < ActiveRecord::Base def description self.last_revision ? self.last_revision.description : '' - end + end def version self.last_revision ? self.last_revision.version : '0' @@ -292,38 +293,37 @@ class DmsfFile < ActiveRecord::Base end # To fulfill searchable module expectations - def self.search(tokens, projects = nil, options = {}) + def self.search(tokens, user, projects = nil, options = {}) tokens = [] << tokens unless tokens.is_a?(Array) projects = [] << projects unless projects.nil? || projects.is_a?(Array) - find_options = {} - find_options[:order] = 'dmsf_files.updated_at ' + (options[:before] ? 'DESC' : 'ASC') - + find_options = {} limit_options = {} limit_options[:limit] = options[:limit] if options[:limit] if options[:offset] limit_options[:conditions] = '(dmsf_files.updated_at ' + (options[:before] ? '<' : '>') + "'#{connection.quoted_date(options[:offset])}')" end - - columns = %w(dmsf_files.name dmsf_file_revisions.title dmsf_file_revisions.description) - columns = ['dmsf_file_revisions.title'] if options[:titles_only] - + + if options[:titles_only] + columns = ['dmsf_file_revisions.title'] + else + columns = %w(dmsf_files.name dmsf_file_revisions.title dmsf_file_revisions.description) + end + token_clauses = columns.collect {|column| "(LOWER(#{column}) LIKE ?)"} sql = (['(' + token_clauses.join(' OR ') + ')'] * tokens.size).join(options[:all_words] ? ' AND ' : ' OR ') find_options[:conditions] = [sql, * (tokens.collect {|w| "%#{w.downcase}%"} * token_clauses.size).sort] project_conditions = [] - project_conditions << Project.allowed_to_condition(User.current, :view_dmsf_files) + project_conditions << Project.allowed_to_condition(user, :view_dmsf_files) project_conditions << "#{DmsfFile.table_name}.project_id IN (#{projects.collect(&:id).join(',')})" unless projects.nil? - results = [] - results_count = 0 + results = [] joins(:project, :revisions). where(project_conditions.join(' AND ') + " AND #{DmsfFile.table_name}.deleted = :false", {:false => false}).scoping do - where(find_options[:conditions]).order(find_options[:order]).scoping do - results_count = count + where(find_options[:conditions]).scoping do results = where(limit_options) end end @@ -381,7 +381,7 @@ class DmsfFile < ActiveRecord::Base next if dmsf_attrs.length == 0 || id_attribute == 0 next unless results.select{|f| f.id.to_s == id_attribute}.empty? - dmsf_file = DmsfFile.where(limit_options[:conditions]).where(:id => id_attribute, :deleted => false).first + dmsf_file = DmsfFile.visible.where(limit_options[:conditions]).where(:id => id_attribute).first if dmsf_file if options[:offset] @@ -392,7 +392,7 @@ class DmsfFile < ActiveRecord::Base end end - allowed = User.current.allowed_to?(:view_dmsf_files, dmsf_file.project) + allowed = user.allowed_to?(:view_dmsf_files, dmsf_file.project) project_included = false project_included = true unless projects unless project_included @@ -409,10 +409,10 @@ class DmsfFile < ActiveRecord::Base end end - if (allowed && project_included) - dmsf_file.event_description = dochash['sample'].force_encoding('UTF-8') if dochash['sample'] - results.push(dmsf_file) - results_count += 1 + if allowed && project_included + # TODO: It works no more :-( + #dmsf_file.last_revision.description = dochash['sample'].force_encoding('UTF-8') if dochash['sample'] + results << dmsf_file end end end @@ -420,14 +420,14 @@ class DmsfFile < ActiveRecord::Base end end end - - [results, results_count] + + results end def self.search_result_ranks_and_ids(tokens, user = User.current, projects = nil, options = {}) - r = self.search(tokens, projects, options)[0] - r.each_index { |x| [x, r[1][x]] } - end + r = self.search(tokens, user, projects, options) + r.map{ |f| [f.updated_at.to_i, f.id]} + end def display_name if self.name.length > 50 @@ -436,4 +436,4 @@ class DmsfFile < ActiveRecord::Base self.name end -end +end \ No newline at end of file diff --git a/app/models/dmsf_folder.rb b/app/models/dmsf_folder.rb index b0a73eff..21d3f5c5 100644 --- a/app/models/dmsf_folder.rb +++ b/app/models/dmsf_folder.rb @@ -25,9 +25,7 @@ class DmsfFolder < ActiveRecord::Base include RedmineDmsf::Lockable cattr_reader :invalid_characters - @@invalid_characters = /\A[^\/\\\?":<>]*\z/ - - attr_accessible :title, :description, :dmsf_folder_id, :project + @@invalid_characters = /\A[^\/\\\?":<>]*\z/ belongs_to :project belongs_to :folder, :class_name => 'DmsfFolder', :foreign_key => 'dmsf_folder_id' @@ -64,31 +62,34 @@ class DmsfFolder < ActiveRecord::Base :conditions => {:entity_type => 1}, :dependent => :destroy end - - if (Rails::VERSION::MAJOR > 3) - scope :visible, -> { where(deleted: false) } - scope :deleted, -> { where(deleted: true) } - else - scope :visible, where(:deleted => false) - scope :deleted, where(:deleted => true) - end + + scope :visible, lambda { |*args| + where(deleted: false) + } + scope :deleted, lambda { |*args| + where(deleted: true) + } acts_as_customizable + + acts_as_searchable :columns => ["#{self.table_name}.title", "#{self.table_name}.description"], + :project_key => 'project_id', + :date_column => 'updated_at', + :permission => :view_dmsf_files, + :scope => self.joins(:project) + + acts_as_event :title => Proc.new {|o| o.title}, + :description => Proc.new {|o| o.description }, + :url => Proc.new {|o| {:controller => 'dmsf', :action => 'show', :id => o.project, :folder_id => o}}, + :datetime => Proc.new {|o| o.updated_at }, + :author => Proc.new {|o| o.user } validates :title, :presence => true validates_uniqueness_of :title, :scope => [:dmsf_folder_id, :project_id, :deleted] - validates_format_of :title, :with => @@invalid_characters, :message => l(:error_contains_invalid_character) - validate :check_cycle - acts_as_event :title => Proc.new {|o| o.title}, - :description => Proc.new {|o| o.description }, - :url => Proc.new {|o| {:controller => 'dmsf', :action => 'show', :id => o.project, :folder_id => o}}, - :datetime => Proc.new {|o| o.updated_at }, - :author => Proc.new {|o| o.user } - before_create :default_values def default_values @notifications = Setting.plugin_redmine_dmsf['dmsf_default_notifications'] @@ -280,50 +281,7 @@ class DmsfFolder < ActiveRecord::Base def available_custom_fields DmsfFileRevisionCustomField.all end - - # To fullfill searchable module expectations - def self.search(tokens, projects = nil, options = {}) - tokens = [] << tokens unless tokens.is_a?(Array) - projects = [] << projects unless projects.nil? || projects.is_a?(Array) - - find_options = {} - find_options[:order] = 'dmsf_folders.updated_at ' + (options[:before] ? 'DESC' : 'ASC') - - limit_options = {} - limit_options[:limit] = options[:limit] if options[:limit] - if options[:offset] - limit_options[:conditions] = '(dmsf_folders.updated_at ' + (options[:before] ? '<' : '>') + "'#{connection.quoted_date(options[:offset])}')" - end - - columns = options[:titles_only] ? ['dmsf_folders.title'] : ['dmsf_folders.title', 'dmsf_folders.description'] - - token_clauses = columns.collect {|column| "(LOWER(#{column}) LIKE ?)"} - - sql = (['(' + token_clauses.join(' OR ') + ')'] * tokens.size).join(options[:all_words] ? ' AND ' : ' OR ') - find_options[:conditions] = [sql, * (tokens.collect {|w| "%#{w.downcase}%"} * token_clauses.size).sort] - - project_conditions = [] - project_conditions << (Project.allowed_to_condition(User.current, :view_dmsf_files)) - project_conditions << "project_id IN (#{projects.collect(&:id).join(',')})" if projects - - results = [] - results_count = 0 - - joins(:project). - where(project_conditions.join(' AND ')).scoping do - where(find_options[:conditions]).order(find_options[:order]).scoping do - results_count = count - results = where(limit_options) - end - end - - [results, results_count] - end - def self.search_result_ranks_and_ids(tokens, user = User.current, projects = nil, options = {}) - self.search(tokens, :user => user, :projects => projects, :options => options) - end - def modified last_update = updated_at subfolders.each do |subfolder| @@ -364,4 +322,4 @@ class DmsfFolder < ActiveRecord::Base end end -end +end \ No newline at end of file From 625912c6f87483cbd3d0c76c740d397322a36810 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Pi=C4=8Dman?= Date: Fri, 15 May 2015 09:11:48 +0200 Subject: [PATCH 29/52] xapian not indexing files/project #388 --- app/models/dmsf_file.rb | 33 +++++++++------------------------ 1 file changed, 9 insertions(+), 24 deletions(-) diff --git a/app/models/dmsf_file.rb b/app/models/dmsf_file.rb index c56161b8..5460e18a 100644 --- a/app/models/dmsf_file.rb +++ b/app/models/dmsf_file.rb @@ -295,7 +295,8 @@ class DmsfFile < ActiveRecord::Base # To fulfill searchable module expectations def self.search(tokens, user, projects = nil, options = {}) tokens = [] << tokens unless tokens.is_a?(Array) - projects = [] << projects unless projects.nil? || projects.is_a?(Array) + projects = [] << projects if projects.is_a?(Project) + project_ids = projects.collect(&:id) if projects find_options = {} limit_options = {} @@ -316,13 +317,13 @@ class DmsfFile < ActiveRecord::Base find_options[:conditions] = [sql, * (tokens.collect {|w| "%#{w.downcase}%"} * token_clauses.size).sort] project_conditions = [] - project_conditions << Project.allowed_to_condition(user, :view_dmsf_files) - project_conditions << "#{DmsfFile.table_name}.project_id IN (#{projects.collect(&:id).join(',')})" unless projects.nil? + project_conditions << Project.allowed_to_condition(user, :view_dmsf_files) + project_conditions << "#{DmsfFile.table_name}.project_id IN (#{project_ids.join(',')})" if project_ids.present? results = [] - joins(:project, :revisions). - where(project_conditions.join(' AND ') + " AND #{DmsfFile.table_name}.deleted = :false", {:false => false}).scoping do + visible.joins(:project, :revisions). + where(project_conditions.join(' AND ')).scoping do where(find_options[:conditions]).scoping do results = where(limit_options) end @@ -390,26 +391,10 @@ class DmsfFile < ActiveRecord::Base else next if dmsf_file.updated_at > options[:offset] end - end + end - allowed = user.allowed_to?(:view_dmsf_files, dmsf_file.project) - project_included = false - project_included = true unless projects - unless project_included - projects.each do |x| - if x.is_a?(ActiveRecord::Relation) - project_included = x.first.id == dmsf_file.project.id - else - if dmsf_file.project - project_included = x[:id] == dmsf_file.project.id - else - project_included = false - end - end - end - end - - if allowed && project_included + if user.allowed_to?(:view_dmsf_files, dmsf_file.project) && + (project_ids.empty? || (project_ids.include?(dmsf_file.project.id))) # TODO: It works no more :-( #dmsf_file.last_revision.description = dochash['sample'].force_encoding('UTF-8') if dochash['sample'] results << dmsf_file From 6ee086548d44d8855bac1ca54e8f19a8e90a7974 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Pi=C4=8Dman?= Date: Fri, 15 May 2015 10:46:13 +0200 Subject: [PATCH 30/52] xapian not indexing files/project #388 --- app/models/dmsf_file.rb | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/app/models/dmsf_file.rb b/app/models/dmsf_file.rb index 5460e18a..b3e58e65 100644 --- a/app/models/dmsf_file.rb +++ b/app/models/dmsf_file.rb @@ -81,11 +81,19 @@ class DmsfFile < ActiveRecord::Base existing_file.nil? || existing_file.id == self.id end - acts_as_event :title => Proc.new {|o| "#{o.title} - #{o.name}"}, - :description => Proc.new {|o| o.description }, - :url => Proc.new {|o| {:controller => 'dmsf_files', :action => 'show', :id => o}}, - :datetime => Proc.new {|o| o.updated_at }, - :author => Proc.new {|o| o.last_revision.user } + acts_as_event :title => Proc.new { |o| o.name }, + :description => Proc.new { |o| + desc = Redmine::Search.cache_store.fetch("DmsfFile-#{o.id}") + if desc + Redmine::Search.cache_store.delete("DmsfFile-#{o.id}") + desc + else + o.description + end + }, + :url => Proc.new { |o| {:controller => 'dmsf_files', :action => 'show', :id => o} }, + :datetime => Proc.new { |o| o.updated_at }, + :author => Proc.new { |o| o.last_revision.user } acts_as_searchable :columns => ["#{table_name}.name", "#{DmsfFileRevision}.title", "#{DmsfFileRevision}.description"], :project_key => 'project_id', @@ -177,7 +185,7 @@ class DmsfFile < ActiveRecord::Base end def description - self.last_revision ? self.last_revision.description : '' + self.last_revision ? self.last_revision.description : '' end def version @@ -394,9 +402,9 @@ class DmsfFile < ActiveRecord::Base end if user.allowed_to?(:view_dmsf_files, dmsf_file.project) && - (project_ids.empty? || (project_ids.include?(dmsf_file.project.id))) - # TODO: It works no more :-( - #dmsf_file.last_revision.description = dochash['sample'].force_encoding('UTF-8') if dochash['sample'] + (project_ids.empty? || (project_ids.include?(dmsf_file.project.id))) + Redmine::Search.cache_store.write("DmsfFile-#{dmsf_file.id}", + dochash['sample'].force_encoding('UTF-8')) if dochash['sample'] results << dmsf_file end end From aa4042bba58148289ce5e42b0116326ccbef3bef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Pi=C4=8Dman?= Date: Tue, 19 May 2015 09:16:32 +0200 Subject: [PATCH 31/52] Redmine 3.0 compatibility --- app/models/dmsf_file.rb | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/app/models/dmsf_file.rb b/app/models/dmsf_file.rb index b3e58e65..68aaf8da 100644 --- a/app/models/dmsf_file.rb +++ b/app/models/dmsf_file.rb @@ -60,14 +60,13 @@ class DmsfFile < ActiveRecord::Base if (Rails::VERSION::MAJOR > 3) accepts_nested_attributes_for :revisions, :locks, :referenced_links end - - if (Rails::VERSION::MAJOR > 3) - scope :visible, -> { where(deleted: false) } - scope :deleted, -> { where(deleted: true) } - else - scope :visible, where(:deleted => false) - scope :deleted, where(:deleted => true) - end + + scope :visible, lambda { |*args| + where(deleted: false) + } + scope :deleted, lambda { |*args| + where(deleted: true) + } validates :name, :presence => true validates_format_of :name, :with => DmsfFolder.invalid_characters, From eaffa3a3c232d03938bf1ccfb950098fa2aebe6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Pi=C4=8Dman?= Date: Tue, 19 May 2015 14:38:45 +0200 Subject: [PATCH 32/52] Search backward compatibility --- app/models/dmsf_file.rb | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/app/models/dmsf_file.rb b/app/models/dmsf_file.rb index 68aaf8da..df4a8762 100644 --- a/app/models/dmsf_file.rb +++ b/app/models/dmsf_file.rb @@ -300,7 +300,7 @@ class DmsfFile < ActiveRecord::Base end # To fulfill searchable module expectations - def self.search(tokens, user, projects = nil, options = {}) + def self.search(tokens, projects = nil, options = {}, user = User.current) tokens = [] << tokens unless tokens.is_a?(Array) projects = [] << projects if projects.is_a?(Project) project_ids = projects.collect(&:id) if projects @@ -402,8 +402,12 @@ class DmsfFile < ActiveRecord::Base if user.allowed_to?(:view_dmsf_files, dmsf_file.project) && (project_ids.empty? || (project_ids.include?(dmsf_file.project.id))) - Redmine::Search.cache_store.write("DmsfFile-#{dmsf_file.id}", - dochash['sample'].force_encoding('UTF-8')) if dochash['sample'] + if (Rails::VERSION::MAJOR > 3) + Redmine::Search.cache_store.write("DmsfFile-#{dmsf_file.id}", + dochash['sample'].force_encoding('UTF-8')) if dochash['sample'] + else + dmsf_file.event_description = dochash['sample'].force_encoding('UTF-8') if dochash['sample'] + end results << dmsf_file end end @@ -413,11 +417,11 @@ class DmsfFile < ActiveRecord::Base end end - results + [results, results.count] end def self.search_result_ranks_and_ids(tokens, user = User.current, projects = nil, options = {}) - r = self.search(tokens, user, projects, options) + r = self.search(tokens, projects, options, user)[0] r.map{ |f| [f.updated_at.to_i, f.id]} end From 53783db85adc2901b30155c32f95dee0072c273b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Pi=C4=8Dman?= Date: Tue, 19 May 2015 14:44:25 +0200 Subject: [PATCH 33/52] Search backward compatibility --- app/models/dmsf_file.rb | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/app/models/dmsf_file.rb b/app/models/dmsf_file.rb index df4a8762..19dcbb32 100644 --- a/app/models/dmsf_file.rb +++ b/app/models/dmsf_file.rb @@ -82,10 +82,14 @@ class DmsfFile < ActiveRecord::Base acts_as_event :title => Proc.new { |o| o.name }, :description => Proc.new { |o| - desc = Redmine::Search.cache_store.fetch("DmsfFile-#{o.id}") - if desc - Redmine::Search.cache_store.delete("DmsfFile-#{o.id}") - desc + if (Rails::VERSION::MAJOR > 3) + desc = Redmine::Search.cache_store.fetch("DmsfFile-#{o.id}") + if desc + Redmine::Search.cache_store.delete("DmsfFile-#{o.id}") + desc + else + o.description + end else o.description end From ddb4ab76f09ecb5bf4490e35b87d325f425568e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Pi=C4=8Dman?= Date: Tue, 19 May 2015 15:20:59 +0200 Subject: [PATCH 34/52] Search backward compatibility --- app/models/dmsf_folder.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/models/dmsf_folder.rb b/app/models/dmsf_folder.rb index 21d3f5c5..0eac7179 100644 --- a/app/models/dmsf_folder.rb +++ b/app/models/dmsf_folder.rb @@ -76,7 +76,8 @@ class DmsfFolder < ActiveRecord::Base :project_key => 'project_id', :date_column => 'updated_at', :permission => :view_dmsf_files, - :scope => self.joins(:project) + :scope => self.joins(:project), + :include => :project # Redmine < 3.0.0 compatibility acts_as_event :title => Proc.new {|o| o.title}, :description => Proc.new {|o| o.description }, From 7209d9d1e00212968fff805a0bb0fe480f269bfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Pi=C4=8Dman?= Date: Fri, 22 May 2015 10:22:11 +0200 Subject: [PATCH 35/52] Redmine 3.0 compatibility --- app/models/dmsf_folder.rb | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/app/models/dmsf_folder.rb b/app/models/dmsf_folder.rb index 0eac7179..4390b7e9 100644 --- a/app/models/dmsf_folder.rb +++ b/app/models/dmsf_folder.rb @@ -72,12 +72,19 @@ class DmsfFolder < ActiveRecord::Base acts_as_customizable - acts_as_searchable :columns => ["#{self.table_name}.title", "#{self.table_name}.description"], + if (Rails::VERSION::MAJOR > 3) + acts_as_searchable :columns => ["#{self.table_name}.title", "#{self.table_name}.description"], :project_key => 'project_id', :date_column => 'updated_at', :permission => :view_dmsf_files, - :scope => self.joins(:project), - :include => :project # Redmine < 3.0.0 compatibility + :scope => self.joins(:project) + else + acts_as_searchable :columns => ["#{self.table_name}.title", "#{self.table_name}.description"], + :project_key => 'project_id', + :date_column => 'updated_at', + :permission => :view_dmsf_files, + :include => :project + end acts_as_event :title => Proc.new {|o| o.title}, :description => Proc.new {|o| o.description }, From 176b4780463ae21a9ecd60b11fc95c01dbb9f064 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Pi=C4=8Dman?= Date: Tue, 26 May 2015 12:14:02 +0200 Subject: [PATCH 36/52] Searchable document details #391 --- app/models/dmsf_file.rb | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/app/models/dmsf_file.rb b/app/models/dmsf_file.rb index 19dcbb32..d2b8b9d3 100644 --- a/app/models/dmsf_file.rb +++ b/app/models/dmsf_file.rb @@ -85,20 +85,22 @@ class DmsfFile < ActiveRecord::Base if (Rails::VERSION::MAJOR > 3) desc = Redmine::Search.cache_store.fetch("DmsfFile-#{o.id}") if desc - Redmine::Search.cache_store.delete("DmsfFile-#{o.id}") - desc + Redmine::Search.cache_store.delete("DmsfFile-#{o.id}") else - o.description + desc = o.description + desc += " / #{o.last_revision.comment}" if o.last_revision.comment.present? end else - o.description + desc = o.description + desc += " / #{o.last_revision.comment}" if o.last_revision.comment.present? end + desc }, :url => Proc.new { |o| {:controller => 'dmsf_files', :action => 'show', :id => o} }, :datetime => Proc.new { |o| o.updated_at }, :author => Proc.new { |o| o.last_revision.user } - acts_as_searchable :columns => ["#{table_name}.name", "#{DmsfFileRevision}.title", "#{DmsfFileRevision}.description"], + acts_as_searchable :columns => ["#{table_name}.name", "#{DmsfFileRevision.table_name}.title", "#{DmsfFileRevision.table_name}.description", "#{DmsfFileRevision.table_name}.comment"], :project_key => 'project_id', :date_column => "#{table_name}.updated_at" @@ -317,9 +319,9 @@ class DmsfFile < ActiveRecord::Base end if options[:titles_only] - columns = ['dmsf_file_revisions.title'] - else - columns = %w(dmsf_files.name dmsf_file_revisions.title dmsf_file_revisions.description) + columns = [searchable_options[:columns][1]] + else + columns = searchable_options[:columns] end token_clauses = columns.collect {|column| "(LOWER(#{column}) LIKE ?)"} @@ -336,7 +338,7 @@ class DmsfFile < ActiveRecord::Base visible.joins(:project, :revisions). where(project_conditions.join(' AND ')).scoping do where(find_options[:conditions]).scoping do - results = where(limit_options) + results = where(limit_options).uniq end end From 7c43e2ed573f7373f275ef7d9db1481ef453b52e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Pi=C4=8Dman?= Date: Mon, 8 Jun 2015 15:09:00 +0200 Subject: [PATCH 37/52] Cannot save a modified approval workflow --- app/controllers/dmsf_workflows_controller.rb | 5 +++-- app/helpers/dmsf_workflows_helper.rb | 18 ++++++++++++------ 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/app/controllers/dmsf_workflows_controller.rb b/app/controllers/dmsf_workflows_controller.rb index 706f856e..f600f9a4 100644 --- a/app/controllers/dmsf_workflows_controller.rb +++ b/app/controllers/dmsf_workflows_controller.rb @@ -238,7 +238,7 @@ class DmsfWorkflowsController < ApplicationController end def update - if request.put? && params[:dmsf_workflow] && @dmsf_workflow.update_attributes( + if params[:dmsf_workflow] && @dmsf_workflow.update_attributes( {:name => params[:dmsf_workflow][:name]}) flash[:notice] = l(:notice_successful_update) if @project @@ -246,7 +246,8 @@ class DmsfWorkflowsController < ApplicationController else redirect_to dmsf_workflows_path end - else + else + flash[:error] = @dmsf_workflow.errors.full_messages.to_sentence redirect_to dmsf_workflow_path(@dmsf_workflow) end end diff --git a/app/helpers/dmsf_workflows_helper.rb b/app/helpers/dmsf_workflows_helper.rb index ad631d6a..90ca653a 100644 --- a/app/helpers/dmsf_workflows_helper.rb +++ b/app/helpers/dmsf_workflows_helper.rb @@ -1,6 +1,8 @@ +# encoding: utf-8 +# # Redmine plugin for Document Management System "Features" # -# Copyright (C) 2011-14 Karel Picman +# Copyright (C) 2011-15 Karel Pičman # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -23,11 +25,15 @@ module DmsfWorkflowsHelper principal_count = scope.count principal_pages = Redmine::Pagination::Paginator.new principal_count, 10, params['page'] principals = scope.offset(principal_pages.offset).limit(principal_pages.per_page).all - - if dmsf_workflow_step_assignment_id - s = content_tag('div', principals_radio_button_tags('step_action', principals), :id => 'users_for_delegate') - else - s = content_tag('div', principals_check_box_tags('user_ids[]', principals), :id => 'users') + + if dmsf_workflow_step_assignment_id + s = content_tag('div', + content_tag('div', principals_radio_button_tags('user_ids[]', principals), :id => 'users_for_delegate'), + :class => 'objects-selection') + else + s = content_tag('div', + content_tag('div', principals_check_box_tags('user_ids[]', principals), :id => 'users'), + :class => 'objects-selection') end links = pagination_links_full(principal_pages, principal_count, :per_page_links => false) {|text, parameters, options| From 165690bf8abdb8ee0f294ad0cc8cae4f4d094e5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Pi=C4=8Dman?= Date: Mon, 8 Jun 2015 15:35:29 +0200 Subject: [PATCH 38/52] Adding a step without any user selected --- app/controllers/dmsf_workflows_controller.rb | 30 +++++++++++--------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/app/controllers/dmsf_workflows_controller.rb b/app/controllers/dmsf_workflows_controller.rb index f600f9a4..3b68db7a 100644 --- a/app/controllers/dmsf_workflows_controller.rb +++ b/app/controllers/dmsf_workflows_controller.rb @@ -271,26 +271,30 @@ class DmsfWorkflowsController < ApplicationController end def add_step - if request.post? - users = User.where(:id => params[:user_ids]) + if request.post? if params[:step] == '0' step = @dmsf_workflow.dmsf_workflow_steps.collect{|s| s.step}.uniq.count + 1 else step = params[:step].to_i end operator = (params[:commit] == l(:dmsf_and)) ? DmsfWorkflowStep::OPERATOR_AND : DmsfWorkflowStep::OPERATOR_OR - users.each do |user| - ws = DmsfWorkflowStep.new( - :dmsf_workflow_id => @dmsf_workflow.id, - :step => step, - :user_id => user.id, - :operator => operator) - if ws.save - @dmsf_workflow.dmsf_workflow_steps << ws - else - flash[:error] = l(:error_workflow_assign) + users = User.where(:id => params[:user_ids]) + if users.count > 0 + users.each do |user| + ws = DmsfWorkflowStep.new( + :dmsf_workflow_id => @dmsf_workflow.id, + :step => step, + :user_id => user.id, + :operator => operator) + if ws.save + @dmsf_workflow.dmsf_workflow_steps << ws + else + flash[:error] = @dmsf_workflow.errors.full_messages.to_sentence + end end - end + else + flash[:error] = l(:error_workflow_assign) + end end respond_to do |format| format.html From b71da745253f6253443447f65632342d863ced0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Pi=C4=8Dman?= Date: Wed, 10 Jun 2015 14:02:37 +0200 Subject: [PATCH 39/52] Redmine >= 3.0 compatibility --- app/controllers/dmsf_workflows_controller.rb | 9 ++ .../dmsf_workflows/_new_step_form.html.erb | 36 ++++++ .../dmsf_workflows/_new_step_modal.html.erb | 31 +++++ app/views/dmsf_workflows/_steps.html.erb | 108 +++++++----------- app/views/dmsf_workflows/new_step.html.erb | 19 +++ app/views/dmsf_workflows/new_step.js.erb | 2 + config/routes.rb | 3 +- init.rb | 2 +- 8 files changed, 143 insertions(+), 67 deletions(-) create mode 100644 app/views/dmsf_workflows/_new_step_form.html.erb create mode 100644 app/views/dmsf_workflows/_new_step_modal.html.erb create mode 100644 app/views/dmsf_workflows/new_step.html.erb create mode 100644 app/views/dmsf_workflows/new_step.js.erb diff --git a/app/controllers/dmsf_workflows_controller.rb b/app/controllers/dmsf_workflows_controller.rb index 3b68db7a..e360e9d7 100644 --- a/app/controllers/dmsf_workflows_controller.rb +++ b/app/controllers/dmsf_workflows_controller.rb @@ -270,6 +270,15 @@ class DmsfWorkflowsController < ApplicationController render :layout => false end + def new_step + @steps = @dmsf_workflow.dmsf_workflow_steps.collect{|s| s.step}.uniq + + respond_to do |format| + format.html + format.js + end + end + def add_step if request.post? if params[:step] == '0' diff --git a/app/views/dmsf_workflows/_new_step_form.html.erb b/app/views/dmsf_workflows/_new_step_form.html.erb new file mode 100644 index 00000000..5157e57f --- /dev/null +++ b/app/views/dmsf_workflows/_new_step_form.html.erb @@ -0,0 +1,36 @@ +<% +# encoding: utf-8 +# Redmine plugin for Document Management System "Features" +# +# Copyright (C) 2011-15 Karel Pičman +# +# 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. +%> + +
    + <%=l(:label_dmsf_workflow_add_approver)%> +

    <%= label_tag 'user_search', l(:label_user_search) %><%= text_field_tag 'user_search', nil %>

    + <%= javascript_tag "observeSearchfield('user_search', 'users', '#{ escape_javascript autocomplete_for_user_dmsf_workflow_path(@dmsf_workflow, :dmsf_workflow_step_assignment_id => nil, :dmsf_file_revision_id => nil, :project_id => @project ? @project.id : nil) }')" %> + +
    + <%= render_principals_for_new_dmsf_workflow_users(@dmsf_workflow, nil, nil) %> +
    +
    + +
    + <%= l(:label_dmsf_workflow_step) %> + <%= select_tag 'step', dmsf_workflow_steps_options_for_select(@steps), + :id => 'selected_step', :style => "width:100px" %> +
    \ No newline at end of file diff --git a/app/views/dmsf_workflows/_new_step_modal.html.erb b/app/views/dmsf_workflows/_new_step_modal.html.erb new file mode 100644 index 00000000..61ed2c36 --- /dev/null +++ b/app/views/dmsf_workflows/_new_step_modal.html.erb @@ -0,0 +1,31 @@ +<% +# encoding: utf-8 +# Redmine plugin for Document Management System "Features" +# +# Copyright (C) 2011-15 Karel Pičman +# +# 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. +%> + +

    <%= l(:dmsf_new_step) %>

    + +<%= form_for(:dmsf_workflow, {:url => edit_dmsf_workflow_path(@dmsf_workflow), :method => :post}) do |f| %> + <%= render :partial => 'new_step_form' %> +

    + <%= submit_tag l(:dmsf_and), :id => 'add-step-and' %> + <%= submit_tag l(:dmsf_or), :id => 'add-step-or' %> + <%= submit_tag l(:button_cancel), :name => nil, :onclick => 'hideModal(this);', :type => 'button' %> +

    +<% end %> \ No newline at end of file diff --git a/app/views/dmsf_workflows/_steps.html.erb b/app/views/dmsf_workflows/_steps.html.erb index db08246f..6d282c99 100644 --- a/app/views/dmsf_workflows/_steps.html.erb +++ b/app/views/dmsf_workflows/_steps.html.erb @@ -1,6 +1,8 @@ -<%# Redmine plugin for Document Management System "Features" +<% +# encoding: utf-8 +# Redmine plugin for Document Management System "Features" # -# Copyright (C) 2013 Karel Pičman +# Copyright (C) 2011-15 Karel Pičman # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -14,7 +16,8 @@ # # 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.%> +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +%> <% if @dmsf_workflow.project %>

    <%= link_to l(:label_dmsf_workflow_plural), settings_project_path(@project, :tab => 'dmsf_workflow') %> » <%=h @dmsf_workflow %>

    @@ -29,67 +32,42 @@
    <% end %> -
    - -
    -<% steps = @dmsf_workflow.dmsf_workflow_steps.collect{|s| s.step}.uniq %> -<% if steps.any? %> - - - - - - - - <% steps.each do |i|%> - - - - - - - <% end; reset_cycle %> - -
    <%= l(:label_dmsf_workflow_step) %><%= l(:label_dmsf_workflow_approval_plural) %><%= l(:button_sort)%> -
    <%= i %> - <% @dmsf_workflow.dmsf_workflow_steps.collect{|s| (s.step == i) ? s : nil}.compact.each_with_index do |step, j| %> - <% if j != 0 %> - <%= step.soperator %>  - <% end %> - <%= link_to_user step.user %> - <% end %> - - <%= reorder_links('workflow_step', {:action => 'edit', :id => @dmsf_workflow, :step => i}, :put) %> - - <%= delete_link edit_dmsf_workflow_path(@dmsf_workflow, :step => i) %> -
    -<% else %> -

    <%= l(:label_no_data) %>

    -<% end %> -
    - -
    - <%= form_for(@dmsf_workflow, :url => edit_dmsf_workflow_path(@dmsf_workflow), - :html => {:method => :post}) do |f| %> -
    <%=l(:label_user_new)%> - -

    <%= label_tag 'user_search', l(:label_user_search) %><%= text_field_tag 'user_search', nil %>

    - <%= javascript_tag "observeSearchfield('user_search', 'users', '#{ escape_javascript autocomplete_for_user_dmsf_workflow_path(@dmsf_workflow, :dmsf_workflow_step_assignment_id => nil, :dmsf_file_revision_id => nil, :project_id => @project ? @project.id : nil) }')" %> - -
    - <%= render_principals_for_new_dmsf_workflow_users(@dmsf_workflow, nil, nil) %> -
    - -

    - <%= l(:label_dmsf_workflow_step) %> - <%= select_tag 'step', - dmsf_workflow_steps_options_for_select(steps), - :id => 'selected_step', :style => "width:100px" %> -

    -

    <%= l(:label_dmsf_workflow_add_approver) %>

    -

    <%= submit_tag l(:dmsf_and) %> <%= l(:label_or) %> <%= submit_tag l(:dmsf_or) %>

    -
    +
    +

    + <%= link_to l(:dmsf_new_step), new_step_dmsf_workflow_path(@dmsf_workflow), :remote => true, :class => 'icon icon-add' %> +

    + <% steps = @dmsf_workflow.dmsf_workflow_steps.collect{|s| s.step}.uniq %> + <% if steps.any? %> + + + + + + + + <% steps.each do |i|%> + + + + + + + <% end; reset_cycle %> + +
    <%= l(:label_dmsf_workflow_step) %><%= l(:label_dmsf_workflow_approval_plural) %><%= l(:button_sort)%> +
    <%= i %> + <% @dmsf_workflow.dmsf_workflow_steps.collect{|s| (s.step == i) ? s : nil}.compact.each_with_index do |step, j| %> + <% if j != 0 %> + <%= step.soperator %>  + <% end %> + <%= link_to_user step.user %> + <% end %> + + <%= reorder_links('workflow_step', {:action => 'edit', :id => @dmsf_workflow, :step => i}, :put) %> + + <%= delete_link edit_dmsf_workflow_path(@dmsf_workflow, :step => i) %> +
    + <% else %> +

    <%= l(:label_no_data) %>

    <% end %> -
    -
    \ No newline at end of file diff --git a/app/views/dmsf_workflows/new_step.html.erb b/app/views/dmsf_workflows/new_step.html.erb new file mode 100644 index 00000000..8baa7cf3 --- /dev/null +++ b/app/views/dmsf_workflows/new_step.html.erb @@ -0,0 +1,19 @@ +<% +# encoding: utf-8 +# +# Redmine plugin for Kontron customisation +# Copyright (c) 2011-15 Kontron +# Karel Pičman +# +# Repository folders +%> + +

    <%= l(:label_repository_folders) %>

    + +<%= form_for(:repository_folder, {:url => edit_dmsf_workflow_path(@dmsf_workflow), :method => :post}) do |f| %> + <%= render :partial => 'new_step_form' %> +

    + <%= submit_tag l(:dmsf_and), :id => 'add-step-and' %> + <%= submit_tag l(:dmsf_or), :id => 'add-step-or' %> +

    +<% end %> \ No newline at end of file diff --git a/app/views/dmsf_workflows/new_step.js.erb b/app/views/dmsf_workflows/new_step.js.erb new file mode 100644 index 00000000..691a431f --- /dev/null +++ b/app/views/dmsf_workflows/new_step.js.erb @@ -0,0 +1,2 @@ +$('#ajax-modal').html('<%= escape_javascript(render :partial => 'new_step_modal') %>'); +showModal('ajax-modal', '700px'); \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index 1b827724..ab597dfe 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -126,9 +126,10 @@ RedmineApp::Application.routes.draw do post 'new_action' get 'start' post 'assignment' + get 'new_step' end end - + match 'dmsf_workflows/:id/edit', :controller => 'dmsf_workflows', :action => 'add_step', :id => /\d+/, :via => :post match 'dmsf_workflows/:id/edit', :controller => 'dmsf_workflows', :action => 'remove_step', :id => /\d+/, :via => :delete match 'dmsf_workflows/:id/edit', :controller => 'dmsf_workflows', :action => 'reorder_steps', :id => /\d+/, :via => :put diff --git a/init.rb b/init.rb index 7d6cd202..e40154da 100644 --- a/init.rb +++ b/init.rb @@ -84,7 +84,7 @@ Redmine::Plugin.register :redmine_dmsf do permission :file_approval, {:dmsf_workflows => [:action, :new_action, :autocomplete_for_user, :start, :assign, :assignment]} permission :manage_workflows, - {:dmsf_workflows => [:index, :new, :create, :destroy, :show, :add_step, :remove_step, :reorder_steps, :update]} + {:dmsf_workflows => [:index, :new, :create, :destroy, :show, :new_step, :add_step, :remove_step, :reorder_steps, :update]} end # Administration menu extension From 1e91304d9b00567261c852d80d11c83b0dc806a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Pi=C4=8Dman?= Date: Wed, 10 Jun 2015 15:46:09 +0200 Subject: [PATCH 40/52] YML formating --- config/locales/cs.yml | 5 +++-- config/locales/de.yml | 12 ++++++++---- config/locales/en.yml | 2 ++ config/locales/es.yml | 5 +++-- config/locales/fr.yml | 10 +++++++--- config/locales/ja.yml | 5 +++-- config/locales/pl.yml | 5 +++-- config/locales/ru.yml | 5 +++-- config/locales/sl.yml | 5 +++-- config/locales/zh-TW.yml | 22 +++++++++++++++------- config/locales/zh.yml | 5 +++-- 11 files changed, 53 insertions(+), 28 deletions(-) diff --git a/config/locales/cs.yml b/config/locales/cs.yml index 6f703555..33611d0e 100644 --- a/config/locales/cs.yml +++ b/config/locales/cs.yml @@ -290,13 +290,14 @@ cs: label_notifications_off: Vypnout notifikace field_target_file: Zdrojový soubor title_download_entries: Historie stahování + label_external: Externí + label_link_name: Název odkazu label_link_external_url: URL label_target_folder: Cílový adresář label_source_folder: Zdrojový adresář label_target_project: Cílový projekt - label_source_project: Zdrojový projekt - label_external: Externí + label_source_project: Zdrojový projekt text_email_doc_updated_subject: "Dokumenty projektu %{project} aktualizovány" text_email_doc_updated: právě aktualizoval dokumenty projektu diff --git a/config/locales/de.yml b/config/locales/de.yml index 708375a7..81d7ee7c 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -23,6 +23,8 @@ de: dmsf: DMS label_dmsf_file_plural: DMS + label_dmsf_file_revision_plural: Dmsf document revisions + label_dmsf_file_revision_access_plural: Dmsf document accesses warning_no_entries_selected: Keine Einträge ausgewählt error_email_to_must_be_entered: Es muss ein Email-Empfänger angegeben werden. warning_file_already_locked: Datei schon gesperrt @@ -150,9 +152,9 @@ de: permission_view_dmsf_files: Betrachte Dateien permission_folder_manipulation: Ordner bearbeiten permission_file_manipulation: Dateien bearbeiten - permission_force_file_unlock: Erzwinge Aufhebung der Dateisperre - permission_file_delete: Datei löschen + permission_force_file_unlock: Erzwinge Aufhebung der Dateisperre permission_manage_workflows: Workflows verwalten + permission_file_delete: Datei löschen label_file: Datei field_folder: Ordner error_create_cycle_in_folder_dependency: Zirkelabhängigkeit in der Ordnerstruktur erstellt @@ -179,6 +181,7 @@ de: heading_access_first: Erste heading_access_last: Letzte label_dmsf_updated: DMS aktualisiert + label_dmsf_downloaded: DMSF document downloaded title_total_size_of_all_files: Gesamtgröße aller Dateien in diesem Ordner project_module_dmsf: DMS warning_no_project_to_copy_file_to: Kein Projekt, in das die Datei kopiert werden kann. @@ -287,13 +290,14 @@ de: label_notifications_off: Benachrichtigungen aus field_target_file: Quelldatei title_download_entries: Download entries + label_external: Außen + label_link_name: Name der Verknüpfung label_link_external_url: URL label_target_folder: Zielordner label_source_folder: Quellordner label_target_project: Zielprojekt - label_source_project: Quellprojekt - label_external: Außen + label_source_project: Quellprojekt text_email_doc_updated_subject: "Dokumente im Projekt %{project} wurden aktualisiert" text_email_doc_updated: hat folgende Dokumente bearbeitet diff --git a/config/locales/en.yml b/config/locales/en.yml index ef0098cc..30590524 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -145,6 +145,8 @@ en: heading_uploaded_files: Uploaded Files submit_commit: Commit link_documents: Documents + permission_view_dmsf_file_revision_accesses: View downloads in Activity stream + permission_view_dmsf_file_revisions: View revisions in Activity stream permission_view_dmsf_folders: Browse documents permission_user_preferences: User preferences permission_view_dmsf_files: View documents diff --git a/config/locales/es.yml b/config/locales/es.yml index a649b72e..8be1ce50 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -290,13 +290,14 @@ es: label_notifications_off: Notificaciones OFF field_target_file: Archivo fuente title_download_entries: Entradas de descarga + label_external: External + label_link_name: Nombre de enlace label_link_external_url: URL label_target_folder: Directorio destino label_source_folder: Directorio fuente label_target_project: Proyecto destino - label_source_project: Proyecto fuente - label_external: External + label_source_project: Proyecto fuente text_email_doc_updated_subject: "Documentos de %{project} actualizados" text_email_doc_updated: acaba de actualizar los ducumentos de diff --git a/config/locales/fr.yml b/config/locales/fr.yml index aa5318b8..0ce6f73b 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -1,3 +1,5 @@ +# encoding: utf-8 +# # Redmine plugin for Document Management System "Features" # # Copyright (C) 2011 Vít Jonáš @@ -119,6 +121,7 @@ fr: option_version_same: (identique) option_version_minor: (modification mineure) option_version_major: (modification majeure) + option_version_custom: Custom label_new_content: Nouvelle version du fichier label_maximum_files_upload: Nombre maximal de documents pouvant être transmis note_maximum_number_of_files_uploaded: Nombre maximal de documents pouvant être transmis en une fois. La valeur 0 signifie illimité. @@ -287,13 +290,14 @@ fr: label_notifications_off: Désactiver les notifications field_target_file: Fichier source title_download_entries: Historique des téléchargements + label_external: External + label_link_name: Nom du lien label_link_external_url: Adresse Internet label_target_folder: Dossier cible label_source_folder: Dossier source label_target_project: Projet cible - label_source_project: Projet source - label_external: External + label_source_project: Projet source text_email_doc_updated_subject: "Documents de %{project} mis à jour" text_email_doc_updated: a mis à jour des documents de @@ -320,4 +324,4 @@ fr: open_approvals: Approbations en attente label_maximum_ajax_upload_filesize: Taille maximale de fichier pour téléversement via AJAX - note_maximum_ajax_upload_filesize: "Taille maximale, en méga octets, de fichier pour téléversement via l'interface standard AJAX. Sinon l'interface standard de Redmine doit être utilisée." + note_maximum_ajax_upload_filesize: "Taille maximale, en méga octets, de fichier pour téléversement via l'interface standard AJAX. Sinon l'interface standard de Redmine doit être utilisée." \ No newline at end of file diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 5ce5eb39..d8983452 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -290,13 +290,14 @@ ja: label_notifications_off: Notifications off field_target_file: Source file title_download_entries: Download entries + label_external: External + label_link_name: Link name label_link_external_url: URL label_target_folder: Target folder label_source_folder: Source folder label_target_project: Target project - label_source_project: Source project - label_external: External + label_source_project: Source project text_email_doc_updated_subject: "Documents of %{project} updated" text_email_doc_updated: has just actualized documents of diff --git a/config/locales/pl.yml b/config/locales/pl.yml index 744814d3..d004445d 100644 --- a/config/locales/pl.yml +++ b/config/locales/pl.yml @@ -290,13 +290,14 @@ pl: label_notifications_off: Powiadomienia wyłączone field_target_file: Plik źródłowy title_download_entries: Pobrane + label_external: External + label_link_name: Nazwa odnośnika label_link_external_url: URL label_target_folder: Folder docelowy label_source_folder: Folder źródłowy label_target_project: Projekt docelowy - label_source_project: Projekt źródłowy - label_external: External + label_source_project: Projekt źródłowy text_email_doc_updated_subject: "Dokumenty projektu %{project} zostały zaktualizowane" text_email_doc_updated: dokumenty zostały zaktualizowane diff --git a/config/locales/ru.yml b/config/locales/ru.yml index 2d315615..54613cd4 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -290,13 +290,14 @@ ru: label_notifications_off: Notifications off field_target_file: Source file title_download_entries: Download entries + label_external: External + label_link_name: Link name label_link_external_url: URL label_target_folder: Target folder label_source_folder: Source folder label_target_project: Target project - label_source_project: Source project - label_external: External + label_source_project: Source project text_email_doc_updated_subject: "Documents of %{project} updated" text_email_doc_updated: has just actualized documents of diff --git a/config/locales/sl.yml b/config/locales/sl.yml index 4c341e09..30e4b39c 100644 --- a/config/locales/sl.yml +++ b/config/locales/sl.yml @@ -290,13 +290,14 @@ sl: label_notifications_off: Notifications off field_target_file: Source file title_download_entries: Download entries + label_external: External + label_link_name: Link name label_link_external_url: URL label_target_folder: Target folder label_source_folder: Source folder label_target_project: Target project - label_source_project: Source project - label_external: External + label_source_project: Source project text_email_doc_updated_subject: "Documents of %{project} updated" text_email_doc_updated: has just actualized documents of diff --git a/config/locales/zh-TW.yml b/config/locales/zh-TW.yml index f12c1796..710da98c 100644 --- a/config/locales/zh-TW.yml +++ b/config/locales/zh-TW.yml @@ -80,10 +80,8 @@ zh-TW: title_waiting_for_approval: 等待批准 title_approved: 已經被批准 title_unlock_file: 解除鎖定。允許其它使用者修改。 - title_lock_file: 鎖定檔案。禁止其它使用者修改。 - submit_download: 下載 - title_download_checked: 以ZIP下載所選取的檔案 - submit_email: 電子郵件 + title_lock_file: 鎖定檔案。禁止其它使用者修改。 + title_download_checked: 以ZIP下載所選取的檔案 title_send_checked_by_email: 以電子郵件發送所選取的檔案 link_user_preferences: 你的DMSF的系統偏好設定 heading_send_documents_by_email: 電子郵件寄送檔案 @@ -156,6 +154,7 @@ zh-TW: permission_file_manipulation: 文件操作 permission_force_file_unlock: 強制解除檔案鎖定 permission_manage_workflows: Manage workflows + permission_file_delete: Delete documents label_file: 檔案 field_folder: 資料夾 error_create_cycle_in_folder_dependency: 資料夾之間的關係,不得為一個cycle循環。 @@ -291,13 +290,14 @@ zh-TW: label_notifications_off: Notifications off field_target_file: Source file title_download_entries: Download entries + label_external: External + label_link_name: Link name label_link_external_url: URL label_target_folder: Target folder label_source_folder: Source folder label_target_project: Target project - label_source_project: Source project - label_external: External + label_source_project: Source project text_email_doc_updated_subject: "Documents of %{project} updated" text_email_doc_updated: has just actualized documents of @@ -309,6 +309,14 @@ zh-TW: label_display_notified_recipients: Display notified recipients note_display_notified_recipients: The user will be informed about all recipients of just sent the email notification. warning_email_notifications: "Email notifications sent to %{to}" + + link_trash_bin: Trash bin + title_restore: Restore + notice_dmsf_file_restored: The document has been successfully restored + notice_dmsf_folder_restored: The folder has been successfully restored + notice_dmsf_link_restored: The link has been successfully restored + title_restore_checked: Restore checked + error_parent_folder: "The parent folder doesn't exist" my: blocks: @@ -316,4 +324,4 @@ zh-TW: open_approvals: Open approvals label_maximum_ajax_upload_filesize: Maximum file size uploadable via AJAX - note_maximum_ajax_upload_filesize: Limits maximum file size that can uploaded via standard AJAX interface otherwise Redmine standard upload form must be used. Number is in MB. + note_maximum_ajax_upload_filesize: Limits maximum file size that can uploaded via standard AJAX interface otherwise Redmine standard upload form must be used. Number is in MB. \ No newline at end of file diff --git a/config/locales/zh.yml b/config/locales/zh.yml index f64e4892..851e320a 100644 --- a/config/locales/zh.yml +++ b/config/locales/zh.yml @@ -290,13 +290,14 @@ zh: label_notifications_off: Notifications off field_target_file: Source file title_download_entries: Download entries + label_external: External + label_link_name: Link name label_link_external_url: URL label_target_folder: Target folder label_source_folder: Source folder label_target_project: Target project - label_source_project: Source project - label_external: External + label_source_project: Source project text_email_doc_updated_subject: "Documents of %{project} updated" text_email_doc_updated: has just actualized documents of From 1327458c1721cd1777c1cb925cc20f0948687e39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Pi=C4=8Dman?= Date: Thu, 11 Jun 2015 12:53:44 +0200 Subject: [PATCH 41/52] empty? => blank? --- app/models/dmsf_file.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/dmsf_file.rb b/app/models/dmsf_file.rb index d2b8b9d3..cafe32d1 100644 --- a/app/models/dmsf_file.rb +++ b/app/models/dmsf_file.rb @@ -407,7 +407,7 @@ class DmsfFile < ActiveRecord::Base end if user.allowed_to?(:view_dmsf_files, dmsf_file.project) && - (project_ids.empty? || (project_ids.include?(dmsf_file.project.id))) + (project_ids.blank? || (project_ids.include?(dmsf_file.project.id))) if (Rails::VERSION::MAJOR > 3) Redmine::Search.cache_store.write("DmsfFile-#{dmsf_file.id}", dochash['sample'].force_encoding('UTF-8')) if dochash['sample'] From 78127b8e37acc9c671764297acb3688e160d737c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Pi=C4=8Dman?= Date: Mon, 15 Jun 2015 13:34:39 +0200 Subject: [PATCH 42/52] YML formating --- config/locales/cs.yml | 16 ++++++------- config/locales/de.yml | 14 +++++------ config/locales/en.yml | 14 +++++------ config/locales/es.yml | 10 ++++---- config/locales/fr.yml | 10 ++++---- config/locales/ja.yml | 8 +++---- config/locales/pl.yml | 10 ++++---- config/locales/ru.yml | 12 +++++----- config/locales/sl.yml | 6 ++--- config/locales/zh-TW.yml | 10 ++++---- config/locales/zh.yml | 10 ++++---- extra/xapian_indexer.rb | 52 ++++++++++++++++++++-------------------- 12 files changed, 86 insertions(+), 86 deletions(-) diff --git a/config/locales/cs.yml b/config/locales/cs.yml index 33611d0e..0ecd5e08 100644 --- a/config/locales/cs.yml +++ b/config/locales/cs.yml @@ -22,9 +22,9 @@ cs: dmsf: DMSF - label_dmsf_file_plural: Dmsf soubory - label_dmsf_file_revision_plural: Dmsf document revisions - label_dmsf_file_revision_access_plural: Dmsf document accesses + label_dmsf_file_plural: Dokumenty + label_dmsf_file_revision_plural: Revize dokumentů + label_dmsf_file_revision_access_plural: Přístupy k dokumentům warning_no_entries_selected: Není nic vybráno error_email_to_must_be_entered: Musí být zadán adresát warning_file_already_locked: Soubor už je zamčen @@ -167,7 +167,7 @@ cs: warning_some_entries_were_not_deleted: "Některé položky nebyly smazány: %{entries}" title_delete_checked: Smaž vybrané title_items: položek - title_filename_for_download: Název Zip archívu ke stažení + title_filename_for_download: Název Zip archivu ke stažení label_number_of_folders: Složky label_number_of_documents: Dokumenty error_file_storage_directory_does_not_exist: Cílová složka neexistuje a nemůže být vytvořena @@ -180,8 +180,8 @@ cs: heading_access_downloads_emails: Stažené/Emaily heading_access_first: První heading_access_last: Poslední - label_dmsf_updated: DMSF změněno - label_dmsf_downloaded: DMSF document downloaded + label_dmsf_updated: Změněno + label_dmsf_downloaded: Staženo title_total_size_of_all_files: Celková velikost všech souborů v adresáři project_module_dmsf: DMSF warning_no_project_to_copy_file_to: Neexistuje projekt, do kterého můžete kopírovat @@ -191,7 +191,7 @@ cs: field_target_project: Cílový projekt field_target_folder: Cílový adresář title_copy_or_move: Kopírovat/Přesunout - label_dmsf_folder_plural: Dmsf složky + label_dmsf_folder_plural: Složky comment_moved_from: "Přesunuto z %{source}" error_target_folder_same: Cílový adresář a projekt jsou stejné jako aktuální error_file_cannot_be_moved: Soubor nemůže být přesunut @@ -209,7 +209,7 @@ cs: parent_directory: Nadřazený adresář note_webdav: "Webdav je po založení k dispozici na http://.../dmsf/webdav/" label_webdav: Webdav functionalita - label_dmsf_plural: "Kopíruj DMSF soubory a složky (%{files} souborů v %{folders} složkách)" + label_dmsf_plural: "Kopíruj dokumenty a složky (%{files} souborů v %{folders} složkách)" warning_folder_already_locked: Tato složka je již zamčená notice_folder_locked: Složka byla úspěšně zamčena diff --git a/config/locales/de.yml b/config/locales/de.yml index 81d7ee7c..340b5e3f 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -23,8 +23,8 @@ de: dmsf: DMS label_dmsf_file_plural: DMS - label_dmsf_file_revision_plural: Dmsf document revisions - label_dmsf_file_revision_access_plural: Dmsf document accesses + label_dmsf_file_revision_plural: Dokumenteversion + label_dmsf_file_revision_access_plural: Dokumentezugriffe warning_no_entries_selected: Keine Einträge ausgewählt error_email_to_must_be_entered: Es muss ein Email-Empfänger angegeben werden. warning_file_already_locked: Datei schon gesperrt @@ -83,7 +83,7 @@ de: title_lock_file: Sperre um Änderungen anderer Nutzer zu verhindern title_download_checked: Download der ausgewählten Dateien in einem ZIP-Archiv title_send_checked_by_email: Sende gewählte Dateien per Email - link_user_preferences: Deine Einstellungen + link_user_preferences: Deine DMS Projekt Einstellungen heading_send_documents_by_email: Sende Dateien per Email label_email_from: Von label_email_to: An @@ -145,8 +145,8 @@ de: heading_uploaded_files: Hochgeladene Dateien submit_commit: OK link_documents: Dateien - permission_view_dmsf_file_revision_accesses: View downloads in Activity stream - permission_view_dmsf_file_revisions: View revisions in Activity stream + permission_view_dmsf_file_revision_accesses: Betrachte Dokumentezugriffe in Aktivitäten + permission_view_dmsf_file_revisions: Betrachte Dokumenteversion in Aktivitäten permission_view_dmsf_folders: Durchforste Dateien permission_user_preferences: Benutzereinstellungen permission_view_dmsf_files: Betrachte Dateien @@ -180,8 +180,8 @@ de: heading_access_downloads_emails: Downloads oder Emailversand heading_access_first: Erste heading_access_last: Letzte - label_dmsf_updated: DMS aktualisiert - label_dmsf_downloaded: DMSF document downloaded + label_dmsf_updated: aktualisiert + label_dmsf_downloaded: gespeichert title_total_size_of_all_files: Gesamtgröße aller Dateien in diesem Ordner project_module_dmsf: DMS warning_no_project_to_copy_file_to: Kein Projekt, in das die Datei kopiert werden kann. diff --git a/config/locales/en.yml b/config/locales/en.yml index 30590524..36aa7b7f 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -22,9 +22,9 @@ en: dmsf: DMSF - label_dmsf_file_plural: Dmsf files - label_dmsf_file_revision_plural: Dmsf document revisions - label_dmsf_file_revision_access_plural: Dmsf document accesses + label_dmsf_file_plural: Documents + label_dmsf_file_revision_plural: Document revisions + label_dmsf_file_revision_access_plural: Document accesses warning_no_entries_selected: No entries selected error_email_to_must_be_entered: Email To must be entered warning_file_already_locked: File already locked @@ -180,8 +180,8 @@ en: heading_access_downloads_emails: Downloads/Emails heading_access_first: First heading_access_last: Last - label_dmsf_updated: DMSF document updated - label_dmsf_downloaded: DMSF document downloaded + label_dmsf_updated: Updated + label_dmsf_downloaded: Downloaded title_total_size_of_all_files: Total size of all files under this folder project_module_dmsf: DMSF warning_no_project_to_copy_file_to: No project to copy file to @@ -191,7 +191,7 @@ en: field_target_project: Target project field_target_folder: Target folder title_copy_or_move: Copy/Move - label_dmsf_folder_plural: Dmsf folders + label_dmsf_folder_plural: Folders comment_moved_from: "Moved from %{source}" error_target_folder_same: Target folder and project are the same as current error_file_cannot_be_moved: "File can't be moved" @@ -209,7 +209,7 @@ en: parent_directory: Parent Directory note_webdav: Webdav once enabled can be found at http://.../dmsf/webdav/ label_webdav: Webdav functionality - label_dmsf_plural: "Copy DMSF files and folders (%{files} files in %{folders} folders)" + label_dmsf_plural: "Copy Documents and folders (%{files} files in %{folders} folders)" warning_folder_already_locked: This folder is already locked notice_folder_locked: The folder was successfully locked diff --git a/config/locales/es.yml b/config/locales/es.yml index 8be1ce50..2586673d 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -22,9 +22,9 @@ es: dmsf: DMSF - label_dmsf_file_plural: DMSF Archivos - label_dmsf_file_revision_plural: Dmsf document revisions - label_dmsf_file_revision_access_plural: Dmsf document accesses + label_dmsf_file_plural: Archivos + label_dmsf_file_revision_plural: Document revisions + label_dmsf_file_revision_access_plural: Document accesses warning_no_entries_selected: No ha seleccionado ningún ítem error_email_to_must_be_entered: Ingrese un email warning_file_already_locked: El archivo ya está bloqueado @@ -191,7 +191,7 @@ es: field_target_project: Proyecto destino field_target_folder: Directorio destino title_copy_or_move: Copiar/Mover - label_dmsf_folder_plural: directorio Dmsf + label_dmsf_folder_plural: directorio comment_moved_from: "Mover desde %{source}" error_target_folder_same: El directorio y proyecto destino son el actual error_file_cannot_be_moved: "Los archivos no puesen ser movidos" @@ -209,7 +209,7 @@ es: parent_directory: Directorio padre note_webdav: Si Webdav está habilitado, se puede encontrar en http://.../dmsf/webdav/ label_webdav: Funcionalidad Webdav - label_dmsf_plural: "Copiar archivos y directorios DMSF (%{files} archivos en %{folders} directorios)" + label_dmsf_plural: "Copiar archivos y directorios (%{files} archivos en %{folders} directorios)" warning_folder_already_locked: El directorio ya se encuentra bloqueado notice_folder_locked: "El directorio se bloqueó exitosamente" diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 0ce6f73b..de5fd4af 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -22,9 +22,9 @@ fr: dmsf: DMSF - label_dmsf_file_plural: Fichiers DMSF - label_dmsf_file_revision_plural: Dmsf document revisions - label_dmsf_file_revision_access_plural: Dmsf document accesses + label_dmsf_file_plural: Fichiers + label_dmsf_file_revision_plural: Document revisions + label_dmsf_file_revision_access_plural: Document accesses warning_no_entries_selected: Aucun fichier sélectionné error_email_to_must_be_entered: "La saisie d'une adresse mail est obligatoire" warning_file_already_locked: Fichier déjà verrouillé @@ -191,7 +191,7 @@ fr: field_target_project: Projet cible field_target_folder: Dossier cible title_copy_or_move: Copie/Déplacement - label_dmsf_folder_plural: Les dossiers de DMSF + label_dmsf_folder_plural: Les dossiers comment_moved_from: "Déplacé depuis %{source}" error_target_folder_same: Le projet et le dossier cible sont identiques au projet et dossier source error_file_cannot_be_moved: Le fichier ne peut pas être déplacé @@ -209,7 +209,7 @@ fr: parent_directory: Dossier parent note_webdav: "Après l'activation du module Webdav, celui-ci sera accessible par http://.../dmsf/webdav/" label_webdav: Module Webdav - label_dmsf_plural: "Copier les fichiers et les dossiers DMSF (%{files} fichiers dans %{folders} dossiers)" + label_dmsf_plural: "Copier les fichiers et les dossiers (%{files} fichiers dans %{folders} dossiers)" warning_folder_already_locked: Ce dossier est déjà verrouillé notice_folder_locked: Dossier verrouillé diff --git a/config/locales/ja.yml b/config/locales/ja.yml index d8983452..c9f73d15 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -22,9 +22,9 @@ ja: dmsf: DMSF - label_dmsf_file_plural: Dmsf ファイル - label_dmsf_file_revision_plural: Dmsf document revisions - label_dmsf_file_revision_access_plural: Dmsf document accesses + label_dmsf_file_plural: ファイル + label_dmsf_file_revision_plural: Document revisions + label_dmsf_file_revision_access_plural: Document accesses warning_no_entries_selected: エントリーが選ばれていません error_email_to_must_be_entered: 電子メールの To 先は省略できません warning_file_already_locked: ファイルは既にロックされています @@ -209,7 +209,7 @@ ja: parent_directory: "Parent Directory" note_webdav: "Webdav once enabled can be found at http://.../dmsf/webdav/" label_webdav: "Webdav functionality" - label_dmsf_plural: "Copy DMSF files and folders (%{files} files in %{folders} folders)" + label_dmsf_plural: "Copy documents and folders (%{files} files in %{folders} folders)" warning_folder_already_locked: "This folder is already locked" notice_folder_locked: "The folder was successfully locked" diff --git a/config/locales/pl.yml b/config/locales/pl.yml index d004445d..10d043f8 100644 --- a/config/locales/pl.yml +++ b/config/locales/pl.yml @@ -23,9 +23,9 @@ pl: dmsf: DMSF - label_dmsf_file_plural: Pliki Dmsf - label_dmsf_file_revision_plural: Dmsf document revisions - label_dmsf_file_revision_access_plural: Dmsf document accesses + label_dmsf_file_plural: Pliki + label_dmsf_file_revision_plural: Document revisions + label_dmsf_file_revision_access_plural: Document accesses warning_no_entries_selected: Nie zaznaczono żadnych wierszy error_email_to_must_be_entered: Musisz podać adres email warning_file_already_locked: Plik jest już zablokowany @@ -192,7 +192,7 @@ pl: field_target_project: Projekt docelowy field_target_folder: Folder docelowy title_copy_or_move: Kopiuj/Przenieś - label_dmsf_folder_plural: Foldery Dmsf + label_dmsf_folder_plural: Foldery comment_moved_from: "Przeniesiono z %{source}" error_target_folder_same: Docelowy projekt i folder są identyczne z obecnym error_file_cannot_be_moved: "Plik nie może zostać przeniesiony" @@ -210,7 +210,7 @@ pl: parent_directory: Folder nadrzędny note_webdav: Webdav once enabled can be found at http://.../dmsf/webdav/ label_webdav: Webdav functionality - label_dmsf_plural: "Skopiuj pliki i foldery DMSF (%{files} plików w %{folders} folderach)" + label_dmsf_plural: "Skopiuj pliki i foldery (%{files} plików w %{folders} folderach)" warning_folder_already_locked: Folder jest już zablokowany notice_folder_locked: Folder został zablokowany diff --git a/config/locales/ru.yml b/config/locales/ru.yml index 54613cd4..ab3ecf4f 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -22,9 +22,9 @@ ru: dmsf: DMSF - label_dmsf_file_plural: Файлы DMSF - label_dmsf_file_revision_plural: Dmsf document revisions - label_dmsf_file_revision_access_plural: Dmsf document accesses + label_dmsf_file_plural: Файлы + label_dmsf_file_revision_plural: Document revisions + label_dmsf_file_revision_access_plural: Document accesses warning_no_entries_selected: Файлы не выбраны error_email_to_must_be_entered: Нужно указать, на какую почту отправить письмо warning_file_already_locked: Файл уже заблокирован @@ -181,7 +181,7 @@ ru: heading_access_first: Первый heading_access_last: Последний label_dmsf_updated: Документ обновлен - label_dmsf_downloaded: DMSF document downloaded + label_dmsf_downloaded: Документ downloaded title_total_size_of_all_files: Общий размер всех файлов в этой папке project_module_dmsf: DMSF warning_no_project_to_copy_file_to: Не выбран проект, в который нужно скопировать файл @@ -191,7 +191,7 @@ ru: field_target_project: Целевой проект field_target_folder: Целевая папка title_copy_or_move: Копировать/Переместить - label_dmsf_folder_plural: DMSF папки + label_dmsf_folder_plural: Папки comment_moved_from: "Перемещен из %{source}" error_target_folder_same: Целевая папка и проект совпадают с текущими error_file_cannot_be_moved: Файл не может быть перемещен @@ -209,7 +209,7 @@ ru: parent_directory: "Родительская директория" note_webdav: "После включения, WebDAV можно найти по адресу http://redmine-host/dmsf/webdav/" label_webdav: "Функциональность WebDAV" - label_dmsf_plural: "Скопировать DMSF файлы и папки (%{files} файлов в %{folders} папках)" + label_dmsf_plural: "Скопировать файлы и папки (%{files} файлов в %{folders} папках)" warning_folder_already_locked: "Эта папка уже заблокирована" notice_folder_locked: "Папка была успешно заблокирована" diff --git a/config/locales/sl.yml b/config/locales/sl.yml index 30e4b39c..2d26d65f 100644 --- a/config/locales/sl.yml +++ b/config/locales/sl.yml @@ -23,8 +23,8 @@ sl: dmsf: Arhiv label_dmsf_file_plural: Arhivske datoteke - label_dmsf_file_revision_plural: Dmsf document revisions - label_dmsf_file_revision_access_plural: Dmsf document accesses + label_dmsf_file_revision_plural: Document revisions + label_dmsf_file_revision_access_plural: Document accesses warning_no_entries_selected: Ničesar niste izbrali error_email_to_must_be_entered: Email Naslovnik mora bit izbran warning_file_already_locked: Datoteka že zaklenjena @@ -181,7 +181,7 @@ sl: heading_access_first: Prvi heading_access_last: Zadnji label_dmsf_updated: Arhiv posodobljen - label_dmsf_downloaded: DMSF document downloaded + label_dmsf_downloaded: Arhiv downloaded title_total_size_of_all_files: Skupna velikost vseh datotek v tej mapi project_module_dmsf: Arhiv warning_no_project_to_copy_file_to: Ni projekta kamor bi kopiral datoteko diff --git a/config/locales/zh-TW.yml b/config/locales/zh-TW.yml index 710da98c..9fdfa073 100644 --- a/config/locales/zh-TW.yml +++ b/config/locales/zh-TW.yml @@ -23,8 +23,8 @@ zh-TW: dmsf: 文件總管 label_dmsf_file_plural: 文件檔案 - label_dmsf_file_revision_plural: Dmsf document revisions - label_dmsf_file_revision_access_plural: Dmsf document accesses + label_dmsf_file_revision_plural: Document revisions + label_dmsf_file_revision_access_plural: Document accesses warning_no_entries_selected: 尚未選取任何項目 error_email_to_must_be_entered: 請輸入收件者的電子郵件 warning_file_already_locked: 檔案己經鎖定 @@ -180,8 +180,8 @@ zh-TW: heading_access_downloads_emails: 下載/電子郵件 heading_access_first: First heading_access_last: Last - label_dmsf_updated: DMSF updated - label_dmsf_downloaded: DMSF document downloaded + label_dmsf_updated: Updated + label_dmsf_downloaded: Downloaded title_total_size_of_all_files: 資料夾所有檔案的檔案大小 project_module_dmsf: 文件總管 warning_no_project_to_copy_file_to: No project to copy file to @@ -191,7 +191,7 @@ zh-TW: field_target_project: Target project field_target_folder: Target folder title_copy_or_move: Copy/Move - label_dmsf_folder_plural: Dmsf folders + label_dmsf_folder_plural: Folders comment_moved_from: "Moved from %{source}" error_target_folder_same: Target folder and project are the same as current error_file_cannot_be_moved: "File can't be moved" diff --git a/config/locales/zh.yml b/config/locales/zh.yml index 851e320a..9ffbaee9 100644 --- a/config/locales/zh.yml +++ b/config/locales/zh.yml @@ -22,9 +22,9 @@ zh: dmsf: 文档管家 - label_dmsf_file_plural: Dmsf files - label_dmsf_file_revision_plural: Dmsf document revisions - label_dmsf_file_revision_access_plural: Dmsf document accesses + label_dmsf_file_plural: Documents + label_dmsf_file_revision_plural: Document revisions + label_dmsf_file_revision_access_plural: Document accesses warning_no_entries_selected: 未选择任何条目 error_email_to_must_be_entered: 请输入电子邮件 warning_file_already_locked: 文件已经锁定 @@ -191,7 +191,7 @@ zh: field_target_project: Target project field_target_folder: Target folder title_copy_or_move: Copy/Move - label_dmsf_folder_plural: Dmsf folders + label_dmsf_folder_plural: Folders comment_moved_from: "Moved from %{source}" error_target_folder_same: Target folder and project are the same as current error_file_cannot_be_moved: "File can't be moved" @@ -209,7 +209,7 @@ zh: parent_directory: Parent Directory note_webdav: Webdav once enabled can be found at http://.../dmsf/webdav/ label_webdav: Webdav functionality - label_dmsf_plural: "Copy DMSF files and folders (%{files} files in %{folders} folders)" + label_dmsf_plural: "Copy documents and folders (%{files} files in %{folders} folders)" warning_folder_already_locked: This folder is already locked notice_folder_locked: The folder was successfully locked diff --git a/extra/xapian_indexer.rb b/extra/xapian_indexer.rb index 1d2093d2..3a818790 100644 --- a/extra/xapian_indexer.rb +++ b/extra/xapian_indexer.rb @@ -542,13 +542,13 @@ if not $onlyrepos then end # Indexing repositories -if not $onlyfiles then - if not File.exist?($scriptindex) then +unless $onlyfiles + unless File.exist?($scriptindex) log("- ERROR! #{$scriptindex} does not exist, exiting...") exit 1 end - $databasepath = File.join( $dbrootpath.rstrip, "repodb" ) - if not File.directory?($databasepath) + $databasepath = File.join($dbrootpath.rstrip, 'repodb') + unless File.directory?($databasepath) log("Db directory #{$databasepath} does not exist, creating...") begin Dir.mkdir($databasepath) @@ -557,32 +557,32 @@ if not $onlyfiles then log("ERROR! #{$databasepath} can not be created!, exiting ...") exit 1 end - end - $project=nil - $projects.each do |proj| - begin - scope = Project.active.has_module(:repository) - $project = scope.find_by_identifier(proj) - raise ActiveRecord::RecordNotFound unless $project - log("- Indexing repositories for #{$project.name} ...", :level=>1) - $repositories = $project.repositories.select { |repository| repository.supports_cat? } - $repositories.each do |repository| - if repository.identifier.nil? then - log("\t>Ignoring repo id #{repository.id}, repo has undefined identifier", :level=>1) - else - if !$userch.nil? then - changeset=Changeset.where("revision='#{$userch}' and repository_id='#{repository.id}'").first - update_log(repository,changeset,nil,nil) unless changeset.nil? - end - delete_log(repository) if ($resetlog) + end + + projects.each do |identifier| + begin + project = Project.active.find_by_identifier(identifier) + raise ActiveRecord::RecordNotFound unless project + raise ActiveRecord::RecordNotFound unless project.has_module(:repository) + log("- Indexing repositories for #{project.name} ...", :level => 1) + repositories = project.repositories.select { |repository| repository.supports_cat? } + repositories.each do |repository| + if repository.identifier.nil? then + log("\t>Ignoring repo id #{repository.id}, repo has undefined identifier", :level => 1) + else + if $userch + changeset = Changeset.where(:revision => $userch, :repository_id => repository.id).first + update_log(repository, changeset, nil, nil) if changeset + end + delete_log(repository) if ($resetlog) indexing(repository) - end + end end rescue ActiveRecord::RecordNotFound - log("- ERROR project identifier #{proj} not found, ignoring...", :level => 1) - Rails.logger.error "Project identifier #{proj} not found " + log("- ERROR project identifier #{identifier} not found or repository module not enabled, ignoring...", :level => 1) + Rails.logger.error "Project identifier #{identifier} not found " end end end -exit 0 +exit 0 \ No newline at end of file From 7bbb6d24b2beab38b2889a59da69cfbf2e05e227 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Pi=C4=8Dman?= Date: Mon, 15 Jun 2015 14:50:13 +0200 Subject: [PATCH 43/52] Duplicated email recipients in the info box --- app/models/dmsf_mailer.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/models/dmsf_mailer.rb b/app/models/dmsf_mailer.rb index 2acb69ec..55c3029f 100644 --- a/app/models/dmsf_mailer.rb +++ b/app/models/dmsf_mailer.rb @@ -1,7 +1,9 @@ +# encoding: utf-8 +# # Redmine plugin for Document Management System "Features" # # Copyright (C) 2011 Vít Jonáš -# Copyright (C) 2011-14 Karel Pičman +# Copyright (C) 2011-15 Karel Pičman # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -114,7 +116,7 @@ class DmsfMailer < Mailer end end - notify_members.collect { |m| m.user } + notify_members.collect { |m| m.user }.uniq end end \ No newline at end of file From 9fe31ac837cefc334eda1d3332503c2438eec85f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Pi=C4=8Dman?= Date: Thu, 18 Jun 2015 15:17:27 +0200 Subject: [PATCH 44/52] Withs of modal windows in % instead of px --- app/views/dmsf_workflows/action.js.erb | 4 ++-- app/views/dmsf_workflows/assign.js.erb | 4 ++-- app/views/dmsf_workflows/log.js.erb | 4 ++-- app/views/dmsf_workflows/new_step.js.erb | 20 +++++++++++++++++++- 4 files changed, 25 insertions(+), 7 deletions(-) diff --git a/app/views/dmsf_workflows/action.js.erb b/app/views/dmsf_workflows/action.js.erb index 1338d7ac..fe39820b 100644 --- a/app/views/dmsf_workflows/action.js.erb +++ b/app/views/dmsf_workflows/action.js.erb @@ -1,6 +1,6 @@ <%# Redmine plugin for Document Management System "Features" # -# Copyright (C) 2013 Karel Pičman +# Copyright (C) 2011-15 Karel Pičman # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -17,5 +17,5 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.%> $('#ajax-modal').html('<%= escape_javascript(render :partial => 'action', :locals => {:workflow => @dmsf_workflow}) %>'); -showModal('ajax-modal', '400px'); +showModal('ajax-modal', '35%'); $('#ajax-modal').addClass('new-action'); \ No newline at end of file diff --git a/app/views/dmsf_workflows/assign.js.erb b/app/views/dmsf_workflows/assign.js.erb index 89693237..eae3c44a 100644 --- a/app/views/dmsf_workflows/assign.js.erb +++ b/app/views/dmsf_workflows/assign.js.erb @@ -1,6 +1,6 @@ <%# Redmine plugin for Document Management System "Features" # -# Copyright (C) 2013 Karel Pičman +# Copyright (C) 2011-15 Karel Pičman # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -17,5 +17,5 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.%> $('#ajax-modal').html('<%= escape_javascript(render :partial => 'assign', :locals => {:workflow => @dmsf_workflow}) %>'); -showModal('ajax-modal', '400px'); +showModal('ajax-modal', '30%'); $('#ajax-modal').addClass('assignment'); \ No newline at end of file diff --git a/app/views/dmsf_workflows/log.js.erb b/app/views/dmsf_workflows/log.js.erb index 9131f960..7dc28e91 100644 --- a/app/views/dmsf_workflows/log.js.erb +++ b/app/views/dmsf_workflows/log.js.erb @@ -1,6 +1,6 @@ <%# Redmine plugin for Document Management System "Features" # -# Copyright (C) 2013 Karel Pičman +# Copyright (C) 2011-15 Karel Pičman # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -17,5 +17,5 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.%> $('#ajax-modal').html('<%= escape_javascript(render :partial => 'log', :locals => {:workflow => @dmsf_workflow}) %>'); -showModal('ajax-modal', '800px'); +showModal('ajax-modal', '90%'); $('#ajax-modal').addClass('workflow-log'); \ No newline at end of file diff --git a/app/views/dmsf_workflows/new_step.js.erb b/app/views/dmsf_workflows/new_step.js.erb index 691a431f..56600c73 100644 --- a/app/views/dmsf_workflows/new_step.js.erb +++ b/app/views/dmsf_workflows/new_step.js.erb @@ -1,2 +1,20 @@ +<%# Redmine plugin for Document Management System "Features" +# +# Copyright (C) 2011-15 Karel Pičman +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.%> + $('#ajax-modal').html('<%= escape_javascript(render :partial => 'new_step_modal') %>'); -showModal('ajax-modal', '700px'); \ No newline at end of file +showModal('ajax-modal', '40%'); \ No newline at end of file From cef0549bd447625be6e4f22c37606fb2fed78c86 Mon Sep 17 00:00:00 2001 From: Atmis Date: Mon, 22 Jun 2015 22:44:30 +0200 Subject: [PATCH 45/52] Update fr.yml --- config/locales/fr.yml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/config/locales/fr.yml b/config/locales/fr.yml index de5fd4af..65819018 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -23,8 +23,8 @@ fr: dmsf: DMSF label_dmsf_file_plural: Fichiers - label_dmsf_file_revision_plural: Document revisions - label_dmsf_file_revision_access_plural: Document accesses + label_dmsf_file_revision_plural: Révisions du document + label_dmsf_file_revision_access_plural: Accès au document warning_no_entries_selected: Aucun fichier sélectionné error_email_to_must_be_entered: "La saisie d'une adresse mail est obligatoire" warning_file_already_locked: Fichier déjà verrouillé @@ -63,14 +63,14 @@ fr: submit_create: Ajouter link_create_folder: Créer un sous-dossier title_check_uncheck_all_for_zip_download_or_email: Sélectionner/Ignorer tous les documents pour le téléchargement ou pour la transmission par mail - title_check_uncheck_all_for_restore_or_delete: Check/Uncheck all for restore or delete + title_check_uncheck_all_for_restore_or_delete: Sélectionner/Ignorer pour restaurer ou supprimer link_title: Titre link_size: Taille link_modified: Modifié link_ver: Version link_author: Auteur title_check_for_zip_download_or_email: Sélectionner pour le téléchargement ou la transmission par mail - title_check_for_restore_or_delete: Check for restore or delete + title_check_for_restore_or_delete: Sélectionner pour restaurer ou supprimer title_delete: Supprimer title_notifications_active_deactivate: "Notifications activées : cliquer pour désactiver" title_notifications_not_active_activate: "Notifications désactivées : cliquer pour activer" @@ -145,8 +145,8 @@ fr: heading_uploaded_files: Document(s) transmis submit_commit: Appliquer link_documents: Documents - permission_view_dmsf_file_revision_accesses: View downloads in Activity stream - permission_view_dmsf_file_revisions: View revisions in Activity stream + permission_view_dmsf_file_revision_accesses: Voir les téléchargements dans le flux d'activité + permission_view_dmsf_file_revisions: Voir les révisions dans le flux d'activité permission_view_dmsf_folders: Parcourir les documents permission_user_preferences: Préférences utilisateur permission_view_dmsf_files: Afficher documents @@ -181,7 +181,7 @@ fr: heading_access_first: Premier heading_access_last: Dernier label_dmsf_updated: Dépôt ou mise à jour du document - label_dmsf_downloaded: DMSF document downloaded + label_dmsf_downloaded: Document DMSF téléchargé title_total_size_of_all_files: Taille totale des fichiers de ce dossier project_module_dmsf: DMSF warning_no_project_to_copy_file_to: "Le projet de destination n'est pas défini" @@ -290,7 +290,7 @@ fr: label_notifications_off: Désactiver les notifications field_target_file: Fichier source title_download_entries: Historique des téléchargements - label_external: External + label_external: Externe label_link_name: Nom du lien label_link_external_url: Adresse Internet @@ -315,8 +315,8 @@ fr: notice_dmsf_file_restored: Le document a été récupéré avec succès notice_dmsf_folder_restored: Le dossier a été récupéré avec succès notice_dmsf_link_restored: Le lien a été récupéré avec succès - title_restore_checked: Restore checked - error_parent_folder: "The parent folder doesn't exist" + title_restore_checked: Restauration vérifiée + error_parent_folder: "Le dossier parent n'existe pas" my: blocks: @@ -324,4 +324,4 @@ fr: open_approvals: Approbations en attente label_maximum_ajax_upload_filesize: Taille maximale de fichier pour téléversement via AJAX - note_maximum_ajax_upload_filesize: "Taille maximale, en méga octets, de fichier pour téléversement via l'interface standard AJAX. Sinon l'interface standard de Redmine doit être utilisée." \ No newline at end of file + note_maximum_ajax_upload_filesize: "Taille maximale, en méga octets, de fichier pour téléversement via l'interface standard AJAX. Sinon l'interface standard de Redmine doit être utilisée." From e26346fc0abdfd39722e27fe437ea68881ddf025 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Pi=C4=8Dman?= Date: Tue, 23 Jun 2015 10:01:37 +0200 Subject: [PATCH 46/52] Links are not visible --- app/models/dmsf_folder.rb | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/app/models/dmsf_folder.rb b/app/models/dmsf_folder.rb index 4390b7e9..3da4b799 100644 --- a/app/models/dmsf_folder.rb +++ b/app/models/dmsf_folder.rb @@ -37,26 +37,26 @@ class DmsfFolder < ActiveRecord::Base has_many :files, :class_name => 'DmsfFile', :foreign_key => 'dmsf_folder_id', :dependent => :destroy if (Rails::VERSION::MAJOR > 3) - has_many :folder_links, -> { where target_type: DmsfFolder.model_name }, + has_many :folder_links, -> { where :target_type => 'DmsfFolder' }, :class_name => 'DmsfLink', :foreign_key => 'dmsf_folder_id', :dependent => :destroy - has_many :file_links, -> { where target_type: DmsfFile.model_name }, + has_many :file_links, -> { where :target_type => 'DmsfFile' }, :class_name => 'DmsfLink', :foreign_key => 'dmsf_folder_id', :dependent => :destroy - has_many :url_links, -> { where target_type: 'DmsfUrl' }, + has_many :url_links, -> { where :target_type => 'DmsfUrl' }, :class_name => 'DmsfLink', :foreign_key => 'dmsf_folder_id', :dependent => :destroy - has_many :referenced_links, -> { where target_type: DmsfFolder.model_name }, + has_many :referenced_links, -> { where :target_type => 'DmsfFolder' }, :class_name => 'DmsfLink', :foreign_key => 'target_id', :dependent => :destroy has_many :locks, -> { where(entity_type: 1).order("#{DmsfLock.table_name}.updated_at DESC") }, :class_name => 'DmsfLock', :foreign_key => 'entity_id', :dependent => :destroy accepts_nested_attributes_for :user, :project, :folder, :subfolders, :files, :folder_links, :file_links, :url_links, :referenced_links, :locks else has_many :folder_links, :class_name => 'DmsfLink', :foreign_key => 'dmsf_folder_id', - :conditions => {:target_type => DmsfFolder.model_name}, :dependent => :destroy + :conditions => { :target_type => 'DmsfFolder' }, :dependent => :destroy has_many :file_links, :class_name => 'DmsfLink', :foreign_key => 'dmsf_folder_id', - :conditions => {:target_type => DmsfFile.model_name}, :dependent => :destroy + :conditions => { :target_type => 'DmsfFile' }, :dependent => :destroy has_many :url_links, :class_name => 'DmsfLink', :foreign_key => 'dmsf_folder_id', - :conditions => {:target_type => 'DmsfUrl'}, :dependent => :destroy + :conditions => { :target_type => 'DmsfUrl' }, :dependent => :destroy has_many :referenced_links, :class_name => 'DmsfLink', :foreign_key => 'target_id', - :conditions => {:target_type => DmsfFolder.model_name}, :dependent => :destroy + :conditions => { :target_type => 'DmsfFolder' }, :dependent => :destroy has_many :locks, :class_name => 'DmsfLock', :foreign_key => 'entity_id', :order => "#{DmsfLock.table_name}.updated_at DESC", :conditions => {:entity_type => 1}, From fd49d688743b5544c5781a3ff555de1207c0de8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Pi=C4=8Dman?= Date: Wed, 24 Jun 2015 14:35:08 +0200 Subject: [PATCH 47/52] Error when uploading files #396 --- app/controllers/dmsf_upload_controller.rb | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/app/controllers/dmsf_upload_controller.rb b/app/controllers/dmsf_upload_controller.rb index af1b9638..4063ab55 100644 --- a/app/controllers/dmsf_upload_controller.rb +++ b/app/controllers/dmsf_upload_controller.rb @@ -60,11 +60,16 @@ class DmsfUploadController < ApplicationController return end @disk_filename = DmsfHelper.temp_filename(@tempfile.original_filename) - FileUtils.mv(@tempfile.path, "#{DmsfHelper.temp_dir}/#{@disk_filename}") + begin + FileUtils.cp @tempfile.path, "#{DmsfHelper.temp_dir}/#{@disk_filename}" + rescue Exception => e + Rails.logger.error e.message + end if File.size("#{DmsfHelper.temp_dir}/#{@disk_filename}") <= 0 begin - File.delete("#{DmsfHelper.temp_dir}/#{@disk_filename}") - rescue + File.delete "#{DmsfHelper.temp_dir}/#{@disk_filename}" + rescue Exception => e + Rails.logger.error e.message end render :layout => nil, :json => { :jsonrpc => '2.0', :error => { From 294e39836cb55a6dde13e968b23a0ad97e0b08a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Pi=C4=8Dman?= Date: Mon, 13 Jul 2015 12:35:39 +0200 Subject: [PATCH 48/52] Deleted folder (still in trash) results in errors while accessing parent folder via webdav #404 --- lib/redmine_dmsf/patches/project_patch.rb | 4 ++-- lib/redmine_dmsf/webdav/controller.rb | 4 +++- lib/redmine_dmsf/webdav/dmsf_resource.rb | 8 ++++---- lib/redmine_dmsf/webdav/resource_proxy.rb | 4 +++- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/lib/redmine_dmsf/patches/project_patch.rb b/lib/redmine_dmsf/patches/project_patch.rb index 086b76e4..08d405e2 100644 --- a/lib/redmine_dmsf/patches/project_patch.rb +++ b/lib/redmine_dmsf/patches/project_patch.rb @@ -67,8 +67,8 @@ module RedmineDmsf module InstanceMethods def dmsf_count - file_count = self.dmsf_files.visible.count + self.file_links.count - folder_count = self.dmsf_folders.visible.count + self.folder_links.count + file_count = self.dmsf_files.visible.count + self.file_links.visible.count + folder_count = self.dmsf_folders.visible.count + self.folder_links.visible.count self.dmsf_folders.visible.each do |f| file_count += f.deep_file_count folder_count += f.deep_folder_count diff --git a/lib/redmine_dmsf/webdav/controller.rb b/lib/redmine_dmsf/webdav/controller.rb index a1140166..c8be5f39 100644 --- a/lib/redmine_dmsf/webdav/controller.rb +++ b/lib/redmine_dmsf/webdav/controller.rb @@ -1,7 +1,9 @@ +# encoding: utf-8 +# # Redmine plugin for Document Management System "Features" # # Copyright (C) 2012 Daniel Munn -# Copyright (C) 2011-14 Karel Picman +# Copyright (C) 2011-15 Karel Pičman # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License diff --git a/lib/redmine_dmsf/webdav/dmsf_resource.rb b/lib/redmine_dmsf/webdav/dmsf_resource.rb index 8a024cf6..e921700b 100644 --- a/lib/redmine_dmsf/webdav/dmsf_resource.rb +++ b/lib/redmine_dmsf/webdav/dmsf_resource.rb @@ -54,7 +54,7 @@ module RedmineDmsf return @children if @children @children = [] return [] unless collection? - folder.subfolders.map do |p| + folder.subfolders.visible.map do |p| @children.push child(p.title) end folder.files.visible.map do |p| @@ -81,13 +81,13 @@ module RedmineDmsf # Todo: Move folder data retrieval into folder function, and use folder method to determine existence def folder return @folder unless @folder == false - return nil if project.nil? || project.id.nil? #if the project doesnt exist, this entity can't exist + return nil if project.nil? || project.id.nil? #if the project doesn't exist, this entity can't exist @folder = nil # Note: Folder is searched for as a generic search to prevent SQL queries being generated: # if we were to look within parent, we'd have to go all the way up the chain as part of the - # existence check, and although I'm sure we'd love to access the heirarchy, I can't yet + # existence check, and although I'm sure we'd love to access the hierarchy, I can't yet # see a practical need for it - folders = DmsfFolder.visible.where(:project_id => project.id, :title => basename).order('title ASC').all + folders = DmsfFolder.visible.where(:project_id => project.id, :title => basename).order('title ASC').to_a return nil unless folders.length > 0 if (folders.length > 1) then folders.delete_if { |x| '/' + x.dmsf_path_str != projectless_path } diff --git a/lib/redmine_dmsf/webdav/resource_proxy.rb b/lib/redmine_dmsf/webdav/resource_proxy.rb index 96df6af7..1742c8a3 100644 --- a/lib/redmine_dmsf/webdav/resource_proxy.rb +++ b/lib/redmine_dmsf/webdav/resource_proxy.rb @@ -1,7 +1,9 @@ +# encoding: utf-8 +# # Redmine plugin for Document Management System "Features" # # Copyright (C) 2012 Daniel Munn -# Copyright (C) 2011-14 Karel Picman +# Copyright (C) 2011-15 Karel Pičman # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License From f9db08b8fd0bdce88b95f8588dcc1757c962cda3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Pi=C4=8Dman?= Date: Mon, 13 Jul 2015 12:38:55 +0200 Subject: [PATCH 49/52] Deleted folder (still in trash) results in errors while accessing parent folder via webdav #404 --- lib/redmine_dmsf/webdav/dmsf_resource.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/redmine_dmsf/webdav/dmsf_resource.rb b/lib/redmine_dmsf/webdav/dmsf_resource.rb index e921700b..a399a7f2 100644 --- a/lib/redmine_dmsf/webdav/dmsf_resource.rb +++ b/lib/redmine_dmsf/webdav/dmsf_resource.rb @@ -132,7 +132,7 @@ module RedmineDmsf # If folder is false, means it couldn't pick up parent, # as such its probably fine to bail out, however we'll # perform a search in this scenario - files = DmsfFile.visible.where(:project_id => project.id, :name => basename).order('name ASC').all + files = DmsfFile.visible.where(:project_id => project.id, :name => basename).order('name ASC').to_a files.delete_if {|x| File.dirname('/' + x.dmsf_path_str) != File.dirname(projectless_path)} if files.length > 0 @file = files[0] From 37d948cf2b0429211c74cf3acfe3af52fca23072 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Pi=C4=8Dman?= Date: Tue, 14 Jul 2015 09:54:35 +0200 Subject: [PATCH 50/52] Mime type icons upgrade --- assets/images/filetypes/odg.png | Bin 803 -> 566 bytes assets/images/filetypes/odg_gray.png | Bin 419 -> 385 bytes assets/images/filetypes/odp.png | Bin 759 -> 490 bytes assets/images/filetypes/odp_gray.png | Bin 416 -> 362 bytes assets/images/filetypes/ods.png | Bin 764 -> 427 bytes assets/images/filetypes/ods_gray.png | Bin 401 -> 348 bytes assets/images/filetypes/odt.png | Bin 719 -> 421 bytes assets/images/filetypes/odt_gray.png | Bin 413 -> 350 bytes 8 files changed, 0 insertions(+), 0 deletions(-) diff --git a/assets/images/filetypes/odg.png b/assets/images/filetypes/odg.png index 9c2df41003dbee22bd2b82a74bcf35ed14afba1f..7a0531ece9554b34d1f1dfe7aaf20296b3e323e1 100644 GIT binary patch delta 552 zcmV+@0@wYc2DSu{8Gi-<001BJ|6u?C0sTotK~#7Fm5;w`6JZ#~Unq61x*WJj-5ho8 zUmz|b7!VwFP_Zat#iCTOhFChZXw?h>GYA^n9`%${q8=u2me`=d-VrdK7XmRIYGZPD z`7wNbo)>u$ZN&~BINs;;{lEzT!cK~N1h&8?v|pa}+xu63lYfESU4dGywkfODWzgLv zG-8Bk5nxo{^=$#)_tEe7|Dw@o;6wHj$Z-jc7$I5&RN&=|K&4Vauh-j@dcBVK<^_=F z5*jf=tf;{AX@PRNyk(%AjD@aEJ3eEYNvZ+Qad><~NB<&6qFye?2E6y)*giT(IIc@!I|Q8XV-AX=-!d36%= z@d2>M4!iuV0_kf4wrwMdA}k&omVxGC0@3$1tmuc4nSUI>7kvoqvBNHZt3c|CKrWY) zM_0d({MaFc&2_v@A4KNnApE7BXuQ~s)VK^tmp3Yq922lC3t<={Xt&XBt;3uf#*_FC z)Sm6)v4lN#*yV2(xN}h;o6Yi3vYMa59KHR?gR6t`&Mt3MVCKAlX_^Rv;J;v(H!3iF qPQWmXZ6U!M6-b;BI)QDX^Y$lM1M5Hn%C0N`00009-mArnJx>bP4S%Jn_g2i2g#9oBNVTi+GiNj@z!e@)YX^g>aj==wN z0{?Rb{e2j*iW$3{FaM7v|Bx#Gkt_V8K>w~v{H9`ymY#x|yoH^wa(?X&*#pysOhVNz`(%A#mCOb#mvmi&&|)n z%+$)r)yd4#!O-5z(bm`3*WS|B(&GBm|CQ>dLAf%LmAw$~#wt=&^qx{xvt#7~e zTdS0*?Wp?InbqoB?brfdssp9WcDCF!e{_3q(Xd19Y?LB`puYfGf2HwVV$sqM(n65R zit03KBo0Y<3QOU#0Gich%y1l%@U%JA078>PhHevv;*f0h66Fr5BYRrSThXS+q!2&s znJF^t+c%@)#;YqeeFlZ;hC{%KFr( zc|Xdpm^WwFhj9^7Hd$juaYc&@&o~BU8Y`ka0~M8~cV&Np#DB_x`oXgQRp%SeOnrL? ShPW>P0000}C?rOVB1@tu)|NIY?F0#kSfsMEN^fUjVP_Zn6cW&HAc(BEg@xD!K>`-r zNg5%+yMNwyEVAI$Gvz#I&TtywV>+{ire>ztZv34OnPr+ux{UKA>ybymB#1Rp|po%-nRzfw#_@n462*KXL3> yvF@^JYT~zQ-cQ*2?+;b_z#giqD5`3%s`vu}e7q;6_e+2P0000Xh2H)}<833CNS(a$b+ zKgD!69Rw9^s3;+#$il)7ih?>Q=mG+zDI|o3JQ&EFzyk*nW&9TvO$V}$-o7_tQ8Y>( zJbd4L^Sm!>1Hi_sGy0i;C3u4M+jIZ8hrInNcwuXFI-OWPK7UC-{s)33*g_cIO)&@Gp69iVFl0gFc=JQb8`n4{+rkr(4#|_yD7MS zNTEvQzfq#LAhlhxWhm4Gjjg5_j zgpiSuk&=>-f||UAoxYKhq@SRsprE9oqpYl~tF5iAudlD8r?;i1v81T9sHn85uf4Le zvbeaoy}iAVox+Hqz>A{7jH1GirNoh^#*(PUm8-~?tjU?J%ABywow3cYx5uNl(W$%C ztiIN$zTCIJ%zwPR!LPvBv%}iJz`)GR%+b-&*Vot1+2YdT`qbn4*5vxv<@?#@``hRH z-01t>>HFXD|KsE1;OhM0@A={E{NwHXGuEW_y6nm|Lybr?f3uh^!@Si@$mNk@_+gN^!Wbu`TqC%{`vd=|NsB@ zf(|AC0004WQchC c5Em5%07W@7#`{!lkN^Mx07*qoM6N<$g1^&;aR2}S diff --git a/assets/images/filetypes/odp_gray.png b/assets/images/filetypes/odp_gray.png index da112664bd32632fdd29cd7691de4a1702fc41ee..a43b9fa44301ae248592b4ac071da53d46f5808d 100644 GIT binary patch delta 297 zcmV+^0oMMY1L^{hB#|*Gf8PfV2Qw0X-mQcH008?*L_t(2&ux&QPQzdng}?q82A0ht z5r}#WQ-lygN;4WX2Gfx84!i<(OZEyRA;6^TM17DNq>2SE1Ti|D`6hMnW?J-J`b zjf`y04S#y$(tl^K906Z`FETD{*4e+{KRITfk|8`r4g_HwJR)Tpe@i8`B=RGG1EoQO zjcv|JW39B-MBo1f6lI5$B-Tpn@*o5R>v_bKvskOsT^Ot`T+z_g9F5CICLv%5_iU}# zw~RUA`LKf!90gENu*3jC=^z>$Goby6gef>M8Wh9Gft;Kr7QirQAJrljz#?2Q1nDA# v>t2f$V%_3_zN{bkA7GP0!2DgtW_kDp9p8#yBmkVR00000NkvXXu0mjf`+k2^ delta 349 zcmV-j0iyou0-ytsBnkm@Qb$4nuFf3kks&O9-U18|1~k7u%VYom0VGL8K~yNuRna|b z6j2lg;Ag_b5D2Vdt7+2(3;hGJ3Wy*QFqNHE`g9iQ#U`SVCPffzEVL4WL{Jd1x3OD- z_|XIt*xA{+uSJG+b5C>OJm=-Wselg|Y5N*$jj>hv|K6v~I88I3_H&Zmq$f3e%VYPQf^GK%&-&4d{cJh4=BknJKFdtKQXl1x zbvu^DTa7p7Em$z`hJyNum+n@ZxvpqD(1}j*+Nvn(1A~fA5p>gVat;QSX@jUx(}S-6 zPeJ3CAAb7dqjk}lI|oI?q8E;h{dVR^X`e7Rn{eBTzdmU#+Ve$}iy$gj3~X9<3B}~% v4#j1MHpDGA6*1AD^m$qoMW<5~6Gg#4`|!Ee0@gXm00000NkvXXu0mjft`Vdy diff --git a/assets/images/filetypes/ods.png b/assets/images/filetypes/ods.png index c385a9db88ad61af6a42d67c07fc07f7c910b3a7..7fac192dea936a2461c595c51b14a8052ee77239 100644 GIT binary patch delta 412 zcmV;N0b~CB1*-#)8Gi-<001BJ|6u?C0dh%1K~#9!mCnsd0$~^hVEMiq=)bubuc#=B z{xAxPf)*`;77;;_ED;sHFa;|&8l_SR!{Kly{*&XE9Z1d#AJ@4%lY*rSjchiH zZnq20A7Ae@J%0!HPN&0cu$zK&g-j+B965*JotWir3KmZk9LGVs-3DiVk)~(fiCONZ z;7B2zPNUguf^!JoiCONZU?HYp+cp}F1~~JJG(Gc9%yKscdkU#k3bk4doI~(V%yKsc z^V0000woh7?eqQb^!@Si@$mNk^!Wbu z`TqC%{`vd=|NsA!LuL~I0004WQchCq$gGR2b7^U|?Vf@%Qufa(8hM z5@LXW;D6eh>Z%-0W=3|XNC1!@$;8N!Q&$HO@vqJaXJT|L6{>@XM6)w7GBR?NmI#3W z1A`+IBP%-_OGQa>VL^VL5JNZ!Hq%hwM}zY% zG<;)_;ngJ^ouXgS5+p>CO%VkRO$~Qx^9M9~&S^Qf?$!Ig@GRfU z_i!R(_`n(edF6Dv8a{Ijd{3K^acvj5_jg{t=&{ceyD*O`pa>0bf1Q{(Xrih1^+$mU z2y4`9)M#@QB#~75^075glpSJPK@v&t55W~yXq_&tpvn0jRI?vO#T{_zEL98uoLdTGUO3Y>2cMKB h8!R7BD9;jp0j&y&5hOBaC$s69nakm zZ4^3R#T|4n9eP_%NBN2`zG<$iqUm4TF4WQe!;!fcL)CQK1?u=Xz24@JqUtxZR&nON zg?nhj22pjJYm=dsG5-ftQ4~c5ouX_xhP(uR`QxwOmR5c`GuK>9fm?2hxGUneCR5-L gO;c4(bD?SY2aj^ONzDfC-~a#s07*qoM6N<$f@PYWD*ylh diff --git a/assets/images/filetypes/odt.png b/assets/images/filetypes/odt.png index db37574826386343f9f80f223c9c9457814723cb..cf6986d8ce3abce662d883b11135fcbf66476462 100644 GIT binary patch delta 406 zcmV;H0crlv1*HR!8Gi-<001BJ|6u?C0c=S`K~#9!mCij&LSY;QaM3SuKa4{ZL=Z&~ zK?FfWFG2-ELN7ElG&Hm{2nH231TBGqgoMx~hdW3P4qil*>$|8ZdOyJFe<*wmuBO9p zdH&}d9v1+Y$M+Z#7{U2!v& zEDQeIH%QhL8hdJB?F#918try_qD(o!z?>$U_Jm{*HNid#=a##n;IU4+D5gnz_gh{Ixu!)1!XXN$pU zjKOSipsC{NwHX+=2W z^ZoDi{qgbf@b>=n`2O|z{`dO+`TPI>|Nnn2>jMA)0Dl2=QchCk7 zR2b7^U|?VfaCdWcvbQmpmS%tezruq2yhKiBMs}!(2aq4a#K@6YR0I)m&rb|yVzkVZ zE`o@Jv41l$GBR>!W=Mko1A`?KBP%-_OHM|Rj;3j{G(#{GdsuFER%S+epq{RJk~9OG z6@v?y?^$H3t&t$fki)>>4C4D5dWC8w#7lr|aRl-s^o@jg65`{;KmraK>ER+K!n{85 zaj|M30lV}xZ({>K3m`v61tegbmdYi-F9_mCD?5P%tW#6CxctHVC|23mtf8JJzrIODf3&O1E^3ny$lXX=DYU5>H{IB?fm sFHaZ2<9SN=?H<@dQM6h`F;x`&1JPr`VFH=5`2YX_07*qoM6N<$f^Nv0Y5)KL From ede56490a1e8d28bcafc8bfeaf2d79a1621829f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Pi=C4=8Dman?= Date: Tue, 14 Jul 2015 09:55:01 +0200 Subject: [PATCH 51/52] Doc upgrade --- dmsf_user_guide.odt | Bin 958590 -> 978070 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/dmsf_user_guide.odt b/dmsf_user_guide.odt index 534074c3343698b8aee588cfa9312f2056f2d378..7cea4bf8266a5562008c1f659242db84b6c415d0 100644 GIT binary patch delta 86464 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 Date: Tue, 14 Jul 2015 09:55:45 +0200 Subject: [PATCH 52/52] devel 1.5.2 -> 1.5.2 --- CHANGELOG.md | 30 +++++++++++++++++++++++++++++- init.rb | 2 +- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a87043d1..014603ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,36 @@ Changelog for Redmine DMSF ========================== +1.5.2 *2015-07-13* +------------------ + + Redmine >= 3.0 compatibility + + +* Bug: #404 - Deleted folder (still in trash) results in errors while accessing parent folder via webdav +* Bug: #401 - Link between project on Redmine 3.0 +* Bug: #400 - internal server error fulltext search +* Bug: #396 - Error when uploading files +* Bug: #394 - DMSF install to Redmine 3.0.3 problem +* Bug: #393 - File can't be created in storage directory (Redmine 3.0.3) +* Bug: #392 - Redmine 3 Search screen error with Xapian +* New: #391 - Searchable document details +* Bug: #387 - Wrong sorting by Modified column +* Bug: #384 - Error when trying to uninstall DMSF +* New: #383 - Missleading number of entities in documents folder +* Bug: #382 - REST API - list of document produces invalid XML +* Bug: #380 - Internal Error 500 when dmsf page is accessed +* Bug: #378 - Revision view, delete revision bug +* Bug: #377 - Can access WebDAV when redmine is located under sub-URI +* Bug: #376 - Links to deleted documents +* Bug: #374 - Number of downloads +* Bug: #373 - internal 500 error : 1.5.1 stable with redmine 3.0.1 when search in dmsf enabled project +* New: #339 - Maximum Upload Size +* Bug: #319 - webdav problem after upgrading to 1.4.9 from 1.4.6 +* New: #78 - Control DMSF via REST API + 1.5.1: *2015-04-01* ------------------------ +------------------- Approval workflow copying Polish localization diff --git a/init.rb b/init.rb index e40154da..b95901d6 100644 --- a/init.rb +++ b/init.rb @@ -28,7 +28,7 @@ Redmine::Plugin.register :redmine_dmsf do name 'DMSF' author 'Vit Jonas / Daniel Munn / Karel Picman' description 'Document Management System Features' - version '1.5.2 devel' + version '1.5.2' url 'http://www.redmine.org/plugins/dmsf' author_url 'https://github.com/danmunn/redmine_dmsf/graphs/contributors'