From 51f355b861a915d1d5aeeb6c354d1eeaac9c70c1 Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Mon, 8 Feb 2016 09:49:55 +0100 Subject: [PATCH 01/94] WebDAV folder listing optimization --- lib/redmine_dmsf/webdav/dmsf_resource.rb | 4 ++-- lib/redmine_dmsf/webdav/index_resource.rb | 3 ++- lib/redmine_dmsf/webdav/project_resource.rb | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/redmine_dmsf/webdav/dmsf_resource.rb b/lib/redmine_dmsf/webdav/dmsf_resource.rb index 66ff3f6f..1fb236b8 100644 --- a/lib/redmine_dmsf/webdav/dmsf_resource.rb +++ b/lib/redmine_dmsf/webdav/dmsf_resource.rb @@ -49,10 +49,10 @@ module RedmineDmsf unless @childern @children = [] if collection? - folder.subfolders.visible.map do |p| + folder.subfolders.select(:title).visible.map do |p| @children.push child(p.title) end - folder.files.visible.map do |p| + folder.files.select(:name).visible.map do |p| @children.push child(p.name) end end diff --git a/lib/redmine_dmsf/webdav/index_resource.rb b/lib/redmine_dmsf/webdav/index_resource.rb index bc622ef3..bc39e8b4 100644 --- a/lib/redmine_dmsf/webdav/index_resource.rb +++ b/lib/redmine_dmsf/webdav/index_resource.rb @@ -26,7 +26,8 @@ module RedmineDmsf def children unless @projects @projects = [] - Project.has_module(:dmsf).where(Project.allowed_to_condition( + Project.select(:identifier).has_module(:dmsf).where( + Project.allowed_to_condition( User.current, :view_dmsf_folders)).order('lft').all.each do |p| @projects << child(p.identifier) end diff --git a/lib/redmine_dmsf/webdav/project_resource.rb b/lib/redmine_dmsf/webdav/project_resource.rb index 73d19be4..459f0d96 100644 --- a/lib/redmine_dmsf/webdav/project_resource.rb +++ b/lib/redmine_dmsf/webdav/project_resource.rb @@ -27,10 +27,10 @@ module RedmineDmsf unless @children @children = [] if project - project.dmsf_folders.visible.map do |p| + project.dmsf_folders.select(:title).visible.map do |p| @children.push child(p.title) end - project.dmsf_files.visible.map do |p| + project.dmsf_files.select(:name).visible.map do |p| @children.push child(p.name) end end From 3aa767acf9df7a9e47da508904316821da3471cd Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Mon, 15 Feb 2016 12:54:37 +0100 Subject: [PATCH 02/94] Document title format %t doesn't reffer to the title #506 --- CHANGELOG.md | 3 +++ README.md | 2 +- app/models/dmsf_file_revision.rb | 7 ++++--- 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/pt-BR.yml | 2 +- config/locales/ru.yml | 2 +- config/locales/sl.yml | 2 +- config/locales/zh-TW.yml | 2 +- config/locales/zh.yml | 2 +- init.rb | 2 +- 16 files changed, 21 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 88009758..f673388d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ Changelog for Redmine DMSF ========================== +1.5.7 *2015-??-??* +------------------ + 1.5.6 *2015-01-25* ------------------ diff --git a/README.md b/README.md index 4afc77ba..0250c684 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ Redmine DMSF Plugin =================== -The current version of Redmine DMSF is **1.5.6** [![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.7** [![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. diff --git a/app/models/dmsf_file_revision.rb b/app/models/dmsf_file_revision.rb index c8d5e909..b0a87409 100644 --- a/app/models/dmsf_file_revision.rb +++ b/app/models/dmsf_file_revision.rb @@ -3,7 +3,7 @@ # Redmine plugin for Document Management System "Features" # # Copyright (C) 2011 Vít Jonáš -# Copyright (C) 2011-15 Karel Pičman +# Copyright (C) 2011-16 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 @@ -256,7 +256,8 @@ class DmsfFileRevision < ActiveRecord::Base else filename = self.name end - format.sub!('%t', filename) + format.sub!('%t', self.title) + format.sub!('%f', filename) format.sub!('%d', self.updated_at.strftime('%Y%m%d%H%M%S')) format.sub!('%v', self.version) format.sub!('%i', self.file.id.to_s) @@ -264,4 +265,4 @@ class DmsfFileRevision < ActiveRecord::Base format + ext end -end +end \ No newline at end of file diff --git a/config/locales/cs.yml b/config/locales/cs.yml index 5b2017c3..d61abdbe 100644 --- a/config/locales/cs.yml +++ b/config/locales/cs.yml @@ -109,7 +109,7 @@ cs: select_option_deactivated: Deaktivováno select_option_activated: Aktivováno label_title_format: Formát názvu - text_title_format: "Formát názvu souboru pro stažení (%t - název, %d - datum, %v - verze, %i - ID, %r - revize). Např.: %t_%v" + text_title_format: "Formát názvu souboru pro stažení (%t - název, %f - soubor, %d - datum, %v - verze, %i - ID, %r - revize). Např.: %t_%v" title_save_preferences: Uložit nastavení heading_revisions: Revize title_download: Stáhnout diff --git a/config/locales/de.yml b/config/locales/de.yml index 8463641b..3fff02ee 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -109,7 +109,7 @@ de: select_option_deactivated: Aus select_option_activated: Ein label_title_format: Title format - text_title_format: "Format des Dokumente-Titels für Speichern (%t - Titel, %d - Datum, %v - Version, %i - ID, %r - Revision). z.B.: %t_%v" + text_title_format: "Format des Dokumente-Titels für Speichern (%t - Titel, %f - Datei, %d - Datum, %v - Version, %i - ID, %r - Revision). z.B.: %t_%v" title_save_preferences: Einstellungen speichern heading_revisions: Versionen title_download: Download diff --git a/config/locales/en.yml b/config/locales/en.yml index fa7fde8d..836637bf 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -109,7 +109,7 @@ en: select_option_deactivated: Deactivated select_option_activated: Activated label_title_format: Title format - text_title_format: "Document title format for download (%t - title, %d - date, %v - version, %i - ID, %r - revision). Example: %t_%v" + text_title_format: "Document title format for download (%t - title, %f - file, %d - date, %v - version, %i - ID, %r - revision). Example: %t_%v" title_save_preferences: Save preferences heading_revisions: Revisions title_download: Download diff --git a/config/locales/es.yml b/config/locales/es.yml index cab4c704..8be42f4f 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -109,7 +109,7 @@ es: select_option_deactivated: Desactivado select_option_activated: Activado label_title_format: Title format - text_title_format: "Document title format for download (%t - title, %d - date, %v - version, %i - ID, %r - revision). Example: %t_%v" + text_title_format: "Document title format for download (%t - title, %f - file, %d - date, %v - version, %i - ID, %r - revision). Example: %t_%v" title_save_preferences: Guardar Preferencias heading_revisions: Revisiones title_download: Descargar diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 6102f966..87f4a50d 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -109,7 +109,7 @@ fr: select_option_deactivated: Désactivé select_option_activated: Activé label_title_format: Title format - text_title_format: "Document title format for download (%t - title, %d - date, %v - version, %i - ID, %r - revision). Example: %t_%v" + text_title_format: "Document title format for download (%t - title, %f - file, %d - date, %v - version, %i - ID, %r - revision). Example: %t_%v" title_save_preferences: Enregistrer les préférences heading_revisions: Révisions title_download: Télécharger diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 8d15c24d..35ff8e62 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -109,7 +109,7 @@ ja: select_option_deactivated: 無効 select_option_activated: 有効 label_title_format: Title format - text_title_format: "Document title format for download (%t - title, %d - date, %v - version, %i - ID, %r - revision). Example: %t_%v" + text_title_format: "Document title format for download (%t - title, %f - file, %d - date, %v - version, %i - ID, %r - revision). Example: %t_%v" title_save_preferences: 設定を保存します heading_revisions: リビジョン title_download: ダウンロードします diff --git a/config/locales/pl.yml b/config/locales/pl.yml index e2d6c5e4..0e94fc16 100644 --- a/config/locales/pl.yml +++ b/config/locales/pl.yml @@ -109,7 +109,7 @@ pl: select_option_default: Domyślny select_option_deactivated: Wyłączono label_title_format: Title format - text_title_format: "Document title format for download (%t - title, %d - date, %v - version, %i - ID, %r - revision). Example: %t_%v" + text_title_format: "Document title format for download (%t - title, %f - file, %d - date, %v - version, %i - ID, %r - revision). Example: %t_%v" select_option_activated: Aktywowano title_save_preferences: Zapisz ustawienia heading_revisions: Wersje diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml index c282ebee..11acc558 100644 --- a/config/locales/pt-BR.yml +++ b/config/locales/pt-BR.yml @@ -109,7 +109,7 @@ pt-BR: select_option_deactivated: Desativado select_option_activated: Ativado label_title_format: Title format - text_title_format: "Document title format for download (%t - title, %d - date, %v - version, %i - ID, %r - revision). Example: %t_%v" + text_title_format: "Document title format for download (%t - title, %f - file, %d - date, %v - version, %i - ID, %r - revision). Example: %t_%v" title_save_preferences: Salvar preferências heading_revisions: Revisões title_download: Download diff --git a/config/locales/ru.yml b/config/locales/ru.yml index d884ecd4..6f661767 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -109,7 +109,7 @@ ru: select_option_deactivated: Отключено select_option_activated: Включено label_title_format: Title format - text_title_format: "Document title format for download (%t - title, %d - date, %v - version, %i - ID, %r - revision). Example: %t_%v" + text_title_format: "Document title format for download (%t - title, %f - file, %d - date, %v - version, %i - ID, %r - revision). Example: %t_%v" title_save_preferences: Сохранить настройки heading_revisions: Редакции title_download: Скачать diff --git a/config/locales/sl.yml b/config/locales/sl.yml index b29b5583..9b02cf00 100644 --- a/config/locales/sl.yml +++ b/config/locales/sl.yml @@ -109,7 +109,7 @@ sl: select_option_deactivated: Deaktivirano select_option_activated: Aktivirano label_title_format: Title format - text_title_format: "Document title format for download (%t - title, %d - date, %v - version, %i - ID, %r - revision). Example: %t_%v" + text_title_format: "Document title format for download (%t - title, %f - file, %d - date, %v - version, %i - ID, %r - revision). Example: %t_%v" title_save_preferences: Save preferences heading_revisions: Verzije title_download: Prenesi dol diff --git a/config/locales/zh-TW.yml b/config/locales/zh-TW.yml index d15c5227..04333d55 100644 --- a/config/locales/zh-TW.yml +++ b/config/locales/zh-TW.yml @@ -109,7 +109,7 @@ zh-TW: select_option_deactivated: 關閉 select_option_activated: 啟用 label_title_format: Title format - text_title_format: "Document title format for download (%t - title, %d - date, %v - version, %i - ID, %r - revision). Example: %t_%v" + text_title_format: "Document title format for download (%t - title, %f - file, %d - date, %v - version, %i - ID, %r - revision). Example: %t_%v" title_save_preferences: 儲存偏好設定 heading_revisions: 修訂版本 title_download: 下載 diff --git a/config/locales/zh.yml b/config/locales/zh.yml index 65a18212..508ed8d8 100644 --- a/config/locales/zh.yml +++ b/config/locales/zh.yml @@ -110,7 +110,7 @@ zh: select_option_activated: 激活 title_save_preferences: 保存偏好设定 label_title_format: Title format - text_title_format: "Document title format for download (%t - title, %d - date, %v - version, %i - ID, %r - revision). Example: %t_%v" + text_title_format: "Document title format for download (%t - title, %f - file, %d - date, %v - version, %i - ID, %r - revision). Example: %t_%v" heading_revisions: 修订版本 title_download: 下载 title_delete_revision: 删除此修订 diff --git a/init.rb b/init.rb index 473db794..5cdd5cf7 100644 --- a/init.rb +++ b/init.rb @@ -28,7 +28,7 @@ Redmine::Plugin.register :redmine_dmsf do name 'DMSF' author 'Vít Jonáš / Daniel Munn / Karel Pičman' description 'Document Management System Features' - version '1.5.6' + version '1.5.7 devel' url 'http://www.redmine.org/plugins/dmsf' author_url 'https://github.com/danmunn/redmine_dmsf/graphs/contributors' From 546ef9ed8a41951cafdb0538344184825657f53b Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Tue, 16 Feb 2016 10:29:29 +0100 Subject: [PATCH 03/94] DMSF install to Redmine 3.0.3 problem #394 --- README.md | 1 + ...20160215125801_approval_workflow_status.rb | 29 +++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 db/migrate/20160215125801_approval_workflow_status.rb diff --git a/README.md b/README.md index 0250c684..6b1feeb1 100644 --- a/README.md +++ b/README.md @@ -176,6 +176,7 @@ 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. Install dependencies: `bundle install`. +3.1 To install dependencies without Xapian (full-text searching): `bundle install --without xapian`. This option might be useful especially in Windows. 4. Initialize/Update database: `bundle exec 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 the web server. diff --git a/db/migrate/20160215125801_approval_workflow_status.rb b/db/migrate/20160215125801_approval_workflow_status.rb new file mode 100644 index 00000000..1ace097d --- /dev/null +++ b/db/migrate/20160215125801_approval_workflow_status.rb @@ -0,0 +1,29 @@ +# encoding: utf-8 +# +# Redmine plugin for Document Management System "Features" +# +# Copyright (C) 2015 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 TitleFormat < ActiveRecord::Migration + def self.up + add_column :members, :title_format, :text, :null => true, :limit => 100 + end + + def self.down + remove_column :members, :title_format + end +end \ No newline at end of file From 71281cdebd66b592596f48d4f456b41af5258f58 Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Wed, 17 Feb 2016 13:20:06 +0100 Subject: [PATCH 04/94] Global approval workflows Lock/Unlock feature added --- app/controllers/dmsf_workflows_controller.rb | 26 ++++++++----- app/helpers/dmsf_workflows_helper.rb | 28 +++++++++++--- app/models/dmsf_workflow.rb | 16 +++++++- app/views/dmsf_workflows/_main.html.erb | 24 +++++++++--- assets/stylesheets/dmsf.css | 1 + db/migrate/20151209100001_title_format.rb | 2 +- ...20160215125801_approval_workflow_status.rb | 9 +++-- test/fixtures/dmsf_workflows.yml | 7 +++- .../dmsf_workflow_controller_test.rb | 26 ++++++++++++- test/unit/dmsf_lock_test.rb | 4 +- test/unit/dmsf_workflow_test.rb | 38 ++++++++++++++----- 11 files changed, 138 insertions(+), 43 deletions(-) diff --git a/app/controllers/dmsf_workflows_controller.rb b/app/controllers/dmsf_workflows_controller.rb index 126fd262..c50f69f0 100644 --- a/app/controllers/dmsf_workflows_controller.rb +++ b/app/controllers/dmsf_workflows_controller.rb @@ -28,8 +28,9 @@ class DmsfWorkflowsController < ApplicationController layout :workflows_layout - def index - @workflow_pages, @workflows = paginate DmsfWorkflow.global.sorted, :per_page => 25 + def index + @status = params[:status] || 1 + @workflow_pages, @workflows = paginate DmsfWorkflow.status(@status).global.sorted, :per_page => 25 end def action @@ -238,16 +239,21 @@ class DmsfWorkflowsController < ApplicationController end def update - if params[:dmsf_workflow] && @dmsf_workflow.update_attributes( - {:name => params[:dmsf_workflow][:name]}) - flash[:notice] = l(:notice_successful_update) - if @project - redirect_to settings_project_path(@project, :tab => 'dmsf_workflow') + if params[:dmsf_workflow] + res = @dmsf_workflow.update_attributes({:name => params[:dmsf_workflow][:name]}) if params[:dmsf_workflow][:name].present? + res = @dmsf_workflow.update_attributes({:status => params[:dmsf_workflow][:status]}) if params[:dmsf_workflow][:status].present? + if res + flash[:notice] = l(:notice_successful_update) + if @project + redirect_to settings_project_path(@project, :tab => 'dmsf_workflow') + else + redirect_to dmsf_workflows_path + end else - redirect_to dmsf_workflows_path - end + flash[:error] = @dmsf_workflow.errors.full_messages.to_sentence + redirect_to dmsf_workflow_path(@dmsf_workflow) + end 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 d930bb76..a4acf3d2 100644 --- a/app/helpers/dmsf_workflows_helper.rb +++ b/app/helpers/dmsf_workflows_helper.rb @@ -1,8 +1,7 @@ -# encoding: utf-8 -# +# encoding: utf-8# # Redmine plugin for Document Management System "Features" # -# Copyright (C) 2011-15 Karel Pičman +# Copyright (C) 2011-16 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 @@ -56,7 +55,7 @@ module DmsfWorkflowsHelper def dmsf_workflows_for_select(project, dmsf_workflow_id) options = Array.new options << ['', -1] - DmsfWorkflow.sorted.where(['project_id = ? OR project_id IS NULL', project.id]).each do |wf| + DmsfWorkflow.active.sorted.where(['project_id = ? OR project_id IS NULL', project.id]).each do |wf| if wf.project_id options << [wf.name, wf.id] else @@ -69,7 +68,7 @@ module DmsfWorkflowsHelper def dmsf_all_workflows_for_select(dmsf_workflow_id) options = Array.new options << ['', 0] - DmsfWorkflow.sorted.all.each do |wf| + DmsfWorkflow.active.sorted.all.each do |wf| if wf.project_id prj = Project.find_by_id wf.project_id if User.current.allowed_to?(:manage_workflows, prj) @@ -95,4 +94,21 @@ module DmsfWorkflowsHelper end s.html_safe end -end + + def change_status_link(workflow) + url = { :controller => 'dmsf_workflows', :action => 'update', :id => workflow.id } + if workflow.locked? + link_to l(:button_unlock), url.merge(:dmsf_workflow => {:status => DmsfWorkflow::STATUS_ACTIVE}), :method => :put, :class => 'icon icon-unlock' + else + link_to l(:button_lock), url.merge(:dmsf_workflow => {:status => DmsfWorkflow::STATUS_LOCKED}), :method => :put, :class => 'icon icon-lock' + end + end + + def workflows_status_options_for_select(selected) + worflows_count_by_status = DmsfWorkflow.global.group('status').count.to_hash + options_for_select([[l(:label_all), ''], + ["#{l(:status_active)} (#{worflows_count_by_status[DmsfWorkflow::STATUS_ACTIVE].to_i})", DmsfWorkflow::STATUS_ACTIVE.to_s], + ["#{l(:status_locked)} (#{worflows_count_by_status[DmsfWorkflow::STATUS_LOCKED].to_i})", DmsfWorkflow::STATUS_LOCKED.to_s]], selected.to_s) + end + +end \ No newline at end of file diff --git a/app/models/dmsf_workflow.rb b/app/models/dmsf_workflow.rb index 6bf6393c..d74b8a0d 100644 --- a/app/models/dmsf_workflow.rb +++ b/app/models/dmsf_workflow.rb @@ -24,6 +24,8 @@ class DmsfWorkflow < ActiveRecord::Base scope :sorted, lambda { order('name ASC') } scope :global, lambda { where('project_id IS NULL') } + scope :active, lambda { where(:status => STATUS_ACTIVE) } + scope :status, lambda { |arg| where(arg.blank? ? nil : {:status => arg.to_i}) } validate :name_validation validates :name, :presence => true @@ -61,6 +63,9 @@ class DmsfWorkflow < ActiveRecord::Base STATE_APPROVED = 2 STATE_REJECTED = 4 + STATUS_LOCKED = 0 + STATUS_ACTIVE = 1 + def participiants users = Array.new self.dmsf_workflow_steps.each do |step| @@ -247,4 +252,13 @@ class DmsfWorkflow < ActiveRecord::Base end return new_wf end -end + + def locked? + self.status == STATUS_LOCKED + end + + def active? + self.status == STATUS_ACTIVE + end + +end \ No newline at end of file diff --git a/app/views/dmsf_workflows/_main.html.erb b/app/views/dmsf_workflows/_main.html.erb index b40cde8d..1eddcd10 100644 --- a/app/views/dmsf_workflows/_main.html.erb +++ b/app/views/dmsf_workflows/_main.html.erb @@ -16,7 +16,7 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.%> -<% @workflows = DmsfWorkflow.sorted.where(:project_id => @project.id) if @project && @workflows.nil? %> +<% @workflows = DmsfWorkflow.active.sorted.where(:project_id => @project.id) if @project && @workflows.nil? %> <% unless @project %>
@@ -24,9 +24,18 @@

<%=l(:label_dmsf_workflow_plural)%>

-<% end %> + + <%= form_tag(dmsf_workflows_path, :method => :get) do %> +
+ <%= l(:label_filter_plural) %> + + <%= select_tag 'status', workflows_status_options_for_select(@status), :class => 'small', :onchange => "this.form.submit(); return false;" %> +
+ <% end %> +   +<% end %> -<% if @workflows.any? %> +<% if @workflows.any? %> @@ -34,9 +43,12 @@ <% for workflow in @workflows %> - - - + + + <% end %> diff --git a/assets/stylesheets/dmsf.css b/assets/stylesheets/dmsf.css index 459cb512..d4cfe0ae 100644 --- a/assets/stylesheets/dmsf.css +++ b/assets/stylesheets/dmsf.css @@ -211,6 +211,7 @@ table.access-table tbody td, table.access-table tbody tr:hover td { #admin-menu a.approvalworkflows { background-image: url(../images/ticket_go.png); } #users_for_delegate {height: 200px; overflow:auto;} #users_for_delegate label {display: block;} +tr.workflow.locked a { color: #aaa; } .revision_box{ padding: 0px 0px 0px 0px; diff --git a/db/migrate/20151209100001_title_format.rb b/db/migrate/20151209100001_title_format.rb index 1ace097d..8efd7d77 100644 --- a/db/migrate/20151209100001_title_format.rb +++ b/db/migrate/20151209100001_title_format.rb @@ -2,7 +2,7 @@ # # Redmine plugin for Document Management System "Features" # -# Copyright (C) 2015 Karel Pičman +# Copyright (C) 2011-16 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/20160215125801_approval_workflow_status.rb b/db/migrate/20160215125801_approval_workflow_status.rb index 1ace097d..1e447a0c 100644 --- a/db/migrate/20160215125801_approval_workflow_status.rb +++ b/db/migrate/20160215125801_approval_workflow_status.rb @@ -2,7 +2,7 @@ # # Redmine plugin for Document Management System "Features" # -# Copyright (C) 2015 Karel Pičman +# Copyright (C) 2011-16 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 @@ -18,12 +18,13 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -class TitleFormat < ActiveRecord::Migration +class ApprovalWorkflowStatus < ActiveRecord::Migration def self.up - add_column :members, :title_format, :text, :null => true, :limit => 100 + add_column :dmsf_workflows, :status, :integer, :null => false, :default => DmsfWorkflow::STATUS_ACTIVE + DmsfWorkflow.all.each {|wf| wf.update_attribute(:status, DmsfWorkflow::STATUS_ACTIVE)} end def self.down - remove_column :members, :title_format + remove_column :dmsf_workflows, :status end end \ No newline at end of file diff --git a/test/fixtures/dmsf_workflows.yml b/test/fixtures/dmsf_workflows.yml index ebb4d1cf..ed0981cb 100644 --- a/test/fixtures/dmsf_workflows.yml +++ b/test/fixtures/dmsf_workflows.yml @@ -6,4 +6,9 @@ wf1: wf2: id: 2 - name: wf2 \ No newline at end of file + name: wf2 + +wf3: + id: 3 + name: wf3 + status: 0 \ No newline at end of file diff --git a/test/functional/dmsf_workflow_controller_test.rb b/test/functional/dmsf_workflow_controller_test.rb index 0564d1ee..ab220685 100644 --- a/test/functional/dmsf_workflow_controller_test.rb +++ b/test/functional/dmsf_workflow_controller_test.rb @@ -43,6 +43,7 @@ class DmsfWorkflowsControllerTest < RedmineDmsf::Test::TestCase @project1 = Project.find_by_id 1 @project1.enable_module! :dmsf @wf1 = DmsfWorkflow.find_by_id 1 + @wf3 = DmsfWorkflow.find_by_id 3 @wfsa2 = DmsfWorkflowStepAssignment.find_by_id 2 @revision1 = DmsfFileRevision.find_by_id 1 @revision2 = DmsfFileRevision.find_by_id 2 @@ -66,6 +67,7 @@ class DmsfWorkflowsControllerTest < RedmineDmsf::Test::TestCase assert_kind_of DmsfWorkflowStep, @wfs5 assert_kind_of Project, @project1 assert_kind_of DmsfWorkflow, @wf1 + assert_kind_of DmsfWorkflow, @wf3 assert_kind_of DmsfWorkflowStepAssignment, @wfsa2 assert_kind_of DmsfFileRevision, @revision1 assert_kind_of DmsfFileRevision, @revision2 @@ -133,7 +135,14 @@ class DmsfWorkflowsControllerTest < RedmineDmsf::Test::TestCase assert_response :forbidden end - def test_index + def test_index_administration + @request.session[:user_id] = @user_admin.id + get :index + assert_response :success + assert_template 'index' + end + + def test_index_project get :index, :project_id => @project1.id assert_response :success assert_template 'index' @@ -144,6 +153,19 @@ class DmsfWorkflowsControllerTest < RedmineDmsf::Test::TestCase assert_response :success assert_template 'new' end + + def test_lock + put :update, :id => @wf1.id, :dmsf_workflow => { :status => DmsfWorkflow::STATUS_LOCKED } + @wf1.reload + assert @wf1.locked?, "#{@wf1.name} status is #{@wf1.status}" + end + + def test_unlock + @request.session[:user_id] = @user_admin.id + put :update, :id => @wf3.id, :dmsf_workflow => { :status => DmsfWorkflow::STATUS_ACTIVE } + @wf3.reload + assert @wf3.active?, "#{@wf3.name} status is #{@wf3.status}" + end def test_show get :show, :id => @wf1.id @@ -153,7 +175,7 @@ class DmsfWorkflowsControllerTest < RedmineDmsf::Test::TestCase def test_create assert_difference 'DmsfWorkflow.count', +1 do - post :create, :dmsf_workflow => {:name => 'wf3'}, :project_id => @project1.id + post :create, :dmsf_workflow => {:name => 'wf4'}, :project_id => @project1.id end assert_redirected_to settings_project_path(@project1, :tab => 'dmsf_workflow') end diff --git a/test/unit/dmsf_lock_test.rb b/test/unit/dmsf_lock_test.rb index 211f97c6..e8fb63e2 100644 --- a/test/unit/dmsf_lock_test.rb +++ b/test/unit/dmsf_lock_test.rb @@ -21,8 +21,7 @@ require File.expand_path('../../test_helper.rb', __FILE__) -class DmsfLockTest < RedmineDmsf::Test::UnitTest - #attr_reader :lock +class DmsfLockTest < RedmineDmsf::Test::UnitTest fixtures :projects, :users, :email_addresses, :dmsf_folders, :dmsf_files, :dmsf_file_revisions, :roles, :members, :member_roles, :enabled_modules, :enumerations, :dmsf_locks @@ -77,6 +76,7 @@ class DmsfLockTest < RedmineDmsf::Test::UnitTest end def test_locked_folder_cannot_be_unlocked_by_someone_without_rights_or_anon + User.current = nil assert_no_difference ('@folder2.lock.count') do assert_raise DmsfLockError do @folder2.unlock! diff --git a/test/unit/dmsf_workflow_test.rb b/test/unit/dmsf_workflow_test.rb index a56b7e71..ed3cd15a 100644 --- a/test/unit/dmsf_workflow_test.rb +++ b/test/unit/dmsf_workflow_test.rb @@ -2,7 +2,7 @@ # # Redmine plugin for Document Management System "Features" # -# Copyright (C) 2011-16 Karel Picman +# Copyright (C) 2011-16 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 @@ -27,15 +27,16 @@ class DmsfWorkflowTest < RedmineDmsf::Test::UnitTest :dmsf_workflow_step_assignments, :dmsf_workflow_step_actions def setup - @wf1 = DmsfWorkflow.find_by_id(1) - @wf2 = DmsfWorkflow.find_by_id(2) - @wfs1 = DmsfWorkflowStep.find_by_id(1) - @wfs2 = DmsfWorkflowStep.find_by_id(2) - @wfs3 = DmsfWorkflowStep.find_by_id(3) - @wfs4 = DmsfWorkflowStep.find_by_id(4) - @wfs5 = DmsfWorkflowStep.find_by_id(5) - @wfsa1 = DmsfWorkflowStepAssignment.find_by_id(1) - @wfsac1 = DmsfWorkflowStepAction.find_by_id(1) + @wf1 = DmsfWorkflow.find_by_id 1 + @wf2 = DmsfWorkflow.find_by_id 2 + @wf3 = DmsfWorkflow.find_by_id 3 + @wfs1 = DmsfWorkflowStep.find_by_id 1 + @wfs2 = DmsfWorkflowStep.find_by_id 2 + @wfs3 = DmsfWorkflowStep.find_by_id 3 + @wfs4 = DmsfWorkflowStep.find_by_id 4 + @wfs5 = DmsfWorkflowStep.find_by_id 5 + @wfsa1 = DmsfWorkflowStepAssignment.find_by_id 1 + @wfsac1 = DmsfWorkflowStepAction.find_by_id 1 @revision1 = DmsfFileRevision.find_by_id 1 @revision2 = DmsfFileRevision.find_by_id 2 @project = Project.find_by_id 2 @@ -45,6 +46,7 @@ class DmsfWorkflowTest < RedmineDmsf::Test::UnitTest def test_truth assert_kind_of DmsfWorkflow, @wf1 assert_kind_of DmsfWorkflow, @wf2 + assert_kind_of DmsfWorkflow, @wf3 assert_kind_of DmsfWorkflowStep, @wfs1 assert_kind_of DmsfWorkflowStep, @wfs2 assert_kind_of DmsfWorkflowStep, @wfs3 @@ -210,4 +212,20 @@ class DmsfWorkflowTest < RedmineDmsf::Test::UnitTest assert_equal participiants.count, 2 end + def test_locked + assert @wf3.locked?, "#{@wf2.name} status is #{@wf3.status}" + end + + def test_active + assert @wf1.active?, "#{@wf1.name} status is #{@wf1.status}" + end + + def test_scope_active + assert_equal DmsfWorkflow.count, (DmsfWorkflow.active.count + 1) + end + + def test_scope_status + assert_equal 1, DmsfWorkflow.status(DmsfWorkflow::STATUS_LOCKED).count + end + end From e7126d85d27c3a818706712d3cb97203b78aac32 Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Wed, 17 Feb 2016 16:32:13 +0100 Subject: [PATCH 05/94] SQLite compatibility --- app/controllers/dmsf_controller.rb | 4 +-- app/controllers/dmsf_files_controller.rb | 8 ++--- app/models/dmsf_file.rb | 27 ++++++++------- app/models/dmsf_file_revision.rb | 16 +++++---- app/models/dmsf_file_revision_access.rb | 5 +-- app/models/dmsf_folder.rb | 27 ++++++++------- app/models/dmsf_link.rb | 17 ++++++---- app/views/my/blocks/_open_approvals.html.erb | 6 ++-- db/migrate/20160217133001_status_deleted.rb | 35 ++++++++++++++++++++ test/integration/dmsf_webdav_delete_test.rb | 22 ++++++------ test/unit/dmsf_file_revision_test.rb | 4 +-- test/unit/dmsf_file_test.rb | 8 ++--- test/unit/dmsf_folder_test.rb | 6 ++-- test/unit/dmsf_link_test.rb | 14 ++++---- 14 files changed, 124 insertions(+), 75 deletions(-) create mode 100644 db/migrate/20160217133001_status_deleted.rb diff --git a/app/controllers/dmsf_controller.rb b/app/controllers/dmsf_controller.rb index 1c9deea9..cbb56203 100644 --- a/app/controllers/dmsf_controller.rb +++ b/app/controllers/dmsf_controller.rb @@ -4,7 +4,7 @@ # # Copyright (C) 2011 Vít Jonáš # Copyright (C) 2012 Daniel Munn -# Copyright (C) 2011-15 Karel Pičman +# Copyright (C) 2011-16 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 @@ -121,7 +121,7 @@ class DmsfController < ApplicationController @locked_for_user = false else - if @folder.deleted + if @folder.deleted? render_404 return end diff --git a/app/controllers/dmsf_files_controller.rb b/app/controllers/dmsf_files_controller.rb index 27bfb5a3..d8982266 100644 --- a/app/controllers/dmsf_files_controller.rb +++ b/app/controllers/dmsf_files_controller.rb @@ -3,7 +3,7 @@ # Redmine plugin for Document Management System "Features" # # Copyright (C) 2011 Vít Jonáš -# Copyright (C) 2011-15 Karel Pičman +# Copyright (C) 2011-16 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 @@ -42,7 +42,7 @@ class DmsfFilesController < ApplicationController raise DmsfAccessError if @revision.file != @file end check_project(@revision.file) - raise ActionController::MissingFile if @file.deleted + raise ActionController::MissingFile if @file.deleted? log_activity('downloaded') access = DmsfFileRevisionAccess.new access.user = User.current @@ -74,7 +74,7 @@ class DmsfFilesController < ApplicationController raise DmsfAccessError if @revision.file != @file end check_project(@revision.file) - raise ActionController::MissingFile if @revision.file.deleted + raise ActionController::MissingFile if @revision.file.deleted? log_activity('downloaded') access = DmsfFileRevisionAccess.new access.user = User.current @@ -231,7 +231,7 @@ class DmsfFilesController < ApplicationController end def delete_revision - if @revision # && !@revision.deleted + if @revision if @revision.delete(true) flash[:notice] = l(:notice_revision_deleted) log_activity('deleted') diff --git a/app/models/dmsf_file.rb b/app/models/dmsf_file.rb index b975d7c7..6b69c34b 100644 --- a/app/models/dmsf_file.rb +++ b/app/models/dmsf_file.rb @@ -43,14 +43,13 @@ class DmsfFile < ActiveRecord::Base :class_name => 'DmsfLock', :foreign_key => 'entity_id', :dependent => :destroy has_many :referenced_links, -> { where target_type: DmsfFile.model_name.to_s}, :class_name => 'DmsfLink', :foreign_key => 'target_id', :dependent => :destroy - accepts_nested_attributes_for :revisions, :locks, :referenced_links, :project + accepts_nested_attributes_for :revisions, :locks, :referenced_links, :project - scope :visible, lambda { |*args| - where(deleted: false) - } - scope :deleted, lambda { |*args| - where(deleted: true) - } + STATUS_DELETED = 1 + STATUS_ACTIVE = 0 + + scope :visible, -> { where(:deleted => STATUS_ACTIVE) } + scope :deleted, -> { where(:deleted => STATUS_DELETED) } validates :name, :presence => true validates_format_of :name, :with => DmsfFolder.invalid_characters, @@ -82,7 +81,7 @@ class DmsfFile < ActiveRecord::Base 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" + :date_column => "#{table_name}.updated_at" before_create :default_values def default_values @@ -121,7 +120,7 @@ class DmsfFile < ActiveRecord::Base def last_revision unless @last_revision - @last_revision = deleted ? self.revisions.first : self.revisions.visible.first + @last_revision = self.deleted? ? self.revisions.first : self.revisions.visible.first end @last_revision end @@ -129,6 +128,10 @@ class DmsfFile < ActiveRecord::Base def set_last_revision(new_revision) @last_revision = new_revision end + + def deleted? + self.deleted == STATUS_DELETED + end def delete(commit) if locked_for_user? @@ -143,7 +146,7 @@ class DmsfFile < ActiveRecord::Base if commit self.destroy else - self.deleted = true + self.deleted = STATUS_DELETED self.deleted_by_user = User.current save end @@ -155,13 +158,13 @@ class DmsfFile < ActiveRecord::Base end def restore - if self.dmsf_folder_id && (self.folder.nil? || self.folder.deleted) + if self.dmsf_folder_id && (self.folder.nil? || self.folder.deleted?) errors[:base] << l(:error_parent_folder) return false end self.revisions.each { |r| r.restore } self.referenced_links.each { |l| l.restore } - self.deleted = false + self.deleted = STATUS_ACTIVE self.deleted_by_user = nil save end diff --git a/app/models/dmsf_file_revision.rb b/app/models/dmsf_file_revision.rb index b0a87409..505a3aff 100644 --- a/app/models/dmsf_file_revision.rb +++ b/app/models/dmsf_file_revision.rb @@ -30,9 +30,11 @@ class DmsfFileRevision < ActiveRecord::Base has_many :dmsf_workflow_step_assignment, :dependent => :destroy accepts_nested_attributes_for :access, :dmsf_workflow_step_assignment, :file, :user - # Returns a list of revisions that are not deleted here, or deleted at parent level either - scope :visible, -> { where(deleted: false) } - scope :deleted, -> { where(deleted: true) } + STATUS_DELETED = 1 + STATUS_ACTIVE = 0 + + scope :visible, -> { where(:deleted => STATUS_ACTIVE) } + scope :deleted, -> { where(:deleted => STATUS_DELETED) } acts_as_customizable acts_as_event :title => Proc.new {|o| "#{l(:label_dmsf_updated)}: #{o.file.dmsf_path_str}"}, @@ -49,11 +51,11 @@ class DmsfFileRevision < ActiveRecord::Base joins( "INNER JOIN #{DmsfFile.table_name} ON #{DmsfFileRevision.table_name}.dmsf_file_id = #{DmsfFile.table_name}.id " + "INNER JOIN #{Project.table_name} ON #{DmsfFile.table_name}.project_id = #{Project.table_name}.id"). - where("#{DmsfFile.table_name}.deleted = :false", {:false => false}) + where("#{DmsfFile.table_name}.deleted = ?", STATUS_ACTIVE) validates :title, :presence => true validates_format_of :name, :with => DmsfFolder.invalid_characters, - :message => l(:error_contains_invalid_character) + :message => l(:error_contains_invalid_character) def project self.file.project if self.file @@ -88,14 +90,14 @@ class DmsfFileRevision < ActiveRecord::Base if commit self.destroy else - self.deleted = true + self.deleted = DmsfFile::STATUS_DELETED self.deleted_by_user = User.current save end end def restore - self.deleted = false + self.deleted = DmsfFile::STATUS_ACTIVE self.deleted_by_user = nil save end diff --git a/app/models/dmsf_file_revision_access.rb b/app/models/dmsf_file_revision_access.rb index 44140ee4..0136fa78 100644 --- a/app/models/dmsf_file_revision_access.rb +++ b/app/models/dmsf_file_revision_access.rb @@ -1,6 +1,7 @@ # Redmine plugin for Document Management System "Features" # -# Copyright (C) 2011 Vít Jonáš +# Copyright (C) 2011 Vít Jonáš +# Copyright (C) 2011-16 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 @@ -42,6 +43,6 @@ class DmsfFileRevisionAccess < ActiveRecord::Base "INNER JOIN #{DmsfFileRevision.table_name} ON #{DmsfFileRevisionAccess.table_name}.dmsf_file_revision_id = #{DmsfFileRevision.table_name}.id " + "INNER JOIN #{DmsfFile.table_name} ON #{DmsfFileRevision.table_name}.dmsf_file_id = #{DmsfFile.table_name}.id " + "INNER JOIN #{Project.table_name} ON #{DmsfFile.table_name}.project_id = #{Project.table_name}.id"). - where("#{DmsfFile.table_name}.deleted = :false", {:false => false}) + where("#{DmsfFile.table_name}.deleted = ?", DmsfFile::STATUS_ACTIVE) end diff --git a/app/models/dmsf_folder.rb b/app/models/dmsf_folder.rb index a5dd2f88..1367f2fc 100644 --- a/app/models/dmsf_folder.rb +++ b/app/models/dmsf_folder.rb @@ -3,7 +3,7 @@ # Redmine plugin for Document Management System "Features" # # Copyright (C) 2011 Vít Jonáš -# Copyright (C) 2011-15 Karel Pičman +# Copyright (C) 2011-16 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 @@ -48,12 +48,11 @@ class DmsfFolder < ActiveRecord::Base :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 - scope :visible, lambda { |*args| - where(deleted: false) - } - scope :deleted, lambda { |*args| - where(deleted: true) - } + STATUS_DELETED = 1 + STATUS_ACTIVE = 0 + + scope :visible, -> { where(:deleted => STATUS_ACTIVE) } + scope :deleted, -> { where(:deleted => STATUS_DELETED) } acts_as_customizable @@ -71,10 +70,10 @@ class DmsfFolder < ActiveRecord::Base validates :title, :presence => true validates_uniqueness_of :title, :scope => [:dmsf_folder_id, :project_id, :deleted], - conditions: -> { where.not(deleted: true) } + conditions: -> { where(:deleted => STATUS_ACTIVE) } validates_format_of :title, :with => @@invalid_characters, :message => l(:error_contains_invalid_character) - validate :check_cycle + validate :check_cycle before_create :default_values def default_values @@ -119,19 +118,23 @@ class DmsfFolder < ActiveRecord::Base if commit self.destroy else - self.deleted = true + self.deleted = STATUS_DELETED self.deleted_by_user = User.current self.save end end + + def deleted? + self.deleted == STATUS_DELETED + end def restore - if self.dmsf_folder_id && (self.folder.nil? || self.folder.deleted) + if self.dmsf_folder_id && (self.folder.nil? || self.folder.deleted?) errors[:base] << l(:error_parent_folder) return false end self.referenced_links.each { |l| l.restore } - self.deleted = false + self.deleted = STATUS_ACTIVE self.deleted_by_user = nil self.save end diff --git a/app/models/dmsf_link.rb b/app/models/dmsf_link.rb index e92662be..897f9697 100644 --- a/app/models/dmsf_link.rb +++ b/app/models/dmsf_link.rb @@ -2,7 +2,7 @@ # # Redmine plugin for Document Management System "Features" # -# Copyright (C) 2011-15 Karel Pičman +# Copyright (C) 2011-16 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 @@ -42,8 +42,11 @@ class DmsfLink < ActiveRecord::Base end end - scope :visible, -> { where(deleted: false) } - scope :deleted, -> { where(deleted: true) } + STATUS_DELETED = 1 + STATUS_ACTIVE = 0 + + scope :visible, -> { where(:deleted => STATUS_ACTIVE) } + scope :deleted, -> { where(:deleted => STATUS_DELETED) } def target_folder_id if self.target_type == DmsfFolder.model_name.to_s @@ -119,20 +122,20 @@ class DmsfLink < ActiveRecord::Base if commit self.destroy else - self.deleted = true + self.deleted = STATUS_DELETED self.deleted_by_user = User.current save end end def restore - if self.dmsf_folder_id && (self.folder.nil? || self.folder.deleted) + if self.dmsf_folder_id && (self.folder.nil? || self.folder.deleted?) errors[:base] << l(:error_parent_folder) return false end - self.deleted = false + self.deleted = STATUS_ACTIVE self.deleted_by_user = nil save end -end +end \ No newline at end of file diff --git a/app/views/my/blocks/_open_approvals.html.erb b/app/views/my/blocks/_open_approvals.html.erb index 58763291..5727c0f9 100644 --- a/app/views/my/blocks/_open_approvals.html.erb +++ b/app/views/my/blocks/_open_approvals.html.erb @@ -1,7 +1,9 @@ <%# +# encoding: utf-8 +# # Redmine plugin for Document Management System "Features" # -# Copyright (C) 2011-15 Karel Picman +# Copyright (C) 2011-16 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 @@ -24,7 +26,7 @@ <% assignments = Array.new %> <% all_assignments.each do |assignment| %> <% if assignment.dmsf_file_revision.file.last_revision && - !assignment.dmsf_file_revision.file.last_revision.deleted && + !assignment.dmsf_file_revision.file.last_revision.deleted? && (assignment.dmsf_file_revision.workflow == DmsfWorkflow::STATE_WAITING_FOR_APPROVAL) && (assignment.dmsf_file_revision == assignment.dmsf_file_revision.file.last_revision) %> <% assignments << assignment %> diff --git a/db/migrate/20160217133001_status_deleted.rb b/db/migrate/20160217133001_status_deleted.rb new file mode 100644 index 00000000..9951c35e --- /dev/null +++ b/db/migrate/20160217133001_status_deleted.rb @@ -0,0 +1,35 @@ +# encoding: utf-8 +# +# Redmine plugin for Document Management System "Features" +# +# Copyright (C) 2011-16 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 StatusDeleted < ActiveRecord::Migration + def self.up + change_column :dmsf_folders, :deleted, :integer, :null => false, :default => DmsfFolder::STATUS_ACTIVE + change_column :dmsf_files, :deleted, :integer, :null => false, :default => DmsfFile::STATUS_ACTIVE + change_column :dmsf_file_revisions, :deleted, :integer, :null => false, :default => DmsfFileRevision::STATUS_ACTIVE + change_column :dmsf_links, :deleted, :integer, :null => false, :default => DmsfLink::STATUS_ACTIVE + end + + def self.down + change_column :dmsf_folders, :deleted, :boolean, :null => false, :default => false + change_column :dmsf_files, :deleted, :boolean, :null => false, :default => false + change_column :dmsf_file_revisions, :deleted, :boolean, :null => false, :default => false + change_column :dmsf_links, :deleted, :boolean, :null => false, :default => false + end +end \ No newline at end of file diff --git a/test/integration/dmsf_webdav_delete_test.rb b/test/integration/dmsf_webdav_delete_test.rb index ce4da8d4..3dfe6a91 100644 --- a/test/integration/dmsf_webdav_delete_test.rb +++ b/test/integration/dmsf_webdav_delete_test.rb @@ -104,7 +104,7 @@ class DmsfWebdavDeleteTest < RedmineDmsf::Test::IntegrationTest delete "/dmsf/webdav/#{@project1.identifier}/#{@file1.name}", nil, @admin assert_response :success # If its in the 20x range it's acceptable, should be 204. @file1.reload - assert @file1.deleted, "File #{@file1.name} hasn't been deleted" + assert @file1.deleted?, "File #{@file1.name} hasn't been deleted" end def test_unathorized_user @@ -112,7 +112,7 @@ class DmsfWebdavDeleteTest < RedmineDmsf::Test::IntegrationTest delete "/dmsf/webdav/#{@project1.identifier}/#{@file1.name}", nil, @jsmith assert_response :missing # Without folder_view permission, he will not even be aware of its existence. @file1.reload - assert !@file1.deleted, "File #{@file1.name} is expected to exist" + assert !@file1.deleted?, "File #{@file1.name} is expected to exist" end def test_unathorized_user_forbidden @@ -121,7 +121,7 @@ class DmsfWebdavDeleteTest < RedmineDmsf::Test::IntegrationTest delete "/dmsf/webdav/#{@project1.identifier}/#{@file1.name}", nil, @jsmith assert_response :forbidden # Now jsmith's role has view_folder rights, however they do not hold file manipulation rights. @file1.reload - assert !@file1.deleted, "File #{@file1.name} is expected to exist" + assert !@file1.deleted?, "File #{@file1.name} is expected to exist" end def test_view_folder_not_allowed @@ -130,7 +130,7 @@ class DmsfWebdavDeleteTest < RedmineDmsf::Test::IntegrationTest delete "/dmsf/webdav/#{@project1.identifier}/#{@folder1.title}", nil, @jsmith assert_response :missing # Without folder_view permission, he will not even be aware of its existence. @folder1.reload - assert !@folder1.deleted, "Folder #{@folder1.title} is expected to exist" + assert !@folder1.deleted?, "Folder #{@folder1.title} is expected to exist" end def test_folder_manipulation_not_allowed @@ -139,7 +139,7 @@ class DmsfWebdavDeleteTest < RedmineDmsf::Test::IntegrationTest delete "/dmsf/webdav/#{@project1.identifier}/#{@folder1.title}", nil, @jsmith assert_response :forbidden # Without manipulation permission, action is forbidden. @folder1.reload - assert !@folder1.deleted, "Foler #{@folder1.title} is expected to exist" + assert !@folder1.deleted?, "Foler #{@folder1.title} is expected to exist" end def test_folder_delete_by_admin @@ -147,7 +147,7 @@ class DmsfWebdavDeleteTest < RedmineDmsf::Test::IntegrationTest delete "/dmsf/webdav/#{@project1.identifier}/#{@folder6.title}", nil, @admin assert_response :success @folder6.reload - assert @folder6.deleted, "Folder #{@folder1.title} is not expected to exist" + assert @folder6.deleted?, "Folder #{@folder1.title} is not expected to exist" end def test_folder_delete_by_user @@ -157,7 +157,7 @@ class DmsfWebdavDeleteTest < RedmineDmsf::Test::IntegrationTest delete "/dmsf/webdav/#{@project1.identifier}/#{@folder6.title}", nil, @jsmith assert_response :success @folder6.reload - assert @folder6.deleted, "Folder #{@folder1.title} is not expected to exist" + assert @folder6.deleted?, "Folder #{@folder1.title} is not expected to exist" end def test_file_delete_by_administrator @@ -165,7 +165,7 @@ class DmsfWebdavDeleteTest < RedmineDmsf::Test::IntegrationTest delete "/dmsf/webdav/#{@project1.identifier}/#{@file1.name}", nil, @admin assert_response :success @file1.reload - assert @file1.deleted, "File #{@file1.name} is not expected to exist" + assert @file1.deleted?, "File #{@file1.name} is not expected to exist" end def test_file_delete_by_user @@ -175,7 +175,7 @@ class DmsfWebdavDeleteTest < RedmineDmsf::Test::IntegrationTest delete "/dmsf/webdav/#{@project1.identifier}/#{@file1.name}", nil, @jsmith assert_response :success @file1.reload - assert @file1.deleted, "File #{@file1.name} is not expected to exist" + assert @file1.deleted?, "File #{@file1.name} is not expected to exist" end def test_locked_folder @@ -186,7 +186,7 @@ class DmsfWebdavDeleteTest < RedmineDmsf::Test::IntegrationTest delete "/dmsf/webdav/#{@project1.identifier}/#{@folder6.title}", nil, @jsmith assert_response 423 # Locked @folder6.reload - assert !@folder6.deleted, "Folder #{@folder6.title} is expected to exist" + assert !@folder6.deleted?, "Folder #{@folder6.title} is expected to exist" end def test_locked_file @@ -197,7 +197,7 @@ class DmsfWebdavDeleteTest < RedmineDmsf::Test::IntegrationTest delete "/dmsf/webdav/#{@project1.identifier}/#{@file1.name}", nil, @jsmith assert_response 423 # Locked @file1.reload - assert !@file1.deleted, "File #{@file1.name} is expected to exist" + assert !@file1.deleted?, "File #{@file1.name} is expected to exist" end end \ No newline at end of file diff --git a/test/unit/dmsf_file_revision_test.rb b/test/unit/dmsf_file_revision_test.rb index 61bdead4..7b14d211 100644 --- a/test/unit/dmsf_file_revision_test.rb +++ b/test/unit/dmsf_file_revision_test.rb @@ -35,10 +35,10 @@ class DmsfFileRevisionTest < RedmineDmsf::Test::UnitTest def test_delete_restore @revision5.delete false - assert @revision5.deleted, + assert @revision5.deleted?, "File revision #{@revision5.name} hasn't been deleted" @revision5.restore - assert !@revision5.deleted, + assert !@revision5.deleted?, "File revision #{@revision5.name} hasn't been restored" end diff --git a/test/unit/dmsf_file_test.rb b/test/unit/dmsf_file_test.rb index 9ffdf3ee..371eafe8 100644 --- a/test/unit/dmsf_file_test.rb +++ b/test/unit/dmsf_file_test.rb @@ -54,12 +54,12 @@ class DmsfFileTest < RedmineDmsf::Test::UnitTest end def test_project_dmsf_file_listing_contains_deleted_items - assert @project1.dmsf_files.index{ |f| f.deleted }, + assert @project1.dmsf_files.index{ |f| f.deleted? }, 'Expected at least one deleted item in ' end def test_project_dmsf_file_visible_listing_contains_no_deleted_items - assert @project1.dmsf_files.visible.index{ |f| f.deleted }.nil?, + assert @project1.dmsf_files.visible.index{ |f| f.deleted? }.nil?, 'There is a deleted file, this was unexpected' end @@ -106,7 +106,7 @@ class DmsfFileTest < RedmineDmsf::Test::UnitTest User.current = @admin @file4.folder.unlock! assert @file4.delete(false), @file4.errors.full_messages.to_sentence - assert @file4.deleted, "File #{@file4.name} is not deleted" + assert @file4.deleted?, "File #{@file4.name} is not deleted" assert_equal 0, @file4.revisions.visible.count assert_equal 0, @file4.referenced_links.visible.count @file4.folder.lock! @@ -117,7 +117,7 @@ class DmsfFileTest < RedmineDmsf::Test::UnitTest @file4.folder.unlock! assert @file4.delete(false), @file4.errors.full_messages.to_sentence @file4.restore - assert !@file4.deleted, "File #{@file4} hasn't been restored" + assert !@file4.deleted?, "File #{@file4} hasn't been restored" assert_equal 1, @file4.revisions.visible.count assert_equal 2, @file4.referenced_links.visible.count @file4.folder.lock! diff --git a/test/unit/dmsf_folder_test.rb b/test/unit/dmsf_folder_test.rb index 8116a6a8..17217f79 100644 --- a/test/unit/dmsf_folder_test.rb +++ b/test/unit/dmsf_folder_test.rb @@ -35,14 +35,14 @@ class DmsfFolderTest < RedmineDmsf::Test::UnitTest def test_delete assert @folder6.delete(false), @folder6.errors.full_messages.to_sentence - assert @folder6.deleted, "Folder #{@folder6} hasn't been deleted" + assert @folder6.deleted?, "Folder #{@folder6} hasn't been deleted" end def test_restore assert @folder6.delete(false), @folder6.errors.full_messages.to_sentence - assert @folder6.deleted, "Folder #{@folder6} hasn't been deleted" + assert @folder6.deleted?, "Folder #{@folder6} hasn't been deleted" assert @folder6.restore, @folder6.errors.full_messages.to_sentence - assert !@folder6.deleted, "Folder #{@folder6} hasn't been restored" + assert !@folder6.deleted?, "Folder #{@folder6} hasn't been restored" end def test_destroy diff --git a/test/unit/dmsf_link_test.rb b/test/unit/dmsf_link_test.rb index 66b41a48..001935cb 100644 --- a/test/unit/dmsf_link_test.rb +++ b/test/unit/dmsf_link_test.rb @@ -2,7 +2,7 @@ # # Redmine plugin for Document Management System "Features" # -# Copyright (C) 2011-15 Karel Pičman +# Copyright (C) 2011-16 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 @@ -185,28 +185,28 @@ class DmsfLinksTest < RedmineDmsf::Test::UnitTest def test_delete_file_link assert @file_link.delete(false), @file_link.errors.full_messages.to_sentence - assert @file_link.deleted, "File link hasn't been deleted" + assert @file_link.deleted?, "File link hasn't been deleted" end def test_restore_file_link assert @file_link.delete(false), @file_link.errors.full_messages.to_sentence - assert @file_link.deleted, "File link hasn't been deleted" + assert @file_link.deleted?, "File link hasn't been deleted" assert @file_link.restore, @file_link.errors.full_messages.to_sentence - assert !@file_link.deleted, "File link hasn't been restored" + assert !@file_link.deleted?, "File link hasn't been restored" end def test_delete_folder_link assert @folder_link.delete(false), @folder_link.errors.full_messages.to_sentence - assert @folder_link.deleted, "Folder link hasn't been deleted" + assert @folder_link.deleted?, "Folder link hasn't been deleted" end def test_restore_folder_link assert @folder_link.delete(false), @folder_link.errors.full_messages.to_sentence - assert @folder_link.deleted, "Folder link hasn't been deleted" + assert @folder_link.deleted?, "Folder link hasn't been deleted" assert @folder_link.restore, @folder_link.errors.full_messages.to_sentence - assert !@folder_link.deleted, "Folder link hasn't been restored" + assert !@folder_link.deleted?, "Folder link hasn't been restored" end def test_destroy_file_link From 66459e37a661509aaff051f68377af671fef3a3b Mon Sep 17 00:00:00 2001 From: Christian Franke Date: Wed, 17 Feb 2016 18:13:23 +0100 Subject: [PATCH 06/94] bugfix --- init.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/init.rb b/init.rb index 406f9581..88ce4e8c 100644 --- a/init.rb +++ b/init.rb @@ -181,7 +181,7 @@ Redmine::Plugin.register :redmine_dmsf do end raise 'Not supported image format' unless file.image? url = url_for(:controller => :dmsf_files, :action => 'view', :id => file) - if size.include? "%" + if size and size.include? "%" image_tag(url, :alt => file.title, :width => size, :height => size) else image_tag(url, :alt => file.title, :size => size) From 3b57d6ccf51ad1273fef5e629234f252104e1949 Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Thu, 18 Feb 2016 09:52:24 +0100 Subject: [PATCH 07/94] Non-fatal MySQL error when migrating documents #504 --- lib/tasks/dmsf_convert_documents.rake | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/tasks/dmsf_convert_documents.rake b/lib/tasks/dmsf_convert_documents.rake index 4210dc74..595661a6 100644 --- a/lib/tasks/dmsf_convert_documents.rake +++ b/lib/tasks/dmsf_convert_documents.rake @@ -3,7 +3,7 @@ # Redmine plugin for Document Management System "Features" # # Copyright (C) 2011 Vít Jonáš -# Copyright (C) 2011-15 Karel Pičman +# Copyright (C) 2011-16 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 @@ -66,8 +66,13 @@ class DmsfConvertDocuments folder = DmsfFolder.new - folder.project = project - folder.user = document.attachments.reorder("#{Attachment.table_name}.created_on ASC").first.try(:author) + folder.project = project + attachment = document.attachments.reorder("#{Attachment.table_name}.created_on ASC").first + if attachment + folder.user = attachment.author + else + folder.user = Users.active.where(:admin => true).first + end folder.title = document.title folder.title.gsub!(/[\/\\\?":<>]/, '-') if replace From 624a228b6c32b0cc105ccd0bd4136c4e794d30ed Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Thu, 18 Feb 2016 10:00:56 +0100 Subject: [PATCH 08/94] Information about migrating documents #503 --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 6b1feeb1..ab8008a1 100644 --- a/README.md +++ b/README.md @@ -196,6 +196,10 @@ Before installing ensure that the Redmine instance is stopped. rake redmine:dmsf_convert_documents project=test RAILS_ENV="production" + (If you don't run the rake task as the web server user don't forget to change the ownership of the imported files, e.g. + chown -R www-data:www-data /redmine/files/dmsf + afterwards) + b) To alert all users who are expected to do an approval in the current approval steps Example: From a71396fda4fe1096f7a140023af00eda4de8f517 Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Thu, 18 Feb 2016 10:02:43 +0100 Subject: [PATCH 09/94] Information about migrating documents #503 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ab8008a1..4756ac39 100644 --- a/README.md +++ b/README.md @@ -196,7 +196,7 @@ Before installing ensure that the Redmine instance is stopped. rake redmine:dmsf_convert_documents project=test RAILS_ENV="production" - (If you don't run the rake task as the web server user don't forget to change the ownership of the imported files, e.g. + (If you don't run the rake task as the web server user, don't forget to change the ownership of the imported files, e.g. chown -R www-data:www-data /redmine/files/dmsf afterwards) From c887e76af5f782c806a75f71e72e0414002656cc Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Thu, 18 Feb 2016 10:24:50 +0100 Subject: [PATCH 10/94] [Enhancement] Automatically check the inline radiobutton when use custom version #500 --- app/views/dmsf_files/_file_new_revision.html.erb | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/app/views/dmsf_files/_file_new_revision.html.erb b/app/views/dmsf_files/_file_new_revision.html.erb index 5470c50d..6dba77f0 100644 --- a/app/views/dmsf_files/_file_new_revision.html.erb +++ b/app/views/dmsf_files/_file_new_revision.html.erb @@ -5,7 +5,7 @@ # # Copyright (C) 2011 Vít Jonáš # Copyright (C) 2012 Daniel Munn -# Copyright (C) 2011-15 Karel Pičman +# Copyright (C) 2011-16 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 @@ -69,7 +69,12 @@ <%= @file.last_revision.major_version + 1 %>.0 <%= l(:option_version_major) %>
<%= radio_button_tag('version', 3) %> - <%= select_tag 'custom_version_major', options_for_select(0..99, @file.last_revision.major_version + 2) %>.<%= select_tag 'custom_version_minor', options_for_select(0..99, @file.last_revision.minor_version + 1) %> + <%= select_tag 'custom_version_major', + options_for_select(0..99, @file.last_revision.major_version + 2), + :onchange => '$("#version_3").prop("checked", true)'%>. + <%= select_tag 'custom_version_minor', + options_for_select(0..99, @file.last_revision.minor_version + 1), + :onchange => '$("#version_3").prop("checked", true)'%> <%= l(:option_version_custom) %> From 6fcfc7c4605de26d75077120938a5a8c3ed0d2d2 Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Fri, 19 Feb 2016 13:16:18 +0100 Subject: [PATCH 11/94] If a folder or file is locked, we can't activate or deactivate notifications #501 --- app/controllers/dmsf_controller.rb | 1 + app/controllers/dmsf_files_controller.rb | 1 + app/controllers/dmsf_upload_controller.rb | 21 +-- app/views/dmsf/_dir.html.erb | 26 +-- app/views/dmsf/_file.html.erb | 189 +++++++++++----------- app/views/dmsf/_url.html.erb | 43 +++-- app/views/dmsf/edit.html.erb | 42 ++--- app/views/dmsf/show.html.erb | 70 +++----- app/views/dmsf_files/show.html.erb | 29 ++-- assets/images/filedetails.png | Bin 803 -> 739 bytes assets/images/operations.png | Bin 459 -> 0 bytes assets/images/ticket_go.png | Bin 608 -> 0 bytes assets/stylesheets/dmsf.css | 3 +- config/locales/cs.yml | 3 +- config/locales/de.yml | 3 +- config/locales/en.yml | 3 +- config/locales/es.yml | 3 +- config/locales/fr.yml | 11 +- config/locales/ja.yml | 16 +- config/locales/pl.yml | 5 +- config/locales/pt-BR.yml | 3 +- config/locales/ru.yml | 5 +- config/locales/sl.yml | 5 +- config/locales/zh-TW.yml | 7 +- config/locales/zh.yml | 5 +- init.rb | 4 +- 26 files changed, 236 insertions(+), 262 deletions(-) delete mode 100644 assets/images/operations.png delete mode 100644 assets/images/ticket_go.png diff --git a/app/controllers/dmsf_controller.rb b/app/controllers/dmsf_controller.rb index cbb56203..87816e56 100644 --- a/app/controllers/dmsf_controller.rb +++ b/app/controllers/dmsf_controller.rb @@ -298,6 +298,7 @@ class DmsfController < ApplicationController def edit @parent = @folder.folder @pathfolder = copy_folder(@folder) + @force_file_unlock_allowed = User.current.allowed_to?(:force_file_unlock, @project) end def save diff --git a/app/controllers/dmsf_files_controller.rb b/app/controllers/dmsf_files_controller.rb index d8982266..abb54e86 100644 --- a/app/controllers/dmsf_files_controller.rb +++ b/app/controllers/dmsf_files_controller.rb @@ -98,6 +98,7 @@ class DmsfFilesController < ApplicationController @revision = @file.last_revision @file_delete_allowed = User.current.allowed_to?(:file_delete, @project) + @file_manipulation_allowed = User.current.allowed_to?(:file_manipulation, @project) @revision_pages = Paginator.new @file.revisions.visible.count, params['per_page'] ? params['per_page'].to_i : 25, params['page'] respond_to do |format| diff --git a/app/controllers/dmsf_upload_controller.rb b/app/controllers/dmsf_upload_controller.rb index 6077fcd8..59d31f67 100644 --- a/app/controllers/dmsf_upload_controller.rb +++ b/app/controllers/dmsf_upload_controller.rb @@ -153,11 +153,7 @@ class DmsfUploadController < ApplicationController file.notification = Setting.plugin_redmine_dmsf[:dmsf_default_notifications].present? new_revision.minor_version = 0 new_revision.major_version = 0 - else - if file.locked_for_user? - failed_uploads.push(commited_file) - next - end + else if file.last_revision last_revision = file.last_revision new_revision.source_revision = last_revision @@ -168,6 +164,11 @@ class DmsfUploadController < ApplicationController new_revision.major_version = 0 end end + + if file.locked_for_user? + failed_uploads.push(commited_file) + next + end commited_disk_filepath = "#{DmsfHelper.temp_dir}/#{commited_file[:disk_filename].gsub(/[\/\\]/,'')}" @@ -186,16 +187,6 @@ class DmsfUploadController < ApplicationController end new_revision.mime_type = Redmine::MimeType.of(new_revision.name) new_revision.size = File.size(commited_disk_filepath) - - if file.locked? - begin - file.unlock! - flash[:notice] = l(:notice_file_unlocked) - rescue DmsfLockError => e - flash[:error] = e.message - next - end - end # Need to save file first to generate id for it in case of creation. # File id is needed to properly generate revision disk filename diff --git a/app/views/dmsf/_dir.html.erb b/app/views/dmsf/_dir.html.erb index de2714cc..94e1f721 100644 --- a/app/views/dmsf/_dir.html.erb +++ b/app/views/dmsf/_dir.html.erb @@ -3,7 +3,7 @@ # # Redmine plugin for Document Management System "Features" # -# Copyright (C) 2011-15 Karel Pičman +# Copyright (C) 2011-16 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 @@ -56,14 +56,24 @@
- diff --git a/app/views/dmsf/_url.html.erb b/app/views/dmsf/_url.html.erb index 9b7848c0..28bfd7e9 100644 --- a/app/views/dmsf/_url.html.erb +++ b/app/views/dmsf/_url.html.erb @@ -3,7 +3,7 @@ # # Redmine plugin for Document Management System "Features" # -# Copyright (C) 2011-15 Karel Pičman +# Copyright (C) 2011-16 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 @@ -22,11 +22,13 @@ @@ -34,25 +36,20 @@ - + \ No newline at end of file diff --git a/app/views/dmsf/edit.html.erb b/app/views/dmsf/edit.html.erb index d21f21de..300e2c15 100644 --- a/app/views/dmsf/edit.html.erb +++ b/app/views/dmsf/edit.html.erb @@ -1,7 +1,11 @@ -<%# Redmine plugin for Document Management System "Features" +<%# +# encoding: utf-8 # -# Copyright (C) 2011 Vít Jonáš -# Copyright (C) 2012 Daniel Munn +# Redmine plugin for Document Management System "Features" +# +# Copyright (C) 2011 Vít Jonáš +# Copyright (C) 2012 Daniel Munn +# Copyright (C) 2011-16 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 @@ -20,12 +24,17 @@ <% html_title(l(:dmsf)) %>
- <% if User.current.allowed_to?(:folder_manipulation, @project) && params[:action] == 'edit' %> - <% unless @folder.locked? %> - <%= link_to(l(:button_lock), + <% if !@folder.new_record? && User.current.allowed_to?(:folder_manipulation, @project) %> + <% if !@folder.locked_for_user? %> + <% unless @folder.locked? %> + <%= link_to(l(:button_lock), lock_dmsf_path(:id => @project, :folder_id => @folder), - :title => l(:title_lock_file), - :class => 'icon icon-dmsf-lock') %> + :title => l(:title_lock_file), :class => 'icon icon-dmsf-lock') %> + <% else %> + <%= link_to_if(@folder.unlockable?, l(:button_unlock), + unlock_dmsf_path(:id => @project, :folder_id => @folder), + :title => l(:title_unlock_file), :class => 'icon icon-dmsf-unlock')%> + <% end %> <% if @folder.notification %> <%= link_to(l(:label_notifications_off), notify_deactivate_dmsf_path(:id => @project, :folder_id => @folder), @@ -39,22 +48,17 @@ <% end %> <%= link_to(l(:label_link_to), new_dmsf_link_path(:project_id => @project.id, :dmsf_folder_id => @folder.id, :type => 'link_to'), - :title => l(:title_create_link), - :class => 'icon icon-link') %> + :title => l(:title_create_link), :class => 'icon icon-link') %> <%= link_to(l(:button_copy), copy_folder_path(:id => @folder), :title => l(:title_copy), :class => 'icon icon-copy') %> <%= link_to(l(:button_delete), delete_dmsf_path(:id => @project, :folder_id => @folder), :data => {:confirm => l(:text_are_you_sure)}, - :title => l(:title_delete), - :class => 'icon icon-del') %> - <% else %> - <% if (!@folder.locked_for_user? || User.current.allowed_to?(:force_file_unlock, @project)) && @folder.unlockable? %> - <%= link_to(l(:button_unlock), - unlock_dmsf_path(:id => @project, :folder_id => @folder), - :title => l(:title_unlock_file), - :class => 'icon icon-dmsf-unlock')%> - <% end %> + :title => l(:title_delete), :class => 'icon icon-del') %> + <% elsif @force_file_unlock_allowed %> + <%= link_to_if(@folder.unlockable?, l(:button_unlock), + unlock_dmsf_path(:id => @project, :folder_id => @folder), + :title => l(:title_unlock_file), :class => 'icon icon-dmsf-unlock')%> <% end %> <% end %>
diff --git a/app/views/dmsf/show.html.erb b/app/views/dmsf/show.html.erb index 8bf99cff..5945ac72 100644 --- a/app/views/dmsf/show.html.erb +++ b/app/views/dmsf/show.html.erb @@ -5,7 +5,7 @@ # # Copyright (C) 2011 Vít Jonáš # Copyright (C) 2012 Daniel Munn -# Copyright (C) 2011-15 Karel Pičman +# Copyright (C) 2011-16 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 @@ -22,12 +22,11 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. %> -<% html_title(l(:dmsf)) %> +<% html_title l(:dmsf) %>
<% if @folder_manipulation_allowed %> <% if @folder.nil? %> - <%= link_to(l(:button_edit), - edit_root_dmsf_path(:id => @project), + <%= link_to(l(:button_edit), edit_root_dmsf_path(:id => @project), :title => l(:link_edit, :title => l(:link_documents)), :class => 'icon icon-edit') %> <% elsif !@locked_for_user %> @@ -37,54 +36,31 @@ :class => 'icon icon-edit') %> <% end %> <% if @folder && (!@locked_for_user || User.current.allowed_to?(:force_file_unlock, @project)) %> - <% if @folder.locked? %> - <% unless @folder.unlockable? %> - <%= link_to(l(:button_unlock), - :title => l(:title_folder_parent_locked, :name => @folder.folder.lock.reverse[0].folder.title), - :class => 'icon icon-dmsf-unlock') if @folder %> - <% else %> - <%= link_to(l(:button_unlock), - unlock_dmsf_path(:id => @project, :folder_id => @folder, :current => request.url), - :title => l(:title_unlock_folder), - :class => 'icon icon-dmsf-unlock') if @folder %> - <% end %> + <% if @folder.locked? %> + <%= link_to_if(@folder.unlockable?, l(:button_unlock), + unlock_dmsf_path(:id => @project, :folder_id => @folder, :current => request.url), + :title => l(:title_unlock_folder), :class => 'icon icon-dmsf-unlock') %> <% else %> <%= link_to(l(:button_lock), lock_dmsf_path(:id => @project, :folder_id => @folder, :current => request.url), - :title => l(:title_lock_folder), - :class => 'icon icon-dmsf-lock') if @folder %> + :title => l(:title_lock_folder), :class => 'icon icon-dmsf-lock') %> <% end %> - <% end %> - <% unless @folder %> - <% if @project.dmsf_notification %> - <%= link_to(l(:label_notifications_off), - notify_deactivate_dmsf_path(:id => @project), - :title => l(:title_notifications_active_deactivate), - :class => 'icon icon-notification-on') %> - <% else %> - <%= link_to(l(:label_notifications_on), - notify_activate_dmsf_path(:id => @project), - :title => l(:title_notifications_active_deactivate), - :class => 'icon icon-notification-off') %> - <% end %> + <% end %> + <% if !@locked_for_user && ((@folder && @folder.notification) || (!@filder && @project.dmsf_notification)) %> + <%= link_to(l(:label_notifications_off), + notify_deactivate_dmsf_path(:id => @project, :folder_id => @folder), + :title => l(:title_notifications_active_deactivate), + :class => 'icon icon-notification-on') %> <% else %> - <% if @folder.notification %> - <%= link_to(l(:label_notifications_off), - notify_deactivate_dmsf_path(:id => @project, :folder_id => @folder), - :title => l(:title_notifications_active_deactivate), - :class => 'icon icon-notification-on') %> - <% else %> - <%= link_to(l(:label_notifications_on), - notify_activate_dmsf_path(:id => @project, :folder_id => @folder), - :title => l(:title_notifications_not_active_activate), - :class => 'icon icon-notification-off') %> - <% end %> - <% end %> - <% if @file_manipulation_allowed %> + <%= link_to(l(:label_notifications_on), + notify_activate_dmsf_path(:id => @project, :folder_id => @folder), + :title => l(:title_notifications_not_active_activate), + :class => 'icon icon-notification-off') %> + <% end %> + <% if @file_manipulation_allowed && !@locked_for_user %> <%= link_to(l(:label_link_from), new_dmsf_link_path(:project_id => @project.id, :dmsf_folder_id => @folder ? @folder.id : @folder, :type => 'link_from'), - :title => l(:title_create_link), - :class => 'icon icon-link') unless @locked_for_user %> + :title => l(:title_create_link), :class => 'icon icon-link') %> <% end %> <%= link_to(l(:link_create_folder), new_dmsf_path(:id => @project, :parent_id => @folder), @@ -288,4 +264,6 @@ <% end %> -<%= render(:partial => 'dmsf_upload/multi_upload') if (@file_manipulation_allowed && !@locked_for_user) %> +<% if (@file_manipulation_allowed && !@locked_for_user) %> + <%= render(:partial => 'dmsf_upload/multi_upload') %> +<% end %> \ No newline at end of file diff --git a/app/views/dmsf_files/show.html.erb b/app/views/dmsf_files/show.html.erb index c7512dce..e3dc0297 100644 --- a/app/views/dmsf_files/show.html.erb +++ b/app/views/dmsf_files/show.html.erb @@ -26,11 +26,14 @@
<% if User.current.allowed_to?(:file_manipulation, @project) %> - <% unless @file.locked? %> - <%= link_to(l(:button_lock), - lock_dmsf_files_path(:id => @file), - :title => l(:title_lock_file), - :class => 'icon icon-dmsf-lock') %> + <% unless @file.locked_for_user? %> + <% unless @file.locked? %> + <%= link_to(l(:button_lock), lock_dmsf_files_path(:id => @file), + :title => l(:title_lock_file), :class => 'icon icon-dmsf-lock') %> + <% else %> + <%= link_to_if(@file.unlockable?, l(:button_unlock), unlock_dmsf_files_path(:id => @file), + :title => l(:title_unlock_file), :class => 'icon icon-dmsf-unlock') %> + <% end %> <% if @file.notification %> <%= link_to(l(:label_notifications_off), notify_deactivate_dmsf_files_path(:id => @file), @@ -50,11 +53,9 @@ :title => l(:title_copy), :class => 'icon icon-copy') %> <%= delete_link @file if @file_delete_allowed %> <% else %> - <% if (!@file.locked_for_user? || User.current.allowed_to?(:force_file_unlock, @project)) && @file.unlockable? %> - <%= link_to(l(:button_unlock), - unlock_dmsf_files_path(:id => @file), - :title => l(:title_unlock_file), - :class => 'icon icon-dmsf-unlock')%> + <% if User.current.allowed_to?(:force_file_unlock, @project) %> + <%= link_to_if(@file.unlockable?, l(:button_unlock), unlock_dmsf_files_path(:id => @file), + :title => l(:title_unlock_file), :class => 'icon icon-dmsf-unlock')%> <% end %> <% end %> <% end %> @@ -62,9 +63,11 @@ <%= render(:partial => '/dmsf/path', :locals => {:folder => @file.folder, :filename => @file.title}) %> -<%= error_messages_for('file') %> -<%= error_messages_for('revision') %> -<%= render(:partial => 'file_new_revision') if User.current.allowed_to?(:file_manipulation, @file.project) %> +<% if User.current.allowed_to?(:file_manipulation, @file.project) && !@file.locked_for_user? %> + <%= error_messages_for('file') %> + <%= error_messages_for('revision') %> + <%= render(:partial => 'file_new_revision') %> +<% end %>

<%= l(:heading_revisions) %>

<% @file.revisions.visible[@revision_pages.offset,@revision_pages.per_page].each do |revision| %> diff --git a/assets/images/filedetails.png b/assets/images/filedetails.png index 0ef642fa157023b1b4059c5936ae9b772eb807fe..f7df57a36972cde11b01897e63c7a63db19b52f8 100644 GIT binary patch delta 729 zcmV;~0w(>V2IB=YiBL{Q4GJ0x0000DNk~Le0000G0000G2nGNE03Y-JVE_OC24YJ` zL;(K){{a7>y{D6rJ{W%obV*G`2jBt{3?&G)a@^+t00MAHL_t(I%e9hCYgA_#hOcwZ z%w){O`H1briX~X6F%6^(KYm6xE~S4!y6nOq(3XM=cSQujP&^KSqp01=@)eG$-ovZ?~nZbGzc4SxF$zw!5>1|Bb%M%?^FP$aKl#;5>&kwEkZt34)(L4B z#G%1JCHOjP}p%JC_{;5UDtpmv0$cA849iMnM77!!{rv3Lf9k=I?0|IP9(K^BH%y67 zwiqcQ>|lR^1Q+|(Z-qG6{?xata%cyLKj*tI=nU+%Yn}da`s7(Uzt7QoxJ6QK zrg3U+j%DdjrQn7t^60H0wZHsj9^m`SvpRWVJ)A$Yl^h#C#pC;HMQT=V*Xy-w$>_vC zv;Rvz++a@&i~s7g)&IQN>guVFTCcQm^aj>kcSRl8n7#6c@;>w$D!p+6yS!FY00000 LNkvXXu0mjfNo`_3 literal 803 zcmV+;1Kj+HP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D02y>eSaefwW^{L9 za%BKeVQFr3E>1;MAa*k@H7+*LgRNQs00NUqL_t(IPi@j$NYhaq2k^i4RIj~+^k6R$ z1eOiDJXA^+CS`PiBuz9dZ&2EWG;5lbQ!{WjHkIX-P^ndBVQ#|`G1Qz>=d8KumabiF zo4ftDzjNES6~TqQi($Dn5l;5D@3 z@2Bg~b!29bHzmf6^Y`tfTm#PuoCITRB&e1kEl?}V%5%kiBF!T$d`IrY8 zFNR0iB}D{CL944ktIFV>V#EEjfS~&!v^bFl+h{SYKN`UN{tkM&2lBe)MatTh3zv)s z&rA!!^>E7Bun*l>y(t||4Tn^~hjpYBrrvUx`*_~T=4>Hl5v9T=z6rPTC45#f!8NcA zTp>3RG{XU75)haV$K)+&+mmQT{bl|L?@|P1d6x#q;1ihN#KAdA-fXG@ddWrDRnJxt zIaHkmv-m1>4X5}@ZtO1Ei?}e#@-`J-od_0bE{vi?=(;nYA7sO8tOYZl7eXdU8g$ir zG0TaJRaG4f5B-6K*XLpRa1DQ2&p{zL0cF=cX!?s`pU#4d#Mqq-EkRoLR_KbtH{Nlf z^(@Qu>LhEZ`dCVTa|SH(c8m#5V_0w)GGV-1&W+)!%Oa0!i?*<|h19n7FSj-*&xtzF z_VkRO84_T=BL~ug{l3n;-L+rY(UBA7hyIthSxbu99-WyG!8G1Eviz+o!64xrE$PhP hv!kalDr~d;{{WEr1Xq=u)D!>!002ovPDHLkV1i5gXU+fs diff --git a/assets/images/operations.png b/assets/images/operations.png deleted file mode 100644 index 8bf76fbbe5af5530a73743c61cc3d2ac5ac8ee17..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 459 zcmV;+0W|)JP)APtL+ zxmrl$Wc`zf3n*g`v;0rbIxj*dHiZTr(m6?gBTa=mqF6QotNZ~m#F3_akIwCAOSP@W zH9<<)RMz+vlE{$UIr?75NZmBOzJ*kxEUe+X}JB?9)jT!y^oDKLQExwUe@XGFqhsIZaLP7 zbI?9%3)Yi}p{w3$>$ba`|E~FKKCee=m)!r({S66-T?q1-?uGyW002ovPDHLkV1gB6 B%HRM1 diff --git a/assets/images/ticket_go.png b/assets/images/ticket_go.png deleted file mode 100644 index 2aaec3813b6df67f837ebd1a7ba3a605f9d564b0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 608 zcmV-m0-ybfP)}|w#Zm@lU_^)(A(3I!A`K`h1kK?MJb1tVf9AaxsVe_-(g6D9rAqDxsumT8dWVWd zB|&`x&fvwM5`#0;c0P@E1H4e)a_ zt<{3%UCJ>bm?9*G5En#byA%+-!OXlvl270*sNl^e`%fGxC1z-i>hlotsLx}>QlDw!er0*7gGd#(JV|z7n40#xL{UV~_c>Ua z#RkEOaO}`2n(ErfnQc1y>zol=G4yav(>wh>*w)3`KsW0{kEy+K4+)m*&0Tm6_}~dB ziyQNtslUXQjnm_EZ|&LMR7Dy9F+^o4s#@<5PrV>F`Wzz`uYs?tGq@l~a5(SSOmdt% zewi(oPmg^bHpMwGWMB28Vo(=j;}4kHD#Qy%YZ{Q?@f!HHF~{NRIws~v7?~aZXoc43 zZ_CvAq|qB|vB6@*ax&ALy4cvk`?*mDr(aK6;lg5{CegocV@q#p<$S}{%$w=KaT{n~ u>{DB8z`p?+yQ38!$}(~A<;#64JNchU$$I%CY#&Gf0000 -# Copyright (C) 2011-15 Karel Pičman +# Copyright (C) 2011-16 Karel Pičman # # # This program is free software; you can redistribute it and/or @@ -219,7 +219,6 @@ cs: warning_folder_not_locked: Složku nelze zamknout notice_folder_unlocked: Složka byla odemčena error_only_user_that_locked_folder_can_unlock_it: Nemáte oprávnění k odemknutí této složky - title_folder_parent_locked: "Nadřazená složka %{name} je zamčená" title_unlock_folder: Odemknout title_lock_folder: Zamknout diff --git a/config/locales/de.yml b/config/locales/de.yml index 3fff02ee..151e3256 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -4,7 +4,7 @@ # # Copyright (C) 2011 Terrence Miller # Copyright (C) 2013 Christian Wetting -# Copyright (C) 2011-15 Karel Pičman +# Copyright (C) 2011-16 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 @@ -219,7 +219,6 @@ de: warning_folder_not_locked: Der Ordner konnte nicht gesperrt werden notice_folder_unlocked: Der Ordner wurde erfolgreich entsperrt error_only_user_that_locked_folder_can_unlock_it: Sie haben keine Berechtigung zur Entsperrung des Ordners - title_folder_parent_locked: "Übergeordnetes Verzeichnis %{name} ist gesperrt" title_unlock_folder: Ordner zur Bearbeitung durch andere Benutzer entsperren title_lock_folder: Ordner zum Schutz vor Bearbeitung durch andere Benutzer sperren diff --git a/config/locales/en.yml b/config/locales/en.yml index 836637bf..764a2440 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -4,7 +4,7 @@ # # Copyright (C) 2011 Vít Jonáš # Copyright (C) 2012 Daniel Munn -# Copyright (C) 2011-15 Karel Pičman +# Copyright (C) 2011-16 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 @@ -219,7 +219,6 @@ en: warning_folder_not_locked: Unfortunately, the folder could not be locked notice_folder_unlocked: The folder was successfully unlocked error_only_user_that_locked_folder_can_unlock_it: You are not authorised to unlock this folder - title_folder_parent_locked: "Parent folder %{name} is locked" title_unlock_folder: Unlock to allow changes for other members title_lock_folder: Lock to prevent changes for other members diff --git a/config/locales/es.yml b/config/locales/es.yml index 8be42f4f..9101df95 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -4,7 +4,7 @@ # # Copyright (C) 2011 Vít Jonáš # Copyright (C) 2015 Agustin Ivorra -# Copyright (C) 2011-15 Karel Pičman +# Copyright (C) 2011-16 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 @@ -219,7 +219,6 @@ es: warning_folder_not_locked: Desafortunadamente, el directorio no se pudo bloquear notice_folder_unlocked: El directorio se desbloqueó exitosamente error_only_user_that_locked_folder_can_unlock_it: No está autorizado para desbloquearel directorio - title_folder_parent_locked: "Directorio padre %{name} está bloqueado" title_unlock_folder: Desbloquear para que sea editado por otros miembros title_lock_folder: Bloquear para evitar que sea editado por otros miembros diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 87f4a50d..e6a72410 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -2,10 +2,10 @@ # # Redmine plugin for Document Management System "Features" # -# Copyright (C) 2011 Vít Jonáš -# Copyright (C) 2012 Daniel Munn -# Copyright (C) 2014 Atmis -# Copyright (C) 2011-15 Karel Pičman +# Copyright (C) 2011 Vít Jonáš +# Copyright (C) 2012 Daniel Munn +# Copyright (C) 2014 Atmis +# Copyright (C) 2011-16 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 @@ -219,7 +219,6 @@ fr: warning_folder_not_locked: Echec du verrouillage du dossier notice_folder_unlocked: Le dossier a été déverrouillé error_only_user_that_locked_folder_can_unlock_it: "Vous n'êtes autorisé à déverrouiller ce dossier" - title_folder_parent_locked: "Le dossier parent %{name} verrouillé" title_unlock_folder: Déverrouiller afin de permettre la modification par les membres du projet title_lock_folder: "Verrouiller afin d'empêcher les modifications du dossier" @@ -333,4 +332,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 35ff8e62..3e8fe905 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -3,7 +3,7 @@ # Redmine plugin for Document Management System "Features" # # Copyright (C) 2011 Vít Jonáš -# Copyright (C) 2011-15 Karel Pičman +# Copyright (C) 2011-16 Karel Pičman # # # This program is free software; you can redistribute it and/or @@ -157,8 +157,7 @@ ja: permission_file_manipulation: ファイルの操作 permission_force_file_unlock: ファイルの強制ロック解除 permission_manage_workflows: ワークフロー管理 - permission_file_delete: ファイルの削除 - permission_file_approval: ファイルの承認 + permission_file_delete: ファイルの削除 label_file: ファイル field_folder: フォルダ error_create_cycle_in_folder_dependency: フォルダの依存関係が循環しています @@ -219,8 +218,7 @@ ja: notice_folder_locked: "フォルダをロックしました" warning_folder_not_locked: "フォルダをロックすることができませんでした" notice_folder_unlocked: "フォルダのロックを解除しました" - error_only_user_that_locked_folder_can_unlock_it: "このフォルダのロックを解除する権限がありません" - title_folder_parent_locked: "親フォルダ %{name} はロックされています" + error_only_user_that_locked_folder_can_unlock_it: "このフォルダのロックを解除する権限がありません" title_unlock_folder: "ロックを解除し他メンバが更新できるようにします" title_lock_folder: "ロックし他メンバとの競合を回避します" @@ -294,13 +292,14 @@ ja: label_notifications_off: 通知オフ field_target_file: リンク元ファイル title_download_entries: ダウンロード記録 + label_external: 外部 + label_link_name: リンク名 label_link_external_url: URL label_target_folder: リンク先フォルダ label_source_folder: リンク元フォルダ label_target_project: リンク先プロジェクト - label_source_project: リンク元プロジェクト - label_external: 外部 + label_source_project: リンク元プロジェクト text_email_doc_updated_subject: "プロジェクト'%{project}'のファイルが更新されました" text_email_doc_updated: が次のファイルを更新しました。 @@ -333,5 +332,4 @@ ja: open_approvals: 未承認 label_maximum_ajax_upload_filesize: アップロードファイルサイズ上限 - note_maximum_ajax_upload_filesize: アップロード可能なファイルサイズの上限。AjaxおよびRedmineの仕様に制限される(2ギガバイト程度までは確認済み)単位はMB。 - + note_maximum_ajax_upload_filesize: アップロード可能なファイルサイズの上限。AjaxおよびRedmineの仕様に制限される(2ギガバイト程度までは確認済み)単位はMB。 \ No newline at end of file diff --git a/config/locales/pl.yml b/config/locales/pl.yml index 0e94fc16..b19bed33 100644 --- a/config/locales/pl.yml +++ b/config/locales/pl.yml @@ -4,9 +4,8 @@ # # Copyright (C) 2011 Vít Jonáš # Copyright (C) 2012 Daniel Munn -# Copyright (C) 2011-15 Karel Pičman +# Copyright (C) 2011-16 Karel Pičman # Polish translation created by Sebastian Białas www.bs-it.pl -# # 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 @@ -220,7 +219,6 @@ pl: warning_folder_not_locked: Niestety folder nie może zostać zablokowany notice_folder_unlocked: Folder został odblokowany error_only_user_that_locked_folder_can_unlock_it: Nie posiadasz uprawnień do odblokowania tego folderu - title_folder_parent_locked: "Folder nadrzędny %{name} jest zablokowany" title_unlock_folder: Odblokuj w celu umożliwienia wprowadzania zmian innym użytkownikom title_lock_folder: Zablokuj aby zabezpieczyć przed wprowadzaniem zmian przez innych użytkowników @@ -238,6 +236,7 @@ pl: field_label_dmsf_workflow: Proces akceptacji field_label_dmsf_workflow_name: Nazwa procesu akceptacji label_dmsf_workflow_plural: Procesy akceptacji + label_dmsf_workflow_plural_num: Procesy akceptacji (%{count}) label_dmsf_workflow_step: Krok label_dmsf_workflow_step_plural: Kroki label_dmsf_workflow_approval: Akceptacja diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml index 11acc558..c5739415 100644 --- a/config/locales/pt-BR.yml +++ b/config/locales/pt-BR.yml @@ -4,7 +4,7 @@ # # Copyright (C) 2011 Vít Jonáš # Copyright (C) 2012 Daniel Munn -# Copyright (C) 2011-15 Karel Pičman +# Copyright (C) 2011-16 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 @@ -219,7 +219,6 @@ pt-BR: warning_folder_not_locked: A pasta não pode ser bloqueada notice_folder_unlocked: A pasta foi desbloqueada com sucesso error_only_user_that_locked_folder_can_unlock_it: Você não está autorizado a desbloquear esta pasta - title_folder_parent_locked: "A pasta %{name} está bloqueada" title_unlock_folder: Clique aqui para desbloquear e permitir alterações por outros usuários title_lock_folder: Clique aqui para impedir alterações por outros usuários diff --git a/config/locales/ru.yml b/config/locales/ru.yml index 6f661767..97c238e7 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -2,8 +2,8 @@ # # Redmine plugin for Document Management System "Features" # -# Copyright (C) 2011 Vít Jonáš -# Copyright (C) 2011-15 Karel Pičman +# Copyright (C) 2011 Vít Jonáš +# Copyright (C) 2011-16 Karel Pičman # # # This program is free software; you can redistribute it and/or @@ -219,7 +219,6 @@ ru: warning_folder_not_locked: "К сожалению, папка не может быть заблокирована" notice_folder_unlocked: "Папка была успешно разблокирована" error_only_user_that_locked_folder_can_unlock_it: "Только пользователь, который заблокировал папку, может её разблокировать" - title_folder_parent_locked: "Родительская папка %{name} заблокирована" title_unlock_folder: "Разблокируйте папку, чтобы разрешить изменение её другими участниками" title_lock_folder: "Заблокируйте папку, чтобы запретить ёё изменение другими участниками" diff --git a/config/locales/sl.yml b/config/locales/sl.yml index 9b02cf00..7b39b918 100644 --- a/config/locales/sl.yml +++ b/config/locales/sl.yml @@ -2,8 +2,8 @@ # # Redmine plugin for Document Management System "Features" # -# Copyright (C) 2011 -# Copyright (C) 2011-15 Karel Pičman +# Copyright (C) 2011 Zdravko Balorda +# Copyright (C) 2011-16 Karel Pičman # # # This program is free software; you can redistribute it and/or @@ -219,7 +219,6 @@ sl: warning_folder_not_locked: Ne, ta mapa ne more biti zaklenjena notice_folder_unlocked: Mapa je uspešno odklenjena error_only_user_that_locked_folder_can_unlock_it: Nimate privilegijev, da bi odklenili to mapo - title_folder_parent_locked: "Nadrejena mapa %{name} je zaklenjena" title_unlock_folder: Odkleni, da bi drugim članom omogočil spreminjanje title_lock_folder: Zakleni, da bi drugim članom preprečil spreminjanje diff --git a/config/locales/zh-TW.yml b/config/locales/zh-TW.yml index 04333d55..94f28e1c 100644 --- a/config/locales/zh-TW.yml +++ b/config/locales/zh-TW.yml @@ -2,9 +2,9 @@ # # Redmine plugin for Document Management System "Features" # -# Copyright (C) 2011 Vít Jonáš -# Copyright (C) 2011-15 Karel Pičman -# Copyright (C) 2013 Aecho Liu +# Copyright (C) 2011 Vít Jonáš +# Copyright (C) 2011-16 Karel Pičman +# Copyright (C) 2013 Aecho Liu # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -219,7 +219,6 @@ zh-TW: warning_folder_not_locked: 不好意思,這個資料夾無法被鎖定。 notice_folder_unlocked: 這個資料夾己經成功地解除鎖定了。 error_only_user_that_locked_folder_can_unlock_it: 您未被授權,解除這個資料夾的鎖定狀態。 - title_folder_parent_locked: "上層資料夾 %{name} 被鎖定了。" title_unlock_folder: 解除鎖定。允許其它使用者修改。 title_lock_folder: 鎖定資料夾。禁止其它使用者修改。 diff --git a/config/locales/zh.yml b/config/locales/zh.yml index 508ed8d8..2a299542 100644 --- a/config/locales/zh.yml +++ b/config/locales/zh.yml @@ -2,8 +2,8 @@ # # Redmine plugin for Document Management System "Features" # -# Copyright (C) 2011 Vít Jonáš -# Copyright (C) 2011-15 Karel Pičman +# Copyright (C) 2011 Vít Jonáš +# Copyright (C) 2011-16 Karel Pičman # # # This program is free software; you can redistribute it and/or @@ -219,7 +219,6 @@ zh: warning_folder_not_locked: Unfortunately, the folder could not be locked notice_folder_unlocked: The folder was successfully unlocked error_only_user_that_locked_folder_can_unlock_it: You are not authorised to unlock this folder - title_folder_parent_locked: "Parent folder %{name} is locked" title_unlock_folder: Unlock to allow changes for other members title_lock_folder: Lock to prevent changes for other members diff --git a/init.rb b/init.rb index 5cdd5cf7..9e4d9f41 100644 --- a/init.rb +++ b/init.rb @@ -93,7 +93,9 @@ 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 From d478c51754f319407d8200ac1346104fea58f949 Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Fri, 19 Feb 2016 14:44:08 +0100 Subject: [PATCH 12/94] Document ID in the document's details --- app/views/dmsf_files/show.html.erb | 20 +++++++++++--------- assets/stylesheets/dmsf.css | 22 ++++++++++++++++++---- 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/app/views/dmsf_files/show.html.erb b/app/views/dmsf_files/show.html.erb index e3dc0297..b2b07d65 100644 --- a/app/views/dmsf_files/show.html.erb +++ b/app/views/dmsf_files/show.html.erb @@ -61,7 +61,7 @@ <% end %>
-<%= render(:partial => '/dmsf/path', :locals => {:folder => @file.folder, :filename => @file.title}) %> +<%= render(:partial => '/dmsf/path', :locals => {:folder => @file.folder, :filename => @file.title}) %> <% if User.current.allowed_to?(:file_manipulation, @file.project) && !@file.locked_for_user? %> <%= error_messages_for('file') %> @@ -69,6 +69,11 @@ <%= render(:partial => 'file_new_revision') %> <% end %> +
+ <%= label_tag('', "#{l(:label_document)}:") %> + <%= "##{@file.id}" %> +
+

<%= l(:heading_revisions) %>

<% @file.revisions.visible[@revision_pages.offset,@revision_pages.per_page].each do |revision| %>
@@ -92,7 +97,7 @@ <%= link_to(revision.user.name, user_path(revision.user)) if revision.user %>
-
+
<%= label_tag('', "#{l(:label_title)}:") %> @@ -117,12 +122,9 @@ <% wf = DmsfWorkflow.find_by_id(revision.dmsf_workflow_id) %> <% if wf %> <%= "#{wf.name} - " %> - <%= link_to( - revision.workflow_str(false), - log_dmsf_workflow_path( - :project_id => @project.id, - :id => wf.id, - :dmsf_file_revision_id => revision.id), + <%= link_to(revision.workflow_str(false), + log_dmsf_workflow_path(:project_id => @project.id, + :id => wf.id, :dmsf_file_revision_id => revision.id), :title => DmsfWorkflow.assignments_to_users_str(wf.next_assignments(revision.id)), :remote => true) %> <% else %> @@ -131,7 +133,7 @@
<%= label_tag('', "#{l(:label_mime)}:") %> - <%= h(revision.mime_type) %>  + <%= h(revision.mime_type) %>
<%= label_tag('', "#{l(:label_size)}:") %> <%= number_to_human_size(revision.size) %> diff --git a/assets/stylesheets/dmsf.css b/assets/stylesheets/dmsf.css index 1e52d358..643dfdb5 100644 --- a/assets/stylesheets/dmsf.css +++ b/assets/stylesheets/dmsf.css @@ -2,7 +2,7 @@ * Redmine plugin for Document Management System "Features" * * Copyright (C) 2011 Vít Jonáš -* Copyright (C) 2011-15 Karel Pičman +* Copyright (C) 2011-16 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 @@ -209,11 +209,11 @@ table.access-table tbody td, table.access-table tbody tr:hover td { /* Approval workflow */ #admin-menu a.approvalworkflows { background-image: url(../../../images/ticket_go.png); } -#users_for_delegate {height: 200px; overflow:auto;} -#users_for_delegate label {display: block;} +#users_for_delegate { height: 200px; overflow:auto; } +#users_for_delegate label { display: block; } tr.workflow.locked a { color: #aaa; } -.revision_box{ +.revision_box { padding: 0px 0px 0px 0px; margin-bottom: 10px; background-color:#f6f6f6; @@ -221,6 +221,20 @@ tr.workflow.locked a { color: #aaa; } line-height:1.5em; } +.dmsf_revision_inner_box { + border: 1px solid #e4e4e4; +} + +.dmsf_id_box { + float: right; + white-space: nowrap; + line-height: 1.5em; + color: #505050; + margin-top: 5px; + padding-left: 10px; + //font-size: 0.9em; +} + div.revision_box .ui-widget-header { font-weight: normal; } From 3ad1c18212bec0592404d919953bd57b2a2bf346 Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Fri, 19 Feb 2016 14:46:06 +0100 Subject: [PATCH 13/94] Document ID in the document's details --- assets/stylesheets/dmsf.css | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/assets/stylesheets/dmsf.css b/assets/stylesheets/dmsf.css index 643dfdb5..4c769015 100644 --- a/assets/stylesheets/dmsf.css +++ b/assets/stylesheets/dmsf.css @@ -231,8 +231,7 @@ tr.workflow.locked a { color: #aaa; } line-height: 1.5em; color: #505050; margin-top: 5px; - padding-left: 10px; - //font-size: 0.9em; + padding-left: 10px; } div.revision_box .ui-widget-header { From 14738e43605d7b3e55dc12b3aa72ad868c50c16b Mon Sep 17 00:00:00 2001 From: Christian Franke Date: Sun, 21 Feb 2016 01:10:40 +0100 Subject: [PATCH 14/94] added width attribute in order to resize by width with respect to aspect ratio size attribute now sets the height with respect to aspect ratio --- init.rb | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/init.rb b/init.rb index 88ce4e8c..29b434c5 100644 --- a/init.rb +++ b/init.rb @@ -171,20 +171,29 @@ Redmine::Plugin.register :redmine_dmsf do "{{dmsf_image(file_id, size=300)}} -- with custom title and size\n" + "{{dmsf_image(file_id, size=640x480)}}" macro :dmsf_image do |obj, args| - args, options = extract_macro_options(args, :size, :title) + args, options = extract_macro_options(args, :size, :width, :title) file_id = args.first raise 'DMSF document ID required' unless file_id.present? - size = options[:size] + size = options[:size] + width = options[:width] if file = DmsfFile.find_by_id(file_id) unless User.current && User.current.allowed_to?(:view_dmsf_files, file.project) raise l(:notice_not_authorized) end raise 'Not supported image format' unless file.image? url = url_for(:controller => :dmsf_files, :action => 'view', :id => file) - if size and size.include? "%" - image_tag(url, :alt => file.title, :width => size, :height => size) + if size + if size.include? "%" + image_tag(url, :alt => file.title, :width => size, :height => size) + elsif size.include? "x" + image_tag(url, :alt => file.title, :size => size) + else + image_tag(url, :alt => file.title, :width => 'auto', :height => size) + end + elsif width + image_tag(url, :alt => file.title, :width => width, :height => 'auto') else - image_tag(url, :alt => file.title, :size => size) + image_tag(url, :alt => file.title, :width => 'auto', :height => size) end else raise "Document ID #{file_id} not found" From 1d8223aa953cd26815523cea24d48fa9433b1735 Mon Sep 17 00:00:00 2001 From: Christian Franke Date: Sun, 21 Feb 2016 01:26:42 +0100 Subject: [PATCH 15/94] added additional height attribute and reverted to default behaviour of size attribute --- README.md | 6 ++++++ init.rb | 17 +++++++---------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 6af69759..7c75a579 100644 --- a/README.md +++ b/README.md @@ -122,6 +122,12 @@ Search will now automatically search DMSF content when a Redmine search is perfo ####An inline picture with custom size `{{dmsf_image(8, size=50%)}}` +####An inline picture with custom height +`{{dmsf_image(8, height=300)}}` + +####An inline picture with custom width +`{{dmsf_image(8, width=300)}}` + ####An inline picture with custom size `{{dmsf_image(8, size=640x480)}}` diff --git a/init.rb b/init.rb index 29b434c5..97eb72cd 100644 --- a/init.rb +++ b/init.rb @@ -171,29 +171,26 @@ Redmine::Plugin.register :redmine_dmsf do "{{dmsf_image(file_id, size=300)}} -- with custom title and size\n" + "{{dmsf_image(file_id, size=640x480)}}" macro :dmsf_image do |obj, args| - args, options = extract_macro_options(args, :size, :width, :title) + args, options = extract_macro_options(args, :size, :width, :height, :title) file_id = args.first raise 'DMSF document ID required' unless file_id.present? size = options[:size] width = options[:width] + height = options[:height] if file = DmsfFile.find_by_id(file_id) unless User.current && User.current.allowed_to?(:view_dmsf_files, file.project) raise l(:notice_not_authorized) end raise 'Not supported image format' unless file.image? url = url_for(:controller => :dmsf_files, :action => 'view', :id => file) - if size - if size.include? "%" - image_tag(url, :alt => file.title, :width => size, :height => size) - elsif size.include? "x" - image_tag(url, :alt => file.title, :size => size) - else - image_tag(url, :alt => file.title, :width => 'auto', :height => size) - end + if size and size.include? "%" + image_tag(url, :alt => file.title, :width => size, :height => size) + elsif height + image_tag(url, :alt => file.title, :width => 'auto', :height => height) elsif width image_tag(url, :alt => file.title, :width => width, :height => 'auto') else - image_tag(url, :alt => file.title, :width => 'auto', :height => size) + image_tag(url, :alt => file.title, :size => size) end else raise "Document ID #{file_id} not found" From 26a9e8008f22f192ee8ee26d6b7bb2b8b233d9fb Mon Sep 17 00:00:00 2001 From: Redmine Administrator Date: Sun, 21 Feb 2016 02:23:08 +0100 Subject: [PATCH 16/94] added image thumbnail macro --- README.md | 7 +++++++ init.rb | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/README.md b/README.md index 7c75a579..818c8ad9 100644 --- a/README.md +++ b/README.md @@ -131,6 +131,13 @@ Search will now automatically search DMSF content when a Redmine search is perfo ####An inline picture with custom size `{{dmsf_image(8, size=640x480)}}` +####A thumbnail with height of 200px +`{{dmsftn(8)}}` + +####A thumbnail with custom size +`{{dmsftn(8, size=300)}}` + + The DMSF file/revision id can be found in link for file/revision download from within Redmine. ###Linking DMSF folders from Wiki entries: diff --git a/init.rb b/init.rb index 97eb72cd..fa03e571 100644 --- a/init.rb +++ b/init.rb @@ -169,6 +169,8 @@ Redmine::Plugin.register :redmine_dmsf do desc "Wiki DMSF image:\n\n" + "{{dmsf_image(file_id)}}\n" + "{{dmsf_image(file_id, size=300)}} -- with custom title and size\n" + + "{{dmsf_image(file_id, height=300)}} -- with custom title and height (auto width)\n" + + "{{dmsf_image(file_id, width=300)}} -- with custom title and width (auto height)\n" + "{{dmsf_image(file_id, size=640x480)}}" macro :dmsf_image do |obj, args| args, options = extract_macro_options(args, :size, :width, :height, :title) @@ -196,6 +198,48 @@ Redmine::Plugin.register :redmine_dmsf do raise "Document ID #{file_id} not found" end end + + + desc "Wiki DMSF thumbnail:\n\n" + + "{{dmsftn(file_id)}}\n" + + "{{dmsftn(file_id, size=300)}} -- with custom title and size\n" + + "{{dmsftn(file_id, height=300)}} -- with custom title and height (auto width)\n" + + "{{dmsftn(file_id, width=300)}} -- with custom title and width (auto height)\n" + + "{{dmsftn(file_id, size=640x480)}}" + macro :dmsftn do |obj, args| + args, options = extract_macro_options(args, :size, :width, :height, :title) + file_id = args.first + raise 'DMSF document ID required' unless file_id.present? + size = options[:size] + width = options[:width] + height = options[:height] + if file = DmsfFile.find_by_id(file_id) + unless User.current && User.current.allowed_to?(:view_dmsf_files, file.project) + raise l(:notice_not_authorized) + end + raise 'Not supported image format' unless file.image? + url = url_for(:controller => :dmsf_files, :action => 'view', :id => file) + file_view_url = url_for(:controller => :dmsf_files, :action => 'view', :id => file, :download => args[2]) + if size and size.include? "%" + img = image_tag(url, :alt => file.title, :width => size, :height => size) + elsif size and size.include? "x" + img = image_tag(url, :alt => file.title, :size => size) + elsif height + img = image_tag(url, :alt => file.title, :width => 'auto', :height => height) + elsif width + img = image_tag(url, :alt => file.title, :width => width, :height => 'auto') + else + img = image_tag(url, :alt => file.title, :width => 'auto', :height => 200) + end + link_to(img, + file_view_url, :target => '_blank', + :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_view_url}") + else + raise "Document ID #{file_id} not found" + end + end + end # Rubyzip configuration From 2dad76503f70b91c0d4dff09bdc5f4cd19a88ab3 Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Mon, 22 Feb 2016 13:39:16 +0100 Subject: [PATCH 17/94] Email notification subject in Redmine format --- app/models/dmsf_mailer.rb | 40 ++++++++++++++++----------------------- config/locales/cs.yml | 16 ++++++++-------- config/locales/de.yml | 16 ++++++++-------- config/locales/en.yml | 16 ++++++++-------- config/locales/es.yml | 16 ++++++++-------- config/locales/fr.yml | 16 ++++++++-------- config/locales/ja.yml | 16 ++++++++-------- config/locales/pl.yml | 16 ++++++++-------- config/locales/pt-BR.yml | 16 ++++++++-------- config/locales/ru.yml | 16 ++++++++-------- config/locales/sl.yml | 16 ++++++++-------- config/locales/zh-TW.yml | 16 ++++++++-------- config/locales/zh.yml | 16 ++++++++-------- 13 files changed, 112 insertions(+), 120 deletions(-) diff --git a/app/models/dmsf_mailer.rb b/app/models/dmsf_mailer.rb index 5b338627..42d7516c 100644 --- a/app/models/dmsf_mailer.rb +++ b/app/models/dmsf_mailer.rb @@ -3,7 +3,7 @@ # Redmine plugin for Document Management System "Features" # # Copyright (C) 2011 Vít Jonáš -# Copyright (C) 2011-15 Karel Pičman +# Copyright (C) 2011-16 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 @@ -27,48 +27,39 @@ class DmsfMailer < Mailer def files_updated(user, project, files) if user && project && files.count > 0 files = files.select { |file| file.notify? } - - redmine_headers 'Project' => project.identifier if project - + redmine_headers 'Project' => project.identifier if project @files = files - @project = project - + @project = project set_language_if_valid user.language - mail :to => user.mail, - :subject => l(:text_email_doc_updated_subject, :project => project.name) + mail :to => user.mail, + :subject => "[#{@project.name} - #{l(:menu_dmsf)}] #{l(:text_email_doc_updated_subject)}" end end def files_deleted(user, project, files) if user && files.count > 0 files = files.select { |file| file.notify? } - - redmine_headers 'Project' => project.identifier if project - + redmine_headers 'Project' => project.identifier if project @files = files - @project = project - + @project = project set_language_if_valid user.language - mail :to => user.mail, - :subject => l(:text_email_doc_deleted_subject, :project => project.name) + mail :to => user.mail, + :subject => "[#{@project.name} - #{l(:menu_dmsf)}] #{l(:text_email_doc_deleted_subject)}" end end def send_documents(project, user, email_params) - zipped_content_data = open(email_params[:zipped_content], 'rb') { |io| io.read } - + zipped_content_data = open(email_params[:zipped_content], 'rb') { |io| io.read } redmine_headers 'Project' => project.identifier if project - @body = email_params[:body] @links_only = email_params[:links_only] @folders = email_params[:folders] - @files = email_params[:files] - + @files = email_params[:files] unless @links_only == '1' attachments['Documents.zip'] = { :content_type => 'application/zip', :content => zipped_content_data } - end - - mail :to => email_params[:to], :cc => email_params[:cc], :subject => email_params[:subject], :from => user.mail + end + mail :to => email_params[:to], :cc => email_params[:cc], + :subject => email_params[:subject], :from => user.mail end def workflow_notification(user, workflow, revision, subject_id, text1_id, text2_id, notice = nil) @@ -84,7 +75,8 @@ class DmsfMailer < Mailer @text1 = l(text1_id, :name => workflow.name, :filename => revision.file.name, :notice => notice) @text2 = l(text2_id) @notice = notice - mail :to => user.mail, :subject => l(subject_id, :name => workflow.name) + mail :to => user.mail, + :subject => "[#{@project.name} - #{l(:field_label_dmsf_workflow)}] #{@workflow.name} #{l(subject_id)}" end end diff --git a/config/locales/cs.yml b/config/locales/cs.yml index 335b1165..b128632a 100644 --- a/config/locales/cs.yml +++ b/config/locales/cs.yml @@ -267,12 +267,12 @@ cs: info_revision: "r%{rev}" link_workflow: Schvalovací proces notice_workflow_started: Schvalovací proces byl úspěšně zahájen - text_email_subject_approved: "Schvalovací proces %{name} schválen" - text_email_subject_rejected: "Schvalovací proces %{name} zamítnut" - text_email_subject_delegated: "Schvalovací proces %{name} delegován" - text_email_subject_requires_approval: "Schvalovací proces %{name} očekává Vaše schválení" - text_email_subject_updated: "Schvalovací proces %{name} aktualizován" - text_email_subject_started: "Schvalovací proces %{name} spuštěn" + text_email_subject_approved: schválen + text_email_subject_rejected: zamítnut + text_email_subject_delegated: delegován + text_email_subject_requires_approval: očekává Vaše schválení + text_email_subject_updated: aktualizován + text_email_subject_started: spuštěn text_email_finished_approved: "Schvalovací proces '%{name}' přiřazený k dokumentu '%{filename}' byl právě ukončen a dokument je schválen." text_email_finished_rejected: "Schvalovací proces '%{name}' přiřazený k dokumentu '%{filename}' byl dokončen a dokument byl zamítnut, protože '%{notice}'." text_email_finished_delegated: "Schvalovací proces '%{name}' přiřazený k dokumentu '%{filename}' byl delegován, protože '%{notice}' a od Vás se očekává schválení v aktuálním schvalovacím kroku." @@ -301,10 +301,10 @@ cs: label_target_project: Cílový projekt label_source_project: Zdrojový projekt - text_email_doc_updated_subject: "Dokumenty projektu %{project} aktualizovány" + text_email_doc_updated_subject: Dokumenty aktualizovány text_email_doc_updated: právě aktualizoval dokumenty projektu text_email_doc_follows: takto - text_email_doc_deleted_subject: "Dokumenty projektu %{project} smazány" + text_email_doc_deleted_subject: Dokumenty smazány text_email_doc_deleted: právě smazal dokumety projektu label_links_only: pouze odkazy diff --git a/config/locales/de.yml b/config/locales/de.yml index 151e3256..ba799217 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -267,12 +267,12 @@ de: info_revision: "r%{rev}" link_workflow: Workflow notice_workflow_started: Genehmigungs-Workflow gestartet - text_email_subject_approved: "Genehmigungs-Workflow %{name} genehmigt" - text_email_subject_rejected: "Genehmigungs-Workflow %{name} abgelehnt" - text_email_subject_delegated: "Genehmigungs-Workflow %{name} deligiert" - text_email_subject_requires_approval: "Genehmigungs-Workflow %{name} benötigt deine Genehmigung" - text_email_subject_updated: "Genehmigungs-Workflow %{name} bearbeitet" - text_email_subject_started: "Genehmigungs-Workflow %{name} gestartet" + text_email_subject_approved: genehmigt + text_email_subject_rejected: abgelehnt + text_email_subject_delegated: deligiert + text_email_subject_requires_approval: benötigt deine Genehmigung + text_email_subject_updated: bearbeitet + text_email_subject_started: gestartet text_email_finished_approved: "Der Genehmigungs-Workflow '%{name}' zugewiesen an die Datei '%{filename}' ist abgeschlossen und die Datei wurde genehmigt." text_email_finished_rejected: "Der Genehmigungs-Workflow '%{name}' zugewiesen an die Datei '%{filename}' ist abgeschlossen, aber die Datei wurde abgelehnt, weil: '%{notice}'." text_email_finished_delegated: "Der Genehmigungs-Workflow '%{name}' zugewiesen an die Datei '%{filename}' wurde an dich deligiert, weil: '%{notice}' und weil deine Zustimmung im aktuellen Genehmigungsschritt benötigt wird." @@ -301,10 +301,10 @@ de: label_target_project: Zielprojekt label_source_project: Quellprojekt - text_email_doc_updated_subject: "Dokumente im Projekt %{project} wurden aktualisiert" + text_email_doc_updated_subject: Dokumente wurden aktualisiert text_email_doc_updated: hat folgende Dokumente bearbeitet text_email_doc_follows: wie folgt - text_email_doc_deleted_subject: "Dokumente im Projekt %{project} wurden gelöscht" + text_email_doc_deleted_subject: Dokumente wurden gelöscht text_email_doc_deleted: hat folgende Dokumente gelöscht label_links_only: nur Verknüpfungen diff --git a/config/locales/en.yml b/config/locales/en.yml index 764a2440..35a2a1e3 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -267,12 +267,12 @@ en: info_revision: "r%{rev}" link_workflow: Workflow notice_workflow_started: Approval workflow successfully started - text_email_subject_approved: "Approval workflow %{name} approved" - text_email_subject_rejected: "Approval workflow %{name} rejected" - text_email_subject_delegated: "Approval workflow %{name} delegated" - text_email_subject_requires_approval: "Approval workflow %{name} requires your approval" - text_email_subject_updated: "Approval workflow %{name} updated" - text_email_subject_started: "Approval workflow %{name} started" + text_email_subject_approved: approved + text_email_subject_rejected: rejected + text_email_subject_delegated: delegated + text_email_subject_requires_approval: requires your approval + text_email_subject_updated: updated + text_email_subject_started: started text_email_finished_approved: "The approval workflow '%{name}' assigned to '%{filename}' document has just been finished and the document has been approved." text_email_finished_rejected: "The approval workflow '%{name}' assigned to '%{filename}' document has just been finished and the document has been rejected because of '%{notice}'." text_email_finished_delegated: "The approval workflow '%{name}' assigned to '%{filename}' document has just been delegated because of '%{notice}' and you are expected to do an approval in the current approval step." @@ -301,10 +301,10 @@ en: label_target_project: Target project label_source_project: Source project - text_email_doc_updated_subject: "Documents of %{project} updated" + text_email_doc_updated_subject: Documents updated text_email_doc_updated: has just actualized documents of text_email_doc_follows: as follows - text_email_doc_deleted_subject: "Documents of %{project} deleted" + text_email_doc_deleted_subject: Documents deleted text_email_doc_deleted: has just deleted documents of label_links_only: links only diff --git a/config/locales/es.yml b/config/locales/es.yml index 9101df95..7cf07310 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -267,12 +267,12 @@ es: info_revision: "r%{rev}" link_workflow: Flujo de Trabajo notice_workflow_started: "Flujo de trabajo de aprobación iniciado satisfactoriamente" - text_email_subject_approved: "Flujo de trabajo de aprobación %{name} aprobado" - text_email_subject_rejected: "Flujo de trabajo de aprobación %{name} rechazado" - text_email_subject_delegated: "Flujo de trabajo de aprobación %{name} delegado" - text_email_subject_requires_approval: "Flujo de trabajo de aprobación %{name} requiere su aprobación" - text_email_subject_updated: "Flujo de trabajo de aprobación %{name} actualizado" - text_email_subject_started: "Flujo de trabajo de aprobación %{name} comenzado" + text_email_subject_approved: aprobado + text_email_subject_rejected: rechazado + text_email_subject_delegated: delegado + text_email_subject_requires_approval: requiere su aprobación + text_email_subject_updated: actualizado + text_email_subject_started: comenzado text_email_finished_approved: "El flujo de trabajo de aprobación '%{name}' asignado al documento '%{filename}' acaba de ser terminado y él ha sido aprobado." text_email_finished_rejected: "El flujo de trabajo de aprobación '%{name}' asignado al documento '%{filename}' acaba de ser terminado y él ha sido rechazado por el siguiente motivo '%{notice}'." text_email_finished_delegated: "El flujo de trabajo de aprobación '%{name}' asignado al documento '%{filename}' acaba de ser delegado por '%{notice}' y se espera que haga una aprobación en la etapa de aprobación actual." @@ -301,10 +301,10 @@ es: label_target_project: Proyecto destino label_source_project: Proyecto fuente - text_email_doc_updated_subject: "Documentos de %{project} actualizados" + text_email_doc_updated_subject: Documentos actualizados text_email_doc_updated: acaba de actualizar los ducumentos de text_email_doc_follows: lo siguiente - text_email_doc_deleted_subject: "Documentos de %{project} eliminados" + text_email_doc_deleted_subject: Documentos eliminados text_email_doc_deleted: acaba de eliminar documentos de label_links_only: Solo enlaces diff --git a/config/locales/fr.yml b/config/locales/fr.yml index e6a72410..5749c4ce 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -267,12 +267,12 @@ fr: info_revision: "r%{rev}" link_workflow: Flux notice_workflow_started: Flux de validation démarré avec succès - text_email_subject_approved: "Flux de validation %{name} approuvé" - text_email_subject_rejected: "Flux de validation %{name} rejeté" - text_email_subject_delegated: "Flux de validation %{name} délégué" - text_email_subject_requires_approval: "Flux de validation %{name} requiert votre approbation" - text_email_subject_updated: "Flux de validation %{name} mis à jour" - text_email_subject_started: "Flux de validation %{name} démarré" + text_email_subject_approved: approuvé + text_email_subject_rejected: rejeté + text_email_subject_delegated: délégué + text_email_subject_requires_approval: requiert votre approbation + text_email_subject_updated: mis à jour + text_email_subject_started: démarré text_email_finished_approved: "Le flux de validation '%{name}' assigné au document '%{filename}' vient de se terminer et le document a été approuvé." text_email_finished_rejected: "Le flux de validation '%{name}' assigné au document '%{filename}' vient de se terminer et le document a été rejeté pour la raison '%{notice}'." text_email_finished_delegated: "Le flux de validation '%{name}' assigné au document '%{filename}' a été délégué pour la raison '%{notice}' et vous êtes tenu d'approuver l'étape actuelle." @@ -301,10 +301,10 @@ fr: label_target_project: Projet cible label_source_project: Projet source - text_email_doc_updated_subject: "Documents de %{project} mis à jour" + text_email_doc_updated_subject: Documents mis à jour text_email_doc_updated: a mis à jour des documents de text_email_doc_follows: comme suit - text_email_doc_deleted_subject: "Documents de %{project} supprimés" + text_email_doc_deleted_subject: Documents supprimés text_email_doc_deleted: a supprimé des documents de label_links_only: liens seulement diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 3e8fe905..1d00865a 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -267,12 +267,12 @@ ja: info_revision: "r%{rev}" link_workflow: ワークフロー notice_workflow_started: 承認ワークフローが開始されました - text_email_subject_approved: "承認ワークフロー '%{name}' は承認されました" - text_email_subject_rejected: "承認ワークフロー '%{name}' は否認されました" - text_email_subject_delegated: "承認ワークフロー '%{name}' は代理承認が設定されました" - text_email_subject_requires_approval: "承認ワークフロー '%{name}' はあなたの承認待ちです" - text_email_subject_updated: "承認ワークフロー '%{name}' が更新されました" - text_email_subject_started: "承認ワークフロー '%{name}' が開始されました" + text_email_subject_approved: は承認されました + text_email_subject_rejected: は否認されました + text_email_subject_delegated: は代理承認が設定されました + text_email_subject_requires_approval: はあなたの承認待ちです + text_email_subject_updated: が更新されました + text_email_subject_started: が開始されました" text_email_finished_approved: "承認ワークフロー '%{name}' において '%{filename}' が承認されました。" text_email_finished_rejected: "承認ワークフロー '%{name}' において '%{filename}' が否認されました。理由:'%{notice}'。" text_email_finished_delegated: "承認ワークフロー '%{name}' において代理承認が依頼されました。承認対象 '%{filename}' の内容をご確認の上、承認・否認のご判断をお願い致します(依頼主コメント:'%{notice}')。" @@ -301,10 +301,10 @@ ja: label_target_project: リンク先プロジェクト label_source_project: リンク元プロジェクト - text_email_doc_updated_subject: "プロジェクト'%{project}'のファイルが更新されました" + text_email_doc_updated_subject: プロジェクトのファイルが更新されました text_email_doc_updated: が次のファイルを更新しました。 text_email_doc_follows: 対象ファイル: - text_email_doc_deleted_subject: "プロジェクト'%{project}'のファイルが削除されました" + text_email_doc_deleted_subject: プロジェクトのファイルが削除されました text_email_doc_deleted: が次のプロジェクトのファイルを削除しました。 label_links_only: リンクのみ diff --git a/config/locales/pl.yml b/config/locales/pl.yml index b19bed33..d9ef7d2c 100644 --- a/config/locales/pl.yml +++ b/config/locales/pl.yml @@ -267,12 +267,12 @@ pl: info_revision: "r%{rev}" link_workflow: Proces akceptacji notice_workflow_started: Proces akceptacji został uruchomiony - text_email_subject_approved: "Proces akceptacji %{name} został zakończony akceptacją" - text_email_subject_rejected: "Proces akceptacji %{name} został odrzucony" - text_email_subject_delegated: "Proces akceptacji %{name} został delegowany" - text_email_subject_requires_approval: "Proces akceptacji %{name} wymaga Twojej akceptacji" - text_email_subject_updated: "Proces akceptacji %{name} został zaktualizowany" - text_email_subject_started: "Proces akceptacji %{name} został uruchomiony" + text_email_subject_approved: został zakończony akceptacją + text_email_subject_rejected: został odrzucony + text_email_subject_delegated: został delegowany + text_email_subject_requires_approval: wymaga Twojej akceptacji + text_email_subject_updated: został zaktualizowany + text_email_subject_started: został uruchomiony text_email_finished_approved: "Proces akceptacji '%{name}' dokumentu '%{filename}' został właśnie zakończony. Dokument został zaakceptowany." text_email_finished_rejected: "Proces akceptacji '%{name}' dokumentu '%{filename}' został właśnie zakończony. Dokument został odrzucony z powodu '%{notice}'." text_email_finished_delegated: "Proces akceptacji '%{name}' dokumentu '%{filename}' został właśnie delegowany z powodu '%{notice}'. Zostałeś wskazany jako akceptujący w bieżącym kroku zatwierdzania." @@ -301,10 +301,10 @@ pl: label_target_project: Projekt docelowy label_source_project: Projekt źródłowy - text_email_doc_updated_subject: "Dokumenty projektu %{project} zostały zaktualizowane" + text_email_doc_updated_subject: Dokumenty zostały zaktualizowane text_email_doc_updated: dokumenty zostały zaktualizowane text_email_doc_follows: następujące - text_email_doc_deleted_subject: "Dokumenty projektu %{project} zostały usunięte" + text_email_doc_deleted_subject: Dokumenty zostały usunięte text_email_doc_deleted: dokumenty zostały usunięte label_links_only: odnośniki diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml index c5739415..4f2b05ab 100644 --- a/config/locales/pt-BR.yml +++ b/config/locales/pt-BR.yml @@ -267,12 +267,12 @@ pt-BR: info_revision: "r%{rev}" link_workflow: Workflow notice_workflow_started: Workflow de aprovação foi iniciado com êxito - text_email_subject_approved: "Workflow de aprovação %{name}: aprovado" - text_email_subject_rejected: "Workflow de aprovação %{name} reprovado" - text_email_subject_delegated: "Workflow de aprovação %{name} atribuído para" - text_email_subject_requires_approval: "Workflow de aprovação %{name} requer sua aprovação" - text_email_subject_updated: "Workflow de aprovação %{name}: atualizado" - text_email_subject_started: "Workflow de aprovação %{name}: iniciado" + text_email_subject_approved: aprovado + text_email_subject_rejected: reprovado + text_email_subject_delegated: atribuído para + text_email_subject_requires_approval: requer sua aprovação + text_email_subject_updated: atualizado + text_email_subject_started: iniciado text_email_finished_approved: "O workflow de aprovação'%{name}' definido para o documento '%{filename}' foi finalizado e o documento foi aprovado." text_email_finished_rejected: "O workflow de aprovação '%{name}' definido para o documento '%{filename}' foi finalizado e o documento foi reprovado devido a '%{notice}'." text_email_finished_delegated: "O workflow de aprovação '%{name}' definido para o documento '%{filename}' foi atribuido pois '%{notice}' e está aguardando a sua aprovação na etapa atual." @@ -301,10 +301,10 @@ pt-BR: label_target_project: Target project label_source_project: Source project - text_email_doc_updated_subject: "%{project} : Documentos atualizados" + text_email_doc_updated_subject: Documentos atualizados text_email_doc_updated: atualizou os documentos da área text_email_doc_follows: as follows - text_email_doc_deleted_subject: "%{project} : Exclusão de documentos" + text_email_doc_deleted_subject: Exclusão de documentos text_email_doc_deleted: deletou os documentos da área label_links_only: links only diff --git a/config/locales/ru.yml b/config/locales/ru.yml index 97c238e7..8a17540e 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -267,12 +267,12 @@ ru: info_revision: "r%{rev}" link_workflow: Согласование notice_workflow_started: Процесс согласования успешно запущен - text_email_subject_approved: "Процесс согласования %{name} успешно завершен" - text_email_subject_rejected: "Процесс согласования %{name} отклонен" - text_email_subject_delegated: "Процесс согласования %{name} делегирован" - text_email_subject_requires_approval: "Процесс согласования %{name} требует вашего участия" - text_email_subject_updated: "Процесс согласования %{name} обновлен" - text_email_subject_started: "Процесс согласования %{name} запущен" + text_email_subject_approved: успешно завершен + text_email_subject_rejected: отклонен + text_email_subject_delegated: делегирован + text_email_subject_requires_approval: ребует вашего участия + text_email_subject_updated: обновлен + text_email_subject_started: запущен text_email_finished_approved: "Процесс согласования '%{name}' документа '%{filename}' только что завершился и документ был согласован." text_email_finished_rejected: "Процесс согласования '%{name}' документа '%{filename}' только что завершился и документ был отклонен по причине '%{notice}'." text_email_finished_delegated: "Процесс согласования '%{name}' документа '%{filename}' только что был делегирован по причине '%{notice}' и от Вас ожидается согласование." @@ -301,10 +301,10 @@ ru: label_target_project: Целевой проект label_source_project: Исходный проект - text_email_doc_updated_subject: "Документы проекта %{project} обновлены" + text_email_doc_updated_subject: Документы обновлены text_email_doc_updated: только что обновил документы text_email_doc_follows: следующим образом - text_email_doc_deleted_subject: "Документы проекта %{project} удалены" + text_email_doc_deleted_subject: Документы удалены text_email_doc_deleted: только что удалил документы label_links_only: только ссылки diff --git a/config/locales/sl.yml b/config/locales/sl.yml index 7b39b918..81ceb2e5 100644 --- a/config/locales/sl.yml +++ b/config/locales/sl.yml @@ -267,12 +267,12 @@ sl: info_revision: "r%{rev}" link_workflow: Workflow notice_workflow_started: Approval workflow successfully started - text_email_subject_approved: "Approval workflow %{name} approved" - text_email_subject_rejected: "Approval workflow %{name} rejected" - text_email_subject_delegated: "Approval workflow %{name} delegated" - text_email_subject_requires_approval: "Approval workflow %{name} requires your approval" - text_email_subject_updated: "Approval workflow %{name} updated" - text_email_subject_started: "Approval workflow %{name} started" + text_email_subject_approved: approved + text_email_subject_rejected: rejected + text_email_subject_delegated: delegated + text_email_subject_requires_approval: requires your approval + text_email_subject_updated: updated + text_email_subject_started: started text_email_finished_approved: "The approval workflow '%{name}' assigned to '%{filename}' document has just been finished and the document has been approved." text_email_finished_rejected: "The approval workflow '%{name}' assigned to '%{filename}' document has just been finished and the document has been rejected because of '%{notice}'." text_email_finished_delegated: "The approval workflow '%{name}' assigned to '%{filename}' document has just been delegated because of '%{notice}' and you are expected to do an approval in the current approval step." @@ -301,10 +301,10 @@ sl: label_target_project: Target project label_source_project: Source project - text_email_doc_updated_subject: "Documents of %{project} updated" + text_email_doc_updated_subject: Documents updated text_email_doc_updated: has just actualized documents of text_email_doc_follows: as follows - text_email_doc_deleted_subject: "Documents of %{project} deleted" + text_email_doc_deleted_subject: Documents deleted text_email_doc_deleted: has just deleted documents of label_links_only: links only diff --git a/config/locales/zh-TW.yml b/config/locales/zh-TW.yml index 94f28e1c..052906f4 100644 --- a/config/locales/zh-TW.yml +++ b/config/locales/zh-TW.yml @@ -267,12 +267,12 @@ zh-TW: info_revision: "r%{rev}" link_workflow: Workflow notice_workflow_started: Approval workflow successfully started - text_email_subject_approved: "Approval workflow %{name} approved" - text_email_subject_rejected: "Approval workflow %{name} rejected" - text_email_subject_delegated: "Approval workflow %{name} delegated" - text_email_subject_requires_approval: "Approval workflow %{name} requires your approval" - text_email_subject_updated: "Approval workflow %{name} updated" - text_email_subject_started: "Approval workflow %{name} started" + text_email_subject_approved: approved + text_email_subject_rejected: rejected + text_email_subject_delegated: delegated + text_email_subject_requires_approval: requires your approval + text_email_subject_updated: updated + text_email_subject_started: started text_email_finished_approved: "The approval workflow '%{name}' assigned to '%{filename}' document has just been finished and the document has been approved." text_email_finished_rejected: "The approval workflow '%{name}' assigned to '%{filename}' document has just been finished and the document has been rejected because of '%{notice}'." text_email_finished_delegated: "The approval workflow '%{name}' assigned to '%{filename}' document has just been delegated because of '%{notice}' and you are expected to do an approval in the current approval step." @@ -301,10 +301,10 @@ zh-TW: label_target_project: Target project label_source_project: Source project - text_email_doc_updated_subject: "Documents of %{project} updated" + text_email_doc_updated_subject: Documents updated text_email_doc_updated: has just actualized documents of text_email_doc_follows: as follows - text_email_doc_deleted_subject: "Documents of %{project} deleted" + text_email_doc_deleted_subject: Documents deleted text_email_doc_deleted: has just deleted documents of label_links_only: links only diff --git a/config/locales/zh.yml b/config/locales/zh.yml index 2a299542..17a6a59a 100644 --- a/config/locales/zh.yml +++ b/config/locales/zh.yml @@ -267,12 +267,12 @@ zh: info_revision: "r%{rev}" link_workflow: Workflow notice_workflow_started: Approval workflow successfully started - text_email_subject_approved: "Approval workflow %{name} approved" - text_email_subject_rejected: "Approval workflow %{name} rejected" - text_email_subject_delegated: "Approval workflow %{name} delegated" - text_email_subject_requires_approval: "Approval workflow %{name} requires your approval" - text_email_subject_updated: "Approval workflow %{name} updated" - text_email_subject_started: "Approval workflow %{name} started" + text_email_subject_approved: approved + text_email_subject_rejected: rejected + text_email_subject_delegated: delegated + text_email_subject_requires_approval: requires your approval + text_email_subject_updated: updated + text_email_subject_started: started text_email_finished_approved: "The approval workflow '%{name}' assigned to '%{filename}' document has just been finished and the document has been approved." text_email_finished_rejected: "The approval workflow '%{name}' assigned to '%{filename}' document has just been finished and the document has been rejected because of '%{notice}'." text_email_finished_delegated: "The approval workflow '%{name}' assigned to '%{filename}' document has just been delegated because of '%{notice}' and you are expected to do an approval in the current approval step." @@ -301,10 +301,10 @@ zh: label_target_project: Target project label_source_project: Source project - text_email_doc_updated_subject: "Documents of %{project} updated" + text_email_doc_updated_subject: Documents updated text_email_doc_updated: has just actualized documents of text_email_doc_follows: as follows - text_email_doc_deleted_subject: "Documents of %{project} deleted" + text_email_doc_deleted_subject: Documents deleted text_email_doc_deleted: has just deleted documents of label_links_only: links only From c55fbe8dfae3d30eb749908a2ce7ba2b07cadf01 Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Mon, 22 Feb 2016 15:11:30 +0100 Subject: [PATCH 18/94] Email notification subject in Redmine format --- app/controllers/dmsf_workflows_controller.rb | 3 +- app/models/dmsf_mailer.rb | 7 ++-- app/models/dmsf_workflow.rb | 6 ++-- ...0222140401_approval_workflow_std_fields.rb | 34 +++++++++++++++++++ 4 files changed, 45 insertions(+), 5 deletions(-) create mode 100644 db/migrate/20160222140401_approval_workflow_std_fields.rb diff --git a/app/controllers/dmsf_workflows_controller.rb b/app/controllers/dmsf_workflows_controller.rb index c50f69f0..39412d05 100644 --- a/app/controllers/dmsf_workflows_controller.rb +++ b/app/controllers/dmsf_workflows_controller.rb @@ -2,7 +2,7 @@ # # Redmine plugin for Document Management System "Features" # -# Copyright (C) 2011-15 Karel Pičman +# Copyright (C) 2011-16 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 @@ -223,6 +223,7 @@ class DmsfWorkflowsController < ApplicationController @dmsf_workflow = DmsfWorkflow.new @dmsf_workflow.name = params[:dmsf_workflow][:name] @dmsf_workflow.project_id = @project.id if @project + @dmsf_workflow.author = User.current @dmsf_workflow.save end end diff --git a/app/models/dmsf_mailer.rb b/app/models/dmsf_mailer.rb index 42d7516c..500f4362 100644 --- a/app/models/dmsf_mailer.rb +++ b/app/models/dmsf_mailer.rb @@ -29,7 +29,8 @@ class DmsfMailer < Mailer files = files.select { |file| file.notify? } redmine_headers 'Project' => project.identifier if project @files = files - @project = project + @project = project + message_id project set_language_if_valid user.language mail :to => user.mail, :subject => "[#{@project.name} - #{l(:menu_dmsf)}] #{l(:text_email_doc_updated_subject)}" @@ -41,7 +42,8 @@ class DmsfMailer < Mailer files = files.select { |file| file.notify? } redmine_headers 'Project' => project.identifier if project @files = files - @project = project + @project = project + message_id project set_language_if_valid user.language mail :to => user.mail, :subject => "[#{@project.name} - #{l(:menu_dmsf)}] #{l(:text_email_doc_deleted_subject)}" @@ -70,6 +72,7 @@ class DmsfMailer < Mailer end set_language_if_valid user.language @user = user + message_id workflow @workflow = workflow @revision = revision @text1 = l(text1_id, :name => workflow.name, :filename => revision.file.name, :notice => notice) diff --git a/app/models/dmsf_workflow.rb b/app/models/dmsf_workflow.rb index d74b8a0d..6c306edf 100644 --- a/app/models/dmsf_workflow.rb +++ b/app/models/dmsf_workflow.rb @@ -2,7 +2,7 @@ # # Redmine plugin for Document Management System "Features" # -# Copyright (C) 2011-15 Karel Picman +# Copyright (C) 2011-16 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 @@ -20,7 +20,8 @@ class DmsfWorkflow < ActiveRecord::Base - has_many :dmsf_workflow_steps, -> { order 'step ASC, operator DESC' }, :dependent => :destroy + has_many :dmsf_workflow_steps, -> { order 'step ASC, operator DESC' }, :dependent => :destroy + belongs_to :author, :class_name => 'User' scope :sorted, lambda { order('name ASC') } scope :global, lambda { where('project_id IS NULL') } @@ -245,6 +246,7 @@ class DmsfWorkflow < ActiveRecord::Base new_wf = self.dup new_wf.name = name if name new_wf.project_id = project ? project.id : nil + new_wf.author = User.current if new_wf.save self.dmsf_workflow_steps.each do |step| step.copy_to(new_wf) diff --git a/db/migrate/20160222140401_approval_workflow_std_fields.rb b/db/migrate/20160222140401_approval_workflow_std_fields.rb new file mode 100644 index 00000000..edb7bbef --- /dev/null +++ b/db/migrate/20160222140401_approval_workflow_std_fields.rb @@ -0,0 +1,34 @@ +# encoding: utf-8 +# +# Redmine plugin for Document Management System "Features" +# +# Copyright (C) 2011-16 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 ApprovalWorkflowStdFields < ActiveRecord::Migration + def self.up + add_column :dmsf_workflows, :updated_on, :timestamp + add_column :dmsf_workflows, :created_on, :datetime + add_column :dmsf_workflows, :author_id, :integer + DmsfWorkflow.update_all 'updated_on = now(), created_on = now(), author_id = (select id from users where admin = 1 limit 1)' + end + + def self.down + remove_column :dmsf_workflows, :updated_on + remove_column :dmsf_workflows, :created_on + remove_column :dmsf_workflows, :author_id + end +end \ No newline at end of file From fff94e1f3a64e0f6c653868a80474c4790e1fa92 Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Mon, 22 Feb 2016 15:38:20 +0100 Subject: [PATCH 19/94] Email notification subject in Redmine format --- app/controllers/dmsf_controller.rb | 10 +++++++++- app/controllers/dmsf_files_controller.rb | 24 ++++++++++++++---------- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/app/controllers/dmsf_controller.rb b/app/controllers/dmsf_controller.rb index 87816e56..417f543c 100644 --- a/app/controllers/dmsf_controller.rb +++ b/app/controllers/dmsf_controller.rb @@ -590,9 +590,17 @@ class DmsfController < ApplicationController log_activity(f, 'deleted') end begin - DmsfMailer.get_notify_users(@project, deleted_files).each do |u| + recipients = DmsfMailer.get_notify_users(@project, deleted_files) + recipients.each do |u| DmsfMailer.files_deleted(u, @project, deleted_files).deliver end + if Setting.plugin_redmine_dmsf[:dmsf_display_notified_recipients] == '1' + unless recipients.empty? + to = recipients.collect{ |r| r.name }.first(DMSF_MAX_NOTIFICATION_RECEIVERS_INFO).join(', ') + to << ((recipients.count > DMSF_MAX_NOTIFICATION_RECEIVERS_INFO) ? ',...' : '.') + flash[:warning] = l(:warning_email_notifications, :to => to) + end + end rescue Exception => e Rails.logger.error "Could not send email notifications: #{e.message}" end diff --git a/app/controllers/dmsf_files_controller.rb b/app/controllers/dmsf_files_controller.rb index abb54e86..7c20cd89 100644 --- a/app/controllers/dmsf_files_controller.rb +++ b/app/controllers/dmsf_files_controller.rb @@ -211,17 +211,23 @@ class DmsfFilesController < ApplicationController if commit log_activity('deleted') begin - DmsfMailer.get_notify_users(@project, [@file]).each do |u| + recipients = DmsfMailer.get_notify_users(@project, [@file]) + recipients.each do |u| DmsfMailer.files_deleted(u, @project, [@file]).deliver end + if Setting.plugin_redmine_dmsf[:dmsf_display_notified_recipients] == '1' + unless recipients.empty? + to = recipients.collect{ |r| r.name }.first(DMSF_MAX_NOTIFICATION_RECEIVERS_INFO).join(', ') + to << ((recipients.count > DMSF_MAX_NOTIFICATION_RECEIVERS_INFO) ? ',...' : '.') + flash[:warning] = l(:warning_email_notifications, :to => to) + end + end rescue Exception => e Rails.logger.error "Could not send email notifications: #{e.message}" end end - else - @file.errors.each do |e, msg| - flash[:error] = msg - end + else + flash[:error] = @file.errors.full_messages.join(', ') end end if commit @@ -236,10 +242,8 @@ class DmsfFilesController < ApplicationController if @revision.delete(true) flash[:notice] = l(:notice_revision_deleted) log_activity('deleted') - else - @revision.errors.each do |e, msg| - flash[:error] = msg - end + else + flash[:error] = @revision.errors.full_messages.join(', ') end end redirect_to :action => 'show', :id => @file @@ -331,4 +335,4 @@ class DmsfFilesController < ApplicationController end end -end +end \ No newline at end of file From 808c017bcd0d9e2fe01fca36037aed0bcb79122a Mon Sep 17 00:00:00 2001 From: chris-ibcsf Date: Mon, 22 Feb 2016 22:46:37 +0100 Subject: [PATCH 20/94] style fixes --- init.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/init.rb b/init.rb index 393775a6..ad53563d 100644 --- a/init.rb +++ b/init.rb @@ -220,9 +220,9 @@ Redmine::Plugin.register :redmine_dmsf do raise 'Not supported image format' unless file.image? url = url_for(:controller => :dmsf_files, :action => 'view', :id => file) file_view_url = url_for(:controller => :dmsf_files, :action => 'view', :id => file, :download => args[2]) - if size and size.include? "%" + if size && size.include?("%") img = image_tag(url, :alt => file.title, :width => size, :height => size) - elsif size and size.include? "x" + elsif size && size.include?("x") img = image_tag(url, :alt => file.title, :size => size) elsif height img = image_tag(url, :alt => file.title, :width => 'auto', :height => height) From 42ea32aefad21cf6f1174fa5baa9b5acd4466f08 Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Tue, 23 Feb 2016 16:26:11 +0100 Subject: [PATCH 21/94] SQLite compatibility --- db/migrate/20160222140401_approval_workflow_std_fields.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/db/migrate/20160222140401_approval_workflow_std_fields.rb b/db/migrate/20160222140401_approval_workflow_std_fields.rb index edb7bbef..e27bdbe2 100644 --- a/db/migrate/20160222140401_approval_workflow_std_fields.rb +++ b/db/migrate/20160222140401_approval_workflow_std_fields.rb @@ -23,7 +23,10 @@ class ApprovalWorkflowStdFields < ActiveRecord::Migration add_column :dmsf_workflows, :updated_on, :timestamp add_column :dmsf_workflows, :created_on, :datetime add_column :dmsf_workflows, :author_id, :integer - DmsfWorkflow.update_all 'updated_on = now(), created_on = now(), author_id = (select id from users where admin = 1 limit 1)' + # Set updated_on + DmsfWorkflow.all.each(&:save) + # Set created_on and author_id + DmsfWorkflow.update_all 'created_on = updated_on, author_id = (select id from users where admin = 1 limit 1)' end def self.down From af6aabe2aba79eff2a1db75efa349bb9634a2c8c Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Tue, 23 Feb 2016 17:37:33 +0100 Subject: [PATCH 22/94] Links deletion independently on documents --- app/controllers/dmsf_controller.rb | 32 +++++++++++++++-------------- app/models/dmsf_file.rb | 6 ++---- app/models/dmsf_folder.rb | 6 ++---- app/views/dmsf/_file_trash.html.erb | 31 +++++++++++++++++++--------- 4 files changed, 42 insertions(+), 33 deletions(-) diff --git a/app/controllers/dmsf_controller.rb b/app/controllers/dmsf_controller.rb index 417f543c..c33e38a0 100644 --- a/app/controllers/dmsf_controller.rb +++ b/app/controllers/dmsf_controller.rb @@ -188,19 +188,22 @@ class DmsfController < ApplicationController return end - if selected_dir_links.present? - selected_dir_links.each do |id| - link = DmsfLink.find_by_id id - selected_folders << link.target_id if link && !selected_folders.include?(link.target_id.to_s) + if selected_dir_links.present? && + (params[:email_entries].present? || params[:download_entries].present?) + selected_dir_links.each do |id| + link = DmsfLink.find_by_id id + selected_folders << link.target_id if link && !selected_folders.include?(link.target_id.to_s) end end - if selected_file_links.present? - selected_file_links.each do |id| - link = DmsfLink.find_by_id id - selected_files << link.target_id if link && !selected_files.include?(link.target_id.to_s) + if selected_file_links.present? && + (params[:email_entries].present? || params[:download_entries].present?) + selected_file_links.each do |id| + link = DmsfLink.find_by_id id + selected_files << link.target_id if link && !selected_files.include?(link.target_id.to_s) end - end + end + if params[:email_entries].present? email_entries(selected_folders, selected_files) elsif params[:restore_entries].present? @@ -503,15 +506,14 @@ class DmsfController < ApplicationController end if selected_files && selected_files.is_a?(Array) selected_files.each do |selected_file_id| - file = DmsfFile.visible.find_by_id selected_file_id + file = DmsfFile.visible.find_by_id selected_file_id + unless file && file.last_revision && File.exists?(file.last_revision.disk_file) + raise FileNotFound + end unless (file.project == @project) || User.current.allowed_to?(:view_dmsf_files, file.project) raise DmsfAccessError end - if file && file.last_revision && File.exists?(file.last_revision.disk_file) - zip.add_file(file, member, (file.folder.dmsf_path_str if file.folder)) if file - else - raise FileNotFound - end + zip.add_file(file, member, (file.folder.dmsf_path_str if file.folder)) if file end end max_files = Setting.plugin_redmine_dmsf['dmsf_max_file_download'].to_i diff --git a/app/models/dmsf_file.rb b/app/models/dmsf_file.rb index 6b69c34b..d656734d 100644 --- a/app/models/dmsf_file.rb +++ b/app/models/dmsf_file.rb @@ -141,8 +141,7 @@ class DmsfFile < ActiveRecord::Base end begin # Revisions and links of a deleted file SHOULD be deleted too - self.revisions.each { |r| r.delete(commit, true) } - self.referenced_links.each { |l| l.delete(commit) } + self.revisions.each { |r| r.delete(commit, true) } if commit self.destroy else @@ -162,8 +161,7 @@ class DmsfFile < ActiveRecord::Base errors[:base] << l(:error_parent_folder) return false end - self.revisions.each { |r| r.restore } - self.referenced_links.each { |l| l.restore } + self.revisions.each { |r| r.restore } self.deleted = STATUS_ACTIVE self.deleted_by_user = nil save diff --git a/app/models/dmsf_folder.rb b/app/models/dmsf_folder.rb index 1367f2fc..484bd4f4 100644 --- a/app/models/dmsf_folder.rb +++ b/app/models/dmsf_folder.rb @@ -113,8 +113,7 @@ class DmsfFolder < ActiveRecord::Base elsif !self.subfolders.visible.empty? || !self.files.visible.empty? errors[:base] << l(:error_folder_is_not_empty) return false - end - self.referenced_links.each { |l| l.delete(commit) } + end if commit self.destroy else @@ -132,8 +131,7 @@ class DmsfFolder < ActiveRecord::Base if self.dmsf_folder_id && (self.folder.nil? || self.folder.deleted?) errors[:base] << l(:error_parent_folder) return false - end - self.referenced_links.each { |l| l.restore } + end self.deleted = STATUS_ACTIVE self.deleted_by_user = nil self.save diff --git a/app/views/dmsf/_file_trash.html.erb b/app/views/dmsf/_file_trash.html.erb index 9c25af8d..3f0c9687 100644 --- a/app/views/dmsf/_file_trash.html.erb +++ b/app/views/dmsf/_file_trash.html.erb @@ -3,7 +3,7 @@ # # Redmine plugin for Document Management System "Features" # -# Copyright (C) 2011-15 Karel Pičman +# Copyright (C) 2011-16 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 @@ -38,15 +38,26 @@
From 8555da434445747c973dfd62655c2f4a2402781e Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Tue, 23 Feb 2016 17:47:59 +0100 Subject: [PATCH 23/94] Links deletion independently on documents --- test/unit/dmsf_file_test.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/unit/dmsf_file_test.rb b/test/unit/dmsf_file_test.rb index 371eafe8..b113250c 100644 --- a/test/unit/dmsf_file_test.rb +++ b/test/unit/dmsf_file_test.rb @@ -108,7 +108,8 @@ class DmsfFileTest < RedmineDmsf::Test::UnitTest assert @file4.delete(false), @file4.errors.full_messages.to_sentence assert @file4.deleted?, "File #{@file4.name} is not deleted" assert_equal 0, @file4.revisions.visible.count - assert_equal 0, @file4.referenced_links.visible.count + # Links should not be deleted + assert_equal 2, @file4.referenced_links.visible.count @file4.folder.lock! end From 5db2aef083ccda359a9b884bf53d6176eca6c269 Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Mon, 29 Feb 2016 14:10:15 +0100 Subject: [PATCH 24/94] fix workflow creation error #512 --- test/unit/dmsf_workflow_test.rb | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/test/unit/dmsf_workflow_test.rb b/test/unit/dmsf_workflow_test.rb index ed3cd15a..3364cdb7 100644 --- a/test/unit/dmsf_workflow_test.rb +++ b/test/unit/dmsf_workflow_test.rb @@ -87,12 +87,19 @@ class DmsfWorkflowTest < RedmineDmsf::Test::UnitTest assert_equal 1, @wf1.errors.count end - def test_validate_name_uniqueness + def test_validate_name_uniqueness_globaly @wf2.name = @wf1.name assert !@wf2.save assert_equal 1, @wf2.errors.count end + def test_validate_name_uniqueness_localy + @wf2.name = @wf1.name + @wf2.project_id = @wf1.project_id + assert !@wf2.save + assert_equal 1, @wf2.errors.count + end + def test_destroy @wf1.destroy assert_nil DmsfWorkflow.find_by_id(1) From 62997ecc0b0423a7e86a939b68cb3138dca67682 Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Tue, 1 Mar 2016 13:15:58 +0100 Subject: [PATCH 25/94] Better exception handling when a file is physically missing --- app/controllers/dmsf_controller.rb | 20 ++++-------- lib/dmsf_zip.rb | 7 ++-- lib/redmine_dmsf.rb | 12 ++++--- lib/redmine_dmsf/errors.rb | 28 ++++++++++++++++ .../errors}/dmsf_access_error.rb | 5 ++- .../errors}/dmsf_content_error.rb | 5 ++- .../errors/dmsf_email_max_file_error.rb | 32 +++++++++++++++++++ .../errors/dmsf_file_not_found_error.rb | 23 +++++++++++++ .../errors}/dmsf_lock_error.rb | 5 ++- .../errors/dmsf_zip_max_file_error.rb | 32 +++++++++++++++++++ lib/redmine_dmsf/webdav/download.rb | 2 +- 11 files changed, 148 insertions(+), 23 deletions(-) create mode 100644 lib/redmine_dmsf/errors.rb rename lib/{ => redmine_dmsf/errors}/dmsf_access_error.rb (85%) rename lib/{ => redmine_dmsf/errors}/dmsf_content_error.rb (85%) create mode 100644 lib/redmine_dmsf/errors/dmsf_email_max_file_error.rb create mode 100644 lib/redmine_dmsf/errors/dmsf_file_not_found_error.rb rename lib/{ => redmine_dmsf/errors}/dmsf_lock_error.rb (84%) create mode 100644 lib/redmine_dmsf/errors/dmsf_zip_max_file_error.rb diff --git a/app/controllers/dmsf_controller.rb b/app/controllers/dmsf_controller.rb index c33e38a0..13895578 100644 --- a/app/controllers/dmsf_controller.rb +++ b/app/controllers/dmsf_controller.rb @@ -23,10 +23,6 @@ class DmsfController < ApplicationController unloadable - class ZipMaxFilesError < StandardError; end - class EmailMaxFileSize < StandardError; end - class FileNotFound < StandardError; end - before_filter :find_project before_filter :authorize before_filter :find_folder, :except => [:new, :create, :edit_root, :save_root] @@ -217,17 +213,15 @@ class DmsfController < ApplicationController redirect_to :back else download_entries(selected_folders, selected_files) - end - rescue ZipMaxFilesError - flash[:error] = l(:error_max_files_exceeded, :number => Setting.plugin_redmine_dmsf['dmsf_max_file_download']) - redirect_to :back - rescue EmailMaxFileSize - flash[:error] = l(:error_max_email_filesize_exceeded, :number => Setting.plugin_redmine_dmsf['dmsf_max_email_filesize']) - redirect_to :back + end rescue FileNotFound render_404 rescue DmsfAccessError - render_403 + render_403 + rescue Exception => e + flash[:error] = e.message + Rails.logger.error e.message + redirect_to :back end def tag_changed @@ -518,7 +512,7 @@ class DmsfController < ApplicationController end max_files = Setting.plugin_redmine_dmsf['dmsf_max_file_download'].to_i if max_files > 0 && zip.files.length > max_files - raise ZipMaxFilesError, zip.files.length + raise ZipMaxFilesError#, zip.files.length end zip end diff --git a/lib/dmsf_zip.rb b/lib/dmsf_zip.rb index 477345fd..088148f1 100644 --- a/lib/dmsf_zip.rb +++ b/lib/dmsf_zip.rb @@ -1,6 +1,9 @@ +# encoding: utf-8 +# # Redmine plugin for Document Management System "Features" # -# Copyright (C) 2011 Vít Jonáš +# Copyright (C) 2011 Vít Jonáš +# Copyright (C) 2011-16 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 @@ -60,4 +63,4 @@ class DmsfZip folder.files.visible.each { |file| self.add_file(file, member, root_path) } end -end +end \ No newline at end of file diff --git a/lib/redmine_dmsf.rb b/lib/redmine_dmsf.rb index 4c6d70eb..4ce45298 100644 --- a/lib/redmine_dmsf.rb +++ b/lib/redmine_dmsf.rb @@ -1,8 +1,10 @@ +# encoding: utf-8 +# # Redmine plugin for Document Management System "Features" # # Copyright (C) 2011 Vít Jonáš # Copyright (C) 2012 Daniel Munn -# Copyright (C) 2011-14 Karel Pičman +# Copyright (C) 2011-16 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 @@ -21,8 +23,9 @@ DMSF_MAX_NOTIFICATION_RECEIVERS_INFO = 10 # DMSF libraries -require 'redmine_dmsf/patches' #plugin patches -require 'redmine_dmsf/webdav' #DAV4Rack implementation +require 'redmine_dmsf/patches' # plugin patches +require 'redmine_dmsf/webdav' # DAV4Rack implementation +require 'redmine_dmsf/errors' # Exceptions # Hooks @@ -33,4 +36,5 @@ module RedmineDmsf end # Add the plugin view folder into ActionMailer's paths to search -ActionMailer::Base.append_view_path(File.expand_path(File.dirname(__FILE__) + '/../app/views')) \ No newline at end of file +ActionMailer::Base.append_view_path(File.expand_path( + File.dirname(__FILE__) + '/../app/views')) \ No newline at end of file diff --git a/lib/redmine_dmsf/errors.rb b/lib/redmine_dmsf/errors.rb new file mode 100644 index 00000000..97fcdece --- /dev/null +++ b/lib/redmine_dmsf/errors.rb @@ -0,0 +1,28 @@ +# encoding: utf-8 +# +# Redmine plugin for Document Management System "Features" +# +# Copyright (C) 2011 Vít Jonáš +# Copyright (C) 2012 Daniel Munn +# Copyright (C) 2011-16 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. + +require 'redmine_dmsf/errors/dmsf_access_error.rb' +require 'redmine_dmsf/errors/dmsf_content_error.rb' +require 'redmine_dmsf/errors/dmsf_email_max_file_error.rb' +require 'redmine_dmsf/errors/dmsf_file_not_found_error.rb' +require 'redmine_dmsf/errors/dmsf_lock_error.rb' +require 'redmine_dmsf/errors/dmsf_zip_max_file_error.rb' \ No newline at end of file diff --git a/lib/dmsf_access_error.rb b/lib/redmine_dmsf/errors/dmsf_access_error.rb similarity index 85% rename from lib/dmsf_access_error.rb rename to lib/redmine_dmsf/errors/dmsf_access_error.rb index 98d54324..3131e5ba 100644 --- a/lib/dmsf_access_error.rb +++ b/lib/redmine_dmsf/errors/dmsf_access_error.rb @@ -1,6 +1,9 @@ +# encoding: utf-8 +# # Redmine plugin for Document Management System "Features" # -# Copyright (C) 2011 Vít Jonáš +# Copyright (C) 2011 Vít Jonáš +# Copyright (C) 2011-16 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/dmsf_content_error.rb b/lib/redmine_dmsf/errors/dmsf_content_error.rb similarity index 85% rename from lib/dmsf_content_error.rb rename to lib/redmine_dmsf/errors/dmsf_content_error.rb index 2cebdffc..1e73e9a2 100644 --- a/lib/dmsf_content_error.rb +++ b/lib/redmine_dmsf/errors/dmsf_content_error.rb @@ -1,6 +1,9 @@ +# encoding: utf-8 +# # Redmine plugin for Document Management System "Features" # -# Copyright (C) 2011 Vít Jonáš +# Copyright (C) 2011 Vít Jonáš +# Copyright (C) 2011-16 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/errors/dmsf_email_max_file_error.rb b/lib/redmine_dmsf/errors/dmsf_email_max_file_error.rb new file mode 100644 index 00000000..5ca70174 --- /dev/null +++ b/lib/redmine_dmsf/errors/dmsf_email_max_file_error.rb @@ -0,0 +1,32 @@ +# encoding: utf-8 +# +# Redmine plugin for Document Management System "Features" +# +# Copyright (C) 2011-16 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 EmailMaxFileSize < StandardError + include Redmine::I18n + + def initialize(message = nil) + if message.present? + super message + else + super l(:error_max_email_filesize_exceeded, + :number => Setting.plugin_redmine_dmsf['dmsf_max_email_filesize']) + end + end +end diff --git a/lib/redmine_dmsf/errors/dmsf_file_not_found_error.rb b/lib/redmine_dmsf/errors/dmsf_file_not_found_error.rb new file mode 100644 index 00000000..b55d226e --- /dev/null +++ b/lib/redmine_dmsf/errors/dmsf_file_not_found_error.rb @@ -0,0 +1,23 @@ +# encoding: utf-8 +# +# Redmine plugin for Document Management System "Features" +# +# Copyright (C) 2011-16 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 FileNotFound < StandardError + +end diff --git a/lib/dmsf_lock_error.rb b/lib/redmine_dmsf/errors/dmsf_lock_error.rb similarity index 84% rename from lib/dmsf_lock_error.rb rename to lib/redmine_dmsf/errors/dmsf_lock_error.rb index b74493c5..0ccc326a 100644 --- a/lib/dmsf_lock_error.rb +++ b/lib/redmine_dmsf/errors/dmsf_lock_error.rb @@ -1,6 +1,9 @@ +# encoding: utf-8 +# # Redmine plugin for Document Management System "Features" # -# Copyright (C) 2012 Daniel Munn +# Copyright (C) 2012 Daniel Munn +# Copyright (C) 2011-16 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/errors/dmsf_zip_max_file_error.rb b/lib/redmine_dmsf/errors/dmsf_zip_max_file_error.rb new file mode 100644 index 00000000..c888c486 --- /dev/null +++ b/lib/redmine_dmsf/errors/dmsf_zip_max_file_error.rb @@ -0,0 +1,32 @@ +# encoding: utf-8 +# +# Redmine plugin for Document Management System "Features" +# +# Copyright (C) 2011-16 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 ZipMaxFilesError < StandardError + include Redmine::I18n + + def initialize(message = nil) + if message.present? + super message + else + super l(:error_max_files_exceeded, + :number => Setting.plugin_redmine_dmsf['dmsf_max_file_download']) + end + end +end diff --git a/lib/redmine_dmsf/webdav/download.rb b/lib/redmine_dmsf/webdav/download.rb index f7e2698d..4a8c0dbf 100644 --- a/lib/redmine_dmsf/webdav/download.rb +++ b/lib/redmine_dmsf/webdav/download.rb @@ -45,7 +45,7 @@ module RedmineDmsf if available serving(env) else - raise NotFound + return fail(403, 'Not Found') end end end From 0f1eccffd79d90fd14a4e1868e2a1828ab179acf Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Tue, 1 Mar 2016 13:48:47 +0100 Subject: [PATCH 26/94] Better exception handling when a file is physically missing --- lib/redmine_dmsf/webdav/download.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/redmine_dmsf/webdav/download.rb b/lib/redmine_dmsf/webdav/download.rb index 4a8c0dbf..4a2cb945 100644 --- a/lib/redmine_dmsf/webdav/download.rb +++ b/lib/redmine_dmsf/webdav/download.rb @@ -45,7 +45,7 @@ module RedmineDmsf if available serving(env) else - return fail(403, 'Not Found') + fail(404, "File not found: #{@path}") end end end From a8e8e1b3e5c383de7ab9f3de87a372b97e0fde73 Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Wed, 2 Mar 2016 08:42:39 +0100 Subject: [PATCH 27/94] WebDAV logging enabled --- config/routes.rb | 11 +++++++---- lib/redmine_dmsf/webdav/controller.rb | 2 +- lib/redmine_dmsf/webdav/no_parse.rb | 17 +++++++---------- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/config/routes.rb b/config/routes.rb index 04dd9e33..6f940c06 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,8 +1,10 @@ +# encoding: utf-8 +# # Redmine plugin for Document Management System "Features" # # Copyright (C) 2011 Vít Jonáš # Copyright (C) 2012 Daniel Munn -# Copyright (C) 2011-14 Karel Pičman +# Copyright (C) 2011-16 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 @@ -113,8 +115,9 @@ RedmineApp::Application.routes.draw do mount DAV4Rack::Handler.new( :root_uri_path => "#{Redmine::Utils::relative_url_root}/dmsf/webdav", :resource_class => RedmineDmsf::Webdav::ResourceProxy, - :controller_class => RedmineDmsf::Webdav::Controller - ), :at => "/dmsf/webdav" + :controller_class => RedmineDmsf::Webdav::Controller, + :log_to => Rails.logger + ), :at => '/dmsf/webdav' # Approval workflow resources :dmsf_workflows do @@ -141,4 +144,4 @@ RedmineApp::Application.routes.draw do end end -end +end \ No newline at end of file diff --git a/lib/redmine_dmsf/webdav/controller.rb b/lib/redmine_dmsf/webdav/controller.rb index 963961e6..f8ab6027 100644 --- a/lib/redmine_dmsf/webdav/controller.rb +++ b/lib/redmine_dmsf/webdav/controller.rb @@ -118,7 +118,7 @@ module RedmineDmsf # Escape URL string def url_format(resource) # Additionally escape square brackets, otherwise files with - # file name like file[1].pdf are not visible in some WebDAV clients + # file names like file[1].pdf are not visible in some WebDAV clients URI.encode(super, '[]') end diff --git a/lib/redmine_dmsf/webdav/no_parse.rb b/lib/redmine_dmsf/webdav/no_parse.rb index bef4080c..4c1edf7a 100644 --- a/lib/redmine_dmsf/webdav/no_parse.rb +++ b/lib/redmine_dmsf/webdav/no_parse.rb @@ -1,6 +1,9 @@ +# encoding: utf-8 +# # Redmine plugin for Document Management System "Features" # -# Copyright (C) 2012 Daniel Munn +# Copyright (C) 2012 Daniel Munn +# Copyright (C) 2011-16 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 @@ -24,21 +27,15 @@ module RedmineDmsf end def call(env) - if env['REQUEST_METHOD'] == "PUT" && env.has_key?('CONTENT_TYPE') then + if env['REQUEST_METHOD'] == 'PUT' && env.has_key?('CONTENT_TYPE') then if (@urls.dup.delete_if {|x| !env['PATH_INFO'].starts_with? x}.length > 0) then - logger "RedmineDmsf::NoParse prevented mime parsing for PUT #{env['PATH_INFO']}" + Rails.logger.info "RedmineDmsf::NoParse prevented mime parsing for PUT #{env['PATH_INFO']}" env['CONTENT_TYPE'] = 'text/plain' end end @app.call(env) end - - private - - def logger(env) - env['action_dispatch.logger'] || Logger.new($stdout) - end - + end end From 1fddd2a3f558b2a233a32b1deb65497a9defce81 Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Wed, 2 Mar 2016 09:19:00 +0100 Subject: [PATCH 28/94] Get property for missing disk files raises an error in Windows --- 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 1fb236b8..f05d95f8 100644 --- a/lib/redmine_dmsf/webdav/dmsf_resource.rb +++ b/lib/redmine_dmsf/webdav/dmsf_resource.rb @@ -538,7 +538,7 @@ module RedmineDmsf def get_property(element) raise NotImplemented if (element[:ns_href] != 'DAV:') unless folder - return NotFound unless (file && file.last_revision && File.exist?(file.last_revision.disk_file)) + return NotFound unless (file && file.last_revision) end case element[:name] when 'supportedlock' From 6355882a2799f9fb8f72ac4bb4a72d0d3aee4fd5 Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Wed, 2 Mar 2016 09:42:59 +0100 Subject: [PATCH 29/94] Folder notification icon doesnt work - a keying mistake --- app/views/dmsf/show.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/dmsf/show.html.erb b/app/views/dmsf/show.html.erb index 5945ac72..c1a44069 100644 --- a/app/views/dmsf/show.html.erb +++ b/app/views/dmsf/show.html.erb @@ -46,7 +46,7 @@ :title => l(:title_lock_folder), :class => 'icon icon-dmsf-lock') %> <% end %> <% end %> - <% if !@locked_for_user && ((@folder && @folder.notification) || (!@filder && @project.dmsf_notification)) %> + <% if !@locked_for_user && ((@folder && @folder.notification) || (!@folder && @project.dmsf_notification)) %> <%= link_to(l(:label_notifications_off), notify_deactivate_dmsf_path(:id => @project, :folder_id => @folder), :title => l(:title_notifications_active_deactivate), From 0f090710bbb39dad445c7095329dd8f1ccf43715 Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Wed, 2 Mar 2016 12:36:53 +0100 Subject: [PATCH 30/94] Can't list locked files on Windows --- lib/redmine_dmsf/webdav/dmsf_resource.rb | 40 +++++++++--------------- 1 file changed, 15 insertions(+), 25 deletions(-) diff --git a/lib/redmine_dmsf/webdav/dmsf_resource.rb b/lib/redmine_dmsf/webdav/dmsf_resource.rb index f05d95f8..833e70d5 100644 --- a/lib/redmine_dmsf/webdav/dmsf_resource.rb +++ b/lib/redmine_dmsf/webdav/dmsf_resource.rb @@ -542,9 +542,9 @@ module RedmineDmsf end case element[:name] when 'supportedlock' - supported_lock + supportedlock when 'lockdiscovery' - discover_lock + lockdiscovery else super end @@ -576,66 +576,56 @@ module RedmineDmsf end Download.new(file.last_revision.disk_file) end - - # discover_lock + # As the name suggests, we're returning lock recovery information for requested resource - def discover_lock + def lockdiscovery x = Nokogiri::XML::DocumentFragment.parse '' entity = file || folder return nil unless entity.locked? - if !entity.folder.nil? && entity.folder.locked? - locks = entity.lock.reverse[0].folder.locks(false)# longwinded way of getting base items locks + if entity.folder && entity.folder.locked? + locks = entity.lock.reverse[0].folder.locks(false) # longwinded way of getting base items locks else locks = entity.lock(false) end Nokogiri::XML::Builder.with(x) do |doc| doc.lockdiscovery { - locks.each {|lock| + locks.each do |lock| next if lock.expired? doc.activelock { - doc.locktype { - doc.write - } + doc.locktype { doc.write } doc.lockscope { if lock.lock_scope == :scope_exclusive doc.exclusive else doc.shared end - } - if lock.folder.nil? - doc.depth '0' - else - doc.depth 'infinity' - end + } + doc.depth lock.folder.nil? ? '0' : 'infinity' doc.owner lock.user.to_s if lock.expires_at.nil? - doc.timeout = 'Infinite' + doc.timeout 'Infinite' else doc.timeout "Second-#{(lock.expires_at.to_i - Time.now.to_i)}" end - lock_entity = lock.folder || lock.file lock_path = "#{request.scheme}://#{request.host}:#{request.port}#{path_prefix}#{URI.escape(lock_entity.project.identifier)}/" lock_path << lock_entity.dmsf_path.map {|x| URI.escape(x.respond_to?('name') ? x.name : x.title) }.join('/') lock_path << '/' if lock_entity.is_a?(DmsfFolder) && lock_path[-1,1] != '/' doc.lockroot { doc.href lock_path } - if (lock.user.id == User.current.id || User.current.allowed_to?(:force_file_unlock, self.project)) + if ((lock.user.id == User.current.id) || User.current.allowed_to?(:force_file_unlock, self.project)) doc.locktoken { doc.href lock.uuid } end } - } + end } end - x end - - # supported_lock + # As the name suggests, we're returning locks supported by our implementation - def supported_lock + def supportedlock x = Nokogiri::XML::DocumentFragment.parse '' Nokogiri::XML::Builder.with(x) do |doc| doc.supportedlock { From 35b35f313256aba7c9290cbb40fbc2863925858b Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Wed, 2 Mar 2016 13:11:07 +0100 Subject: [PATCH 31/94] Project file structure revision --- lib/redmine_dmsf.rb | 26 +++++++++++++++++++++++--- lib/redmine_dmsf/errors.rb | 28 ---------------------------- lib/redmine_dmsf/patches.rb | 24 ------------------------ lib/redmine_dmsf/webdav.rb | 28 ---------------------------- 4 files changed, 23 insertions(+), 83 deletions(-) delete mode 100644 lib/redmine_dmsf/errors.rb delete mode 100644 lib/redmine_dmsf/patches.rb delete mode 100644 lib/redmine_dmsf/webdav.rb diff --git a/lib/redmine_dmsf.rb b/lib/redmine_dmsf.rb index 4ce45298..802b9594 100644 --- a/lib/redmine_dmsf.rb +++ b/lib/redmine_dmsf.rb @@ -23,10 +23,30 @@ DMSF_MAX_NOTIFICATION_RECEIVERS_INFO = 10 # DMSF libraries -require 'redmine_dmsf/patches' # plugin patches -require 'redmine_dmsf/webdav' # DAV4Rack implementation -require 'redmine_dmsf/errors' # Exceptions +# Plugin's patches +require 'redmine_dmsf/patches/custom_fields_helper_patch' +require 'redmine_dmsf/patches/acts_as_customizable' +require 'redmine_dmsf/patches/project_patch' +require 'redmine_dmsf/patches/project_tabs_extended' + +# Load up classes that make up our WebDAV solution ontop of DAV4Rack +require 'redmine_dmsf/webdav/no_parse' +require 'redmine_dmsf/webdav/base_resource' +require 'redmine_dmsf/webdav/controller' +require 'redmine_dmsf/webdav/dmsf_resource' +require 'redmine_dmsf/webdav/download' +require 'redmine_dmsf/webdav/index_resource' +require 'redmine_dmsf/webdav/project_resource' +require 'redmine_dmsf/webdav/resource_proxy' + +# Exceptions +require 'redmine_dmsf/errors/dmsf_access_error.rb' +require 'redmine_dmsf/errors/dmsf_content_error.rb' +require 'redmine_dmsf/errors/dmsf_email_max_file_error.rb' +require 'redmine_dmsf/errors/dmsf_file_not_found_error.rb' +require 'redmine_dmsf/errors/dmsf_lock_error.rb' +require 'redmine_dmsf/errors/dmsf_zip_max_file_error.rb' # Hooks require 'redmine_dmsf/hooks/view_projects_form_hook' diff --git a/lib/redmine_dmsf/errors.rb b/lib/redmine_dmsf/errors.rb deleted file mode 100644 index 97fcdece..00000000 --- a/lib/redmine_dmsf/errors.rb +++ /dev/null @@ -1,28 +0,0 @@ -# encoding: utf-8 -# -# Redmine plugin for Document Management System "Features" -# -# Copyright (C) 2011 Vít Jonáš -# Copyright (C) 2012 Daniel Munn -# Copyright (C) 2011-16 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. - -require 'redmine_dmsf/errors/dmsf_access_error.rb' -require 'redmine_dmsf/errors/dmsf_content_error.rb' -require 'redmine_dmsf/errors/dmsf_email_max_file_error.rb' -require 'redmine_dmsf/errors/dmsf_file_not_found_error.rb' -require 'redmine_dmsf/errors/dmsf_lock_error.rb' -require 'redmine_dmsf/errors/dmsf_zip_max_file_error.rb' \ No newline at end of file diff --git a/lib/redmine_dmsf/patches.rb b/lib/redmine_dmsf/patches.rb deleted file mode 100644 index db91a040..00000000 --- a/lib/redmine_dmsf/patches.rb +++ /dev/null @@ -1,24 +0,0 @@ -# Redmine plugin for Document Management System "Features" -# -# Copyright (C) 2011 Vít Jonáš -# Copyright (C) 2012 Daniel Munn -# Copyright (C) 2011-14 Karel Picman -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -require 'redmine_dmsf/patches/custom_fields_helper_patch' -require 'redmine_dmsf/patches/acts_as_customizable' -require 'redmine_dmsf/patches/project_patch' -require 'redmine_dmsf/patches/project_tabs_extended' \ No newline at end of file diff --git a/lib/redmine_dmsf/webdav.rb b/lib/redmine_dmsf/webdav.rb deleted file mode 100644 index 6e2a836d..00000000 --- a/lib/redmine_dmsf/webdav.rb +++ /dev/null @@ -1,28 +0,0 @@ -# Redmine plugin for Document Management System "Features" -# -# Copyright (C) 2012 Daniel Munn -# -# 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. - -# Load up classes that make up our webdav solution ontop -# of DAV4Rack -require 'redmine_dmsf/webdav/no_parse' -require 'redmine_dmsf/webdav/base_resource' -require 'redmine_dmsf/webdav/controller' -require 'redmine_dmsf/webdav/dmsf_resource' -require 'redmine_dmsf/webdav/download' -require 'redmine_dmsf/webdav/index_resource' -require 'redmine_dmsf/webdav/project_resource' -require 'redmine_dmsf/webdav/resource_proxy' From de8da43bc8b2d6fe288deb32df6d5deff0cb3166 Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Fri, 4 Mar 2016 10:17:47 +0100 Subject: [PATCH 32/94] A new macro for a document's approval workflows status added --- README.md | 21 +++-- init.rb | 150 +----------------------------- lib/redmine_dmsf.rb | 3 + lib/redmine_dmsf/macros.rb | 186 +++++++++++++++++++++++++++++++++++++ 4 files changed, 206 insertions(+), 154 deletions(-) create mode 100644 lib/redmine_dmsf/macros.rb diff --git a/README.md b/README.md index 44cd24dc..11098add 100644 --- a/README.md +++ b/README.md @@ -101,16 +101,16 @@ Search will now automatically search DMSF content when a Redmine search is perfo ###Linking DMSF files from Wiki entries: -####Link to a file with id 17: +####Link to a document with id 17: `{{dmsf(17)}}` -####Link to a file with id 17 with link text "File" +####Link to a document with id 17 with link text "File" `{{dmsf(17, File)}}` -####Link to the description of a file with id 17 +####Link to the description of a document with id 17 `{{dmsfd(17)}}` -####Link to the preview of the first 5 lines from a file with id 17 +####Link to the preview of 5 lines from a document with id 17 `{{dmsft(17, 5)}}` ####An inline picture of the file with id 8; it must be an image file such as JPEG, PNG,... @@ -137,8 +137,11 @@ Search will now automatically search DMSF content when a Redmine search is perfo ####A thumbnail with custom size `{{dmsftn(8, size=300)}}` +####Approval workflow status of a document with id 8 +`{{dmsfw(8)}}` -The DMSF file/revision id can be found in link for file/revision download from within Redmine. + +The DMSF document/revision id can be found in document details. ###Linking DMSF folders from Wiki entries: @@ -166,7 +169,13 @@ In the file /public/help//wiki_syntax_detailed.html, aft
  • {{dmsff(5, Folder)}} (a link to the folder with id 5 with the link text "Folder")
  • {{dmsf_image(8)}} (an inline picture of the file with id 8; it must be an image file such as JPEG, PNG,...)
  • {{dmsf_image(8, size=300)}} (an inline picture with custom size)
  • -
  • {{dmsf_image(8, size=640x480)}} (an inline picture with custom size)
  • +
  • {{dmsf_image(8, size=640x480)}} (an inline picture with custom size)
  • +
  • {{dmsf_image(8, size=50%)}} (an inline picture with custom size)
  • +
  • {{dmsf_image(8, height=300)}} (an inline picture with custom size)
  • +
  • {{dmsf_image(8, width=300)}} (an inline picture with custom size)
  • +
  • {{dmsftn(8)}} (a thumbnail with height of 200px)
  • +
  • {{dmsftn(8, size=300)}} (a thumbnail with custom size)
  • +
  • {{dmsfw(8)}} (approval workflow status of a document with id 8)
  • 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/init.rb b/init.rb index 4442e45d..9115cd93 100644 --- a/init.rb +++ b/init.rb @@ -96,153 +96,7 @@ Redmine::Plugin.register :redmine_dmsf do 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 the link for file/revision download." - macro :dmsf do |obj, args| - raise ArgumentError if args.length < 1 # Requires file id - file = DmsfFile.visible.find args[0].strip - if args[2].blank? - revision = file.last_revision - else - revision = DmsfFileRevision.find(args[2]) - if revision.file != file - raise ActiveRecord::RecordNotFound - end - end - if User.current && User.current.allowed_to?(:view_dmsf_files, file.project) - file_view_url = url_for(:controller => :dmsf_files, :action => 'view', :id => file, :download => args[2]) - return link_to(h(args[1] ? args[1] : file.title), - file_view_url, - :target => '_blank', - :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_view_url}") - else - raise l(:notice_not_authorized) - end - end - - 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 the link for folder opening." - macro :dmsff do |obj, args| - if args.length < 1 - return link_to l(:link_documents), dmsf_folder_url(@project) - else - folder = DmsfFolder.visible.find args[0].strip - if User.current && User.current.allowed_to?(:view_dmsf_folders, folder.project) - return link_to h(args[1] ? args[1] : folder.title), - dmsf_folder_url(folder.project, :folder_id => folder) - else - raise l(:notice_not_authorized) - end - end - end - - desc "Wiki link to DMSF document description:\n\n" + - "{{dmsfd(file_id)}}\n\n" + - "_file_id_ can be found in the link for file/revision download." - macro :dmsfd do |obj, args| - raise ArgumentError if args.length < 1 # Requires file id - file = DmsfFile.visible.find args[0].strip - if User.current && User.current.allowed_to?(:view_dmsf_files, file.project) - return textilizable(file.description) - else - raise l(:notice_not_authorized) - end - end - - desc "Wiki link to DMSF document's content preview:\n\n" + - "{{dmsft(file_id)}}\n\n" + - "_file_id_ can be found in the link for file/revision download." - macro :dmsft do |obj, args| - raise ArgumentError if args.length < 2 # Requires file id and lines number - file = DmsfFile.visible.find args[0].strip - if User.current && User.current.allowed_to?(:view_dmsf_files, file.project) - return file.preview(args[1].strip).gsub("\n", '
    ').html_safe - else - raise l(:notice_not_authorized) - end - end - - desc "Wiki DMSF image:\n\n" + - "{{dmsf_image(file_id)}}\n" + - "{{dmsf_image(file_id, size=300)}} -- with custom title and size\n" + - "{{dmsf_image(file_id, height=300)}} -- with custom title and height (auto width)\n" + - "{{dmsf_image(file_id, width=300)}} -- with custom title and width (auto height)\n" + - "{{dmsf_image(file_id, size=640x480)}}" - macro :dmsf_image do |obj, args| - args, options = extract_macro_options(args, :size, :width, :height, :title) - file_id = args.first - raise 'DMSF document ID required' unless file_id.present? - size = options[:size] - width = options[:width] - height = options[:height] - if file = DmsfFile.find_by_id(file_id) - unless User.current && User.current.allowed_to?(:view_dmsf_files, file.project) - raise l(:notice_not_authorized) - end - raise 'Not supported image format' unless file.image? - url = url_for(:controller => :dmsf_files, :action => 'view', :id => file) - if size && size.include?('%') - image_tag(url, :alt => file.title, :width => size, :height => size) - elsif height - image_tag(url, :alt => file.title, :width => 'auto', :height => height) - elsif width - image_tag(url, :alt => file.title, :width => width, :height => 'auto') - else - image_tag(url, :alt => file.title, :size => size) - end - else - raise "Document ID #{file_id} not found" - end - end - - - desc "Wiki DMSF thumbnail:\n\n" + - "{{dmsftn(file_id)}}\n" + - "{{dmsftn(file_id, size=300)}} -- with custom title and size\n" + - "{{dmsftn(file_id, height=300)}} -- with custom title and height (auto width)\n" + - "{{dmsftn(file_id, width=300)}} -- with custom title and width (auto height)\n" + - "{{dmsftn(file_id, size=640x480)}}" - macro :dmsftn do |obj, args| - args, options = extract_macro_options(args, :size, :width, :height, :title) - file_id = args.first - raise 'DMSF document ID required' unless file_id.present? - size = options[:size] - width = options[:width] - height = options[:height] - if file = DmsfFile.find_by_id(file_id) - unless User.current && User.current.allowed_to?(:view_dmsf_files, file.project) - raise l(:notice_not_authorized) - end - raise 'Not supported image format' unless file.image? - url = url_for(:controller => :dmsf_files, :action => 'view', :id => file) - file_view_url = url_for(:controller => :dmsf_files, :action => 'view', :id => file, :download => args[2]) - if size && size.include?("%") - img = image_tag(url, :alt => file.title, :width => size, :height => size) - elsif size && size.include?("x") - img = image_tag(url, :alt => file.title, :size => size) - elsif height - img = image_tag(url, :alt => file.title, :width => 'auto', :height => height) - elsif width - img = image_tag(url, :alt => file.title, :width => width, :height => 'auto') - else - img = image_tag(url, :alt => file.title, :width => 'auto', :height => 200) - end - link_to(img, - file_view_url, :target => '_blank', - :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_view_url}") - else - raise "Document ID #{file_id} not found" - end - end - - end + end # Rubyzip configuration Zip.unicode_names = true @@ -251,4 +105,4 @@ end Redmine::Search.map do |search| search.register :dmsf_files search.register :dmsf_folders -end +end \ No newline at end of file diff --git a/lib/redmine_dmsf.rb b/lib/redmine_dmsf.rb index 802b9594..410897c7 100644 --- a/lib/redmine_dmsf.rb +++ b/lib/redmine_dmsf.rb @@ -52,6 +52,9 @@ require 'redmine_dmsf/errors/dmsf_zip_max_file_error.rb' require 'redmine_dmsf/hooks/view_projects_form_hook' require 'redmine_dmsf/hooks/base_view_hooks' +# Macros +require 'redmine_dmsf/macros' + module RedmineDmsf end diff --git a/lib/redmine_dmsf/macros.rb b/lib/redmine_dmsf/macros.rb new file mode 100644 index 00000000..9826a29f --- /dev/null +++ b/lib/redmine_dmsf/macros.rb @@ -0,0 +1,186 @@ +# encoding: utf-8 +# +# Redmine plugin for Document Management System "Features" +# +# Copyright (C) 2011-16 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. + +Redmine::WikiFormatting::Macros.register do + + # dmsf - link to a document + desc "Wiki link to DMSF file:\n\n" + + "{{dmsf(file_id [, title [, revision_id]])}}\n\n" + + "_file_id_ / _revision_id_ can be found in the link for file/revision download." + macro :dmsf do |obj, args| + raise ArgumentError if args.length < 1 # Requires file id + file = DmsfFile.visible.find args[0].strip + if args[2].blank? + revision = file.last_revision + else + revision = DmsfFileRevision.find(args[2]) + if revision.file != file + raise ActiveRecord::RecordNotFound + end + end + if User.current && User.current.allowed_to?(:view_dmsf_files, file.project) + file_view_url = url_for(:controller => :dmsf_files, :action => 'view', :id => file, :download => args[2]) + return link_to(h(args[1] ? args[1] : file.title), + file_view_url, + :target => '_blank', + :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_view_url}") + else + raise l(:notice_not_authorized) + end + end + + # dmsff - link to a folder + desc "Wiki link to DMSF folder:\n\n" + + "{{dmsff(folder_id [, title])}}\n\n" + + "_folder_id_ 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), dmsf_folder_url(@project) + else + folder = DmsfFolder.visible.find args[0].strip + if User.current && User.current.allowed_to?(:view_dmsf_folders, folder.project) + return link_to h(args[1] ? args[1] : folder.title), + dmsf_folder_url(folder.project, :folder_id => folder) + else + raise l(:notice_not_authorized) + end + end + end + + # dmsfd - link to a document's description + desc "Wiki link to DMSF document description:\n\n" + + "{{dmsfd(file_id)}}\n\n" + + "_file_id_ can be found in the document's details." + macro :dmsfd do |obj, args| + raise ArgumentError if args.length < 1 # Requires file id + file = DmsfFile.visible.find args[0].strip + if User.current && User.current.allowed_to?(:view_dmsf_files, file.project) + return textilizable(file.description) + else + raise l(:notice_not_authorized) + end + end + + # dmsft - link to a document's content preview + desc "Wiki link to DMSF document's content preview:\n\n" + + "{{dmsft(file_id)}}\n\n" + + "_file_id_ can be found in the document's details." + macro :dmsft do |obj, args| + raise ArgumentError if args.length < 2 # Requires file id and lines number + file = DmsfFile.visible.find args[0].strip + if User.current && User.current.allowed_to?(:view_dmsf_files, file.project) + return file.preview(args[1].strip).gsub("\n", '
    ').html_safe + else + raise l(:notice_not_authorized) + end + end + + # dmsf_image - link to an image + desc "Wiki DMSF image:\n\n" + + "{{dmsf_image(file_id)}}\n" + + "{{dmsf_image(file_id, size=300)}} -- with custom title and size\n" + + "{{dmsf_image(file_id, height=300)}} -- with custom title and height (auto width)\n" + + "{{dmsf_image(file_id, width=300)}} -- with custom title and width (auto height)\n" + + "{{dmsf_image(file_id, size=640x480)}}" + macro :dmsf_image do |obj, args| + args, options = extract_macro_options(args, :size, :width, :height, :title) + file_id = args.first + raise 'DMSF document ID required' unless file_id.present? + size = options[:size] + width = options[:width] + height = options[:height] + if file = DmsfFile.find_by_id(file_id) + unless User.current && User.current.allowed_to?(:view_dmsf_files, file.project) + raise l(:notice_not_authorized) + end + raise 'Not supported image format' unless file.image? + url = url_for(:controller => :dmsf_files, :action => 'view', :id => file) + if size && size.include?('%') + image_tag(url, :alt => file.title, :width => size, :height => size) + elsif height + image_tag(url, :alt => file.title, :width => 'auto', :height => height) + elsif width + image_tag(url, :alt => file.title, :width => width, :height => 'auto') + else + image_tag(url, :alt => file.title, :size => size) + end + else + raise "Document ID #{file_id} not found" + end + end + + # dmsftn - link to an image thumbnail + desc "Wiki DMSF thumbnail:\n\n" + + "{{dmsftn(file_id)}}\n" + + "{{dmsftn(file_id, size=300)}} -- with custom title and size\n" + + "{{dmsftn(file_id, height=300)}} -- with custom title and height (auto width)\n" + + "{{dmsftn(file_id, width=300)}} -- with custom title and width (auto height)\n" + + "{{dmsftn(file_id, size=640x480)}}" + macro :dmsftn do |obj, args| + args, options = extract_macro_options(args, :size, :width, :height, :title) + file_id = args.first + raise 'DMSF document ID required' unless file_id.present? + size = options[:size] + width = options[:width] + height = options[:height] + if file = DmsfFile.find_by_id(file_id) + unless User.current && User.current.allowed_to?(:view_dmsf_files, file.project) + raise l(:notice_not_authorized) + end + raise 'Not supported image format' unless file.image? + url = url_for(:controller => :dmsf_files, :action => 'view', :id => file) + file_view_url = url_for(:controller => :dmsf_files, :action => 'view', :id => file, :download => args[2]) + if size && size.include?("%") + img = image_tag(url, :alt => file.title, :width => size, :height => size) + elsif size && size.include?("x") + img = image_tag(url, :alt => file.title, :size => size) + elsif height + img = image_tag(url, :alt => file.title, :width => 'auto', :height => height) + elsif width + img = image_tag(url, :alt => file.title, :width => width, :height => 'auto') + else + img = image_tag(url, :alt => file.title, :width => 'auto', :height => 200) + end + link_to(img, + file_view_url, :target => '_blank', + :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_view_url}") + else + raise "Document ID #{file_id} not found" + end + end + + # dmsfw - link to a document's approval workflow status + desc "Wiki link to DMSF document's approval workflow status:\n\n" + + "{{dmsfw(file_id)}}\n\n" + + "_file_id_ can be found in the document's details." + macro :dmsfw do |obj, args| + raise ArgumentError if args.length < 1 # Requires file id + file = DmsfFile.visible.find args[0].strip + if User.current && User.current.allowed_to?(:view_dmsf_files, file.project) + raise ActiveRecord::RecordNotFound unless file.last_revision + return file.last_revision.workflow_str(false) + else + raise l(:notice_not_authorized) + end + end + +end \ No newline at end of file From 417e80bbcf4c086665f8bae240da25733673fda4 Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Wed, 9 Mar 2016 14:53:09 +0100 Subject: [PATCH 33/94] CCS Redmine style --- app/controllers/dmsf_files_copy_controller.rb | 12 +- app/controllers/dmsf_links_controller.rb | 6 +- app/models/dmsf_link.rb | 12 +- app/views/dmsf/_custom_fields.html.erb | 19 ++- app/views/dmsf/_dir.html.erb | 10 +- app/views/dmsf/_dir_trash.html.erb | 10 +- app/views/dmsf/_file.html.erb | 10 +- app/views/dmsf/_file_trash.html.erb | 10 +- app/views/dmsf/_path.html.erb | 10 +- app/views/dmsf/_url.html.erb | 10 +- app/views/dmsf/_url_trash.html.erb | 12 +- app/views/dmsf/edit.html.erb | 54 +++---- app/views/dmsf/edit_root.html.erb | 26 ++-- app/views/dmsf/email_entries.html.erb | 53 ++++--- app/views/dmsf/show.html.erb | 15 +- app/views/dmsf/trash.html.erb | 18 +-- .../dmsf_files/_file_new_revision.html.erb | 142 ++++++++---------- .../dmsf_files/_revision_access.html.erb | 18 ++- app/views/dmsf_files/show.html.erb | 117 ++++++++------- app/views/dmsf_files_copy/new.html.erb | 42 +++--- app/views/dmsf_folders_copy/new.html.erb | 23 +-- app/views/dmsf_links/_form.html.erb | 68 +++++---- app/views/dmsf_upload/_upload_file.html.erb | 79 +++++----- .../dmsf_upload/_upload_file_locked.html.erb | 65 ++++---- app/views/dmsf_upload/upload_files.html.erb | 13 +- app/views/dmsf_workflows/_main.html.erb | 7 +- app/views/dmsf_workflows/new.html.erb | 4 +- assets/stylesheets/dmsf.css | 120 +++------------ test/unit/dmsf_link_test.rb | 21 ++- 29 files changed, 497 insertions(+), 509 deletions(-) diff --git a/app/controllers/dmsf_files_copy_controller.rb b/app/controllers/dmsf_files_copy_controller.rb index 5213e3ff..85b8af9b 100644 --- a/app/controllers/dmsf_files_copy_controller.rb +++ b/app/controllers/dmsf_files_copy_controller.rb @@ -40,15 +40,15 @@ class DmsfFilesCopyController < ApplicationController render :layout => !request.xhr? end - + def create @target_project = DmsfFile.allowed_target_projects_on_copy.detect {|p| p.id.to_s == params[:target_project_id]} if params[:target_project_id] unless @target_project render_403 return end - @target_folder = DmsfFolder.visible.find(params[:target_folder_id]) unless params[:target_folder_id].blank? - if !@target_folder.nil? && @target_folder.project != @target_project + @target_folder = DmsfFolder.visible.find_by_id(params[:target_folder_id]) unless params[:target_folder_id].blank? + if @target_folder && (@target_folder.project != @target_project) raise DmsfAccessError, l(:error_entry_project_does_not_match_current_project) end @@ -103,10 +103,10 @@ class DmsfFilesCopyController < ApplicationController log_activity(@file, 'was moved (is copy)') redirect_to dmsf_file_path(@file) - end - - private + 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}" end diff --git a/app/controllers/dmsf_links_controller.rb b/app/controllers/dmsf_links_controller.rb index 9a9a5d8e..ca03b680 100644 --- a/app/controllers/dmsf_links_controller.rb +++ b/app/controllers/dmsf_links_controller.rb @@ -2,7 +2,7 @@ # # Redmine plugin for Document Management System "Features" # -# Copyright (C) 2011-15 Karel Pičman +# Copyright (C) 2011-16 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 @@ -31,8 +31,8 @@ class DmsfLinksController < ApplicationController @dmsf_link.project_id = params[:project_id] if params[:dmsf_link].present? - # Reload - @dmsf_link.dmsf_folder_id = params[:dmsf_link][:dmsf_folder_id] # TODO: Add stuff in here for external links so that if error occurs, repopulate the same + # Reload + @dmsf_link.dmsf_folder_id = params[:dmsf_link][:dmsf_folder_id] @dmsf_file_id = params[:dmsf_link][:dmsf_file_id] @type = params[:dmsf_link][:type] @link_external = (@type == 'link_from') && (params[:external_link] == 'true') diff --git a/app/models/dmsf_link.rb b/app/models/dmsf_link.rb index 897f9697..d0fa4152 100644 --- a/app/models/dmsf_link.rb +++ b/app/models/dmsf_link.rb @@ -34,8 +34,12 @@ class DmsfLink < ActiveRecord::Base def validate_url if self.target_type == 'DmsfUrl' - begin - URI.parse self.external_url + begin + if self.external_url.present? + URI.parse self.external_url + else + errors.add :external_url, :invalid + end rescue URI::InvalidURIError errors.add :external_url, :invalid end @@ -124,7 +128,7 @@ class DmsfLink < ActiveRecord::Base else self.deleted = STATUS_DELETED self.deleted_by_user = User.current - save + save(:validate => false) end end @@ -135,7 +139,7 @@ class DmsfLink < ActiveRecord::Base end self.deleted = STATUS_ACTIVE self.deleted_by_user = nil - save + save(:validate => false) end end \ No newline at end of file diff --git a/app/views/dmsf/_custom_fields.html.erb b/app/views/dmsf/_custom_fields.html.erb index 35db5d05..0222e6a7 100644 --- a/app/views/dmsf/_custom_fields.html.erb +++ b/app/views/dmsf/_custom_fields.html.erb @@ -1,7 +1,11 @@ -<%# Redmine plugin for Document Management System "Features" +<% +# encoding: utf-8 # -# Copyright (C) 2011 Vít Jonáš -# Copyright (C) 2012 Daniel Munn +# Redmine plugin for Document Management System "Features" +# +# Copyright (C) 2011 Vít Jonáš +# Copyright (C) 2012 Daniel Munn +# Copyright (C) 2011-16 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 @@ -18,11 +22,12 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.%> <% if object %> -
    +
    <% object.show_custom_field_values.each do |custom_value| %> -
    - <%= label_tag('', "#{h(custom_value.custom_field.name)}:") %> <%= show_value(custom_value) %> -
    +

    + <%= label_tag('', h(custom_value.custom_field.name)) %> + <%= show_value custom_value %> +

    <% end %>
    <% end %> \ No newline at end of file diff --git a/app/views/dmsf/_dir.html.erb b/app/views/dmsf/_dir.html.erb index 94e1f721..0c543231 100644 --- a/app/views/dmsf/_dir.html.erb +++ b/app/views/dmsf/_dir.html.erb @@ -1,4 +1,4 @@ -<%#= +<% # encoding: utf-8 # # Redmine plugin for Document Management System "Features" @@ -23,9 +23,9 @@ <% locked_for_user = subfolder && subfolder.locked_for_user? %> <% locked = subfolder && subfolder.locked? %> -
    - - - - - - - - - - - @@ -37,7 +37,7 @@ <%= file.last_revision.workflow_str(false) %> - - + - + - - - + -
    <%=l(:field_name)%>
    <%= link_to(h(workflow.name), dmsf_workflow_path(workflow)) %><%= delete_link dmsf_workflow_path(workflow) %>
    <%= link_to(h(workflow.name), dmsf_workflow_path(workflow)) %> + <%= change_status_link(workflow) unless @project %> + <%= delete_link dmsf_workflow_path(workflow) %> +
    <%= h(subfolder.user) if subfolder %> <% if @folder_manipulation_allowed %> - <% unless locked %> + <% unless locked_for_user %> <%= link_to(image_tag('edit.png'), edit_dmsf_path(:id => project, :folder_id => subfolder), :title => l(:link_edit, :title => subfolder ? h(subfolder.title) : project.name)) %> <% if subfolder %> - <%= link_to(image_tag('lock.png', :plugin => 'redmine_dmsf'), - lock_dmsf_path(:id => project, :folder_id => subfolder), - :title => l(:title_lock_file)) %> + <% if locked %> + <% if subfolder.unlockable? %> + <%= link_to(image_tag('unlock.png', :plugin => 'redmine_dmsf'), + unlock_dmsf_path(:id => project, :folder_id => subfolder), + :title => l(:title_unlock_file)) %> + <% else %> + + <% end %> + <% else %> + <%= link_to(image_tag('lock.png', :plugin => 'redmine_dmsf'), + lock_dmsf_path(:id => project, :folder_id => subfolder), + :title => l(:title_lock_file)) %> + <% end %> <% else %> <% end %> @@ -77,11 +87,9 @@ :title => l(:title_notifications_not_active_activate)) %> <% end %> <% if link %> - <%= link_to(image_tag('delete.png'), - dmsf_link_path(link), + <%= link_to(image_tag('delete.png'), dmsf_link_path(link), :data => {:confirm => l(:text_are_you_sure)}, - :method => :delete, - :title => l(:title_delete)) %> + :method => :delete, :title => l(:title_delete)) %> <% else %> <%= link_to(image_tag('delete.png'), delete_dmsf_path(:id => project, :folder_id => subfolder), diff --git a/app/views/dmsf/_file.html.erb b/app/views/dmsf/_file.html.erb index c2f81af7..f822b055 100644 --- a/app/views/dmsf/_file.html.erb +++ b/app/views/dmsf/_file.html.erb @@ -3,7 +3,7 @@ # # Redmine plugin for Document Management System "Features" # -# Copyright (C) 2011-15 Karel Pičman +# Copyright (C) 2011-16 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 @@ -20,8 +20,6 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. %> -<% locked_for_user = file.locked_for_user? %> -<% locked = file.locked? %> <% wf = DmsfWorkflow.find_by_id(file.last_revision.dmsf_workflow_id) %> <%= check_box_tag(name, id, false, @@ -30,7 +28,7 @@ <% file_view_url = url_for({:controller => :dmsf_files, :action => 'view', :id => file}) %> <%= link_to(h(title), file_view_url, - :target => "_blank", + :target => '_blank', :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_view_url}") %> @@ -39,7 +37,7 @@ <%= number_to_human_size(file.last_revision.size) %> <%= format_time(file.last_revision.updated_at) %> - <% if locked_for_user %> + <% if file.locked_for_user? %> <% if file.lock.reverse[0].user %> <%= link_to(image_tag(link ? 'locked_gray.png' : 'locked.png', :plugin => 'redmine_dmsf'), user_path(file.lock.reverse[0].user), @@ -48,7 +46,7 @@ <%= content_tag(:span, image_tag(link ? 'locked_gray.png' : 'locked.png', :plugin => 'redmine_dmsf'), :title => l(:notice_account_unknown_email)) %> <% end %> - <% elsif locked %> + <% elsif file.locked? %> <%= content_tag(:span, image_tag(link ? 'lockedbycurrent_gray.png' : 'lockedbycurrent.png', :plugin => 'redmine_dmsf'), :title => l(:title_locked_by_you)) %> <% end %> @@ -69,106 +67,111 @@ <% end %> <%= h(file.last_revision.user) %> - <% if @file_manipulation_allowed || @file_approval_allowed %> - <% if @file_manipulation_allowed %> - <% unless locked %> - <%= link_to(image_tag('filedetails.png', :plugin => 'redmine_dmsf'), - dmsf_file_path(:id => file), - :title => l(:link_details, :title => h(file.last_revision.title))) %> + + <% if @file_manipulation_allowed %> + <%= link_to(image_tag('filedetails.png', :plugin => 'redmine_dmsf'), + dmsf_file_path(:id => file), + :title => l(:link_details, :title => h(file.last_revision.title))) %> + <% unless file.locked_for_user? %> + <% if !file.locked? %> <%= link_to(image_tag('lock.png', :plugin => 'redmine_dmsf'), lock_dmsf_files_path(:id => file), :title => l(:title_lock_file)) %> - <% if file.notification %> - <%= link_to(image_tag('notify.png', :plugin => 'redmine_dmsf'), - notify_deactivate_dmsf_files_path(:id => file), - :title => l(:title_notifications_active_deactivate)) %> - <% else %> - <%= link_to(image_tag('notifynot.png', :plugin => 'redmine_dmsf'), - notify_activate_dmsf_files_path(:id => file), - :title => l(:title_notifications_not_active_activate)) %> - <% end %> - <% if link %> - <%= link_to(image_tag('delete.png'), - dmsf_link_path(link), - :data => {:confirm => l(:text_are_you_sure)}, - :method => :delete, - :title => l(:title_delete)) %> - <% else %> - <% if @file_delete_allowed %> - <%= link_to(image_tag('delete.png'), - dmsf_file_path(:id => file), - :data => {:confirm => l(:text_are_you_sure)}, - :method => :delete, - :title => l(:title_delete)) unless locked_for_user %> - <% else %> - - <% end %> - <% end %> + <% elsif file.unlockable? %> + <%= link_to(image_tag('unlock.png', :plugin => 'redmine_dmsf'), + unlock_dmsf_files_path(:id => file), + :title => l(:title_unlock_file))%> <% else %> - <% if (!locked_for_user || @force_file_unlock_allowed) && file.unlockable? %> - <%= link_to(image_tag('unlock.png', :plugin => 'redmine_dmsf'), - unlock_dmsf_files_path(:id => file), - :title => l(:title_unlock_file))%> + <% end %> + <% if file.notification %> + <%= link_to(image_tag('notify.png', :plugin => 'redmine_dmsf'), + notify_deactivate_dmsf_files_path(:id => file), + :title => l(:title_notifications_active_deactivate)) %> + <% else %> + <%= link_to(image_tag('notifynot.png', :plugin => 'redmine_dmsf'), + notify_activate_dmsf_files_path(:id => file), + :title => l(:title_notifications_not_active_activate)) %> + <% end %> + <% if link %> + <%= link_to(image_tag('delete.png'), + dmsf_link_path(link), + :data => {:confirm => l(:text_are_you_sure)}, + :method => :delete, + :title => l(:title_delete)) %> + <% else %> + <% if @file_delete_allowed %> + <%= link_to(image_tag('delete.png'), + dmsf_file_path(:id => file), + :data => {:confirm => l(:text_are_you_sure)}, + :method => :delete, + :title => l(:title_delete)) unless file.locked_for_user? %> <% else %> - <% end %> + <% end %> + <% end %> + <% else %> + <% if @force_file_unlock_allowed && file.unlockable? %> + <%= link_to(image_tag('unlock.png', :plugin => 'redmine_dmsf'), + unlock_dmsf_files_path(:id => file), + :title => l(:title_unlock_file))%> + <% else %> - - <% end %> + <% end %> + + <% end %> - <% if @file_approval_allowed %> - <% case file.last_revision.workflow %> - <% when DmsfWorkflow::STATE_WAITING_FOR_APPROVAL %> - <% if wf %> - <% assignments = wf.next_assignments(file.last_revision.id) %> - <% index = assignments.find_index{|assignment| assignment.user_id == User.current.id} if assignments %> - <% if index %> - <%= link_to(image_tag('waiting_for_approval.png', :plugin => 'redmine_dmsf'), - action_dmsf_workflow_path( - :project_id => project.id, - :id => wf.id, - :dmsf_workflow_step_assignment_id => assignments[index].id, - :dmsf_file_revision_id => file.last_revision.id), - :title => l(:title_waiting_for_approval), - :remote => true) %> - <% else %> - <%= content_tag(:span, image_tag('waiting_for_approval.png', :plugin => 'redmine_dmsf'), - :title => "#{l(:label_dmsf_wokflow_action_approve)} #{l(:label_dmsf_wokflow_action_reject)} #{l(:label_dmsf_wokflow_action_delegate)}") %> - <% end %> - <% else %> + <% end %> + <% if @file_approval_allowed %> + <% case file.last_revision.workflow %> + <% when DmsfWorkflow::STATE_WAITING_FOR_APPROVAL %> + <% if wf %> + <% assignments = wf.next_assignments(file.last_revision.id) %> + <% index = assignments.find_index{|assignment| assignment.user_id == User.current.id} if assignments %> + <% if index %> + <%= link_to(image_tag('waiting_for_approval.png', :plugin => 'redmine_dmsf'), + action_dmsf_workflow_path( + :project_id => project.id, + :id => wf.id, + :dmsf_workflow_step_assignment_id => assignments[index].id, + :dmsf_file_revision_id => file.last_revision.id), + :title => l(:title_waiting_for_approval), + :remote => true) %> + <% else %> <%= content_tag(:span, image_tag('waiting_for_approval.png', :plugin => 'redmine_dmsf'), :title => "#{l(:label_dmsf_wokflow_action_approve)} #{l(:label_dmsf_wokflow_action_reject)} #{l(:label_dmsf_wokflow_action_delegate)}") %> <% end %> - <% when DmsfWorkflow::STATE_APPROVED %> - <%= content_tag(:span, image_tag('approved.png', :plugin => 'redmine_dmsf'), - :title => l(:title_approved)) %> - <% when DmsfWorkflow::STATE_ASSIGNED %> - <% if User.current && (file.last_revision.dmsf_workflow_assigned_by == User.current.id) && wf %> - <%= link_to(image_tag('assigned.png', :plugin => 'redmine_dmsf'), - start_dmsf_workflow_path( - :id => file.last_revision.dmsf_workflow_id, - :dmsf_file_revision_id => file.last_revision.id), - :title => l(:label_dmsf_wokflow_action_start)) %> - <% else %> - <%= content_tag(:span, image_tag('assigned.png', :plugin => 'redmine_dmsf'), - title => l(:label_dmsf_wokflow_action_start)) %> - <% end %> - <% when DmsfWorkflow::STATE_REJECTED %> + <% else %> + <%= content_tag(:span, image_tag('waiting_for_approval.png', :plugin => 'redmine_dmsf'), + :title => "#{l(:label_dmsf_wokflow_action_approve)} #{l(:label_dmsf_wokflow_action_reject)} #{l(:label_dmsf_wokflow_action_delegate)}") %> + <% end %> + <% when DmsfWorkflow::STATE_APPROVED %> + <%= content_tag(:span, image_tag('approved.png', :plugin => 'redmine_dmsf'), + :title => l(:title_approved)) %> + <% when DmsfWorkflow::STATE_ASSIGNED %> + <% if User.current && (file.last_revision.dmsf_workflow_assigned_by == User.current.id) && wf %> + <%= link_to(image_tag('assigned.png', :plugin => 'redmine_dmsf'), + start_dmsf_workflow_path( + :id => file.last_revision.dmsf_workflow_id, + :dmsf_file_revision_id => file.last_revision.id), + :title => l(:label_dmsf_wokflow_action_start)) %> + <% else %> <%= content_tag(:span, image_tag('assigned.png', :plugin => 'redmine_dmsf'), - :title => l(:title_rejected)) %> - <% else %> - <% if @workflows_available %> - <%= link_to(image_tag('none.png', :plugin => 'redmine_dmsf'), - assign_dmsf_workflow_path( - :project_id => project.id, - :dmsf_file_revision_id => file.last_revision.id), - :title => l(:label_dmsf_wokflow_action_assign), - :remote => true) %> - <% end %> - <% end %> - <% end %> + title => l(:label_dmsf_wokflow_action_start)) %> + <% end %> + <% when DmsfWorkflow::STATE_REJECTED %> + <%= content_tag(:span, image_tag('assigned.png', :plugin => 'redmine_dmsf'), + :title => l(:title_rejected)) %> + <% else %> + <% if @workflows_available && !file.locked_for_user? %> + <%= link_to(image_tag('none.png', :plugin => 'redmine_dmsf'), + assign_dmsf_workflow_path( + :project_id => project.id, + :dmsf_file_revision_id => file.last_revision.id), + :title => l(:label_dmsf_wokflow_action_assign), + :remote => true) %> + <% end %> + <% end %> <% end %> - <%= link_to(h(title), - link.external_url, - :target => "_blank", - :class => 'icon icon-link') %> -
    <%= link.external_url %>
    + <%= link_to(h(title), + link.external_url, + :target => '_blank', + :class => 'icon icon-link') %> +
    + <%= link.external_url %> +
    <%= format_time(link.updated_at) %> <%= h(link.user) %> + + + <% if @file_manipulation_allowed %> - <% if @file_manipulation_allowed %> - - - - <%= link_to(image_tag('delete.png'), - dmsf_link_path(link), - :data => {:confirm => l(:text_are_you_sure)}, - :method => :delete, - :title => l(:title_delete)) %> - <% else %> - - - - - <% end %> - <% end %> + <%= link_to(image_tag('delete.png'), + dmsf_link_path(link), + :data => {:confirm => l(:text_are_you_sure)}, + :method => :delete, + :title => l(:title_delete)) %> + <% else %> + + <% end %> <%= h(file.last_revision.user) %> - <% if @file_manipulation_allowed %> - <%= link_to(image_tag('restore.png', :plugin => 'redmine_dmsf'), - restore_dmsf_file_path(:id => file), - :title => l(:title_restore)) %> - <%= link_to(image_tag('rev_delete.png', :plugin => 'redmine_dmsf'), - dmsf_file_path(:id => file, :commit => 'yes'), - :data => {:confirm => l(:text_are_you_sure)}, - :method => :delete, - :title => l(:title_delete)) %> + <% if @file_manipulation_allowed %> + <% if link %> + <%= link_to(image_tag('restore.png', :plugin => 'redmine_dmsf'), + restore_dmsf_link_path(:id => link), + :title => l(:title_restore)) %> + <%= link_to(image_tag('rev_delete.png', :plugin => 'redmine_dmsf'), + dmsf_link_path(:id => link, :commit => 'yes'), + :data => {:confirm => l(:text_are_you_sure)}, + :method => :delete, + :title => l(:title_delete)) %> + <% else # file %> + <%= link_to(image_tag('restore.png', :plugin => 'redmine_dmsf'), + restore_dmsf_file_path(:id => file), + :title => l(:title_restore)) %> + <%= link_to(image_tag('rev_delete.png', :plugin => 'redmine_dmsf'), + dmsf_file_path(:id => file, :commit => 'yes'), + :data => {:confirm => l(:text_are_you_sure)}, + :method => :delete, + :title => l(:title_delete)) %> + <% end %> <% end %> <%= check_box_tag(name, id, false, +<%= check_box_tag(name, id, false, :title => l(:title_check_for_zip_download_or_email), :id => "subfolder_#{id}") %> + <%= link_to(h(title), dmsf_folder_path(:id => project, :folder_id => subfolder), :class => 'icon icon-folder') %> @@ -36,7 +36,7 @@ <% end %> <%= format_time(subfolder.modified) if subfolder %> +<%= format_time(subfolder.modified) if subfolder %> <% if locked_for_user %> <% if subfolder.lock.reverse[0].user %> <%= link_to(image_tag(link ? 'locked_gray.png' : 'locked.png', :plugin => 'redmine_dmsf'), @@ -54,7 +54,7 @@ <%= h(subfolder.user) if subfolder %> + <% if @folder_manipulation_allowed %> <% unless locked_for_user %> <%= link_to(image_tag('edit.png'), diff --git a/app/views/dmsf/_dir_trash.html.erb b/app/views/dmsf/_dir_trash.html.erb index 3867abbf..6abda6b2 100644 --- a/app/views/dmsf/_dir_trash.html.erb +++ b/app/views/dmsf/_dir_trash.html.erb @@ -1,4 +1,4 @@ -<%#= +<% # encoding: utf-8 # # Redmine plugin for Document Management System "Features" @@ -20,9 +20,9 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. %> -<%= check_box_tag(name, id, false, +<%= check_box_tag(name, id, false, :title => l(:title_check_for_restore_or_delete), :id => "subfolder_#{id}") %> + <%= content_tag(:span, h(title), :title => h(title), :class => 'icon icon-folder') %> @@ -33,13 +33,13 @@ <% end %> + <%= format_time(subfolder.modified) if subfolder %> <%= h(subfolder.user) %> + <% if @folder_manipulation_allowed %> <%= link_to(image_tag('restore.png', :plugin => 'redmine_dmsf'), restore_dmsf_path(:id => project, :folder_id => subfolder), diff --git a/app/views/dmsf/_file.html.erb b/app/views/dmsf/_file.html.erb index f822b055..e4e36679 100644 --- a/app/views/dmsf/_file.html.erb +++ b/app/views/dmsf/_file.html.erb @@ -1,4 +1,4 @@ -<%#= +<% # encode: utf-8 # # Redmine plugin for Document Management System "Features" @@ -22,9 +22,9 @@ <% wf = DmsfWorkflow.find_by_id(file.last_revision.dmsf_workflow_id) %> -<%= check_box_tag(name, id, false, +<%= check_box_tag(name, id, false, :title => l(:title_check_for_zip_download_or_email), :id => "file_#{id}") %> + <% file_view_url = url_for({:controller => :dmsf_files, :action => 'view', :id => file}) %> <%= link_to(h(title), file_view_url, @@ -35,7 +35,7 @@
    <%= h(link ? link.path : file.display_name) %>
    <%= number_to_human_size(file.last_revision.size) %> + <%= format_time(file.last_revision.updated_at) %> <% if file.locked_for_user? %> <% if file.lock.reverse[0].user %> @@ -67,7 +67,7 @@ <% end %> <%= h(file.last_revision.user) %> + <% if @file_manipulation_allowed %> <%= link_to(image_tag('filedetails.png', :plugin => 'redmine_dmsf'), dmsf_file_path(:id => file), diff --git a/app/views/dmsf/_file_trash.html.erb b/app/views/dmsf/_file_trash.html.erb index 3f0c9687..3a7f2d41 100644 --- a/app/views/dmsf/_file_trash.html.erb +++ b/app/views/dmsf/_file_trash.html.erb @@ -1,4 +1,4 @@ -<%#= +<% # encoding: utf-8 # # Redmine plugin for Document Management System "Features" @@ -20,16 +20,16 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. %> -<%= check_box_tag(name, id, false, +<%= check_box_tag(name, id, false, :title => l(:title_check_for_restore_or_delete), :id => "file_#{id}") %> + <%= 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) %> + <%= format_time(file.last_revision.updated_at) %> <%= file.last_revision.version %> <%= h(file.last_revision.user) %> + <% if @file_manipulation_allowed %> <% if link %> <%= link_to(image_tag('restore.png', :plugin => 'redmine_dmsf'), diff --git a/app/views/dmsf/_path.html.erb b/app/views/dmsf/_path.html.erb index 1534f402..0b19ba3d 100644 --- a/app/views/dmsf/_path.html.erb +++ b/app/views/dmsf/_path.html.erb @@ -17,14 +17,15 @@ # # 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 folder %> <%= link_to l(:link_documents), dmsf_folder_path(:id => @project) %> <% folder.dmsf_path.each do |path_element| %> / - <% if !filename && path_element == folder.dmsf_path.last %> + <% if filename.blank? && (path_element == folder.dmsf_path.last) %> <%= h(path_element.title) %> <% else %> <%= link_to h(path_element.title), dmsf_folder_path(:id => @project, :folder_id => path_element) %> @@ -37,4 +38,7 @@ / <%= h(filename) %> <% end %> -

    + <% if title %> + » <%= title %> + <% end %> + \ No newline at end of file diff --git a/app/views/dmsf/_url.html.erb b/app/views/dmsf/_url.html.erb index 28bfd7e9..921a51d6 100644 --- a/app/views/dmsf/_url.html.erb +++ b/app/views/dmsf/_url.html.erb @@ -1,4 +1,4 @@ -<%#= +<% # encoding: utf-8 # # Redmine plugin for Document Management System "Features" @@ -20,8 +20,8 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. %> -
    + <%= link_to(h(title), link.external_url, :target => '_blank', @@ -31,11 +31,11 @@ <%= format_time(link.updated_at) %><%= format_time(link.updated_at) %> <%= h(link.user) %> + diff --git a/app/views/dmsf/_url_trash.html.erb b/app/views/dmsf/_url_trash.html.erb index 926e9bf7..02cab0a8 100644 --- a/app/views/dmsf/_url_trash.html.erb +++ b/app/views/dmsf/_url_trash.html.erb @@ -1,9 +1,9 @@ -<%#= +<% # encoding: utf-8 # # Redmine plugin for Document Management System "Features" # -# Copyright (C) 2011-15 Karel Pičman +# Copyright (C) 2011-16 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 @@ -20,9 +20,9 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. %> -<%= check_box_tag(name, id, false, +<%= check_box_tag(name, id, false, :title => l(:title_check_for_restore_or_delete)) %> + <%= link_to(h(title), link.external_url, :target => "_blank", @@ -30,11 +30,11 @@
    <%= link.external_url %>
    <%= format_time(link.updated_at) %><%= format_time(link.updated_at) %> <%= h(link.user) %> + <% if @file_manipulation_allowed %> <%= link_to(image_tag('restore.png', :plugin => 'redmine_dmsf'), restore_dmsf_link_path(:id => link), diff --git a/app/views/dmsf/edit.html.erb b/app/views/dmsf/edit.html.erb index 300e2c15..a3e336a9 100644 --- a/app/views/dmsf/edit.html.erb +++ b/app/views/dmsf/edit.html.erb @@ -1,4 +1,4 @@ -<%# +<% # encoding: utf-8 # # Redmine plugin for Document Management System "Features" @@ -19,7 +19,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. +%> <% html_title(l(:dmsf)) %> @@ -64,41 +65,34 @@ <% create = @pathfolder == @parent %> -<%= render(:partial => 'path', :locals => {:folder => @pathfolder, :filename => create ? l(:heading_new_folder) : nil}) %> +<%= render(:partial => 'path', + :locals => {:folder => @pathfolder, :filename => create ? l(:heading_new_folder) : nil, :title => nil}) %> <%= form_for(@folder, :url => {:action => create ? 'create' : 'save', :id => @project, :folder_id => @folder, :parent_id => @parent}, :html => {:method=>:post}) do |f| %> <%= error_messages_for(@folder) %> -
    -
    -
    -

    - <%= label_tag('dmsf_folder_title', "#{l(:label_title)}:") %> - <%= f.text_field(:title, :size => '32', :required => true) %> -

    -
    -
    -

    - <%= label_tag('', "#{l(:field_folder)}:") %> - <%= f.select(:dmsf_folder_id, - options_for_select(DmsfFolder.directory_tree(@project, @folder), - :selected => @parent? @parent.id : (@pathfolder.id if @pathfolder))) - %> -

    -
    -
    -

    - <%= label_tag('dmsf_folder_description', "#{l(:label_description)}:") %> +

    +

    + + <%= f.text_field(:title, :size => '32', :required => true) %> +

    +

    + <%= label_tag('', l(:field_folder)) %> + <%= f.select(:dmsf_folder_id, + options_for_select(DmsfFolder.directory_tree(@project, @folder), + :selected => @parent? @parent.id : (@pathfolder.id if @pathfolder))) + %> +

    +

    + <%= label_tag('dmsf_folder_description', l(:label_description)) %> + <%= f.text_area :description, :rows => 8, :class => 'wiki-edit' %>

    -
    - <%= f.text_area(:description, :rows => 15, :class => 'wiki-edit') %> -
    <% values = @folder ? @folder.custom_field_values : @parent ? @parent.custom_field_values : DmsfFolder.new(:project => @project).custom_field_values %> <% values.each do |value| %>

    <%= custom_field_tag_with_label(:dmsf_folder, value) %>

    - <% end %> -
    - <%= submit_tag(create ? l(:submit_create) : l(:submit_save)) %> + <% end %> +
    +

    <%= submit_tag(create ? l(:submit_create) : l(:submit_save)) %>

    <% end %> -<%= wikitoolbar_for 'dmsf_folder_description' %> +<%= wikitoolbar_for 'dmsf_folder_description' %> \ No newline at end of file diff --git a/app/views/dmsf/edit_root.html.erb b/app/views/dmsf/edit_root.html.erb index c16163be..3a6ec4a5 100644 --- a/app/views/dmsf/edit_root.html.erb +++ b/app/views/dmsf/edit_root.html.erb @@ -1,7 +1,11 @@ -<%# Redmine plugin for Document Management System "Features" +<% +# encoding: utf-8 # -# Copyright (C) 2011 Vít Jonáš -# Copyright (C) 2012 Daniel Munn +# Redmine plugin for Document Management System "Features" +# +# Copyright (C) 2011 Vít Jonáš +# Copyright (C) 2012 Daniel Munn +# Copyright (C) 2011-16 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 @@ -15,7 +19,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. +%> <% html_title(l(:dmsf)) %> @@ -35,17 +40,16 @@ <% end %> -<%= render(:partial => 'path', :locals => {:folder => nil, :filename => nil}) %> +<%= render(:partial => 'path', + :locals => {:folder => nil, :filename => nil, :title => nil}) %> <%= form_for(@project, :url => {:action => 'save_root', :id => @project}, :html => {:method=>:post}) do |f| %> -
    -

    - <%= label_tag('project_dmsf_description', "#{l(:label_description)}:") %> +

    +

    + <%= label_tag('project_dmsf_description', l(:label_description)) %> + <%= f.text_area(:dmsf_description, :rows => 8, :class => 'wiki-edit') %>

    -
    - <%= f.text_area(:dmsf_description, :rows => 15, :class => 'wiki-edit') %> -
    <%= submit_tag(l(:submit_save)) %> <% end %> diff --git a/app/views/dmsf/email_entries.html.erb b/app/views/dmsf/email_entries.html.erb index a4b180e0..ada3a4b9 100644 --- a/app/views/dmsf/email_entries.html.erb +++ b/app/views/dmsf/email_entries.html.erb @@ -1,8 +1,11 @@ -<%# Redmine plugin for Document Management System "Features" +<% +# encoding: utf-8 +# +# Redmine plugin for Document Management System "Features" # # Copyright (C) 2011 Vít Jonáš # Copyright (C) 2012 Daniel Munn -# Copyright (C) 2011-14 Karel Pičman +# Copyright (C) 2011-16 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 @@ -16,50 +19,50 @@ # # 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. +%> <% html_title(l(:dmsf)) %> -
    -
    +<%= render(:partial => 'path', + :locals => {:folder => @folder, :filename => nil, :title => l(:heading_send_documents_by_email)}) %> -<%= render(:partial => 'path', :locals => {:folder => @folder, :filename => nil}) %> - -

    <%= l(:heading_send_documents_by_email) %>

    - -<%= form_tag({:action => 'entries_email', :id => @project, :folder_id => @folder}, - { :method=>:post, :class => 'tabular'}) do %> +<%= form_tag({ :action => 'entries_email', :id => @project, :folder_id => @folder }, + { :method => :post }) do %> <%= hidden_field_tag('email[zipped_content]', @email_params[:zipped_content]) %> <%= hidden_field_tag('email[folders]', @email_params[:folders].to_json) %> <%= hidden_field_tag('email[files]', @email_params[:files].to_json) %> -
    +

    - <%= label_tag('', "#{l(:label_email_from)}:") %> - <%= h(Setting.mail_from) %> + <%= label_tag('', l(:label_email_from)) %> + <%= text_field_tag('email[from]', h(Setting.mail_from), :style => 'width: 90%;', :disabled => true) %>

    - <%= label_tag('email[to]', "#{l(:label_email_to)}:") %> + <%= label_tag('email[to]', l(:label_email_to)) %> <%= text_field_tag('email[to]', @email_params['to'], :style => 'width: 90%;') %>

    - <%= label_tag('email[cc]', "#{l(:label_email_cc)}:") %> + <%= label_tag('email[cc]', l(:label_email_cc)) %> <%= text_field_tag('email[cc]', @email_params['cc'], :style => 'width: 90%;') %>

    - <%= label_tag('email[subject]', "#{l(:label_email_subject)}:") %> + <%= label_tag('email[subject]', l(:label_email_subject)) %> <%= text_field_tag('email[subject]', @email_params['subject'], :style => 'width: 90%;') %>

    - <%= label_tag('', "#{l(:label_email_documents)}:") %> - <%= link_to 'Documents.zip', download_email_entries_path(:id => @project, :folder_id => @folder, :path => @email_params[:zipped_content]) %> - <%= l(:label_or) %> <%= check_box_tag('email[links_only]') %> <%= l(:label_links_only) %> + <%= label_tag('', l(:label_email_documents)) %> + + <%= link_to 'Documents.zip', download_email_entries_path(:id => @project, :folder_id => @folder, :path => @email_params[:zipped_content]) %> + <%= l(:label_or) %> + <%= check_box_tag('email[links_only]') %> <%= l(:label_links_only) %> +

    - <%= label_tag('email[body]', "#{l(:label_email_body)}:") %> - <%= text_area_tag('email[body]', @email_params['body'], :rows=> '20', :style => 'width: 90%;') %> -

    -

    <%= submit_tag(l(:label_email_send)) %>

    + <%= label_tag('email[body]', l(:label_email_body)) %> + <%= text_area_tag('email[body]', @email_params['body'], :rows => '20', :style => 'width: 90%;') %> +

    +

    <%= submit_tag(l(:label_email_send)) %>

    <% end %> -<%= wikitoolbar_for 'email_body' %> +<%= wikitoolbar_for 'email_body' %> \ No newline at end of file diff --git a/app/views/dmsf/show.html.erb b/app/views/dmsf/show.html.erb index c1a44069..4a8c91ba 100644 --- a/app/views/dmsf/show.html.erb +++ b/app/views/dmsf/show.html.erb @@ -1,4 +1,4 @@ -<%#= +<% # encoding: utf-8 # # Redmine plugin for Document Management System "Features" @@ -71,7 +71,8 @@ :title => l(:link_trash_bin), :class => 'icon icon-del') if @trash_visible %>
    -<%= render(:partial => 'path', :locals => {:folder => @folder, :filename => nil}) %> +<%= render(:partial => 'path', + :locals => {:folder => @folder, :filename => nil, :title => nil}) %>
    @@ -98,11 +99,12 @@ :dmsf_folder, CustomValue.new(:custom_field_id => params[:custom_field_id].present? ? params[:custom_field_id] : values.first.custom_field_id, :value => params[:custom_value])) %>
    - <% end %> - + <% end %> +
    +
    - @@ -189,7 +191,8 @@ <% end %> -
    + <%= l(:link_title) %>
    +
    + <% end %> \ No newline at end of file diff --git a/app/views/dmsf_files_copy/new.html.erb b/app/views/dmsf_files_copy/new.html.erb index a6252801..dd1b3270 100644 --- a/app/views/dmsf_files_copy/new.html.erb +++ b/app/views/dmsf_files_copy/new.html.erb @@ -22,10 +22,6 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. %> - -<%= stylesheet_link_tag 'select2.min.css', :plugin => 'redmine_dmsf' %> -<%= javascript_include_tag 'select2.min.js', :plugin => 'redmine_dmsf' %> - <% html_title(l(:dmsf)) %> <%= render(:partial => '/dmsf/path', @@ -58,16 +54,15 @@ <% end %> \ No newline at end of file diff --git a/app/views/dmsf_folders_copy/new.html.erb b/app/views/dmsf_folders_copy/new.html.erb index d3c7beca..57e6f3f9 100644 --- a/app/views/dmsf_folders_copy/new.html.erb +++ b/app/views/dmsf_folders_copy/new.html.erb @@ -22,10 +22,6 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. %> - -<%= stylesheet_link_tag 'select2.min.css', :plugin => 'redmine_dmsf' %> -<%= javascript_include_tag 'select2.min.js', :plugin => 'redmine_dmsf' %> - <% html_title(l(:dmsf)) %> <%= render(:partial => '/dmsf/path', @@ -51,11 +47,10 @@ <% end %> \ No newline at end of file diff --git a/app/views/dmsf_links/_form.html.erb b/app/views/dmsf_links/_form.html.erb index cf661626..d59727ad 100644 --- a/app/views/dmsf_links/_form.html.erb +++ b/app/views/dmsf_links/_form.html.erb @@ -80,13 +80,12 @@

    - <%= f.text_field :name, - :maxlength => 255, :required => true, :label => l(:label_link_name) %> + <%= f.text_field :name, :required => true, :label => l(:label_link_name) %>

    <%= f.submit l(:button_create) %>

    @@ -96,14 +95,21 @@ $('#dmsf_link_target_project_id').change(function () { $('#content').load("<%= url_for(:action => 'new', :project_id => @project.id, :reload => true) %>", $('#new_dmsf_link').serialize()); }); + $('#dmsf_link_target_folder_id').change(function () { $('#content').load("<%= url_for(:action => 'new', :project_id => @project.id) %>", $('#new_dmsf_link').serialize()); }); + $('#dmsf_link_target_file_id').change(function () { $('#content').load("<%= url_for(:action => 'new', :project_id => @project.id) %>", $('#new_dmsf_link').serialize()); }); + $("input[name=external_link]:radio").change(function(){ $("#link_internal").toggle(); $("#link_external").toggle(); }); - + + $('#dmsf_link_target_project_id').select2(); + $('#dmsf_link_target_folder_id').select2(); + $('#dmsf_link_target_file_id').select2(); + \ No newline at end of file diff --git a/app/views/dmsf_links/new.html.erb b/app/views/dmsf_links/new.html.erb index 011e1b52..82146124 100644 --- a/app/views/dmsf_links/new.html.erb +++ b/app/views/dmsf_links/new.html.erb @@ -1,6 +1,9 @@ -<%# Redmine plugin for Document Management System "Features" +<% +# encoding: utf-8 # -# Copyright (C) 2014 Karel Pičman +# Redmine plugin for Document Management System "Features" +# +# Copyright (C) 2011-16 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,6 +17,7 @@ # # 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. +%> <%= render 'form' %> \ No newline at end of file diff --git a/app/views/dmsf_workflows/new.html.erb b/app/views/dmsf_workflows/new.html.erb index 8e170668..2c7b1cf8 100644 --- a/app/views/dmsf_workflows/new.html.erb +++ b/app/views/dmsf_workflows/new.html.erb @@ -47,4 +47,6 @@ $('#dmsf_workflow_id').change(function () { $('#content').load("<%= url_for(:action => 'new', :project_id => @project) %>", $('#new_dmsf_workflow').serialize()); }); + + $('#dmsf_workflow_id').select2(); \ No newline at end of file diff --git a/lib/redmine_dmsf/hooks/base_view_hooks.rb b/lib/redmine_dmsf/hooks/base_view_hooks.rb index 130972c2..384375d3 100644 --- a/lib/redmine_dmsf/hooks/base_view_hooks.rb +++ b/lib/redmine_dmsf/hooks/base_view_hooks.rb @@ -1,6 +1,8 @@ +# encoding: utf-8 +# # Redmine plugin for Document Management System "Features" # -# Copyright (C) 2013 Karel Pičman +# Copyright (C) 2011-16 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,9 +25,11 @@ module RedmineDmsf class DmsfViewListener < Redmine::Hook::ViewListener def view_layouts_base_html_head(context={}) - "\n".html_safe + stylesheet_link_tag('dmsf', :plugin => :redmine_dmsf) + "\n".html_safe + stylesheet_link_tag('dmsf', :plugin => :redmine_dmsf) + + "\n".html_safe + stylesheet_link_tag('select2.min.css', :plugin => :redmine_dmsf) + + "\n".html_safe + javascript_include_tag('select2.min.js', :plugin => :redmine_dmsf) end end end -end +end \ No newline at end of file From 0258264dff951eed1340708933200b432a4e5689 Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Thu, 10 Mar 2016 13:43:14 +0100 Subject: [PATCH 37/94] Upload control selector changed --- app/views/dmsf_upload/_multi_upload.html.erb | 58 ++++++++------------ config/locales/cs.yml | 12 ++-- config/locales/de.yml | 12 ++-- config/locales/en.yml | 12 ++-- config/locales/es.yml | 12 ++-- config/locales/fr.yml | 12 ++-- config/locales/ja.yml | 12 ++-- config/locales/pl.yml | 12 ++-- config/locales/pt-BR.yml | 12 ++-- config/locales/ru.yml | 10 ++-- config/locales/sl.yml | 12 ++-- config/locales/zh-TW.yml | 12 ++-- config/locales/zh.yml | 12 ++-- 13 files changed, 83 insertions(+), 117 deletions(-) diff --git a/app/views/dmsf_upload/_multi_upload.html.erb b/app/views/dmsf_upload/_multi_upload.html.erb index a2ccc21a..1ea277b2 100644 --- a/app/views/dmsf_upload/_multi_upload.html.erb +++ b/app/views/dmsf_upload/_multi_upload.html.erb @@ -24,46 +24,36 @@
    <%= form_tag({:controller => 'dmsf_upload', :action => 'upload_files', :id => @project, :folder_id => @folder}, - :id => 'uploadform', :method=>:post, :multipart => true) do %> - <% if Setting.attachment_max_size.to_i >= 102400 %> -
    - <%= l(:label_file_size) %>: - -
    - <% end %> -

    <%= l(:heading_file_upload) %>

    -
    - - <% max_file_upload = Setting.plugin_redmine_dmsf['dmsf_max_file_upload'].to_i %> - <%= l(:note_uploaded_maximum_files_at_once, :number => max_file_upload) if max_file_upload > 0 %> - <%= l(:note_upload_files_greater_than_two_gb) if Setting.attachment_max_size.to_i >= 2097151 %> - -
    + :id => 'uploadform', :method => :post, :multipart => true) do %> +
    + +
    +

    <%= l(:label_upload) %>

    - + <%= render :partial => 'attachments/form' %>

    - <%= submit_tag(l(:submit_upload_files)) %> + <%= submit_tag l(:label_upload) %>
    <% end %>
    -<% +<% sUrl = "jquery.dataTables/#{I18n.locale.to_s.downcase}.json" sUrl = 'jquery.dataTables/en.json' unless File.exist?(sUrl) %> -<% content_for :header_tags do %> +<% content_for :header_tags do %> <%= javascript_include_tag 'bowser.min.js', :plugin => 'redmine_dmsf' %> - <%= stylesheet_link_tag 'jquery.dataTables/jquery-ui.dataTables.css', :plugin => 'redmine_dmsf' %> + <%= stylesheet_link_tag 'jquery.dataTables/jquery-ui.dataTables.css', :plugin => 'redmine_dmsf' %> <%= javascript_include_tag 'jquery.dataTables/jquery.dataTables.min.js', :plugin => 'redmine_dmsf' %> - <% end %> diff --git a/app/views/dmsf/trash.html.erb b/app/views/dmsf/trash.html.erb index b34082b4..9b9a232c 100644 --- a/app/views/dmsf/trash.html.erb +++ b/app/views/dmsf/trash.html.erb @@ -27,13 +27,13 @@
    <%= textilizable(@folder ? @folder.description : @project.dmsf_description) %> -
    +
    <%= error_messages_for('dmsf_workflow') %> <%= form_tag({:action => :entries_operation, :id => @project, :folder_id => @folder}, :method => :post, - :class => 'dmfs_entries', :id => 'entries_form') do %> + :class => 'dmsf_entries', :id => 'entries_form') do %> <%= hidden_field_tag('action') %>
    <% if @file_manipulation_allowed && @folder_manipulation_allowed %> @@ -41,7 +41,7 @@ <% if @file_delete_allowed%> <%= submit_tag(l(:button_delete), :title => l(:title_delete_checked), :name => 'destroy_entries') %> <% end %> - <% end %> + <% end %>
    @@ -63,40 +63,40 @@ - - <% @subfolders.each do |subfolder| %> + + <% @subfolders.each do |subfolder| %> - <%= render(:partial => 'dir_trash', - :locals => { - :project => @project, - :subfolder => subfolder, + <%= render(:partial => 'dir_trash', + :locals => { + :project => @project, + :subfolder => subfolder, :link => nil, - :id => subfolder.id, - :name => 'subfolders[]', + :id => subfolder.id, + :name => 'subfolders[]', :title => subfolder.title }) %> - <% end %> - <% @dir_links.each do |link| %> + <% end %> + <% @dir_links.each do |link| %> - <%= render(:partial => 'dir_trash', - :locals => { - :project => link.target_project, - :subfolder => link.target_folder, + <%= render(:partial => 'dir_trash', + :locals => { + :project => link.target_project, + :subfolder => link.target_folder, :link => link, - :id => link.id, - :name => 'dir_links[]', + :id => link.id, + :name => 'dir_links[]', :title => link.name }) %> - <% end %> + <% end %> <% @files.each do |file| %> - <% unless file.last_revision %> + <% unless file.last_revision %> <% Rails.logger.error "Error: dmsf_file id #{file.id} has no revision!" %> <% next %> - <% end %> + <% end %> - <%= render(:partial => 'file_trash', :locals => { - :project => @project, - :file => file, + <%= render(:partial => 'file_trash', :locals => { + :project => @project, + :file => file, :link => nil, :id => file.id, :name => 'files[]', @@ -104,14 +104,14 @@ <% end %> <% @file_links.each do |link| %> - <% unless link.target_file.last_revision %> + <% unless link.target_file.last_revision %> <% Rails.logger.error "Error: dmsf_file id #{link.target_id} has no revision!" %> <% next %> - <% end %> + <% end %> - <%= render(:partial => 'file_trash', :locals => { - :project => link.target_project, - :file => link.target_file, + <%= render(:partial => 'file_trash', :locals => { + :project => link.target_project, + :file => link.target_file, :link => link, :id => link.id, :name => 'file_links[]', @@ -132,24 +132,24 @@
    -<% end %> +<% end %> <% @@ -157,11 +157,11 @@ sUrl = 'jquery.dataTables/en.json' unless File.exist?(sUrl) %> -<% content_for :header_tags do %> - <%= stylesheet_link_tag 'jquery.dataTables/jquery-ui.dataTables.css', :plugin => 'redmine_dmsf' %> +<% content_for :header_tags do %> + <%= stylesheet_link_tag 'jquery.dataTables/jquery-ui.dataTables.css', :plugin => 'redmine_dmsf' %> <%= javascript_include_tag 'jquery.dataTables/jquery.dataTables.min.js', :plugin => 'redmine_dmsf' %> - <% end %> \ No newline at end of file diff --git a/assets/stylesheets/dmsf.css b/assets/stylesheets/dmsf.css index e4a7c917..de445d49 100644 --- a/assets/stylesheets/dmsf.css +++ b/assets/stylesheets/dmsf.css @@ -48,9 +48,9 @@ table.list td.dmsf_buttons { } table.list th.dmsf_checkbox, -table.list td.dmsf_checkbox { - width: 15px; - padding: 2px 0 0 0; +table.list td.dmsf_checkbox { + width: 15px; + padding: 2px 0 0 0; } table.list th.dmsf_checkbox input { @@ -61,7 +61,7 @@ table.list th.dmsf_checkbox div.DataTables_sort_wrapper { padding: 0; } -form.dmfs_entries { +form.dmsf_entries { margin-bottom: 10px; display: block; } @@ -69,14 +69,14 @@ form.dmfs_entries { div.dmsf_controls, div.dmsf_controls input, div.dmsf_controls select, -form.dmfs_entries #browser_filter.dataTables_filter, -form.dmfs_entries #browser_filter.dataTables_filter input{ +form.dmsf_entries #browser_filter.dataTables_filter, +form.dmsf_entries #browser_filter.dataTables_filter input{ font-size: 0.9em; } div.dmsf_filename { - padding: 0 10px 0 10px; - float: right; + padding: 0 10px 0 10px; + float: right; font-size: 0.8em; white-space: nowrap; } @@ -110,7 +110,7 @@ td.dmsf_workflow { } div.dmsf_upload_select { - float: right; + float: right; font-size: 0.9em; } @@ -129,7 +129,7 @@ tr.dmsf_workflow.locked a { color: #aaa; } margin-bottom: 10px; background-color:#f6f6f6; color:#505050; - line-height:1.5em; + line-height:1.5em; } .dmsf_revision_inner_box { @@ -142,7 +142,7 @@ tr.dmsf_workflow.locked a { color: #aaa; } line-height: 1.5em; color: #505050; margin-top: 5px; - padding-left: 10px; + padding-left: 10px; } div.dmsf_revision_box .ui-widget-header { @@ -151,11 +151,11 @@ div.dmsf_revision_box .ui-widget-header { .dmsf_log_header_box{ padding:6px; - margin-bottom: 10px; + margin-bottom: 10px; } .dmsf_log_header_left { width: 50%; - float: left; + float: left; } .dmsf_log_header_box label{ From 68c68cdf79d6d2463c86b87cf991efe6215d2e81 Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Wed, 30 Mar 2016 09:01:14 +0200 Subject: [PATCH 47/94] It doesn't work :-( --- lib/redmine_dmsf/webdav/resource_proxy.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/redmine_dmsf/webdav/resource_proxy.rb b/lib/redmine_dmsf/webdav/resource_proxy.rb index 1cd5998b..15b933d4 100644 --- a/lib/redmine_dmsf/webdav/resource_proxy.rb +++ b/lib/redmine_dmsf/webdav/resource_proxy.rb @@ -45,6 +45,13 @@ module RedmineDmsf end def authenticate(username, password) + # Bugfix: Current DAV4Rack (including production) authenticate against ALL requests + # Microsoft Web Client will not attempt any authentication (it'd seem) until it's acknowledged + # a completed OPTIONS request. Ideally this is a flaw with the controller, however as I'm not + # going to fork it to ensure compliance, checking the request method in the authentication + # seems the next best step, if the request method is OPTIONS return true, controller will simply + # call the options method within, which accesses nothing, just returns headers about dav env. + return true if @request.request_method.downcase == 'options' && (path == '/' || path.empty?) return false unless username && password User.current = User.try_to_login(username, password) return User.current && !User.current.anonymous? From 3a4d3e8b2b6dcf46341811ef5214d3a57f383c9f Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Tue, 12 Apr 2016 12:07:16 +0200 Subject: [PATCH 48/94] File Storage Directory does not change #522 --- Gemfile | 12 +-- app/controllers/dmsf_upload_controller.rb | 110 +++++++++++---------- app/models/dmsf_file.rb | 111 +++++++++++----------- app/models/dmsf_file_revision.rb | 22 ++--- 4 files changed, 132 insertions(+), 123 deletions(-) diff --git a/Gemfile b/Gemfile index e74c0d78..eed28897 100644 --- a/Gemfile +++ b/Gemfile @@ -1,5 +1,5 @@ # encoding: utf-8 -# +# # Redmine plugin for Document Management System "Features" # # Copyright (C) 2011 Vít Jonáš @@ -22,12 +22,12 @@ source 'https://rubygems.org' -gem 'rubyzip', '>= 1.0.0' -gem 'zip-zip' # Just to avoid 'cannot load such file -- zip/zip' error +gem 'rubyzip' +gem 'zip-zip' gem 'simple_enum' -gem 'uuidtools', '~> 2.1.1' -gem 'dav4rack', '~> 0.3.0' +gem 'uuidtools' +gem 'dav4rack' -group :xapian do +group :xapian do gem 'xapian-full-alaveteli', :require => false end \ No newline at end of file diff --git a/app/controllers/dmsf_upload_controller.rb b/app/controllers/dmsf_upload_controller.rb index 59d31f67..f472fdb5 100644 --- a/app/controllers/dmsf_upload_controller.rb +++ b/app/controllers/dmsf_upload_controller.rb @@ -1,5 +1,5 @@ # encoding: utf-8 -# +# # Redmine plugin for Document Management System "Features" # # Copyright (C) 2011 Vít Jonáš @@ -21,16 +21,16 @@ class DmsfUploadController < ApplicationController unloadable - + menu_item :dmsf - + before_filter :find_project before_filter :authorize before_filter :find_folder, :except => [:upload_file, :upload, :commit] - + helper :all helper :dmsf_workflows - + accept_api_auth :upload, :commit def upload_files @@ -38,7 +38,7 @@ class DmsfUploadController < ApplicationController @uploads = [] if uploaded_files && uploaded_files.is_a?(Hash) # standard file input uploads - uploaded_files.each_value do |uploaded_file| + uploaded_files.each_value do |uploaded_file| upload = DmsfUpload.create_from_uploaded_attachment(@project, @folder, uploaded_file) @uploads.push(upload) if upload end @@ -53,9 +53,9 @@ class DmsfUploadController < ApplicationController end end - # async single file upload handling - def upload_file - @tempfile = params[:file] + # async single file upload handling + def upload_file + @tempfile = params[:file] unless @tempfile.original_filename render_404 return @@ -72,21 +72,21 @@ class DmsfUploadController < ApplicationController rescue Exception => e Rails.logger.error e.message end - render :layout => nil, :json => { :jsonrpc => '2.0', - :error => { - :code => 103, - :message => l(:header_minimum_filesize), - :details => l(:error_minimum_filesize, - :file => @tempfile.original_filename.to_s) + render :layout => nil, :json => { :jsonrpc => '2.0', + :error => { + :code => 103, + :message => l(:header_minimum_filesize), + :details => l(:error_minimum_filesize, + :file => @tempfile.original_filename.to_s) } } else render :layout => false end end - + # REST API document upload - def upload + def upload unless request.content_type == 'application/octet-stream' render :nothing => true, :status => 406 return @@ -109,69 +109,69 @@ class DmsfUploadController < ApplicationController } end end - - def commit_files + + def commit_files commit_files_internal params[:commited_files] - end - + end + # REST API file commit def commit - attachments = params[:attachments] + 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| + 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 + commit_files_internal uploaded_files end private - + def commit_files_internal(commited_files) if commited_files && commited_files.is_a?(Hash) @files = [] failed_uploads = [] commited_files.each_value do |commited_file| name = commited_file[:name] - + new_revision = DmsfFileRevision.new file = DmsfFile.visible.find_file_by_name(@project, @folder, name) unless file link = DmsfLink.find_link_by_file_name(@project, @folder, name) file = link.target_file if link end - + unless file file = DmsfFile.new file.project = @project file.name = name file.folder = @folder - file.notification = Setting.plugin_redmine_dmsf[:dmsf_default_notifications].present? + file.notification = Setting.plugin_redmine_dmsf[:dmsf_default_notifications].present? new_revision.minor_version = 0 new_revision.major_version = 0 - else + else if file.last_revision last_revision = file.last_revision new_revision.source_revision = last_revision new_revision.major_version = last_revision.major_version - new_revision.minor_version = last_revision.minor_version + new_revision.minor_version = last_revision.minor_version else new_revision.minor_version = 0 new_revision.major_version = 0 end end - + if file.locked_for_user? failed_uploads.push(commited_file) next end commited_disk_filepath = "#{DmsfHelper.temp_dir}/#{commited_file[:disk_filename].gsub(/[\/\\]/,'')}" - + new_revision.file = file new_revision.user = User.current new_revision.name = name @@ -183,43 +183,49 @@ class DmsfUploadController < ApplicationController new_revision.major_version = commited_file[:custom_version_major].to_i new_revision.minor_version = commited_file[:custom_version_minor].to_i else - new_revision.increase_version(version, true) + new_revision.increase_version(version, true) end new_revision.mime_type = Redmine::MimeType.of(new_revision.name) new_revision.size = File.size(commited_disk_filepath) - - # Need to save file first to generate id for it in case of creation. - # File id is needed to properly generate revision disk filename + + # Need to save file first to generate id for it in case of creation. + # File id is needed to properly generate revision disk filename if commited_file[:dmsf_file_revision].present? commited_file[:dmsf_file_revision][:custom_field_values].each_with_index do |v, i| new_revision.custom_field_values[i].value = v[1] end end - + if new_revision.valid? && file.save new_revision.disk_filename = new_revision.new_storage_filename else failed_uploads.push(commited_file) next end - + if new_revision.save - new_revision.assign_workflow(commited_file[:dmsf_workflow_id]) - FileUtils.mv(commited_disk_filepath, new_revision.disk_file) - file.set_last_revision new_revision - @files.push(file) + new_revision.assign_workflow(commited_file[:dmsf_workflow_id]) + begin + FileUtils.mv(commited_disk_filepath, new_revision.disk_file) + file.set_last_revision new_revision + @files.push(file) + rescue Exception => e + Rails.logger.error e.message + flash[:error] = e.message + failed_uploads.push(file) + end else failed_uploads.push(commited_file) end end - unless @files.empty? - @files.each { |file| log_activity(file, 'uploaded') if file } + unless @files.empty? + @files.each { |file| log_activity(file, 'uploaded') if file } if (@folder && @folder.notification?) || (!@folder && @project.dmsf_notification?) begin recipients = DmsfMailer.get_notify_users(@project, @files) recipients.each do |u| DmsfMailer.files_updated(u, @project, @files).deliver - end + end if Setting.plugin_redmine_dmsf[:dmsf_display_notified_recipients] == '1' unless recipients.empty? to = recipients.collect{ |r| r.name }.first(DMSF_MAX_NOTIFICATION_RECEIVERS_INFO).join(', ') @@ -238,22 +244,22 @@ class DmsfUploadController < ApplicationController end respond_to do |format| format.js - format.api { - render_validation_errors(failed_uploads) unless failed_uploads.empty? + format.api { + render_validation_errors(failed_uploads) unless failed_uploads.empty? } format.html { redirect_to dmsf_folder_path(:id => @project, :folder_id => @folder) } end - + end - + def log_activity(file, action) Rails.logger.info "#{Time.now.strftime('%Y-%m-%d %H:%M:%S')} #{User.current.login}@#{request.remote_ip}/#{request.env['HTTP_X_FORWARDED_FOR']}: #{action} dmsf://#{file.project.identifier}/#{file.id}/#{file.last_revision.id}" end - + def find_folder - @folder = DmsfFolder.visible.find(params[:folder_id]) if params.keys.include?('folder_id') + @folder = DmsfFolder.visible.find(params[:folder_id]) if params.keys.include?('folder_id') rescue DmsfAccessError render_403 end - + end diff --git a/app/models/dmsf_file.rb b/app/models/dmsf_file.rb index d656734d..d8cd8deb 100644 --- a/app/models/dmsf_file.rb +++ b/app/models/dmsf_file.rb @@ -29,13 +29,13 @@ end class DmsfFile < ActiveRecord::Base unloadable - - include RedmineDmsf::Lockable + + include RedmineDmsf::Lockable belongs_to :project belongs_to :folder, :class_name => 'DmsfFolder', :foreign_key => 'dmsf_folder_id' belongs_to :deleted_by_user, :class_name => 'User', :foreign_key => 'deleted_by_user_id' - + has_many :revisions, -> { order("#{DmsfFileRevision.table_name}.major_version DESC, #{DmsfFileRevision.table_name}.minor_version DESC, #{DmsfFileRevision.table_name}.updated_at DESC") }, :class_name => 'DmsfFileRevision', :foreign_key => 'dmsf_file_id', :dependent => :destroy @@ -44,10 +44,10 @@ class DmsfFile < ActiveRecord::Base has_many :referenced_links, -> { where target_type: DmsfFile.model_name.to_s}, :class_name => 'DmsfLink', :foreign_key => 'target_id', :dependent => :destroy accepts_nested_attributes_for :revisions, :locks, :referenced_links, :project - + STATUS_DELETED = 1 STATUS_ACTIVE = 0 - + scope :visible, -> { where(:deleted => STATUS_ACTIVE) } scope :deleted, -> { where(:deleted => STATUS_DELETED) } @@ -61,27 +61,27 @@ class DmsfFile < ActiveRecord::Base existing_file = DmsfFile.visible.find_file_by_name(self.project, self.folder, self.name) errors.add(:name, l('activerecord.errors.messages.taken')) unless existing_file.nil? || existing_file.id == self.id - end + end acts_as_event :title => Proc.new { |o| o.name }, - :description => Proc.new { |o| - desc = Redmine::Search.cache_store.fetch("DmsfFile-#{o.id}") + :description => Proc.new { |o| + desc = Redmine::Search.cache_store.fetch("DmsfFile-#{o.id}") if desc - Redmine::Search.cache_store.delete("DmsfFile-#{o.id}") + Redmine::Search.cache_store.delete("DmsfFile-#{o.id}") else desc = o.description desc += ' / ' if o.description.present? && o.last_revision.comment.present? desc += o.last_revision.comment if o.last_revision.comment.present? - end + 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.table_name}.title", "#{DmsfFileRevision.table_name}.description", "#{DmsfFileRevision.table_name}.comment"], :project_key => 'project_id', - :date_column => "#{table_name}.updated_at" + :date_column => "#{table_name}.updated_at" before_create :default_values def default_values @@ -96,19 +96,22 @@ class DmsfFile < ActiveRecord::Base @@storage_path = nil def self.storage_path - unless @@storage_path.present? - @@storage_path = Setting.plugin_redmine_dmsf['dmsf_storage_directory'].strip if Setting.plugin_redmine_dmsf['dmsf_storage_directory'].present? - @@storage_path = Pathname(Redmine::Configuration['attachments_storage_path']).join('dmsf') if @@storage_path.blank? && Redmine::Configuration['attachments_storage_path'].present? - @@storage_path = Rails.root.join('files/dmsf').to_s if @@storage_path.blank? - Dir.mkdir(@@storage_path) unless File.exists?(@@storage_path) - end + path = Setting.plugin_redmine_dmsf['dmsf_storage_directory'].strip if Setting.plugin_redmine_dmsf['dmsf_storage_directory'].present? + path = Pathname(Redmine::Configuration['attachments_storage_path']).join('dmsf') if path.blank? && Redmine::Configuration['attachments_storage_path'].present? + path = Rails.root.join('files/dmsf').to_s if path.blank? + DmsfFile.storage_path = path if path != @@storage_path @@storage_path end # Lets introduce a write for storage path, that way we can also # better interact from test-cases etc - def self.storage_path=(obj) - @@storage_path = obj + def self.storage_path=(path) + begin + FileUtils.mkdir_p(path) unless File.exists?(path) + rescue Exception => e + Rails.logger.error e.message + end + @@storage_path = path end def self.find_file_by_name(project, folder, name) @@ -128,7 +131,7 @@ class DmsfFile < ActiveRecord::Base def set_last_revision(new_revision) @last_revision = new_revision end - + def deleted? self.deleted == STATUS_DELETED end @@ -141,7 +144,7 @@ class DmsfFile < ActiveRecord::Base end begin # Revisions and links of a deleted file SHOULD be deleted too - self.revisions.each { |r| r.delete(commit, true) } + self.revisions.each { |r| r.delete(commit, true) } if commit self.destroy else @@ -161,7 +164,7 @@ class DmsfFile < ActiveRecord::Base errors[:base] << l(:error_parent_folder) return false end - self.revisions.each { |r| r.restore } + self.revisions.each { |r| r.restore } self.deleted = STATUS_ACTIVE self.deleted_by_user = nil save @@ -172,8 +175,8 @@ class DmsfFile < ActiveRecord::Base end def description - self.last_revision ? self.last_revision.description : '' - end + self.last_revision ? self.last_revision.description : '' + end def version self.last_revision ? self.last_revision.version : '0' @@ -291,33 +294,33 @@ class DmsfFile < ActiveRecord::Base 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 - - if options[:offset] + project_ids = projects.collect(&:id) if projects + + if options[:offset] limit_options = ["dmsf_files.updated_at #{options[:before] ? '<' : '>'} ?", options[:offset]] end - + if options[:titles_only] columns = [searchable_options[:columns][1]] - else + else columns = searchable_options[:columns] end - + token_clauses = columns.collect{ |column| "(LOWER(#{column}) LIKE ?)" } - sql = (['(' + token_clauses.join(' OR ') + ')'] * tokens.size).join(options[:all_words] ? ' AND ' : ' OR ') + sql = (['(' + token_clauses.join(' OR ') + ')'] * tokens.size).join(options[:all_words] ? ' AND ' : ' OR ') find_options = [sql, * (tokens.collect {|w| "%#{w.downcase}%"} * token_clauses.size).sort] project_conditions = [] - project_conditions << Project.allowed_to_condition(user, :view_dmsf_files) + 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 = [] - + results = [] + scope = self.visible.joins(:project, :revisions) - scope = scope.limit(options[:limit]) unless options[:limit].blank? + scope = scope.limit(options[:limit]) unless options[:limit].blank? scope = scope.where(limit_options) unless limit_options.blank? - scope = scope.where(project_conditions.join(' AND ')) + scope = scope.where(project_conditions.join(' AND ')) results = scope.where(find_options).uniq.to_a if !options[:titles_only] && $xapian_bindings_available @@ -361,7 +364,7 @@ class DmsfFile < ActiveRecord::Base enquire.query = query matchset = enquire.mset(0, 1000) - if matchset + if matchset matchset.matches.each { |m| docdata = m.document.data{url} dochash = Hash[*docdata.scan(/(url|sample|modtime|author|type|size)=\/?([^\n\]]+)/).flatten] @@ -372,14 +375,14 @@ class DmsfFile < ActiveRecord::Base id_attribute = dmsf_attrs[0][1] if dmsf_attrs.length > 0 next if dmsf_attrs.length == 0 || id_attribute == 0 next unless results.select{|f| f.id.to_s == id_attribute}.empty? - + dmsf_file = DmsfFile.visible.where(limit_options).where(:id => id_attribute).first if dmsf_file - if user.allowed_to?(:view_dmsf_files, dmsf_file.project) && - (project_ids.blank? || (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 user.allowed_to?(:view_dmsf_files, dmsf_file.project) && + (project_ids.blank? || (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'] break if(!options[:limit].blank? && results.count >= options[:limit]) results << dmsf_file end @@ -389,14 +392,14 @@ class DmsfFile < ActiveRecord::Base end end end - + [results, results.count] end - + def self.search_result_ranks_and_ids(tokens, user = User.current, projects = nil, options = {}) r = self.search(tokens, projects, options, user)[0] r.map{ |f| [f.updated_at.to_i, f.id]} - end + end def display_name if self.name.length > 50 @@ -404,33 +407,33 @@ class DmsfFile < ActiveRecord::Base end self.name end - + def image? self.last_revision && !!(self.last_revision.disk_filename =~ /\.(bmp|gif|jpg|jpe|jpeg|png|svg)$/i) end - + def preview(limit) result = 'No preview available' if (self.last_revision.disk_filename =~ /\.(txt|ini|diff|c|cpp|php|csv|rb|h|erb|html|css|py)$/i) begin f = File.new(self.last_revision.disk_file) - f.each_line do |line| + f.each_line do |line| case f.lineno when 1 result = line when limit.to_i + 1 break else - result << line - end + result << line + end end rescue Exception => e result = e.message end - end + end result end - + def formatted_name(format) if self.last_revision self.last_revision.formatted_name(format) @@ -438,5 +441,5 @@ class DmsfFile < ActiveRecord::Base self.name end end - + end diff --git a/app/models/dmsf_file_revision.rb b/app/models/dmsf_file_revision.rb index 505a3aff..b11a257b 100644 --- a/app/models/dmsf_file_revision.rb +++ b/app/models/dmsf_file_revision.rb @@ -27,12 +27,12 @@ class DmsfFileRevision < ActiveRecord::Base belongs_to :folder, :class_name => 'DmsfFolder', :foreign_key => 'dmsf_folder_id' belongs_to :deleted_by_user, :class_name => 'User', :foreign_key => 'deleted_by_user_id' has_many :access, :class_name => 'DmsfFileRevisionAccess', :foreign_key => 'dmsf_file_revision_id', :dependent => :destroy - has_many :dmsf_workflow_step_assignment, :dependent => :destroy - accepts_nested_attributes_for :access, :dmsf_workflow_step_assignment, :file, :user - + has_many :dmsf_workflow_step_assignment, :dependent => :destroy + accepts_nested_attributes_for :access, :dmsf_workflow_step_assignment, :file, :user + STATUS_DELETED = 1 STATUS_ACTIVE = 0 - + scope :visible, -> { where(:deleted => STATUS_ACTIVE) } scope :deleted, -> { where(:deleted => STATUS_DELETED) } @@ -42,7 +42,7 @@ class DmsfFileRevision < ActiveRecord::Base :datetime => Proc.new {|o| o.updated_at }, :description => Proc.new {|o| o.comment }, :author => Proc.new {|o| o.user } - + acts_as_activity_provider :type => 'dmsf_file_revisions', :timestamp => "#{DmsfFileRevision.table_name}.updated_at", :author_key => "#{DmsfFileRevision.table_name}.user_id", @@ -51,11 +51,11 @@ class DmsfFileRevision < ActiveRecord::Base joins( "INNER JOIN #{DmsfFile.table_name} ON #{DmsfFileRevision.table_name}.dmsf_file_id = #{DmsfFile.table_name}.id " + "INNER JOIN #{Project.table_name} ON #{DmsfFile.table_name}.project_id = #{Project.table_name}.id"). - where("#{DmsfFile.table_name}.deleted = ?", STATUS_ACTIVE) + where("#{DmsfFile.table_name}.deleted = ?", STATUS_ACTIVE) - validates :title, :presence => true + validates :title, :presence => true validates_format_of :name, :with => DmsfFolder.invalid_characters, - :message => l(:error_contains_invalid_character) + :message => l(:error_contains_invalid_character) def project self.file.project if self.file @@ -136,7 +136,7 @@ class DmsfFileRevision < ActiveRecord::Base project_base = project.identifier.gsub(/[^\w\.\-]/,'_') storage_base << "/p_#{project_base}" end - Dir.mkdir(storage_base) unless File.exists?(storage_base) + FileUtils.mkdir_p(storage_base) unless File.exists?(storage_base) "#{storage_base}/#{self.disk_filename}" end @@ -249,7 +249,7 @@ class DmsfFileRevision < ActiveRecord::Base parts = self.version.split '.' parts.size == 2 ? parts[0].to_i * 1000 + parts[1].to_i : 0 end - + def formatted_name(format) return self.name if format.blank? if self.name =~ /(.*)(\..*)$/ @@ -257,7 +257,7 @@ class DmsfFileRevision < ActiveRecord::Base ext = $2 else filename = self.name - end + end format.sub!('%t', self.title) format.sub!('%f', filename) format.sub!('%d', self.updated_at.strftime('%Y%m%d%H%M%S')) From 8d58bbda2de44f079785da2574ce5052c6c74c35 Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Tue, 12 Apr 2016 14:31:27 +0200 Subject: [PATCH 49/94] Bug with "delegate approval step" #523 --- app/helpers/dmsf_workflows_helper.rb | 62 ++++++++++++++-------------- 1 file changed, 32 insertions(+), 30 deletions(-) diff --git a/app/helpers/dmsf_workflows_helper.rb b/app/helpers/dmsf_workflows_helper.rb index 87052239..15b9d44b 100644 --- a/app/helpers/dmsf_workflows_helper.rb +++ b/app/helpers/dmsf_workflows_helper.rb @@ -1,5 +1,5 @@ # encoding: utf-8 -# +# # Redmine plugin for Document Management System "Features" # # Copyright (C) 2011-16 Karel Pičman @@ -19,31 +19,33 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. module DmsfWorkflowsHelper - + def render_principals_for_new_dmsf_workflow_users(workflow, dmsf_workflow_step_assignment_id, dmsf_file_revision_id) scope = workflow.delegates(params[:q], dmsf_workflow_step_assignment_id, dmsf_file_revision_id) 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 + + # Delegation + if dmsf_workflow_step_assignment_id s = content_tag('div', - content_tag('div', principals_radio_button_tags('user_ids[]', principals), :id => 'users_for_delegate'), + content_tag('div', principals_radio_button_tags('step_action', principals), :id => 'users_for_delegate'), :class => 'objects-selection') - else + # New step + 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| - link_to text, autocomplete_for_user_dmsf_workflow_path(workflow, parameters.merge(:q => params[:q], :format => 'js')), :remote => true + link_to text, autocomplete_for_user_dmsf_workflow_path(workflow, parameters.merge(:q => params[:q], :format => 'js')), :remote => true } s + content_tag('p', links, :class => 'pagination') - s.html_safe + s.html_safe end - + def dmsf_workflow_steps_options_for_select(steps) options = Array.new options << [l(:dmsf_new_step), 0] @@ -51,10 +53,10 @@ module DmsfWorkflowsHelper options << [step.to_s, step] end options_for_select(options, 0) - end - + end + def dmsf_workflows_for_select(project, dmsf_workflow_id) - options = Array.new + options = Array.new options << ['', -1] DmsfWorkflow.active.sorted.where(['project_id = ? OR project_id IS NULL', project.id]).each do |wf| if wf.project_id @@ -62,14 +64,14 @@ module DmsfWorkflowsHelper else options << ["#{wf.name} (global)", wf.id] end - end + end options_for_select(options, :selected => dmsf_workflow_id) - end - + end + def dmsf_all_workflows_for_select(dmsf_workflow_id) - options = Array.new - options << ['', 0] - DmsfWorkflow.active.sorted.all.each do |wf| + options = Array.new + options << ['', 0] + DmsfWorkflow.active.sorted.all.each do |wf| if wf.project_id prj = Project.find_by_id wf.project_id if User.current.allowed_to?(:manage_workflows, prj) @@ -83,33 +85,33 @@ module DmsfWorkflowsHelper else # Global approval workflows options << [wf.name, wf.id] - end - end + end + end options_for_select(options, :selected => dmsf_workflow_id) - end - + end + def principals_radio_button_tags(name, principals) s = '' principals.each do |principal| s << "\n" end - s.html_safe + s.html_safe end - - def change_status_link(workflow) + + def change_status_link(workflow) url = { :controller => 'dmsf_workflows', :action => 'update', :id => workflow.id } if workflow.locked? - link_to l(:button_unlock), url.merge(:dmsf_workflow => {:status => DmsfWorkflow::STATUS_ACTIVE}), :method => :put, :class => 'icon icon-unlock' + link_to l(:button_unlock), url.merge(:dmsf_workflow => {:status => DmsfWorkflow::STATUS_ACTIVE}), :method => :put, :class => 'icon icon-unlock' else - link_to l(:button_lock), url.merge(:dmsf_workflow => {:status => DmsfWorkflow::STATUS_LOCKED}), :method => :put, :class => 'icon icon-lock' + link_to l(:button_lock), url.merge(:dmsf_workflow => {:status => DmsfWorkflow::STATUS_LOCKED}), :method => :put, :class => 'icon icon-lock' end end - + def workflows_status_options_for_select(selected) worflows_count_by_status = DmsfWorkflow.global.group('status').count.to_hash options_for_select([[l(:label_all), ''], - ["#{l(:status_active)} (#{worflows_count_by_status[DmsfWorkflow::STATUS_ACTIVE].to_i})", DmsfWorkflow::STATUS_ACTIVE.to_s], + ["#{l(:status_active)} (#{worflows_count_by_status[DmsfWorkflow::STATUS_ACTIVE].to_i})", DmsfWorkflow::STATUS_ACTIVE.to_s], ["#{l(:status_locked)} (#{worflows_count_by_status[DmsfWorkflow::STATUS_LOCKED].to_i})", DmsfWorkflow::STATUS_LOCKED.to_s]], selected.to_s) end - + end \ No newline at end of file From b4aaa58faa4a4af56a2c7aa068744d39af3792d1 Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Tue, 12 Apr 2016 14:32:36 +0200 Subject: [PATCH 50/94] Approval workflows, operators are switched --- app/controllers/dmsf_workflows_controller.rb | 200 ++++++++++--------- app/models/dmsf_workflow.rb | 149 +++++++------- app/views/dmsf_workflows/_steps.html.erb | 39 ++-- app/views/dmsf_workflows/new.html.erb | 22 +- 4 files changed, 207 insertions(+), 203 deletions(-) diff --git a/app/controllers/dmsf_workflows_controller.rb b/app/controllers/dmsf_workflows_controller.rb index 007bdd79..b22f570a 100644 --- a/app/controllers/dmsf_workflows_controller.rb +++ b/app/controllers/dmsf_workflows_controller.rb @@ -1,5 +1,5 @@ # encoding: utf-8 -# +# # Redmine plugin for Document Management System "Features" # # Copyright (C) 2011-16 Karel Pičman @@ -19,55 +19,55 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. class DmsfWorkflowsController < ApplicationController - unloadable + unloadable model_object DmsfWorkflow - - before_filter :find_model_object, :except => [:create, :new, :index, :assign, :assignment] + + before_filter :find_model_object, :except => [:create, :new, :index, :assign, :assignment] before_filter :find_project before_filter :authorize_custom - + layout :workflows_layout - + def index @status = params[:status] || 1 @workflow_pages, @workflows = paginate DmsfWorkflow.status(@status).global.sorted, :per_page => 25 end - - def action + + def action end - + def new_action if params[:commit] == l(:button_submit) action = DmsfWorkflowStepAction.new( :dmsf_workflow_step_assignment_id => params[:dmsf_workflow_step_assignment_id], :action => (params[:step_action].to_i >= 10) ? DmsfWorkflowStepAction::ACTION_DELEGATE : params[:step_action], - :note => params[:note]) + :note => params[:note]) if request.post? if action.save revision = DmsfFileRevision.find_by_id params[:dmsf_file_revision_id] if revision - if @dmsf_workflow.try_finish revision, action, (params[:step_action].to_i / 10) + if @dmsf_workflow.try_finish revision, action, (params[:step_action].to_i / 10) file = DmsfFile.joins(:revisions).where(:dmsf_file_revisions => {:id => revision.id}).first if file begin file.unlock! true rescue DmsfLockError => e - flash[:info] = e.message + flash[:info] = e.message end - end + end if revision.workflow == DmsfWorkflow::STATE_APPROVED - # Just approved + # Just approved recipients = DmsfMailer.get_notify_users(@project) recipients.each do |user| DmsfMailer.workflow_notification( user, - @dmsf_workflow, + @dmsf_workflow, revision, :text_email_subject_approved, :text_email_finished_approved, :text_email_to_see_history).deliver if user end - if Setting.plugin_redmine_dmsf[:dmsf_display_notified_recipients] == '1' + if Setting.plugin_redmine_dmsf[:dmsf_display_notified_recipients] == '1' unless recipients.blank? to = recipients.collect{ |r| r.name }.first(DMSF_MAX_NOTIFICATION_RECEIVERS_INFO).join(', ') to << ((recipients.count > DMSF_MAX_NOTIFICATION_RECEIVERS_INFO) ? ',...' : '.') @@ -75,15 +75,15 @@ class DmsfWorkflowsController < ApplicationController end end else - # Just rejected + # Just rejected recipients = @dmsf_workflow.participiants recipients.push User.find_by_id revision.dmsf_workflow_assigned_by recipients.uniq! recipients = recipients & DmsfMailer.get_notify_users(@project) recipients.each do |user| DmsfMailer.workflow_notification( - user, - @dmsf_workflow, + user, + @dmsf_workflow, revision, :text_email_subject_rejected, :text_email_finished_rejected, @@ -100,19 +100,19 @@ class DmsfWorkflowsController < ApplicationController end else if action.action == DmsfWorkflowStepAction::ACTION_DELEGATE - # Delegation + # Delegation delegate = User.find_by_id params[:step_action].to_i / 10 if DmsfMailer.get_notify_users(@project).include?(delegate) DmsfMailer.workflow_notification( - delegate, - @dmsf_workflow, + delegate, + @dmsf_workflow, revision, :text_email_subject_delegated, :text_email_finished_delegated, :text_email_to_proceed, action.note).deliver if Setting.plugin_redmine_dmsf[:dmsf_display_notified_recipients] == '1' - flash[:warning] = l(:warning_email_notifications, :to => delegate.name) + flash[:warning] = l(:warning_email_notifications, :to => delegate.name) end end else @@ -124,19 +124,19 @@ class DmsfWorkflowsController < ApplicationController assignments.each do |assignment| if assignment.user && DmsfMailer.get_notify_users(@project).include?(assignment.user) DmsfMailer.workflow_notification( - assignment.user, - @dmsf_workflow, + assignment.user, + @dmsf_workflow, revision, :text_email_subject_requires_approval, :text_email_finished_step, :text_email_to_proceed).deliver end end - to = User.find_by_id revision.dmsf_workflow_assigned_by + to = User.find_by_id revision.dmsf_workflow_assigned_by if to && DmsfMailer.get_notify_users(@project).include?(to) DmsfMailer.workflow_notification( - to, - @dmsf_workflow, + to, + @dmsf_workflow, revision, :text_email_subject_updated, :text_email_finished_step_short, @@ -153,31 +153,31 @@ class DmsfWorkflowsController < ApplicationController flash[:warning] = l(:warning_email_notifications, :to => to) end end - end + end end end end end flash[:notice] = l(:notice_successful_update) elsif action.action != DmsfWorkflowStepAction::ACTION_APPROVE && action.note.blank? - flash[:error] = l(:error_empty_note) + flash[:error] = l(:error_empty_note) end - end + end end - redirect_to :back + redirect_to :back end - - def assign + + def assign end - + def assignment - if (params[:commit] == l(:button_submit)) && + if (params[:commit] == l(:button_submit)) && params[:dmsf_workflow_id].present? && (params[:dmsf_workflow_id] != '-1') revision = DmsfFileRevision.find_by_id params[:dmsf_file_revision_id] if revision - revision.set_workflow(params[:dmsf_workflow_id], params[:action]) + revision.set_workflow(params[:dmsf_workflow_id], params[:action]) revision.assign_workflow(params[:dmsf_workflow_id]) - if request.post? + if request.post? if revision.save file = DmsfFile.find_by_id revision.dmsf_file_id if file @@ -186,23 +186,23 @@ class DmsfWorkflowsController < ApplicationController rescue DmsfLockError => e logger.warn e.message end - flash[:notice] = l(:notice_successful_update) + flash[:notice] = l(:notice_successful_update) end else flash[:error] = l(:error_workflow_assign) end - end - end + end + end end - redirect_to :back + redirect_to :back end def log end - - def new + + def new @dmsf_workflow = DmsfWorkflow.new - + # Reload if params[:dmsf_workflow] && params[:dmsf_workflow][:name].present? @dmsf_workflow.name = params[:dmsf_workflow][:name] @@ -210,15 +210,15 @@ class DmsfWorkflowsController < ApplicationController wf = DmsfWorkflow.find_by_id params[:dmsf_workflow][:id] @dmsf_workflow.name = wf.name if wf end - + render :layout => !request.xhr? end - + def create if params[:dmsf_workflow] if (params[:dmsf_workflow][:id].to_i > 0) wf = DmsfWorkflow.find_by_id params[:dmsf_workflow][:id] - @dmsf_workflow = wf.copy_to(@project, params[:dmsf_workflow][:name]) if wf + @dmsf_workflow = wf.copy_to(@project, params[:dmsf_workflow][:name]) if wf else @dmsf_workflow = DmsfWorkflow.new @dmsf_workflow.name = params[:dmsf_workflow][:name] @@ -234,12 +234,12 @@ class DmsfWorkflowsController < ApplicationController else redirect_to dmsf_workflows_path end - else + else render :action => 'new' end end - - def update + + def update if params[:dmsf_workflow] res = @dmsf_workflow.update_attributes({:name => params[:dmsf_workflow][:name]}) if params[:dmsf_workflow][:name].present? res = @dmsf_workflow.update_attributes({:status => params[:dmsf_workflow][:status]}) if params[:dmsf_workflow][:status].present? @@ -249,7 +249,7 @@ class DmsfWorkflowsController < ApplicationController redirect_to settings_project_path(@project, :tab => 'dmsf_workflow') else redirect_to dmsf_workflows_path - end + end else flash[:error] = @dmsf_workflow.errors.full_messages.to_sentence redirect_to dmsf_workflow_path(@dmsf_workflow) @@ -258,10 +258,10 @@ class DmsfWorkflowsController < ApplicationController redirect_to dmsf_workflow_path(@dmsf_workflow) end end - - def destroy + + def destroy begin - @dmsf_workflow.destroy + @dmsf_workflow.destroy flash[:notice] = l(:notice_successful_delete) rescue flash[:error] = l(:error_unable_delete_dmsf_workflow) @@ -270,33 +270,33 @@ class DmsfWorkflowsController < ApplicationController redirect_to settings_project_path(@project, :tab => 'dmsf_workflow') else redirect_to dmsf_workflows_path - end + end end - - def autocomplete_for_user + + def autocomplete_for_user 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.html format.js - end + end end - - def add_step - if request.post? + + def add_step + if request.post? if params[:step] == '0' - step = @dmsf_workflow.dmsf_workflow_steps.collect{|s| s.step}.uniq.count + 1 + 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 = User.where(:id => params[:user_ids]).to_a if users.count > 0 - users.each do |user| + users.each do |user| ws = DmsfWorkflowStep.new ws.dmsf_workflow_id = @dmsf_workflow.id ws.step = step @@ -310,49 +310,49 @@ class DmsfWorkflowsController < ApplicationController end else flash[:error] = l(:error_workflow_assign) - end - end + end + end respond_to do |format| - format.html - end + format.html + end end - - def remove_step - if request.delete? + + def remove_step + if request.delete? DmsfWorkflowStep.where(:dmsf_workflow_id => @dmsf_workflow.id, :step => params[:step]).each do |ws| @dmsf_workflow.dmsf_workflow_steps.delete(ws) - end + end @dmsf_workflow.dmsf_workflow_steps.each do |ws| n = ws.step.to_i - if n > params[:step].to_i + if n > params[:step].to_i ws.step = n - 1 unless ws.save flash[:error] = l(:notice_cannot_renumber_steps) end end - end - end + end + end respond_to do |format| - format.html + format.html end end - - def reorder_steps + + def reorder_steps if request.put? unless @dmsf_workflow.reorder_steps(params[:step].to_i, params[:workflow_step][:move_to]) flash[:error] = l(:notice_cannot_renumber_steps) - end - end + end + end respond_to do |format| - format.html + format.html end end - + def start revision = DmsfFileRevision.find_by_id(params[:dmsf_file_revision_id]) - if revision + if revision revision.set_workflow(@dmsf_workflow.id, params[:action]) - if revision.save + if revision.save assignments = @dmsf_workflow.next_assignments revision.id recipients = assignments.collect{ |a| a.user } recipients.uniq! @@ -360,13 +360,13 @@ class DmsfWorkflowsController < ApplicationController recipients.each do |user| DmsfMailer.workflow_notification( user, - @dmsf_workflow, + @dmsf_workflow, revision, :text_email_subject_started, :text_email_started, :text_email_to_proceed).deliver end - if Setting.plugin_redmine_dmsf[:dmsf_display_notified_recipients] == '1' + if Setting.plugin_redmine_dmsf[:dmsf_display_notified_recipients] == '1' unless recipients.blank? to = recipients.collect{ |r| r.name }.first(DMSF_MAX_NOTIFICATION_RECEIVERS_INFO).join(', ') to << ((recipients.count > DMSF_MAX_NOTIFICATION_RECEIVERS_INFO) ? ',...' : '.') @@ -376,14 +376,14 @@ class DmsfWorkflowsController < ApplicationController flash[:notice] = l(:notice_workflow_started) else flash[:error] = l(:notice_cannot_start_workflow) - end + end end redirect_to :back end - -private - def find_project +private + + def find_project if @dmsf_workflow if @dmsf_workflow.project # Project workflow @project = @dmsf_workflow.project @@ -392,19 +392,21 @@ private @project = revision.file.project if revision && revision.file end else - if params[:project_id].present? + if params[:dmsf_workflow] + @project = Project.find_by_id params[:dmsf_workflow][:project_id] + elsif params[:project_id] @project = Project.find_by_id params[:project_id] else @project = Project.find_by_identifier params[:id] end - end + end end - - def workflows_layout + + def workflows_layout @project ? 'base' : 'admin' - end - - def authorize_custom + end + + def authorize_custom if @project authorize else diff --git a/app/models/dmsf_workflow.rb b/app/models/dmsf_workflow.rb index 6c306edf..10997257 100644 --- a/app/models/dmsf_workflow.rb +++ b/app/models/dmsf_workflow.rb @@ -18,34 +18,34 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -class DmsfWorkflow < ActiveRecord::Base - +class DmsfWorkflow < ActiveRecord::Base + has_many :dmsf_workflow_steps, -> { order 'step ASC, operator DESC' }, :dependent => :destroy belongs_to :author, :class_name => 'User' - + scope :sorted, lambda { order('name ASC') } scope :global, lambda { where('project_id IS NULL') } scope :active, lambda { where(:status => STATUS_ACTIVE) } scope :status, lambda { |arg| where(arg.blank? ? nil : {:status => arg.to_i}) } - + validate :name_validation validates :name, :presence => true validates_length_of :name, :maximum => 255 - + def name_validation if self.project_id if self.id - if (DmsfWorkflow.where(['(project_id IS NULL OR (project_id = ? AND id != ?)) AND name = ?', + if (DmsfWorkflow.where(['(project_id IS NULL OR (project_id = ? AND id != ?)) AND name = ?', self.project_id, self.name, self.id]).count > 0) errors.add(:name, l('activerecord.errors.messages.taken')) end else - if (DmsfWorkflow.where(['(project_id IS NULL OR project_id = ?) AND name = ?', + if (DmsfWorkflow.where(['(project_id IS NULL OR project_id = ?) AND name = ?', self.project_id, self.name]).count > 0) errors.add(:name, l('activerecord.errors.messages.taken')) end end - else + else if self.id if DmsfWorkflow.where(['name = ? AND id != ?', self.name, self.id]).count > 0 errors.add(:name, l('activerecord.errors.messages.taken')) @@ -57,16 +57,16 @@ class DmsfWorkflow < ActiveRecord::Base end end end - + STATE_NONE = nil STATE_ASSIGNED = 3 STATE_WAITING_FOR_APPROVAL = 1 STATE_APPROVED = 2 STATE_REJECTED = 4 - + STATUS_LOCKED = 0 STATUS_ACTIVE = 1 - + def participiants users = Array.new self.dmsf_workflow_steps.each do |step| @@ -76,90 +76,90 @@ class DmsfWorkflow < ActiveRecord::Base end def self.workflows(project) - project ? where(:project_id => project) : where('project_id IS NULL') + project ? where(:project_id => project) : where('project_id IS NULL') end - def project + def project Project.find_by_id(project_id) if project_id end def to_s name - end + end def reorder_steps(step, move_to) DmsfWorkflow.transaction do case move_to - when 'highest' - unless step == 1 - dmsf_workflow_steps.each do |ws| + when 'highest' + unless step == 1 + dmsf_workflow_steps.each do |ws| if ws.step < step - return false unless ws.update_attribute('step', ws.step + 1) - elsif ws.step == step + return false unless ws.update_attribute('step', ws.step + 1) + elsif ws.step == step return false unless ws.update_attribute('step', 1) - end - end + end + end end when 'higher' - unless step == 1 + unless step == 1 dmsf_workflow_steps.each do |ws| - if ws.step == step - 1 + if ws.step == step - 1 return false unless ws.update_attribute('step', step) - elsif ws.step == step + elsif ws.step == step return false unless ws.update_attribute('step', step - 1) - end - end + end + end end when 'lower' - unless step == dmsf_workflow_steps.collect{|s| s.step}.uniq.count + unless step == dmsf_workflow_steps.collect{|s| s.step}.uniq.count dmsf_workflow_steps.each do |ws| - if ws.step == step + 1 - return false unless ws.update_attribute('step', step) - elsif ws.step == step - return false unless ws.update_attribute('step', step + 1) - end - end + if ws.step == step + 1 + return false unless ws.update_attribute('step', step) + elsif ws.step == step + return false unless ws.update_attribute('step', step + 1) + end + end end - when 'lowest' + when 'lowest' size = dmsf_workflow_steps.collect{|s| s.step}.uniq.count unless step == size - dmsf_workflow_steps.each do |ws| - if ws.step > step + dmsf_workflow_steps.each do |ws| + if ws.step > step return false unless ws.update_attribute('step', ws.step - 1) - elsif ws.step == step + elsif ws.step == step return false unless ws.update_attribute('step', size) end - end - end - end + end + end + end end - return reload + return reload end - - def delegates(q, dmsf_workflow_step_assignment_id, dmsf_file_revision_id) + + def delegates(q, dmsf_workflow_step_assignment_id, dmsf_file_revision_id) if dmsf_workflow_step_assignment_id && dmsf_file_revision_id sql = [ - 'id NOT IN (SELECT a.user_id FROM dmsf_workflow_step_assignments a WHERE id = ?) AND id IN (SELECT m.user_id FROM members m JOIN dmsf_files f ON f.project_id = m.project_id JOIN dmsf_file_revisions r ON r.dmsf_file_id = f.id WHERE r.id = ?)', - dmsf_workflow_step_assignment_id, + 'id NOT IN (SELECT a.user_id FROM dmsf_workflow_step_assignments a WHERE id = ?) AND id IN (SELECT m.user_id FROM members m JOIN dmsf_files f ON f.project_id = m.project_id JOIN dmsf_file_revisions r ON r.dmsf_file_id = f.id WHERE r.id = ?)', + dmsf_workflow_step_assignment_id, dmsf_file_revision_id] elsif project sql = ['id IN (SELECT user_id FROM members WHERE project_id = ?)', project.id] else sql = '1=1' end - + if q.present? User.active.sorted.where(sql).like(q) else User.active.sorted.where(sql) - end + end end - - def next_assignments(dmsf_file_revision_id) - results = Array.new + + def next_assignments(dmsf_file_revision_id) + results = Array.new nsteps = self.dmsf_workflow_steps.collect{|s| s.step}.uniq nsteps.each do |i| - step_is_finished = false + step_is_finished = false steps = self.dmsf_workflow_steps.collect{|s| s.step == i ? s : nil}.compact steps.each do |step| step.dmsf_workflow_step_assignments.each do |assignment| @@ -168,24 +168,25 @@ class DmsfWorkflow < ActiveRecord::Base case action.action when DmsfWorkflowStepAction::ACTION_APPROVE step_is_finished = true - # Try to find another unfinished AND step + # Try to find another unfinished AND step exists = false stps = self.dmsf_workflow_steps.collect{|s| (s.step == i && s.operator == DmsfWorkflowStep::OPERATOR_AND) ? s : nil}.compact stps.each do |s| s.dmsf_workflow_step_assignments.each do |a| - exists = a.add?(dmsf_file_revision_id) - break if exists + exists = a.add?(dmsf_file_revision_id) + break if exists end - end + break if exists + end step_is_finished = false if exists break when DmsfWorkflowStepAction::ACTION_REJECT - return Array.new + return Array.new end end end break if step_is_finished - end + end break if step_is_finished end unless step_is_finished @@ -199,29 +200,29 @@ class DmsfWorkflow < ActiveRecord::Base end results end - + def self.assignments_to_users_str(assignments) str = '' - if assignments - assignments.each_with_index do |assignment, index| + if assignments + assignments.each_with_index do |assignment, index| if index > 0 str << ', ' end - str << assignment.user.name + str << assignment.user.name end end - str + str end - + def assign(dmsf_file_revision_id) dmsf_workflow_steps.each do |ws| ws.assign(dmsf_file_revision_id) end end - - def try_finish(revision, action, user_id) + + def try_finish(revision, action, user_id) case action.action - when DmsfWorkflowStepAction::ACTION_APPROVE + when DmsfWorkflowStepAction::ACTION_APPROVE assignments = self.next_assignments revision.id return false unless assignments.empty? revision.update_attribute(:workflow, DmsfWorkflow::STATE_APPROVED) @@ -229,7 +230,7 @@ class DmsfWorkflow < ActiveRecord::Base when DmsfWorkflowStepAction::ACTION_REJECT revision.update_attribute(:workflow, DmsfWorkflow::STATE_REJECTED) return true - when DmsfWorkflowStepAction::ACTION_DELEGATE + when DmsfWorkflowStepAction::ACTION_DELEGATE self.dmsf_workflow_steps.each do |step| step.dmsf_workflow_step_assignments.each do |assignment| if assignment.id == action.dmsf_workflow_step_assignment_id @@ -240,27 +241,27 @@ class DmsfWorkflow < ActiveRecord::Base end end return false - end - + end + def copy_to(project, name = nil) - new_wf = self.dup + new_wf = self.dup new_wf.name = name if name new_wf.project_id = project ? project.id : nil new_wf.author = User.current if new_wf.save self.dmsf_workflow_steps.each do |step| step.copy_to(new_wf) - end + end end return new_wf end - + def locked? self.status == STATUS_LOCKED end - + def active? self.status == STATUS_ACTIVE end - + 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 4578fcbb..ef3c2699 100644 --- a/app/views/dmsf_workflows/_steps.html.erb +++ b/app/views/dmsf_workflows/_steps.html.erb @@ -28,45 +28,46 @@ <%= labelled_form_for @dmsf_workflow do |f| %> <%= error_messages_for 'workflow' %> -
    +

    <%= f.text_field :name, :required => true %> <%= f.submit l(:button_save) %> -

    -
    -<% end %> +

    +
    +<% end %> -
    -

    +

    +

    <%= 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|%> - + + <% steps.each do |i|%> + - - + - + <% end; reset_cycle %> diff --git a/app/views/dmsf_workflows/new.html.erb b/app/views/dmsf_workflows/new.html.erb index 2c7b1cf8..3fccb993 100644 --- a/app/views/dmsf_workflows/new.html.erb +++ b/app/views/dmsf_workflows/new.html.erb @@ -27,26 +27,26 @@

    <%= link_to l(:label_dmsf_workflow_plural), dmsf_workflows_path %> » <%= l(:label_dmsf_workflow_new) %>

    <% end %> -<%= labelled_form_for @dmsf_workflow do |f| %> - <%= f.hidden_field(:project_id) if project %> +<%= labelled_form_for @dmsf_workflow do |f| %> + <%= f.hidden_field(:project_id, :value => project.id) if project %> <%= error_messages_for 'dmsf_workflow' %>
    -

    +

    <%= f.text_field :name, :required => true %>

    -

    - <%= f.select(:id, - dmsf_all_workflows_for_select(params[:dmsf_workflow] ? params[:dmsf_workflow][:id] : nil), +

    + <%= f.select(:id, + dmsf_all_workflows_for_select(params[:dmsf_workflow] ? params[:dmsf_workflow][:id] : nil), :label => l(:label_copy_workflow_from)) %> -

    +

    <%= f.submit l(:button_create) %> <% end %> - - \ No newline at end of file + \ No newline at end of file From 33288794008ec31ab792db3df7c9fd04704565db Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Thu, 14 Apr 2016 10:46:14 +0200 Subject: [PATCH 51/94] frev_params function not used --- app/controllers/dmsf_files_controller.rb | 33 ++++++++++-------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/app/controllers/dmsf_files_controller.rb b/app/controllers/dmsf_files_controller.rb index 7c20cd89..213ecdf1 100644 --- a/app/controllers/dmsf_files_controller.rb +++ b/app/controllers/dmsf_files_controller.rb @@ -26,7 +26,7 @@ class DmsfFilesController < ApplicationController before_filter :find_file, :except => [:delete_revision] before_filter :find_revision, :only => [:delete_revision] - before_filter :authorize + before_filter :authorize accept_api_auth :show @@ -41,19 +41,19 @@ class DmsfFilesController < ApplicationController @revision = DmsfFileRevision.find(params[:download].to_i) raise DmsfAccessError if @revision.file != @file end - check_project(@revision.file) + check_project(@revision.file) raise ActionController::MissingFile if @file.deleted? - log_activity('downloaded') + log_activity('downloaded') access = DmsfFileRevisionAccess.new access.user = User.current access.revision = @revision - access.action = DmsfFileRevisionAccess::DownloadAction + access.action = DmsfFileRevisionAccess::DownloadAction access.save! member = Member.where(:user_id => User.current.id, :project_id => @file.project.id).first - send_file(@revision.disk_file, + send_file(@revision.disk_file, :filename => filename_for_content_disposition(@revision.formatted_name(member ? member.title_format : nil)), :type => @revision.detect_content_type, - :disposition => 'inline') + :disposition => 'inline') rescue DmsfAccessError => e Rails.logger.error e.message render_403 @@ -73,16 +73,16 @@ class DmsfFilesController < ApplicationController @revision = DmsfFileRevision.find(params[:download].to_i) raise DmsfAccessError if @revision.file != @file end - check_project(@revision.file) + check_project(@revision.file) raise ActionController::MissingFile if @revision.file.deleted? - log_activity('downloaded') + log_activity('downloaded') access = DmsfFileRevisionAccess.new access.user = User.current access.revision = @revision access.action = DmsfFileRevisionAccess::DownloadAction access.save! member = Member.where(:user_id => User.current.id, :project_id => @file.project.id).first - send_file(@revision.disk_file, + send_file(@revision.disk_file, :filename => filename_for_content_disposition(@revision.formatted_name(member ? member.title_format : nil)), :type => @revision.detect_content_type, :disposition => 'attachment') @@ -150,7 +150,7 @@ class DmsfFilesController < ApplicationController revision.disk_filename = revision.new_storage_filename revision.mime_type = Redmine::MimeType.of(file_upload.original_filename) end - + # Custom fields if params[:dmsf_file_revision][:custom_field_values].present? params[:dmsf_file_revision][:custom_field_values].each_with_index do |v, i| @@ -196,7 +196,7 @@ class DmsfFilesController < ApplicationController flash[:error] = @file.errors.full_messages.join(', ') end else - flash[:error] = revision.errors.full_messages.join(', ') + flash[:error] = revision.errors.full_messages.join(', ') end end end @@ -226,7 +226,7 @@ class DmsfFilesController < ApplicationController Rails.logger.error "Could not send email notifications: #{e.message}" end end - else + else flash[:error] = @file.errors.full_messages.join(', ') end end @@ -242,7 +242,7 @@ class DmsfFilesController < ApplicationController if @revision.delete(true) flash[:notice] = l(:notice_revision_deleted) log_activity('deleted') - else + else flash[:error] = @revision.errors.full_messages.join(', ') end end @@ -305,11 +305,6 @@ class DmsfFilesController < ApplicationController private - def frev_params - params.require(:dmsf_file_revision).permit( - :title, :name, :description, :comment) - end - def log_activity(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}/#{@revision.id if @revision}" end @@ -328,7 +323,7 @@ class DmsfFilesController < ApplicationController rescue ActiveRecord::RecordNotFound render_404 end - + def check_project(entry) if entry && entry.project != @project raise DmsfAccessError, l(:error_entry_project_does_not_match_current_project) From 6e4e46791c18c4c643dc1686433cbb4498a2701a Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Thu, 14 Apr 2016 10:46:44 +0200 Subject: [PATCH 52/94] Document link after a search ... #520 --- 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 d8cd8deb..7ce97d85 100644 --- a/app/models/dmsf_file.rb +++ b/app/models/dmsf_file.rb @@ -75,7 +75,7 @@ class DmsfFile < ActiveRecord::Base end desc }, - :url => Proc.new { |o| {:controller => 'dmsf_files', :action => 'show', :id => o} }, + :url => Proc.new { |o| {:controller => 'dmsf_files', :action => 'view', :id => o} }, :datetime => Proc.new { |o| o.updated_at }, :author => Proc.new { |o| o.last_revision.user } From e727fa2769cdac426ef093807a6930cce084e0ee Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Thu, 14 Apr 2016 12:50:15 +0200 Subject: [PATCH 53/94] nautilus-like folders-files list view #252 - configuration --- Gemfile | 2 +- .../redmine_dmsf/_view_my_account.html.erb | 3 + config/locales/cs.yml | 66 ++++++++-------- config/locales/de.yml | 78 ++++++++++--------- config/locales/en.yml | 74 +++++++++--------- config/locales/es.yml | 64 +++++++-------- config/locales/fr.yml | 66 ++++++++-------- config/locales/ja.yml | 78 ++++++++++--------- config/locales/pl.yml | 70 +++++++++-------- config/locales/pt-BR.yml | 16 ++-- config/locales/ru.yml | 66 ++++++++-------- config/locales/sl.yml | 70 +++++++++-------- config/locales/zh-TW.yml | 50 ++++++------ config/locales/zh.yml | 72 ++++++++--------- lib/redmine_dmsf.rb | 8 +- .../search_controller_hooks.rb | 0 .../hooks/{ => views}/base_view_hooks.rb | 0 .../hooks/views/my_account_view_hooks.rb | 39 ++++++++++ .../{ => views}/view_projects_form_hook.rb | 0 .../patches/user_preference_patch.rb | 39 ++++++++++ 20 files changed, 484 insertions(+), 377 deletions(-) create mode 100644 app/views/hooks/redmine_dmsf/_view_my_account.html.erb rename lib/redmine_dmsf/hooks/{ => controllers}/search_controller_hooks.rb (100%) rename lib/redmine_dmsf/hooks/{ => views}/base_view_hooks.rb (100%) create mode 100644 lib/redmine_dmsf/hooks/views/my_account_view_hooks.rb rename lib/redmine_dmsf/hooks/{ => views}/view_projects_form_hook.rb (100%) create mode 100644 lib/redmine_dmsf/patches/user_preference_patch.rb diff --git a/Gemfile b/Gemfile index eed28897..3a1540e0 100644 --- a/Gemfile +++ b/Gemfile @@ -22,7 +22,7 @@ source 'https://rubygems.org' -gem 'rubyzip' +gem 'rubyzip', '>= 1.0.0' gem 'zip-zip' gem 'simple_enum' gem 'uuidtools' diff --git a/app/views/hooks/redmine_dmsf/_view_my_account.html.erb b/app/views/hooks/redmine_dmsf/_view_my_account.html.erb new file mode 100644 index 00000000..d32164f6 --- /dev/null +++ b/app/views/hooks/redmine_dmsf/_view_my_account.html.erb @@ -0,0 +1,3 @@ +<%= labelled_fields_for :pref, @user.pref do |pref_fields| %> +

    <%= pref_fields.check_box :dmsf_tree_view %>

    +<% end %> \ No newline at end of file diff --git a/config/locales/cs.yml b/config/locales/cs.yml index 9313988d..5576b09b 100644 --- a/config/locales/cs.yml +++ b/config/locales/cs.yml @@ -1,5 +1,5 @@ # encoding: utf-8 -# +# # Redmine plugin for Document Management System "Features" # # Copyright (C) 2011 Vít Jonáš @@ -26,13 +26,13 @@ cs: 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 + error_email_to_must_be_entered: Musí být zadán adresát warning_file_already_locked: Soubor je již zamčen notice_file_locked: Soubor byl zamčen warning_file_not_locked: Soubor není zamčen notice_file_unlocked: Soubor byl odemčen - error_only_user_that_locked_file_can_unlock_it: Soubor může být odemčen pouze uživatelem, který ho zamkl - error_max_files_exceeded: "Limit pro %{number} najednou stažených souborů je překročen" + error_only_user_that_locked_file_can_unlock_it: Soubor může být odemčen pouze uživatelem, který ho zamkl + error_max_files_exceeded: "Limit pro %{number} najednou stažených souborů je překročen" error_entry_project_does_not_match_current_project: Zadaný projekt neodpovídá aktuálnímu projektu notice_folder_created: Adresář byl vytvořen error_folder_creation_failed: Vytváření složky selhalo @@ -81,8 +81,8 @@ cs: title_waiting_for_approval: Čeká na schválení title_approved: Schváleno title_unlock_file: Odemknout a umožnit změny ostatním uživatelům - title_lock_file: Zamknout a zabránit změnám ostatních uživatelů - title_download_checked: Stáhnout vybrané jako Zip + title_lock_file: Zamknout a zabránit změnám ostatních uživatelů + title_download_checked: Stáhnout vybrané jako Zip title_send_checked_by_email: Zaslat vybrané emailem link_user_preferences: Vaše nastavení heading_send_documents_by_email: Odeslat dokumenty emailem @@ -93,8 +93,8 @@ cs: label_email_documents: Documenty label_email_body: Obsah label_email_send: Odesláno - title_notifications_active: Notifikace jsou aktivní - label_upload_upload: Nahrát + title_notifications_active: Notifikace jsou aktivní + label_upload_upload: Nahrát heading_new_folder: Nová složka label_title: Název label_description: Popis @@ -113,9 +113,9 @@ cs: label_created: Vytvořeno label_changed: Změněno info_changed_by_user: "%{changed} uživatelem" - label_filename: Jméno souboru + label_filename: Jméno souboru label_mime: Typ - label_size: Velikost + label_size: Velikost heading_new_revision: Nová revize option_version_same: Stejná option_version_minor: Podružná @@ -135,7 +135,7 @@ cs: option_stem_none: Stem nic (výchozí) option_stem_some: Stem něco option_stem_all: Stem vše - label_stemming_description: Tímto je určeno, jak analyzátor dotazu aplikuje algoritmus Stemmingu. Výchozí hodnota je STEM_NONE. Dostupné hodnoty jsou + label_stemming_description: Tímto je určeno, jak analyzátor dotazu aplikuje algoritmus Stemmingu. Výchozí hodnota je STEM_NONE. Dostupné hodnoty jsou note_do_not_stem: Žádný stemming. note_stem_some: "Hledej stemmed formy výrazů kromě těch, které začínají velkým písmenem nebo jsou následovány určitými znaky nebo jsou použity s operátory, které vyžadují informaci o pozici. Stemmed výrazy začínají písmenem 'Z'." note_stem_all: "Hledej stemmed formy všech slov (poznámka: 'Z' prefix není přidán)." @@ -150,7 +150,7 @@ cs: permission_user_preferences: Nastavení uživatele permission_view_dmsf_files: Zobrazit dokumenty permission_folder_manipulation: Manipulace se složkami - permission_file_manipulation: Manipulace se soubory + permission_file_manipulation: Manipulace se soubory permission_force_file_unlock: Vynucené odemknutí souboru permission_manage_workflows: Spravovat schvalovací procesy permission_file_delete: Mazat dokumenty @@ -163,12 +163,12 @@ cs: error_user_has_not_right_delete_folder: Uživatel nemá právo mazat složky error_user_has_not_right_delete_file: Uživatel nemá právo mazat soubor notice_entries_deleted: Položky smazány - warning_some_entries_were_not_deleted: "Některé položky nebyly smazány: %{entries}" + 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 archivu ke stažení label_number_of_folders: Složky - label_number_of_documents: Dokumenty + label_number_of_documents: Dokumenty error_file_storage_directory_does_not_exist: Cílová složka neexistuje a nemůže být vytvořena error_file_can_not_be_created: Nelze vytvořit soubor v cílové složce error_wrong_zip_encoding: Chybné kódování Zipu @@ -199,8 +199,8 @@ cs: title_copy: Kopírovat error_folder_cannot_be_copied: Složka nemůže být zkopírována notice_folder_copied: Složka zkopírována - - error_max_email_filesize_exceeded: "Přesáhli jste maximální velikost souboru, který lze poslat emailem. (%{number} MB)" + + error_max_email_filesize_exceeded: "Přesáhli jste maximální velikost souboru, který lze poslat emailem. (%{number} MB)" note_maximum_email_filesize: Omezí se maximální velikost souboru, který může být poslán emailem. 0 znamená neomezený. Číslo je v MB. label_maximum_email_filesize: Maximální velikost souboru emailu header_minimum_filesize: Chyba souboru. @@ -222,7 +222,7 @@ cs: select_option_webdav_readwrite: Čtení/Zápis label_webdav_strategy: Webdav strategie note_webdav_strategy: Umožní administrátorovi rozhodnout, zdali je webdav pouze pro čtení nebo i pro zápis. - + error_unable_delete_dmsf_workflow: Nelze smazat schvalovací proces error_empty_note: Musí být vyplněn komentář error_workflow_assign: Chyba při přiřazování @@ -274,14 +274,14 @@ cs: text_email_finished_delegated: "Schvalovací proces '%{name}' přiřazený k dokumentu '%{filename}' byl delegován, protože '%{notice}' a od Vás se očekává schválení v aktuálním schvalovacím kroku." text_email_finished_step: "Schvalovací proces '%{name}' přiřazený k dokumentu '%{filename}' právě ukončil jeden ze schvalovacích kroků a od Vás se očekává schválení v dalším schvalovacím kroku." text_email_finished_step_short: "Schvalovací proces '%{name}' přiřazený k dokumentu '%{filename}' právě ukončil jeden ze schvalovacích kroků." - text_email_started: "Schvalovací proces '%{name}' přiřazený k dokumentu '%{filename}' byl zahájen a od Vás se očekává schválení v aktuálním schvalovacím kroku." + text_email_started: "Schvalovací proces '%{name}' přiřazený k dokumentu '%{filename}' byl zahájen a od Vás se očekává schválení v aktuálním schvalovacím kroku." text_email_to_proceed: Pro schválení klikněte na zaškrtávací ikonku vedle dokumentu v text_email_to_see_history: Pro zobrazení historie schvalovacího procesu klikněte na status dokumentu v text_email_to_see_status: Pro zobrazení aktuálního stavu schvalovacího procesu klikněte na status dokumentu v label_my_open_approvals: My open approvals label_my_locked_documents: My locked documents - - title_create_link: Vytvořit symbolický odkaz + + title_create_link: Vytvořit symbolický odkaz label_link_from: Odkaz z label_link_to: Odkaz do label_notifications_on: Zapnout notifikace @@ -289,25 +289,25 @@ cs: 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_source_project: Zdrojový projekt + text_email_doc_updated_subject: Dokumenty aktualizovány text_email_doc_updated: právě aktualizoval dokumenty projektu text_email_doc_follows: takto text_email_doc_deleted_subject: Dokumenty smazány text_email_doc_deleted: právě smazal dokumety projektu - label_links_only: pouze odkazy - + label_links_only: pouze odkazy + label_display_notified_recipients: Zobrazit příjemce notifikací note_display_notified_recipients: Uživatel bude informován o příjemcích právě odeslané emailové notifikace. warning_email_notifications: "Notifikační email poslán na uživatele %{to}" - + link_trash_bin: Koš title_restore: Obnovit notice_dmsf_file_restored: Document byl úspěšně obnoven @@ -315,19 +315,21 @@ cs: notice_dmsf_link_restored: Odkaz byl úspěšně obnoven title_restore_checked: Obnov vybrané error_parent_folder: Nadřazený adresář neexistuje - + error_resource_or_parent_locked: Nelze zamknout - zdrojový nebo nadřazený objekt je zamčený error_parent_locked: Nelze zamknout - nadřazený objekt je zamčený error_resource_locked: Nelze zamknout - zdrojový objekt je zamčený - error_lock_exclusively: Nelze zamknout již zamčený objekt - error_unlock_parent_locked: Nelze odemknout - nadřazený objekt je zamčený - + error_lock_exclusively: Nelze zamknout již zamčený objekt + error_unlock_parent_locked: Nelze odemknout - nadřazený objekt je zamčený + + field_dmsf_tree_view: Navigate folders in a tree + my: blocks: locked_documents: Zamčené dokumenty open_approvals: Procesy ke schválení - + label_maximum_ajax_upload_filesize: Maximální velikost souboru nahratelná přes AJAX note_maximum_ajax_upload_filesize: Omezuje velikost souboru, který může být nahrán přes standardní rozhraní AJAX, jinak se použije standardní rozhraní Redminu. Číslo je v MB. label_classic: Klasický - label_drag_drop: "Drag&Drop" \ No newline at end of file + label_drag_drop: "Drag&Drop" diff --git a/config/locales/de.yml b/config/locales/de.yml index ac8e1705..bdeef3e3 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -1,5 +1,5 @@ # encoding: utf-8 -# +# # Redmine plugin for Document Management System "Features" # # Copyright (C) 2011 Terrence Miller @@ -26,13 +26,13 @@ de: 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. + error_email_to_must_be_entered: Es muss ein Email-Empfänger angegeben werden. warning_file_already_locked: Datei schon gesperrt notice_file_locked: Datei gesperrt warning_file_not_locked: Datei nicht gesperrt notice_file_unlocked: Datei freigegeben - error_only_user_that_locked_file_can_unlock_it: Nur der Benutzer, der die Datei gesperrt hat, kann sie auch wieder freigeben - error_max_files_exceeded: "Grenze für %{number} gleichzeitig heruntergeladene Dateien überschritten" + error_only_user_that_locked_file_can_unlock_it: Nur der Benutzer, der die Datei gesperrt hat, kann sie auch wieder freigeben + error_max_files_exceeded: "Grenze für %{number} gleichzeitig heruntergeladene Dateien überschritten" error_entry_project_does_not_match_current_project: Eingangsprojekt entspricht nicht aktuellem Projekt notice_folder_created: Ordner erstellt error_folder_creation_failed: Ordnererstellung fehlgeschlagen @@ -40,7 +40,7 @@ de: notice_folder_deleted: Ordner gelöscht error_folder_is_not_empty: Ordner ist nicht leer error_folder_title_is_already_used: Titel wird schon benutzt. Denk dir was Neues aus. - notice_folder_details_were_saved: Ordnerdetails wurden gespeichert + notice_folder_details_were_saved: Ordnerdetails wurden gespeichert error_folder_is_locked: Ordner ist gesperrt error_file_is_locked: Datei ist gesperrt notice_file_deleted: Datei gelöscht @@ -81,8 +81,8 @@ de: title_waiting_for_approval: Warte auf Zustimmung title_approved: Zugestimmt title_unlock_file: Hebe Sperre auf um Änderungen anderer Nutzer zu ermöglichen - title_lock_file: Sperre um Änderungen anderer Nutzer zu verhindern - title_download_checked: Download der ausgewählten Dateien in einem ZIP-Archiv + 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 DMS Projekt Einstellungen heading_send_documents_by_email: Sende Dateien per Email @@ -93,8 +93,8 @@ de: label_email_documents: Dateien label_email_body: Text label_email_send: Senden - title_notifications_active: Benachrichtigungen sind aktiv - label_upload: Upload + title_notifications_active: Benachrichtigungen sind aktiv + label_upload: Upload heading_new_folder: Neuer Ordner label_title: Titel label_description: Beschreibung @@ -113,9 +113,9 @@ de: label_created: Erstellt label_changed: Geändert info_changed_by_user: "%{changed} von" - label_filename: Dateiname + label_filename: Dateiname label_mime: Mime - label_size: Größe + label_size: Größe heading_new_revision: Neue Version option_version_same: gleiche Version option_version_minor: Unterversion @@ -150,8 +150,8 @@ de: permission_user_preferences: Benutzereinstellungen 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_manipulation: Dateien bearbeiten + permission_force_file_unlock: Erzwinge Aufhebung der Dateisperre permission_manage_workflows: Workflows verwalten permission_file_delete: Datei löschen label_file: Datei @@ -163,12 +163,12 @@ de: error_user_has_not_right_delete_folder: Der Nutzer hat kein Recht die Ordner zu löschen. error_user_has_not_right_delete_file: Der Nutzer hat kein Recht die Datei zu löschen. notice_entries_deleted: Einträge löschen - warning_some_entries_were_not_deleted: "Enige Einträge wurden nicht gelöscht: %{entries}" + warning_some_entries_were_not_deleted: "Enige Einträge wurden nicht gelöscht: %{entries}" title_delete_checked: Löschen ausgewählt title_items: items title_filename_for_download: Dateiname beim Herunterladen oder in ZIP-Archiv verwenden label_number_of_folders: Ordner - label_number_of_documents: Dokumente + label_number_of_documents: Dokumente error_file_storage_directory_does_not_exist: Der Dateiablageordner existiert nicht auf dem Server und kann nicht erstellt werden. error_file_can_not_be_created: Datei kann nicht in dem gewählten Ordner erstellt werden. error_wrong_zip_encoding: Falsche ZIP-Kodierung @@ -199,7 +199,7 @@ de: title_copy: Kopieren error_folder_cannot_be_copied: Der Ordner kann nicht kopiert werden. notice_folder_copied: Ordner kopiert - + error_max_email_filesize_exceeded: "Maximale Dateigröße der Anlage wurde überschritten. (%{number} MB)" note_maximum_email_filesize: Maximale Dateigröße der Anlage. 0 bedeutet keinen Limit. Angabe in MB. label_maximum_email_filesize: Maximale Dateigröße der Anlage @@ -209,7 +209,7 @@ de: note_webdav: "Nach der Aktivierung von WebDav kann der Dienst über die URL %{protocol}://%{domain}/dmsf/webdav/[project identifier] erreicht werden." label_webdav: Webdav Funktionalität label_dmsf_plural: "Kopieren von Dateien und Ordnern (%{files} Dateien in %{folders} Ordnern)" - + warning_folder_already_locked: Dieser Ordner ist bereits gesperrt notice_folder_locked: Der Ordner wurde erfolgreich gesperrt warning_folder_not_locked: Der Ordner konnte nicht gesperrt werden @@ -217,12 +217,12 @@ de: error_only_user_that_locked_folder_can_unlock_it: Sie haben keine Berechtigung zur Entsperrung des Ordners title_unlock_folder: Ordner zur Bearbeitung durch andere Benutzer entsperren title_lock_folder: Ordner zum Schutz vor Bearbeitung durch andere Benutzer sperren - + select_option_webdav_readonly: nur Lesen select_option_webdav_readwrite: Lesen/Schreiben label_webdav_strategy: Webdav Strategie - note_webdav_strategy: Erlaubt dem Administrator den Wechsel der WebDav Nutzung zwischen nur lesend und auch schreibenden Zugriffen. - + note_webdav_strategy: Erlaubt dem Administrator den Wechsel der WebDav Nutzung zwischen nur lesend und auch schreibenden Zugriffen. + error_unable_delete_dmsf_workflow: Konnte den Workflow nicht löschen error_empty_note: Die Notiz darf nicht leer sein. error_workflow_assign: Es trat ein Fehler beim Zuweisen des Workflows auf @@ -246,12 +246,12 @@ de: label_dmsf_workflow_add_approver: "Füge einen neuen Genehmiger mit einer logischen Funktion hinzu:" label_or: oder label_action: Aktion - label_note: Notiz + label_note: Notiz title_none: Niemand title_rejection: Ablehnung title_delegation: Deligierung title_assignment: Zuweisung - title_start: Start + title_start: Start title_dmsf_workflow_log: Genehmigungs-Workflow Verlauf title_assigned: Zugewiesen title_approval: Genehmigt @@ -261,7 +261,7 @@ de: dmsf_new_step: Neuer Schritt message_dmsf_wokflow_note: Deine Notiz... info_revision: "r%{rev}" - link_workflow: Workflow + link_workflow: Workflow notice_workflow_started: Genehmigungs-Workflow gestartet text_email_subject_approved: genehmigt text_email_subject_rejected: abgelehnt @@ -274,14 +274,14 @@ de: text_email_finished_delegated: "Der Genehmigungs-Workflow '%{name}' zugewiesen an die Datei '%{filename}' wurde an dich deligiert, weil: '%{notice}' und weil deine Zustimmung im aktuellen Genehmigungsschritt benötigt wird." text_email_finished_step: "Der Genehmigungs-Workflow '%{name}' zugewiesen an die Datei '%{filename}' hat grade einen Zustimmungsschritt abgeschlossen und im nächsten Genehmigungsschritt wird deine Zustimmung benötigt." text_email_finished_step_short: "Der Genehmigungs-Workflow '%{name}' zugewiesen an die Datei '%{filename}' hat grade einen Genehmigungsschritt abgeschlossen." - text_email_started: "Der Genehmigungs-Workflow '%{name}' zugewiesen an '%{filename}' wurde gestartet und im aktuellen Genehmigungsschritt wird deine Zustimmung benötigt." + text_email_started: "Der Genehmigungs-Workflow '%{name}' zugewiesen an '%{filename}' wurde gestartet und im aktuellen Genehmigungsschritt wird deine Zustimmung benötigt." text_email_to_proceed: Um fortzufahren klicke auf das Häckchen neben der Datei in text_email_to_see_history: Um den Verlauf des Genehmigungs-Workflows zu sehen klicke auf den Workflowstatus zur Datei in text_email_to_see_status: Um den aktuellen Status des Genehmigungs-Workflows zu sehen klicke auf den Workflowstatus zur Datei in label_my_open_approvals: Meine offenen Genehmigungen label_my_locked_documents: Meine geschlossenen Genehmigungen - - title_create_link: Verknüpfung anlegen + + title_create_link: Verknüpfung anlegen label_link_from: Verlinke von label_link_to: Verlinke zu label_notifications_on: Benachrichtigungen ein @@ -289,25 +289,25 @@ de: 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_source_project: Quellprojekt + text_email_doc_updated_subject: Dokumente wurden aktualisiert text_email_doc_updated: hat folgende Dokumente bearbeitet text_email_doc_follows: wie folgt text_email_doc_deleted_subject: Dokumente wurden gelöscht text_email_doc_deleted: hat folgende Dokumente gelöscht - label_links_only: nur Verknüpfungen - + label_links_only: nur Verknüpfungen + label_display_notified_recipients: Zeige benachrichtigte Empfänger note_display_notified_recipients: Der User wird über alle Empfänger der Emailbenachrichtigung informiert. warning_email_notifications: "Emailbenachrichtigung wurde gesendet an %{to}" - + link_trash_bin: Trash bin title_restore: Restore notice_dmsf_file_restored: The document has been successfully restored @@ -315,19 +315,21 @@ de: notice_dmsf_link_restored: The link has been successfully restored title_restore_checked: Restore checked error_parent_folder: "The parent folder doesn't exist" - + error_resource_or_parent_locked: Unable to complete lock - resource (or parent) is locked error_parent_locked: Unable to complete lock - resource parent is locked error_resource_locked: Unable to complete lock - resource is locked - error_lock_exclusively: unable to lock exclusively an already-locked resource - error_unlock_parent_locked: Unlock failed - resource parent is locked - + error_lock_exclusively: unable to lock exclusively an already-locked resource + error_unlock_parent_locked: Unlock failed - resource parent is locked + + field_dmsf_tree_view: Navigate folders in a tree + my: blocks: locked_documents: Gesperrte Dateien open_approvals: Offene Genehmigungs-Workflows - + label_maximum_ajax_upload_filesize: Maximale Dateigröße für den Upload via AJAX note_maximum_ajax_upload_filesize: Maximale Dateigröße für den Upload über die AJAX-Schnittstelle. Für größere Dateien muss der Standard-Uploader von Redmine verwendet werden. Angabe in MB. label_classic: Klassisch - label_drag_drop: "Drag&Drop" \ No newline at end of file + label_drag_drop: "Drag&Drop" diff --git a/config/locales/en.yml b/config/locales/en.yml index aef3c57c..0f6e1140 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1,5 +1,5 @@ # encoding: utf-8 -# +# # Redmine plugin for Document Management System "Features" # # Copyright (C) 2011 Vít Jonáš @@ -26,13 +26,13 @@ en: 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 + error_email_to_must_be_entered: Email To must be entered warning_file_already_locked: File already locked notice_file_locked: File locked warning_file_not_locked: File not locked notice_file_unlocked: File unlocked - error_only_user_that_locked_file_can_unlock_it: Only user that locked the file can unlock it - error_max_files_exceeded: "Limit for %{number} simultaneously downloaded files exceeded" + error_only_user_that_locked_file_can_unlock_it: Only user that locked the file can unlock it + error_max_files_exceeded: "Limit for %{number} simultaneously downloaded files exceeded" error_entry_project_does_not_match_current_project: "Entry project doesn't match current project" notice_folder_created: Folder created error_folder_creation_failed: Folder creation failed @@ -40,7 +40,7 @@ en: notice_folder_deleted: Folder deleted error_folder_is_not_empty: Folder is not empty error_folder_title_is_already_used: Title is already used - notice_folder_details_were_saved: Folder details were saved + notice_folder_details_were_saved: Folder details were saved error_folder_is_locked: Folder is locked error_file_is_locked: File is locked notice_file_deleted: File deleted @@ -81,8 +81,8 @@ en: title_waiting_for_approval: Waiting for Approval title_approved: Approved title_unlock_file: Unlock to allow changes for other members - title_lock_file: Lock to prevent changes for other members - title_download_checked: Download checked in Zip archive + title_lock_file: Lock to prevent changes for other members + title_download_checked: Download checked in Zip archive title_send_checked_by_email: Send checked by email link_user_preferences: Your DMSF project preferences heading_send_documents_by_email: Send documents by email @@ -93,8 +93,8 @@ en: label_email_documents: Documents label_email_body: Body label_email_send: Send - title_notifications_active: Notifications active - label_upload: Upload + title_notifications_active: Notifications active + label_upload: Upload heading_new_folder: New Folder label_title: Title label_description: Description @@ -113,9 +113,9 @@ en: label_created: Created label_changed: Changed info_changed_by_user: "%{changed} by" - label_filename: Filename + label_filename: Filename label_mime: Mime - label_size: Size + label_size: Size heading_new_revision: New Revision option_version_same: Same option_version_minor: Minor @@ -150,7 +150,7 @@ en: permission_user_preferences: User preferences permission_view_dmsf_files: View documents permission_folder_manipulation: Folder manipulation - permission_file_manipulation: File manipulation + permission_file_manipulation: File manipulation permission_force_file_unlock: Force file unlock permission_manage_workflows: Manage workflows permission_file_delete: Delete documents @@ -163,12 +163,12 @@ en: error_user_has_not_right_delete_folder: "User hasn't right to delete forders" error_user_has_not_right_delete_file: "User hasn't right to delete file" notice_entries_deleted: Entries deleted - warning_some_entries_were_not_deleted: "Some entries weren't deleted: %{entries}" + warning_some_entries_were_not_deleted: "Some entries weren't deleted: %{entries}" title_delete_checked: Delete checked 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 + label_number_of_documents: Documents error_file_storage_directory_does_not_exist: "File storage directory doesn't exist and can't be created" error_file_can_not_be_created: "File can't be created in storage directory" error_wrong_zip_encoding: Wrong Zip encoding @@ -199,7 +199,7 @@ en: title_copy: Copy error_folder_cannot_be_copied: "Folder can't be copied" notice_folder_copied: Folder copied - + error_max_email_filesize_exceeded: "You've exceeded the maximum filesize for sending via email. (%{number} MB)" note_maximum_email_filesize: Limits maximum filesize that can be sent via email. 0 means unlimited. Number is in MB. label_maximum_email_filesize: Maximum email attachment size @@ -221,14 +221,14 @@ en: select_option_webdav_readonly: Read-only select_option_webdav_readwrite: Read/Write label_webdav_strategy: Webdav strategy - note_webdav_strategy: Enables the administrator to decide if webdav is a read-only or read-write platform for end users. - + note_webdav_strategy: Enables the administrator to decide if webdav is a read-only or read-write platform for end users. + error_unable_delete_dmsf_workflow: Unable to delete the workflow error_empty_note: "The note can't be empty" error_workflow_assign: An error occured while assigning error_cannot_start_workflow: "Workflow can't be started" error_cannot_renumber_steps: "Steps can't be renumbered" - label_dmsf_workflow_new: New approval workflow + label_dmsf_workflow_new: New approval workflow field_label_dmsf_workflow: Approval Workflow field_label_dmsf_workflow_name: Approval workflow name label_dmsf_workflow_plural: Approval workflows @@ -246,23 +246,23 @@ en: label_dmsf_workflow_add_approver: "Add a new approver with a logical function:" label_or: or label_action: Action - label_note: Note + label_note: Note title_none: None title_rejection: Rejection title_delegation: Delegation title_assignment: Assignment - title_start: Start + title_start: Start title_dmsf_workflow_log: Approval Workflow Log title_assigned: Assigned title_approval: Approval title_rejected: Rejected dmsf_and: AND dmsf_or: OR - dmsf_new_step: New step + dmsf_new_step: New step message_dmsf_wokflow_note: Your note... info_revision: "r%{rev}" link_workflow: Workflow - notice_workflow_started: Approval workflow successfully started + notice_workflow_started: Approval workflow successfully started text_email_subject_approved: approved text_email_subject_rejected: rejected text_email_subject_delegated: delegated @@ -274,14 +274,14 @@ en: text_email_finished_delegated: "The approval workflow '%{name}' assigned to '%{filename}' document has just been delegated because of '%{notice}' and you are expected to do an approval in the current approval step." text_email_finished_step: "The approval workflow '%{name}' assigned to '%{filename}' document has just finished one of the approval steps and you are expected to do an approval in the next approval step." text_email_finished_step_short: "The approval workflow '%{name}' assigned to '%{filename}' document has just finished one of the approval steps." - text_email_started: "The approval workflow '%{name}' assigned to '%{filename}' document has just been started and you are expected to do an approval in the current approval step." + text_email_started: "The approval workflow '%{name}' assigned to '%{filename}' document has just been started and you are expected to do an approval in the current approval step." text_email_to_proceed: To proceed click on the check box icon next to the document in text_email_to_see_history: To see the approval history click on the workflow status of the document in text_email_to_see_status: To see the current status of the approval workflow click on the workflow status the document in label_my_open_approvals: My open approvals label_my_locked_documents: My locked documents - - title_create_link: Create a symbolic link + + title_create_link: Create a symbolic link label_link_from: Link from label_link_to: Link to label_notifications_on: Notifications on @@ -296,18 +296,18 @@ en: label_source_folder: Source folder label_target_project: Target project label_source_project: Source project - + text_email_doc_updated_subject: Documents updated text_email_doc_updated: has just actualized documents of text_email_doc_follows: as follows text_email_doc_deleted_subject: Documents deleted text_email_doc_deleted: has just deleted documents of - label_links_only: links only - + label_links_only: links only + 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 @@ -315,19 +315,21 @@ en: notice_dmsf_link_restored: The link has been successfully restored title_restore_checked: Restore checked error_parent_folder: "The parent folder doesn't exist" - + error_resource_or_parent_locked: Unable to complete lock - resource (or parent) is locked error_parent_locked: Unable to complete lock - resource parent is locked error_resource_locked: Unable to complete lock - resource is locked - error_lock_exclusively: unable to lock exclusively an already-locked resource - error_unlock_parent_locked: Unlock failed - resource parent is locked - + error_lock_exclusively: unable to lock exclusively an already-locked resource + error_unlock_parent_locked: Unlock failed - resource parent is locked + + field_dmsf_tree_view: Navigate folders in a tree + my: blocks: locked_documents: Locked documents - openap_provals: Open approvals - + openap_provals: 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. label_classic: Classic - label_drag_drop: "Drag&Drop" \ No newline at end of file + label_drag_drop: "Drag&Drop" diff --git a/config/locales/es.yml b/config/locales/es.yml index b652581a..ad698efd 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -1,5 +1,5 @@ # encoding: utf-8 -# +# # Redmine plugin for Document Management System "Features" # # Copyright (C) 2011 Vít Jonáš @@ -26,13 +26,13 @@ es: 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 + error_email_to_must_be_entered: Ingrese un email warning_file_already_locked: El archivo ya está bloqueado notice_file_locked: Archivo bloqueado warning_file_not_locked: Archivo no bloqueado notice_file_unlocked: Archivo desbloqueado - error_only_user_that_locked_file_can_unlock_it: Solo los usuarios que bloquearon previamente al archivo lo pueden desbloquear - error_max_files_exceeded: "Se excedio el numero permitido de archivos bajados de manera simultánea:" + error_only_user_that_locked_file_can_unlock_it: Solo los usuarios que bloquearon previamente al archivo lo pueden desbloquear + error_max_files_exceeded: "Se excedio el numero permitido de archivos bajados de manera simultánea:" error_entry_project_does_not_match_current_project: Las entradas del proyecto no concuerdan con el proyecto seleccionado notice_folder_created: Carpeta creada satisfactoriamente error_folder_creation_failed: La creacion de la carpeta ha fallado @@ -40,7 +40,7 @@ es: notice_folder_deleted: Carpeta borrada error_folder_is_not_empty: La carpeta no está vacía error_folder_title_is_already_used: El título ingresado ya está siendo usado por otro documento - notice_folder_details_were_saved: Los detalles de la carpeta fueron grabados correctamente + notice_folder_details_were_saved: Los detalles de la carpeta fueron grabados correctamente error_folder_is_locked: Carpeta bloqueado error_file_is_locked: Archivo bloqueado notice_file_deleted: Archivo borrado @@ -58,7 +58,7 @@ es: warning_file_notifications_already_activated: Las notificaciones del archivo seleccionado ya estaban activadas previamente notice_file_notifications_activated: Notificación de archivo activado warning_file_notifications_already_deactivated: Las notificaciones del archivo seleccionado ya estaban desactivadas previamente - notice_file_notifications_deactivated: Notificación de archivo desactivada + notice_file_notifications_deactivated: Notificación de archivo desactivada link_details: "%{title} detalles" link_edit: "Editar %{title}" submit_create: Crear @@ -81,8 +81,8 @@ es: title_waiting_for_approval: Esperando Aprobación title_approved: Aprobado title_unlock_file: Desbloquear para que otros miembros puedan editarlo - title_lock_file: Bloquear para que otros miembros no puedan editarlo - title_download_checked: Descargar archivos seleccionados en Zip + title_lock_file: Bloquear para que otros miembros no puedan editarlo + title_download_checked: Descargar archivos seleccionados en Zip title_send_checked_by_email: Enviar los seleccionados por email link_user_preferences: Preferencias de su proyecto DMSF heading_send_documents_by_email: Enviar documentos por email @@ -93,8 +93,8 @@ es: label_email_documents: Documentos label_email_body: Cuerpo de Mensaje label_email_send: Enviar - title_notifications_active: Activar Notificaciones - label_upload: Subir + title_notifications_active: Activar Notificaciones + label_upload: Subir heading_new_folder: Nuevo directorio label_title: Título label_description: Descripción @@ -113,9 +113,9 @@ es: label_created: Creado label_changed: Modificado info_changed_by_user: "%{changed} por" - label_filename: Nombre + label_filename: Nombre label_mime: Mime - label_size: Tamaño + label_size: Tamaño heading_new_revision: Nueva Revisión option_version_same: Misma option_version_minor: Menor @@ -150,7 +150,7 @@ es: permission_user_preferences: Preferencias de usuario permission_view_dmsf_files: Ver documentos permission_folder_manipulation: Manipulación de directorio - permission_file_manipulation: Manipulación de Archivos + permission_file_manipulation: Manipulación de Archivos permission_force_file_unlock: Forzar desbloqueo de archivo permission_manage_workflows: Gesti{on de flujo de trabajo permission_file_delete: Eliminar documentos @@ -163,7 +163,7 @@ es: error_user_has_not_right_delete_folder: "No tiene permisos para eliminar el directorio" error_user_has_not_right_delete_file: "No tiene permisos para eliminar el archivo" notice_entries_deleted: Entradas eliminadas - warning_some_entries_were_not_deleted: "Algunas entradas no fueron eliminadas: %{entries}" + warning_some_entries_were_not_deleted: "Algunas entradas no fueron eliminadas: %{entries}" title_delete_checked: Eliminación chequeada title_items: items title_filename_for_download: Nombre de archivo utilizado para descargar o crear zip @@ -221,8 +221,8 @@ es: select_option_webdav_readonly: Solo Lectura select_option_webdav_readwrite: Lectura/Escritura label_webdav_strategy: Estrategia Webdav - note_webdav_strategy: Habilitar el administrador para configurar si la plataforma webdav es solo lectura o lectura-escritura para los usuarios finales. - + note_webdav_strategy: Habilitar el administrador para configurar si la plataforma webdav es solo lectura o lectura-escritura para los usuarios finales. + error_unable_delete_dmsf_workflow: Incapaz de eliminar el flujo de trabajo error_empty_note: "La nota no puede estar vacía" error_workflow_assign: "Ocurió un errpr mientras se asignaba" @@ -246,7 +246,7 @@ es: label_dmsf_workflow_add_approver: "Añadir un nuevo aprobador con una función lógica:" label_or: o label_action: "Acción" - label_note: Nota + label_note: Nota title_none: Ninguno title_rejection: Rechazo title_delegation: "Delegación" @@ -261,7 +261,7 @@ es: dmsf_new_step: Nuevo Paso message_dmsf_wokflow_note: Tu nota... info_revision: "r%{rev}" - link_workflow: Flujo de Trabajo + link_workflow: Flujo de Trabajo notice_workflow_started: "Flujo de trabajo de aprobación iniciado satisfactoriamente" text_email_subject_approved: aprobado text_email_subject_rejected: rechazado @@ -274,13 +274,13 @@ es: text_email_finished_delegated: "El flujo de trabajo de aprobación '%{name}' asignado al documento '%{filename}' acaba de ser delegado por '%{notice}' y se espera que haga una aprobación en la etapa de aprobación actual." text_email_finished_step: "El flujo de trabajo de aprobación '%{name}' asignado al documento '%{filename}' acaba de terminar uno de los pasos de aprobación y se espera que haga una aprobación en el siguiente paso." text_email_finished_step_short: "El flujo de trabajo de aprobación '%{name}' asignado al documento '%{filename}' acaba de finalizar uno de los pasos de aprobación." - text_email_started: "El flujo de trabajo de aprobación '%{name}' asignado al documento '%{filename}' acaba de iniciarse y se espera que haga una aprobación en la etapa de aprobación actual." + text_email_started: "El flujo de trabajo de aprobación '%{name}' asignado al documento '%{filename}' acaba de iniciarse y se espera que haga una aprobación en la etapa de aprobación actual." text_email_to_proceed: "Para continuar, haga clic en el icono de la casilla de verificación al lado del documento" text_email_to_see_history: "Para ver el historial de aprobación haga clic en el estado del flujo de trabajo del documento" text_email_to_see_status: "Para ver el estado actual del flujo de trabajo de aprobación, haga clic en el estado del flujo de trabajo del documento" label_my_open_approvals: Mis aprobaciones abiertas label_my_locked_documents: Mis documentos bloqueados - + title_create_link: "Crear un enlace simbólico" label_link_from: Enlace desde label_link_to: Enlace hacia @@ -289,25 +289,25 @@ es: 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_source_project: Proyecto fuente + text_email_doc_updated_subject: Documentos actualizados text_email_doc_updated: acaba de actualizar los ducumentos de text_email_doc_follows: lo siguiente text_email_doc_deleted_subject: Documentos eliminados text_email_doc_deleted: acaba de eliminar documentos de label_links_only: Solo enlaces - + label_display_notified_recipients: Mostrar destinatarios notificados note_display_notified_recipients: "El usuario será informado de todos los destinatarios a los que acaba de enviar la notificación por correo electrónico" warning_email_notifications: "Notificaciones de email enviadas a %{to}" - + link_trash_bin: Tacho title_restore: Recuperar notice_dmsf_file_restored: El documento ha sido restaurado satisfactoriamente @@ -315,19 +315,21 @@ es: notice_dmsf_link_restored: El enlace ha sido restaurado satisfactoriamente title_restore_checked: Restaurar Seleccionados error_parent_folder: "El directorio padre no existe" - + error_resource_or_parent_locked: Unable to complete lock - resource (or parent) is locked error_parent_locked: Unable to complete lock - resource parent is locked error_resource_locked: Unable to complete lock - resource is locked - error_lock_exclusively: unable to lock exclusively an already-locked resource - error_unlock_parent_locked: Unlock failed - resource parent is locked - + error_lock_exclusively: unable to lock exclusively an already-locked resource + error_unlock_parent_locked: Unlock failed - resource parent is locked + + field_dmsf_tree_view: Navigate folders in a tree + my: blocks: locked_documents: Documentos bloqueados open_approvals: Aprobaciones abiertas - + label_maximum_ajax_upload_filesize: "El máximo tamaño de archivo para subir por AJAX" note_maximum_ajax_upload_filesize: "El límite máximo de tamaño de archivo que puede ser subido por la interfaz AJAX estandar, de lo contrario se debe utilizar el formulario estandar de Redmine. El número es en MB." label_classic: Classic - label_drag_drop: "Drag&Drop" \ No newline at end of file + label_drag_drop: "Drag&Drop" diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 05c79e33..62e19416 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -1,5 +1,5 @@ # encoding: utf-8 -# +# # Redmine plugin for Document Management System "Features" # # Copyright (C) 2011 Vít Jonáš @@ -31,8 +31,8 @@ fr: notice_file_locked: Fichier verrouillé warning_file_not_locked: Fichier déverrouillé notice_file_unlocked: Fichier déverrouillé - error_only_user_that_locked_file_can_unlock_it: "Le fichier ne peut être déverrouillé que par celui qui l'a verrouillé" - error_max_files_exceeded: Le nombre de fichiers pouvant être téléchargés simultanément est dépassé + error_only_user_that_locked_file_can_unlock_it: "Le fichier ne peut être déverrouillé que par celui qui l'a verrouillé" + error_max_files_exceeded: Le nombre de fichiers pouvant être téléchargés simultanément est dépassé error_entry_project_does_not_match_current_project: "Le projet saisi ne correspond pas au projet courant" notice_folder_created: Dossier créé error_folder_creation_failed: Erreur de création du dossier @@ -40,7 +40,7 @@ fr: notice_folder_deleted: Dossier supprimé error_folder_is_not_empty: "Le dossier n'est pas vide" error_folder_title_is_already_used: Le titre du fichier est déjà utilisé - notice_folder_details_were_saved: Les détails du dossier ont été enregistrés + notice_folder_details_were_saved: Les détails du dossier ont été enregistrés error_folder_is_locked: Le dossier est verrouillé error_file_is_locked: Le fichier est verrouillé notice_file_deleted: Le fichier a été supprimé @@ -81,8 +81,8 @@ fr: title_waiting_for_approval: Attente de validation title_approved: Validé title_unlock_file: Déverrouiller afin de permettre la modification par les membres du projet - title_lock_file: "Verrouiller afin d'empêcher les modifications du document" - title_download_checked: Télécharger les fichiers sélectionnés au format zip + title_lock_file: "Verrouiller afin d'empêcher les modifications du document" + title_download_checked: Télécharger les fichiers sélectionnés au format zip title_send_checked_by_email: Transmettre les fichiers sélectionnés par mail link_user_preferences: Préférences personnelles du module DMSF heading_send_documents_by_email: Transmettre les documents par mail @@ -93,8 +93,8 @@ fr: label_email_documents: Fichiers label_email_body: Message label_email_send: Envoyer - title_notifications_active: Notifications actives - label_upload: Transmission + title_notifications_active: Notifications actives + label_upload: Transmission heading_new_folder: Nouveau Dossier label_title: Titre label_description: Description @@ -113,9 +113,9 @@ fr: label_created: Créé label_changed: Modifié info_changed_by_user: "%{changed} par" - label_filename: Fichier + label_filename: Fichier label_mime: Type - label_size: Taille + label_size: Taille heading_new_revision: Nouvelle révision option_version_same: (identique) option_version_minor: (modification mineure) @@ -150,7 +150,7 @@ fr: permission_user_preferences: Préférences utilisateur permission_view_dmsf_files: Afficher documents permission_folder_manipulation: Gestion des dossiers - permission_file_manipulation: Gestion des documents + permission_file_manipulation: Gestion des documents permission_force_file_unlock: Forcer le déverrouillage du document permission_manage_workflows: Gérer les flux de validation permission_file_delete: Supprimer les documents @@ -163,12 +163,12 @@ fr: error_user_has_not_right_delete_folder: "L'utilisateur ne dispose pas des droits nécessaires permettant la suppression du dossier" error_user_has_not_right_delete_file: "L'utilisateur ne dispose pas des droits nécessaires permettant la suppression du dossier" notice_entries_deleted: Elément(s) supprimé(s) - warning_some_entries_were_not_deleted: "Certains éléments n'ont pas été supprimés : %{entries}" + 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_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 + label_number_of_documents: Fichiers error_file_storage_directory_does_not_exist: Le répertoire de stockage des fichiers n'existe pas ou n'a pas pu être créé error_file_can_not_be_created: "Le fichier n'a pas pu être enregistré dans le répertoire de stockage" error_wrong_zip_encoding: Mauvais jeu de caractères pour la transformation du nom du ZIP @@ -199,7 +199,7 @@ fr: title_copy: Copie error_folder_cannot_be_copied: Le dossier ne peut pas être copié notice_folder_copied: Dossier copié - + error_max_email_filesize_exceeded: "Vous avez dépassé la taille maximale des fichiers pouvant être transmis par mail (%{number} MB)" note_maximum_email_filesize: Taille maximale, en méga octets, des fichiers pouvant être transmis par mail. 0 indique aucune restriction label_maximum_email_filesize: Taille maximale du fichier attaché @@ -217,12 +217,12 @@ fr: error_only_user_that_locked_folder_can_unlock_it: "Vous n'êtes autorisé à déverrouiller ce dossier" title_unlock_folder: Déverrouiller afin de permettre la modification par les membres du projet title_lock_folder: "Verrouiller afin d'empêcher les modifications du dossier" - + select_option_webdav_readonly: Lecture select_option_webdav_readwrite: Lecture/Ecriture label_webdav_strategy: Accès Webdav note_webdav_strategy: "Permet à l'administrateur d'autoriser les utilisateurs au module Webdav en letcure seule ou en lecture et écriture." - + error_unable_delete_dmsf_workflow: Impossible de supprimer le flux de validation error_empty_note: La note ne peut pas être vide error_workflow_assign: "Une erreur s'est produite lors de l'attribution" @@ -246,22 +246,22 @@ fr: label_dmsf_workflow_add_approver: "Ajouter un nouvel approbateur avec une fonction logique :" label_or: ou label_action: Action - label_note: Note + label_note: Note title_none: Aucun title_rejection: Rejet title_delegation: Délégation title_assignment: Attribution - title_start: Démarrer + title_start: Démarrer title_dmsf_workflow_log: Journal du flux de validation title_assigned: Assigné title_approval: Approbation title_rejected: Rejeté dmsf_and: ET dmsf_or: OU - dmsf_new_step: Nouvelle étape + dmsf_new_step: Nouvelle étape message_dmsf_wokflow_note: Votre note... info_revision: "r%{rev}" - link_workflow: Flux + link_workflow: Flux notice_workflow_started: Flux de validation démarré avec succès text_email_subject_approved: approuvé text_email_subject_rejected: rejeté @@ -280,8 +280,8 @@ fr: text_email_to_see_status: Pour consulter le statut actuel du flux de validation, cliquez sur le statut du flux du document dans label_my_open_approvals: Mes approbations en attente label_my_locked_documents: Mes documents verrouillés - - title_create_link: Créer un lien symbolique + + title_create_link: Créer un lien symbolique label_link_from: Lien depuis label_link_to: Lien vers label_notifications_on: Activer les notifications @@ -289,25 +289,25 @@ fr: field_target_file: Fichier source title_download_entries: Historique des téléchargements label_external: Externe - + 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_source_project: Projet source + text_email_doc_updated_subject: Documents mis à jour text_email_doc_updated: a mis à jour des documents de text_email_doc_follows: comme suit text_email_doc_deleted_subject: Documents supprimés text_email_doc_deleted: a supprimé des documents de - label_links_only: liens seulement - + label_links_only: liens seulement + label_display_notified_recipients: Afficher les destinataires notifiés note_display_notified_recipients: "L'utilisateur sera informé de tous les destinataires à qui un email de notifcation a été envoyé" warning_email_notifications: "Email de notification envoyé à %{to}" - + link_trash_bin: Corbeille title_restore: Récupérer notice_dmsf_file_restored: Le document a été récupéré avec succès @@ -315,12 +315,14 @@ fr: notice_dmsf_link_restored: Le lien a été récupéré avec succès title_restore_checked: Restauration vérifiée error_parent_folder: "Le dossier parent n'existe pas" - + error_resource_or_parent_locked: Unable to complete lock - resource (or parent) is locked error_parent_locked: Unable to complete lock - resource parent is locked error_resource_locked: Unable to complete lock - resource is locked - error_lock_exclusively: unable to lock exclusively an already-locked resource - error_unlock_parent_locked: Unlock failed - resource parent is locked + error_lock_exclusively: unable to lock exclusively an already-locked resource + error_unlock_parent_locked: Unlock failed - resource parent is locked + + field_dmsf_tree_view: Navigate folders in a tree my: blocks: @@ -330,4 +332,4 @@ fr: 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." label_classic: Classic - label_drag_drop: "Drag&Drop" \ No newline at end of file + label_drag_drop: "Drag&Drop" diff --git a/config/locales/ja.yml b/config/locales/ja.yml index a832fc18..47f2ebcb 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -1,5 +1,5 @@ # encoding: utf-8 -# +# # Redmine plugin for Document Management System "Features" # # Copyright (C) 2011 Vít Jonáš @@ -26,13 +26,13 @@ ja: label_dmsf_file_revision_plural: 文書管理ファイルリビジョン label_dmsf_file_revision_access_plural: 文書管理ファイルアクセス warning_no_entries_selected: エントリーが選ばれていません - error_email_to_must_be_entered: 電子メールの To は省略できません + error_email_to_must_be_entered: 電子メールの To は省略できません warning_file_already_locked: ファイルは既にロックされています notice_file_locked: ファイルをロックしました warning_file_not_locked: ファイルはロックされていません notice_file_unlocked: ファイルをロック解除しました - error_only_user_that_locked_file_can_unlock_it: ファイルをロックしたユーザだけがロック解除できます - error_max_files_exceeded: "同時にダウンロードできるファイル数の上限 %{number} を超えています" + error_only_user_that_locked_file_can_unlock_it: ファイルをロックしたユーザだけがロック解除できます + error_max_files_exceeded: "同時にダウンロードできるファイル数の上限 %{number} を超えています" error_entry_project_does_not_match_current_project: 指定したプロジェクトは現在のプロジェクトと一致しません notice_folder_created: フォルダを作成しました error_folder_creation_failed: フォルダを作成できません @@ -40,7 +40,7 @@ ja: notice_folder_deleted: フォルダを削除しました error_folder_is_not_empty: フォルダが空ではありません error_folder_title_is_already_used: タイトルは既に使われています - notice_folder_details_were_saved: フォルダの詳細を保存しました + notice_folder_details_were_saved: フォルダの詳細を保存しました error_folder_is_locked: Folder is locked error_file_is_locked: ファイルはロックされています notice_file_deleted: ファイルを削除しました @@ -81,8 +81,8 @@ ja: title_waiting_for_approval: 承認待ち title_approved: 承認済み title_unlock_file: ロック解除して他のメンバーの変更を許可します - title_lock_file: ロックして他のメンバーの変更を禁止します - title_download_checked: チェックしたものを Zip アーカイブでダウンロードします + title_lock_file: ロックして他のメンバーの変更を禁止します + title_download_checked: チェックしたものを Zip アーカイブでダウンロードします title_send_checked_by_email: チェックしたものを電子メールで送信します link_user_preferences: プロジェクトの文書管理設定 heading_send_documents_by_email: 電子メールによる文書の送信 @@ -93,8 +93,8 @@ ja: label_email_documents: 文書 label_email_body: 本文 label_email_send: 送信 - title_notifications_active: 通知は有効です - label_upload: アップロード + title_notifications_active: 通知は有効です + label_upload: アップロード heading_new_folder: 新規フォルダ label_title: タイトル label_description: 説明 @@ -113,9 +113,9 @@ ja: label_created: 作成者/日時 label_changed: 更新者/日時 info_changed_by_user: "/ %{changed}" - label_filename: ファイル名 + label_filename: ファイル名 label_mime: Mime - label_size: サイズ + label_size: サイズ heading_new_revision: 新しいリビジョン option_version_same: 変更なし option_version_minor: マイナー @@ -140,7 +140,7 @@ ja: note_stem_some: "大文字から始まる、特定の文字の後に続く、あるいは位置情報を必要とするオペレーターと共に使われる語を除くそれ以外の語の語幹を検索します。語幹抽出された語は、先頭に 'Z' が付きます。" note_stem_all: "すべての語の語幹を検索します。(注: 先頭に 'Z' は付きません。)" note_stemming_applied: 語幹抽出アルゴリズムは、確率を測る対象の位置にある語にしか適用できない (論理演算子の用語自体は語幹抽出されない) ことにご注意ください。 - label_default_notifications: ファイル通知の既定値 + label_default_notifications: ファイル通知の既定値 heading_uploaded_files: アップロードされたファイル submit_commit: コミット link_documents: 文書 @@ -150,10 +150,10 @@ ja: permission_user_preferences: ユーザ設定 permission_view_dmsf_files: ファイルの表示 permission_folder_manipulation: フォルダの操作 - permission_file_manipulation: ファイルの操作 + permission_file_manipulation: ファイルの操作 permission_force_file_unlock: ファイルの強制ロック解除 permission_manage_workflows: ワークフロー管理 - permission_file_delete: ファイルの削除 + permission_file_delete: ファイルの削除 label_file: ファイル field_folder: フォルダ error_create_cycle_in_folder_dependency: フォルダの依存関係が循環しています @@ -163,12 +163,12 @@ ja: error_user_has_not_right_delete_folder: ユーザにフォルダを削除する権限がありません error_user_has_not_right_delete_file: ユーザにファイルを削除する権限がありません notice_entries_deleted: エントリーを削除しました - warning_some_entries_were_not_deleted: "いくつかのエントリーは削除されませんでした: %{entries}" + warning_some_entries_were_not_deleted: "いくつかのエントリーは削除されませんでした: %{entries}" title_delete_checked: チェックしたものを削除します title_items: items title_filename_for_download: ファイル名はダウンロードまたは Zip アーカイブに使われます label_number_of_folders: フォルダ - label_number_of_documents: 文書 + label_number_of_documents: 文書 error_file_storage_directory_does_not_exist: ファイル保存フォルダが存在せず作ることもできません error_file_can_not_be_created: ファイルを保存フォルダに作ることができません error_wrong_zip_encoding: Zip エンコーディングが正しくありません @@ -192,14 +192,14 @@ ja: title_copy_or_move: コピー/移動 label_dmsf_folder_plural: Dmsf フォルダ comment_moved_from: "%{source} から移動しました" - error_target_folder_same: コピー/移動先のフォルダとプロジェクトが現在と同じです + error_target_folder_same: コピー/移動先のフォルダとプロジェクトが現在と同じです error_file_cannot_be_moved: ファイルを移動できません error_file_cannot_be_copied: ファイルをコピーできません warning_no_project_to_copy_folder_to: フォルダをコピーするプロジェクトがありません title_copy: コピー error_folder_cannot_be_copied: フォルダをコピーできません - notice_folder_copied: フォルダをコピーしました - + notice_folder_copied: フォルダをコピーしました + error_max_email_filesize_exceeded: "電子メールで送信可能なファイルサイズの上限を越えています (%{number} MB)" note_maximum_email_filesize: "電子メールで送信可能なファイルサイズの上限。0は無制限。単位はMB。" label_maximum_email_filesize: "電子メール添付ファイルサイズ上限" @@ -214,7 +214,7 @@ ja: notice_folder_locked: "フォルダをロックしました" warning_folder_not_locked: "フォルダをロックすることができませんでした" notice_folder_unlocked: "フォルダのロックを解除しました" - error_only_user_that_locked_folder_can_unlock_it: "このフォルダのロックを解除する権限がありません" + error_only_user_that_locked_folder_can_unlock_it: "このフォルダのロックを解除する権限がありません" title_unlock_folder: "ロックを解除し他メンバが更新できるようにします" title_lock_folder: "ロックし他メンバとの競合を回避します" @@ -246,22 +246,22 @@ ja: label_dmsf_workflow_add_approver: "新規承認者の追加:" label_or: または label_action: アクション - label_note: コメント + label_note: コメント title_none: 無し title_rejection: 否認 title_delegation: 代理承認 title_assignment: アサイン - title_start: 開始 + title_start: 開始 title_dmsf_workflow_log: 承認ワークフローの履歴 title_assigned: アサイン title_approval: 承認 title_rejected: 否認 dmsf_and: AND dmsf_or: OR - dmsf_new_step: 新規ステップ + dmsf_new_step: 新規ステップ message_dmsf_wokflow_note: コメント info_revision: "r%{rev}" - link_workflow: ワークフロー + link_workflow: ワークフロー notice_workflow_started: 承認ワークフローが開始されました text_email_subject_approved: は承認されました text_email_subject_rejected: は否認されました @@ -274,14 +274,14 @@ ja: text_email_finished_delegated: "承認ワークフロー '%{name}' において代理承認が依頼されました。承認対象 '%{filename}' の内容をご確認の上、承認・否認のご判断をお願い致します(依頼主コメント:'%{notice}')。" text_email_finished_step: "承認ワークフロー '%{name}' からの承認依頼です。承認対象 '%{filename}' の内容をご確認の上、承認・否認のご判断をお願い致します。" text_email_finished_step_short: "承認ワークフロー '%{name}' において '%{filename}' の承認ステップが一つ終了しました。" - text_email_started: "承認ワークフロー '%{name}' からの承認依頼です。承認対象 '%{filename}' の内容をご確認の上、承認・否認のご判断をお願い致します。" + text_email_started: "承認ワークフロー '%{name}' からの承認依頼です。承認対象 '%{filename}' の内容をご確認の上、承認・否認のご判断をお願い致します。" text_email_to_proceed: 次のURLを開き内容を確認の上、右端のチェックマークをクリックし承認・否認の選択をしてください。 text_email_to_see_history: 次のURLで承認ワークフローの履歴を確認することができます。 text_email_to_see_status: 次のURLで承認ワークフローのステータスを確認することができます。 label_my_open_approvals: 自分の承認待ち label_my_locked_documents: 自分のロック中ファイル - - title_create_link: シンボリックリンクを作成します + + title_create_link: シンボリックリンクを作成します label_link_from: リンク生成(from) label_link_to: リンク生成(to) label_notifications_on: 通知オン @@ -289,25 +289,25 @@ ja: field_target_file: リンク元ファイル title_download_entries: ダウンロード記録 label_external: 外部 - + label_link_name: リンク名 label_link_external_url: URL label_target_folder: リンク先フォルダ label_source_folder: リンク元フォルダ label_target_project: リンク先プロジェクト - label_source_project: リンク元プロジェクト - + label_source_project: リンク元プロジェクト + text_email_doc_updated_subject: プロジェクトのファイルが更新されました text_email_doc_updated: が次のファイルを更新しました。 text_email_doc_follows: 対象ファイル: text_email_doc_deleted_subject: プロジェクトのファイルが削除されました text_email_doc_deleted: が次のプロジェクトのファイルを削除しました。 label_links_only: リンクのみ - + label_display_notified_recipients: 通知受信者の表示 note_display_notified_recipients: 電子メールで通知したすべてのユーザの情報を表示します。 warning_email_notifications: "%{to} へ電子メールで通知しました" - + link_trash_bin: ゴミ箱 title_restore: 復元 notice_dmsf_file_restored: ファイルを復元しました @@ -315,19 +315,21 @@ ja: notice_dmsf_link_restored: リンクを復元しました title_restore_checked: チェックしたものを復元します error_parent_folder: "親フォルダが存在しません" - + error_resource_or_parent_locked: Unable to complete lock - resource (or parent) is locked error_parent_locked: Unable to complete lock - resource parent is locked error_resource_locked: Unable to complete lock - resource is locked - error_lock_exclusively: unable to lock exclusively an already-locked resource - error_unlock_parent_locked: Unlock failed - resource parent is locked - + error_lock_exclusively: unable to lock exclusively an already-locked resource + error_unlock_parent_locked: Unlock failed - resource parent is locked + + field_dmsf_tree_view: Navigate folders in a tree + my: blocks: locked_documents: ロック中 open_approvals: 未承認 - + label_maximum_ajax_upload_filesize: アップロードファイルサイズ上限 note_maximum_ajax_upload_filesize: アップロード可能なファイルサイズの上限。AjaxおよびRedmineの仕様に制限される(2ギガバイト程度までは確認済み)単位はMB。 label_classic: Classic - label_drag_drop: "Drag&Drop" \ No newline at end of file + label_drag_drop: "Drag&Drop" diff --git a/config/locales/pl.yml b/config/locales/pl.yml index 57e1dc8e..ee77e5a1 100644 --- a/config/locales/pl.yml +++ b/config/locales/pl.yml @@ -1,5 +1,5 @@ # encoding: utf-8 -# +# # Redmine plugin for Document Management System "Features" # # Copyright (C) 2011 Vít Jonáš @@ -26,20 +26,20 @@ pl: 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 + error_email_to_must_be_entered: Musisz podać adres email warning_file_already_locked: Plik jest już zablokowany notice_file_locked: Plik zablokowany warning_file_not_locked: Plik nie zablokowany notice_file_unlocked: Plik odblokowany - error_only_user_that_locked_file_can_unlock_it: Plik może zostać odblokowany jedynie przez użytkownika, który go zablokował - error_max_files_exceeded: "Limit %{number} jedocześnie pobieranych plików został przekroczony" + error_only_user_that_locked_file_can_unlock_it: Plik może zostać odblokowany jedynie przez użytkownika, który go zablokował + error_max_files_exceeded: "Limit %{number} jedocześnie pobieranych plików został przekroczony" error_entry_project_does_not_match_current_project: "Podany projekt nie odpowiada obecnemu projektowy" notice_folder_created: Folder został utworzony error_folder_creation_failed: Błąd podczas tworzenia folderu error_folder_title_must_be_entered: Musisz podać tytuł notice_folder_deleted: Folder został usunięty error_folder_is_not_empty: Folder zawiera pliki - error_folder_title_is_already_used: Podany tytuł jest już w użyciu + error_folder_title_is_already_used: Podany tytuł jest już w użyciu notice_folder_details_were_saved: Szczegóły folderu zostały zapisane error_folder_is_locked: Folder jest zablokowany error_file_is_locked: Plik jest zablokowany @@ -81,7 +81,7 @@ pl: title_waiting_for_approval: Oczekiwanie na akceptację title_approved: Zaakceptowany title_unlock_file: Odblokuj aby umożliwić wprowadzanie zmian innym użytkownikom - title_lock_file: Zablokuj aby zabezpieczyć przed wprowadzaniem zmian przez innych użytkowników + title_lock_file: Zablokuj aby zabezpieczyć przed wprowadzaniem zmian przez innych użytkowników title_download_checked: Pobierz zaznaczone jako archiwum Zip title_send_checked_by_email: Wyślij zaznaczone przez email link_user_preferences: Preferencje DMSF dla projektu @@ -93,8 +93,8 @@ pl: label_email_documents: Dokumenty label_email_body: Treść label_email_send: Wyślij - title_notifications_active: Powiadomienia aktywne - label_upload: Prześlij + title_notifications_active: Powiadomienia aktywne + label_upload: Prześlij heading_new_folder: Nowy Folder label_title: Tytuł label_description: Opis @@ -113,7 +113,7 @@ pl: label_created: Utworzono label_changed: Zmieniono info_changed_by_user: "%{changed} przez" - label_filename: Nazwa pliku + label_filename: Nazwa pliku label_mime: Mime label_size: Rozmiar heading_new_revision: Nowa wersja @@ -163,15 +163,15 @@ pl: error_user_has_not_right_delete_folder: "Użytkownik nie posiada uprawnień do usuwania folderów" error_user_has_not_right_delete_file: "Użytkownik nie posiada uprawnień do usuwania plików" notice_entries_deleted: Wpisy usunięte - warning_some_entries_were_not_deleted: "Niektóre wpisy nie zostały usunięte: %{entries}" + warning_some_entries_were_not_deleted: "Niektóre wpisy nie zostały usunięte: %{entries}" title_delete_checked: Usuń zaznaczone 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 + label_number_of_documents: Dokumenty error_file_storage_directory_does_not_exist: "Folder przechowywania plików nie istnieje i nie może zostać utworzony" error_file_can_not_be_created: "Plik nie może zostać utworzony w folderze przechowywania" - error_wrong_zip_encoding: Złe kodowanie Zip + error_wrong_zip_encoding: Złe kodowanie Zip warning_xapian_not_available: Xapian niedostępny menu_dmsf: DMSF label_physical_file_delete: Fizyczne usuwanie plików @@ -199,7 +199,7 @@ pl: title_copy: Kopiuj error_folder_cannot_be_copied: "Folder nie może zostać skopiowany" notice_folder_copied: Folder został skopiowany - + error_max_email_filesize_exceeded: "Maksymalny rozmiar pliku załącznika email został przekroczony. (%{number} MB)" note_maximum_email_filesize: Maksymalny rozmiar pliku, który może zostać wysłany przez email. 0 oznacza brak ograniczeń. Rozmiar w MB. label_maximum_email_filesize: Maksymalny rozmiar załącznika email @@ -221,14 +221,14 @@ pl: select_option_webdav_readonly: Tylko do odczytu select_option_webdav_readwrite: Odczyt/Zapis label_webdav_strategy: Webdav strategy - note_webdav_strategy: Enables the administrator to decide if webdav is a read-only or read-write platform for end users. - + note_webdav_strategy: Enables the administrator to decide if webdav is a read-only or read-write platform for end users. + error_unable_delete_dmsf_workflow: Nie można usunąć procesu workflow error_empty_note: "Notatka nie może być pusta" error_workflow_assign: Wystąpił błąd podczas przydzielania error_cannot_start_workflow: "Workflow nie może zostać uruchomiony" error_cannot_renumber_steps: "Kroki nie mogą zostać przenumerowane" - label_dmsf_workflow_new: Nowy proces akceptacji + label_dmsf_workflow_new: Nowy proces akceptacji field_label_dmsf_workflow: Proces akceptacji field_label_dmsf_workflow_name: Nazwa procesu akceptacji label_dmsf_workflow_plural: Procesy akceptacji @@ -246,23 +246,23 @@ pl: label_dmsf_workflow_add_approver: "Dodaj nowego akceptującego z warunkiem logicznym:" label_or: lub label_action: Akcja - label_note: Notatka + label_note: Notatka title_none: Brak title_rejection: Odrzucenie title_delegation: Delegacja title_assignment: Przydział - title_start: Start + title_start: Start title_dmsf_workflow_log: Historia procesu akceptacji title_assigned: Przydzielony title_approval: Akceptacja title_rejected: Odrzucony dmsf_and: AND dmsf_or: OR - dmsf_new_step: Nowy krok + dmsf_new_step: Nowy krok message_dmsf_wokflow_note: Twoja notatka... info_revision: "r%{rev}" link_workflow: Proces akceptacji - notice_workflow_started: Proces akceptacji został uruchomiony + notice_workflow_started: Proces akceptacji został uruchomiony text_email_subject_approved: został zakończony akceptacją text_email_subject_rejected: został odrzucony text_email_subject_delegated: został delegowany @@ -274,14 +274,14 @@ pl: text_email_finished_delegated: "Proces akceptacji '%{name}' dokumentu '%{filename}' został właśnie delegowany z powodu '%{notice}'. Zostałeś wskazany jako akceptujący w bieżącym kroku zatwierdzania." text_email_finished_step: "Zakończono krok w procesie akceptacji '%{name}' dokumentu '%{filename}'. Jesteś kolejną osobą decyzyjną w procesie akceptacji." text_email_finished_step_short: "Zakończono krok w procesie akceptacji '%{name}' dokumentu '%{filename}'." - text_email_started: "Proces akceptacji '%{name}' dokuentu '%{filename}' został uruchomiony. Jesteś osobą akceptującą w bieżącym kroku zatwierdzania." + text_email_started: "Proces akceptacji '%{name}' dokuentu '%{filename}' został uruchomiony. Jesteś osobą akceptującą w bieżącym kroku zatwierdzania." text_email_to_proceed: Aby procedować zaznacz check box przy dokumencie text_email_to_see_history: Aby zobaczyć historię akceptacji kliknij w proces akceptacji dokumentu text_email_to_see_status: Aby zobaczyć aktualny stan procesu akceptacji kliknij w proces akceptacji dokumentu label_my_open_approvals: Otwarte akceptacje label_my_locked_documents: Zablokowane dokumenty - - title_create_link: Utwórz symbolic link + + title_create_link: Utwórz symbolic link label_link_from: Odnośnik z label_link_to: Odnośnik do label_notifications_on: Powiadomienia włączone @@ -289,25 +289,25 @@ pl: 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_source_project: Projekt źródłowy + text_email_doc_updated_subject: Dokumenty zostały zaktualizowane text_email_doc_updated: dokumenty zostały zaktualizowane text_email_doc_follows: następujące text_email_doc_deleted_subject: Dokumenty zostały usunięte text_email_doc_deleted: dokumenty zostały usunięte label_links_only: odnośniki - + label_display_notified_recipients: Wyświetl odbiorców powiadomienia note_display_notified_recipients: Użytkownik zostanie poinformowany o wszystkich odbiorcach wysłanego powiadomienia email. warning_email_notifications: "Powiadomienie email zostało wysłane do %{to}" - + link_trash_bin: Kosz title_restore: Przywróć notice_dmsf_file_restored: Dokument został przywrócony @@ -315,19 +315,21 @@ pl: notice_dmsf_link_restored: Link został przywrócony title_restore_checked: Restore checked error_parent_folder: "Folder nadrzędny nie istnieje" - + error_resource_or_parent_locked: Unable to complete lock - resource (or parent) is locked error_parent_locked: Unable to complete lock - resource parent is locked error_resource_locked: Unable to complete lock - resource is locked - error_lock_exclusively: unable to lock exclusively an already-locked resource + error_lock_exclusively: unable to lock exclusively an already-locked resource error_unlock_parent_locked: Unlock failed - resource parent is locked - + + field_dmsf_tree_view: Navigate folders in a tree + my: blocks: locked_documents: Dokumenty zablokowane - openap_provals: Otwarte procesy akceptacji - + openap_provals: Otwarte procesy akceptacji + 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. label_classic: Classic - label_drag_drop: "Drag&Drop" \ No newline at end of file + label_drag_drop: "Drag&Drop" diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml index 095af60c..25c15dad 100644 --- a/config/locales/pt-BR.yml +++ b/config/locales/pt-BR.yml @@ -40,7 +40,7 @@ pt-BR: notice_folder_deleted: Pasta deletada error_folder_is_not_empty: Pasta não está vazia error_folder_title_is_already_used: Título já utilizado - notice_folder_details_were_saved: Pasta atualizada + notice_folder_details_were_saved: Pasta atualizada error_folder_is_locked: Pasta está bloqueada error_file_is_locked: Arquivo está bloqueado notice_file_deleted: Arquivo excluído @@ -93,8 +93,8 @@ pt-BR: label_email_documents: DMSF label_email_body: Descrição label_email_send: Enviar - title_notifications_active: Notificações Ativas - label_upload: Envio + title_notifications_active: Notificações Ativas + label_upload: Envio heading_new_folder: Nova Pasta label_title: Taxonomia label_description: Descrição @@ -315,12 +315,14 @@ pt-BR: notice_dmsf_link_restored: O link foi restaurado com sucesso title_restore_checked: Restaurar pasta ou arquivo error_parent_folder: "A pasta de pai não existe" - + error_resource_or_parent_locked: Unable to complete lock - resource (or parent) is locked error_parent_locked: Unable to complete lock - resource parent is locked error_resource_locked: Unable to complete lock - resource is locked - error_lock_exclusively: unable to lock exclusively an already-locked resource - error_unlock_parent_locked: Unlock failed - resource parent is locked + error_lock_exclusively: unable to lock exclusively an already-locked resource + error_unlock_parent_locked: Unlock failed - resource parent is locked + + field_dmsf_tree_view: Navigate folders in a tree my: blocks: @@ -330,4 +332,4 @@ pt-BR: 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. label_classic: Classic - label_drag_drop: "Drag&Drop" \ No newline at end of file + label_drag_drop: "Drag&Drop" diff --git a/config/locales/ru.yml b/config/locales/ru.yml index 35caeabd..0cb33f61 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -1,5 +1,5 @@ # encoding: utf-8 -# +# # Redmine plugin for Document Management System "Features" # # Copyright (C) 2011 Vít Jonáš @@ -26,13 +26,13 @@ ru: label_dmsf_file_revision_plural: Изменения документов label_dmsf_file_revision_access_plural: Доступы к документам warning_no_entries_selected: Файлы не выбраны - error_email_to_must_be_entered: Нужно указать, на какую почту отправить письмо + error_email_to_must_be_entered: Нужно указать, на какую почту отправить письмо warning_file_already_locked: Файл уже заблокирован notice_file_locked: Файл заблокирован warning_file_not_locked: Файл не заблокирован notice_file_unlocked: Файл разблокирован - error_only_user_that_locked_file_can_unlock_it: Только пользователь, который заблокировал файл, может его разблокировать - error_max_files_exceeded: "Ограничение для %{number} одновременно загружаемых файлов превышено" + error_only_user_that_locked_file_can_unlock_it: Только пользователь, который заблокировал файл, может его разблокировать + error_max_files_exceeded: "Ограничение для %{number} одновременно загружаемых файлов превышено" error_entry_project_does_not_match_current_project: Проект, которому принадлежит файл, не соответсвует текущему проекту notice_folder_created: Папка создана error_folder_creation_failed: Папку не удалось создать @@ -40,7 +40,7 @@ ru: notice_folder_deleted: Папка удалена error_folder_is_not_empty: Папка не пустая error_folder_title_is_already_used: Название папки уже используется - notice_folder_details_were_saved: Описание папки было сохранено + notice_folder_details_were_saved: Описание папки было сохранено error_folder_is_locked: Папка заблокированa error_file_is_locked: Файл заблокирован notice_file_deleted: Файл удален @@ -81,8 +81,8 @@ ru: title_waiting_for_approval: Ожидается на утверждение title_approved: Утверждено title_unlock_file: Разблокируйте файл, чтобы разрешить изменение его другими участниками - title_lock_file: Заблокируйте файл, чтобы запретить его изменение другими участниками - title_download_checked: Скачать выбранные файлы + title_lock_file: Заблокируйте файл, чтобы запретить его изменение другими участниками + title_download_checked: Скачать выбранные файлы title_send_checked_by_email: Отправить выбранные файлы по электронной почте link_user_preferences: Ваши настройки DMSF проекта heading_send_documents_by_email: Отправить документы по электронной почте @@ -93,8 +93,8 @@ ru: label_email_documents: Документы label_email_body: Содержание label_email_send: Отправить - title_notifications_active: Уведомления активны - label_upload: Закачать + title_notifications_active: Уведомления активны + label_upload: Закачать heading_new_folder: Новая папка label_title: Заголовок label_description: Описание @@ -113,9 +113,9 @@ ru: label_created: Создан label_changed: Изменен info_changed_by_user: "%{changed} пользователем" - label_filename: Имя файла + label_filename: Имя файла label_mime: MIME-тип - label_size: Размер + label_size: Размер heading_new_revision: Новая редакция option_version_same: Та же версия option_version_minor: Незначительные изменения @@ -150,7 +150,7 @@ ru: permission_user_preferences: Настройки пользователя permission_view_dmsf_files: Просматривать документы permission_folder_manipulation: Управление папками - permission_file_manipulation: Управление файлами + permission_file_manipulation: Управление файлами permission_force_file_unlock: Разблокировка любых файлов permission_manage_workflows: Управление согласованиями permission_file_delete: Удаление документов @@ -163,7 +163,7 @@ ru: error_user_has_not_right_delete_folder: Пользователь не имеет нужных прав для удаления папки error_user_has_not_right_delete_file: Пользователь не имеет нужных прав для удаления файла notice_entries_deleted: Файлы удалены - warning_some_entries_were_not_deleted: "Некоторые файлы не были удалены: %{entries}" + warning_some_entries_were_not_deleted: "Некоторые файлы не были удалены: %{entries}" title_delete_checked: Удалить выбранные документы title_items: элементы title_filename_for_download: Имя файла для скачиваемого архива @@ -222,7 +222,7 @@ ru: select_option_webdav_readwrite: "Чтение/Запись" label_webdav_strategy: "Стратегия WebDAV" note_webdav_strategy: "Позволяет администратору решить в каком режиме предоставить доступ к WebDAV для конечных пользователей (Только для чтения или Чтение+Запись)." - + error_unable_delete_dmsf_workflow: Невозможно удалить процесс согласование error_empty_note: Примечание не может быть пустым error_workflow_assign: Возникла ошибка при назначении @@ -258,10 +258,10 @@ ru: title_rejected: Отклонен dmsf_and: AND dmsf_or: OR - dmsf_new_step: Новый шаг + dmsf_new_step: Новый шаг message_dmsf_wokflow_note: Ваше примечание... info_revision: "r%{rev}" - link_workflow: Согласование + link_workflow: Согласование notice_workflow_started: Процесс согласования успешно запущен text_email_subject_approved: успешно завершен text_email_subject_rejected: отклонен @@ -274,14 +274,14 @@ ru: text_email_finished_delegated: "Процесс согласования '%{name}' документа '%{filename}' только что был делегирован по причине '%{notice}' и от Вас ожидается согласование." text_email_finished_step: "Процесс согласования '%{name}' документа '%{filename}' только что завершил один из шагов согласования и ожидает Вашего соглсования." text_email_finished_step_short: "Процесс согласования '%{name}' документа '%{filename}' только что завершил один из шагов согласования." - text_email_started: "Процесс согласования '%{name}' документа '%{filename}' только что начался и ожидает Вашего соглсования." - text_email_to_proceed: Для продолжения поставьте отметку рядом с документом в - text_email_to_see_history: Для просмотра истории согласования нажмите статус согласования документа в - text_email_to_see_status: Для просмотра текущего статуса согласования нажмите статус согласования документа в + text_email_started: "Процесс согласования '%{name}' документа '%{filename}' только что начался и ожидает Вашего соглсования." + text_email_to_proceed: Для продолжения поставьте отметку рядом с документом в + text_email_to_see_history: Для просмотра истории согласования нажмите статус согласования документа в + text_email_to_see_status: Для просмотра текущего статуса согласования нажмите статус согласования документа в label_my_open_approvals: Мои согласования label_my_locked_documents: Мои заблокированные документы - - title_create_link: Создать символическую ссылку + + title_create_link: Создать символическую ссылку label_link_from: Ссылка из label_link_to: Ссылка на label_notifications_on: Включить уведомления @@ -289,25 +289,25 @@ ru: field_target_file: Исходный файл title_download_entries: Скачать записи label_external: Внешний - + label_link_name: Наименование ссылки label_link_external_url: URL label_target_folder: Целевая папка label_source_folder: Исходная папка label_target_project: Целевой проект - label_source_project: Исходный проект - + label_source_project: Исходный проект + text_email_doc_updated_subject: Документы обновлены text_email_doc_updated: только что обновил документы text_email_doc_follows: следующим образом text_email_doc_deleted_subject: Документы удалены text_email_doc_deleted: только что удалил документы label_links_only: только ссылки - + label_display_notified_recipients: Показывать получателей, получивших уведомление note_display_notified_recipients: Пользователь будет проинфмораирован о всех получателях, кому было направлено уведомление. warning_email_notifications: "Уведомление отправлено %{to}" - + link_trash_bin: Корзина title_restore: Восстановить notice_dmsf_file_restored: Документ был успешно восстановлен @@ -315,19 +315,21 @@ ru: notice_dmsf_link_restored: Папка была успешно восстановлена title_restore_checked: Восстановить отмеченные error_parent_folder: "Родительская папка не существует" - + error_resource_or_parent_locked: Невозможно выполнить блокировку - ресурс (или родительская запись) заблокированы error_parent_locked: Невозможно выполнить блокировку - родительская запись заблокирована error_resource_locked: Невозможно выполнить блокировку - ресурс заблокирован - error_lock_exclusively: невозможно эксклюзивно заблокировать уже забловированный ресурс + error_lock_exclusively: невозможно эксклюзивно заблокировать уже забловированный ресурс error_unlock_parent_locked: Разблокировка не удалась - родительская запись заблокирована - + + field_dmsf_tree_view: Navigate folders in a tree + my: blocks: locked_documents: Заблокированные документы open_approvals: Открытые согласования - + label_maximum_ajax_upload_filesize: Максимальный размер файла, загружаемого посредством AJAX note_maximum_ajax_upload_filesize: Превышает максимальный размер файла, загружаемого посредством интерфейса AJAX, для загрузки можно использовать стандартную форму Redmine. Размер указан в MB. label_classic: Classic - label_drag_drop: "Drag&Drop" \ No newline at end of file + label_drag_drop: "Drag&Drop" diff --git a/config/locales/sl.yml b/config/locales/sl.yml index c89d4983..e477eb1f 100644 --- a/config/locales/sl.yml +++ b/config/locales/sl.yml @@ -1,5 +1,5 @@ # encoding: utf-8 -# +# # Redmine plugin for Document Management System "Features" # # Copyright (C) 2011 Zdravko Balorda @@ -26,13 +26,13 @@ sl: 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 + error_email_to_must_be_entered: Email Naslovnik mora bit izbran warning_file_already_locked: Datoteka že zaklenjena notice_file_locked: Datoteka zaklenjena warning_file_not_locked: Datoteka ni zaklenjena notice_file_unlocked: Datoteka odklenjena - error_only_user_that_locked_file_can_unlock_it: Samo oseba, ki je zaklenila datoteko, jo lahko odklene. - error_max_files_exceeded: "Max %{number} datotek za istočasno nalaganje je preseženo." + error_only_user_that_locked_file_can_unlock_it: Samo oseba, ki je zaklenila datoteko, jo lahko odklene. + error_max_files_exceeded: "Max %{number} datotek za istočasno nalaganje je preseženo." error_entry_project_does_not_match_current_project: Projekt se ujema s trenutno nastavljenim projektom notice_folder_created: Mapa kreirana error_folder_creation_failed: Mape ne morem kreirati @@ -40,7 +40,7 @@ sl: notice_folder_deleted: Mapa izbrisana error_folder_is_not_empty: Mapa ni prazna error_folder_title_is_already_used: Naslov je že uporabljen - notice_folder_details_were_saved: Podatki o mapi so shranjeni + notice_folder_details_were_saved: Podatki o mapi so shranjeni error_folder_is_locked: Папка заблокированa error_file_is_locked: Mapa je zaklenjena notice_file_deleted: Datoteka izbrisana @@ -81,8 +81,8 @@ sl: title_waiting_for_approval: V postopku odobritve title_approved: Odobreno title_unlock_file: Odkleni drugim članom za posodabljanje - title_lock_file: Zakleni za posodabljanje - title_download_checked: Prenesi izbrano v Zip formatu + title_lock_file: Zakleni za posodabljanje + title_download_checked: Prenesi izbrano v Zip formatu title_send_checked_by_email: Pošlji izbrano po elektronski pošti link_user_preferences: Vaše Arhivske nastavitve za projekt heading_send_documents_by_email: Pošlji dokumente po elektronski pošti @@ -93,8 +93,8 @@ sl: label_email_documents: Priloge label_email_body: Vsebina label_email_send: Pošlji - title_notifications_active: Obveščanje aktivno - label_upload: Naloži + title_notifications_active: Obveščanje aktivno + label_upload: Naloži heading_new_folder: Nova mapa label_title: Naziv mape label_description: Opis @@ -113,9 +113,9 @@ sl: label_created: Narejeno label_changed: Spremenjeno info_changed_by_user: "%{changed} po" - label_filename: Datoteka + label_filename: Datoteka label_mime: Mime - label_size: Velikost + label_size: Velikost heading_new_revision: Nova verzija option_version_same: Enako option_version_minor: Minor @@ -150,7 +150,7 @@ sl: permission_user_preferences: Uporabniške nastavitve permission_view_dmsf_files: Preglej dokumente permission_folder_manipulation: Upravljanje z mapami - permission_file_manipulation: Upravljanje z datotekami + permission_file_manipulation: Upravljanje z datotekami permission_force_file_unlock: Prisilno odkleni datoteko permission_manage_workflows: Manage workflows permission_file_delete: Delete documents @@ -163,7 +163,7 @@ sl: error_user_has_not_right_delete_folder: Uporabnik nima privilegija brisati mapo error_user_has_not_right_delete_file: Uporabnik nima privilegija brisati datoteke notice_entries_deleted: Izbrane enote izbrisane - warning_some_entries_were_not_deleted: "Nekatere enote niso izbrisane: %{entries}" + warning_some_entries_were_not_deleted: "Nekatere enote niso izbrisane: %{entries}" title_delete_checked: Izbriši izbrano title_items: items title_filename_for_download: Naziv datoteke za prenos dol ali Zip arhiva @@ -182,7 +182,7 @@ sl: label_dmsf_updated: Arhiv posodobljen label_dmsf_downloaded: Arhiv downloaded title_total_size_of_all_files: Skupna velikost vseh datotek v tej mapi - project_module_dmsf: Arhiv + project_module_dmsf: Arhiv warning_no_project_to_copy_file_to: Ni projekta kamor bi kopiral datoteko comment_copied_from: "Skopirano iz %{source}" notice_file_copied: Datoteka skopirana @@ -199,7 +199,7 @@ sl: title_copy: Kopiraj error_folder_cannot_be_copied: Mapo se ne da skopirati notice_folder_copied: Mapa skopirana - + error_max_email_filesize_exceeded: "Presegli ste največjo velikost datoteke za pošiljanje po email-u. (%{number} MB)" note_maximum_email_filesize: Omejitev največje velikosti datoteke, ki se lahko pošlje po email-u. 0 pomeni neomejeno. Količina je v MB. label_maximum_email_filesize: Največja velikost email priponke @@ -222,7 +222,7 @@ sl: select_option_webdav_readwrite: "Beri/Piši" label_webdav_strategy: Webdav strategija note_webdav_strategy: "Omogoči administratorju da odloči ali je webdav platforma na voljo izključno za branje ali beri/piši za končne uporabnike." - + error_unable_delete_dmsf_workflow: Unable to delete the workflow error_empty_note: "The note can't be empty" error_workflow_assign: An error occured while assigning @@ -246,22 +246,22 @@ sl: label_dmsf_workflow_add_approver: "Add a new approver with a logical function:" label_or: or label_action: Action - label_note: Note + label_note: Note title_none: None title_rejection: Rejection title_delegation: Delegation title_assignment: Assignment - title_start: Start + title_start: Start title_dmsf_workflow_log: Approval Workflow Log title_assigned: Assigned title_approval: Approval title_rejected: Rejected dmsf_and: AND dmsf_or: OR - dmsf_new_step: New step + dmsf_new_step: New step message_dmsf_wokflow_note: Your note... info_revision: "r%{rev}" - link_workflow: Workflow + link_workflow: Workflow notice_workflow_started: Approval workflow successfully started text_email_subject_approved: approved text_email_subject_rejected: rejected @@ -274,14 +274,14 @@ sl: text_email_finished_delegated: "The approval workflow '%{name}' assigned to '%{filename}' document has just been delegated because of '%{notice}' and you are expected to do an approval in the current approval step." text_email_finished_step: "The approval workflow '%{name}' assigned to '%{filename}' document has just finished one of the approval steps and you are expected to do an approval in the next approval step." text_email_finished_step_short: "The approval workflow '%{name}' assigned to '%{filename}' document has just finished one of the approval steps." - text_email_started: "The approval workflow '%{name}' assigned to '%{filename}' document has just been started and you are expected to do an approval in the current approval step." + text_email_started: "The approval workflow '%{name}' assigned to '%{filename}' document has just been started and you are expected to do an approval in the current approval step." text_email_to_proceed: To proceed click on the check box icon next to the document in text_email_to_see_history: To see the approval history click on the workflow status of the document in text_email_to_see_status: To see the current status of the approval workflow click on the workflow status the document in label_my_open_approvals: My open approvals label_my_locked_documents: My locked documents - - title_create_link: Create a symbolic link + + title_create_link: Create a symbolic link label_link_from: Link from label_link_to: Link to label_notifications_on: Notifications on @@ -289,25 +289,25 @@ sl: 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_source_project: Source project + text_email_doc_updated_subject: Documents updated text_email_doc_updated: has just actualized documents of text_email_doc_follows: as follows text_email_doc_deleted_subject: Documents deleted text_email_doc_deleted: has just deleted documents of label_links_only: links only - + 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 @@ -315,19 +315,21 @@ sl: notice_dmsf_link_restored: The link has been successfully restored title_restore_checked: Restore checked error_parent_folder: "The parent folder doesn't exist" - + error_resource_or_parent_locked: Unable to complete lock - resource (or parent) is locked error_parent_locked: Unable to complete lock - resource parent is locked error_resource_locked: Unable to complete lock - resource is locked - error_lock_exclusively: unable to lock exclusively an already-locked resource - error_unlock_parent_locked: Unlock failed - resource parent is locked - + error_lock_exclusively: unable to lock exclusively an already-locked resource + error_unlock_parent_locked: Unlock failed - resource parent is locked + + field_dmsf_tree_view: Navigate folders in a tree + my: blocks: locked_documents: Locked documents 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. label_classic: Classic - label_drag_drop: "Drag&Drop" \ No newline at end of file + label_drag_drop: "Drag&Drop" diff --git a/config/locales/zh-TW.yml b/config/locales/zh-TW.yml index 93800d9e..2ef6fcc9 100644 --- a/config/locales/zh-TW.yml +++ b/config/locales/zh-TW.yml @@ -1,5 +1,5 @@ # encoding: utf-8 -# +# # Redmine plugin for Document Management System "Features" # # Copyright (C) 2011 Vít Jonáš @@ -32,7 +32,7 @@ zh-TW: warning_file_not_locked: 檔案尚未鎖定 notice_file_unlocked: 檔案己解除鎖定 error_only_user_that_locked_file_can_unlock_it: 只有檔案的鎖定者,才能解除鎖定。 - error_max_files_exceeded: "目前容許同時的檔案下載數量為%{number}個,己經超出此限制了。" + error_max_files_exceeded: "目前容許同時的檔案下載數量為%{number}個,己經超出此限制了。" error_entry_project_does_not_match_current_project: 所指定的專案,和目前的專案不一致。 notice_folder_created: 資料夾己建立 error_folder_creation_failed: 資料夾建立失敗 @@ -40,7 +40,7 @@ zh-TW: notice_folder_deleted: 資料夾己刪除 error_folder_is_not_empty: 資料夾尚未清空 error_folder_title_is_already_used: 標題己經被使用了 - notice_folder_details_were_saved: 資料夾描述己儲存 + notice_folder_details_were_saved: 資料夾描述己儲存 error_folder_is_locked: 資料夾己經鎖定 error_file_is_locked: 檔案己經被鎖定了 notice_file_deleted: 檔案己經被刪除了 @@ -81,8 +81,8 @@ zh-TW: title_waiting_for_approval: 等待批准 title_approved: 已經被批准 title_unlock_file: 解除鎖定。允許其它使用者修改。 - title_lock_file: 鎖定檔案。禁止其它使用者修改。 - title_download_checked: 以ZIP下載所選取的檔案 + title_lock_file: 鎖定檔案。禁止其它使用者修改。 + title_download_checked: 以ZIP下載所選取的檔案 title_send_checked_by_email: 以電子郵件發送所選取的檔案 link_user_preferences: 你的DMSF的系統偏好設定 heading_send_documents_by_email: 電子郵件寄送檔案 @@ -93,8 +93,8 @@ zh-TW: label_email_documents: 附加檔案 label_email_body: 郵件內容 label_email_send: 寄信囉 - title_notifications_active: 通知處於啟用中 - label_upload: 上傳 + title_notifications_active: 通知處於啟用中 + label_upload: 上傳 heading_new_folder: 新增資料夾 label_title: 標題 label_description: 描述 @@ -113,9 +113,9 @@ zh-TW: label_created: 建立者/時間 label_changed: 修改者/時間 info_changed_by_user: "%{changed} by" - label_filename: 檔案名稱 + label_filename: 檔案名稱 label_mime: Mime - label_size: 大小 + label_size: 大小 heading_new_revision: 新增修訂版本 option_version_same: Same option_version_minor: Minor @@ -150,7 +150,7 @@ zh-TW: permission_user_preferences: 使用者偏好設定 permission_view_dmsf_files: 查看文件檔案 permission_folder_manipulation: 資料夾操作 - permission_file_manipulation: 文件操作 + permission_file_manipulation: 文件操作 permission_force_file_unlock: 強制解除檔案鎖定 permission_manage_workflows: Manage workflows permission_file_delete: Delete documents @@ -163,7 +163,7 @@ zh-TW: error_user_has_not_right_delete_folder: 使用者沒有權限,刪除資料夾。 error_user_has_not_right_delete_file: 使用者沒有權限,刪除檔案。 notice_entries_deleted: 項目己刪除 - warning_some_entries_were_not_deleted: "部份項目無法刪除: %{entries}" + warning_some_entries_were_not_deleted: "部份項目無法刪除: %{entries}" title_delete_checked: 刪除選取項目 title_items: items title_filename_for_download: 下載時的檔名,或是ZIP的檔案名稱。 @@ -246,22 +246,22 @@ zh-TW: label_dmsf_workflow_add_approver: "Add a new approver with a logical function:" label_or: or label_action: Action - label_note: Note + label_note: Note title_none: None title_rejection: Rejection title_delegation: Delegation title_assignment: Assignment - title_start: Start + title_start: Start title_dmsf_workflow_log: 批准流程Log title_assigned: Assigned title_approval: Approval title_rejected: Rejected dmsf_and: AND dmsf_or: OR - dmsf_new_step: New step + dmsf_new_step: New step message_dmsf_wokflow_note: Your note... info_revision: "r%{rev}" - link_workflow: Workflow + link_workflow: Workflow notice_workflow_started: Approval workflow successfully started text_email_subject_approved: approved text_email_subject_rejected: rejected @@ -274,14 +274,14 @@ zh-TW: text_email_finished_delegated: "The approval workflow '%{name}' assigned to '%{filename}' document has just been delegated because of '%{notice}' and you are expected to do an approval in the current approval step." text_email_finished_step: "The approval workflow '%{name}' assigned to '%{filename}' document has just finished one of the approval steps and you are expected to do an approval in the next approval step." text_email_finished_step_short: "The approval workflow '%{name}' assigned to '%{filename}' document has just finished one of the approval steps." - text_email_started: "The approval workflow '%{name}' assigned to '%{filename}' document has just been started and you are expected to do an approval in the current approval step." + text_email_started: "The approval workflow '%{name}' assigned to '%{filename}' document has just been started and you are expected to do an approval in the current approval step." text_email_to_proceed: To proceed click on the check box icon next to the document in text_email_to_see_history: To see the approval history click on the workflow status of the document in text_email_to_see_status: To see the current status of the approval workflow click on the workflow status the document in label_my_open_approvals: My open approvals label_my_locked_documents: My locked documents - title_create_link: Create a symbolic link + title_create_link: Create a symbolic link label_link_from: Link from label_link_to: Link to label_notifications_on: Notifications on @@ -289,13 +289,13 @@ zh-TW: 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_source_project: Source project text_email_doc_updated_subject: Documents updated text_email_doc_updated: has just actualized documents of @@ -307,7 +307,7 @@ 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 @@ -315,12 +315,14 @@ zh-TW: notice_dmsf_link_restored: The link has been successfully restored title_restore_checked: Restore checked error_parent_folder: "The parent folder doesn't exist" - + error_resource_or_parent_locked: Unable to complete lock - resource (or parent) is locked error_parent_locked: Unable to complete lock - resource parent is locked error_resource_locked: Unable to complete lock - resource is locked - error_lock_exclusively: unable to lock exclusively an already-locked resource - error_unlock_parent_locked: Unlock failed - resource parent is locked + error_lock_exclusively: unable to lock exclusively an already-locked resource + error_unlock_parent_locked: Unlock failed - resource parent is locked + + field_dmsf_tree_view: Navigate folders in a tree my: blocks: @@ -330,4 +332,4 @@ zh-TW: 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. label_classic: Classic - label_drag_drop: "Drag&Drop" \ No newline at end of file + label_drag_drop: "Drag&Drop" diff --git a/config/locales/zh.yml b/config/locales/zh.yml index 382b536f..5ef12d8f 100644 --- a/config/locales/zh.yml +++ b/config/locales/zh.yml @@ -1,5 +1,5 @@ # encoding: utf-8 -# +# # Redmine plugin for Document Management System "Features" # # Copyright (C) 2011 Vít Jonáš @@ -26,13 +26,13 @@ zh: 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: 请输入电子邮件 + error_email_to_must_be_entered: 请输入电子邮件 warning_file_already_locked: 文件已经锁定 notice_file_locked: 文件锁定 warning_file_not_locked: 文件未锁定 notice_file_unlocked: 文件解锁 - error_only_user_that_locked_file_can_unlock_it: 只有锁定文件的用户才能解锁该文件 - error_max_files_exceeded: "超出同时下载%{number}个文件数量限制" + error_only_user_that_locked_file_can_unlock_it: 只有锁定文件的用户才能解锁该文件 + error_max_files_exceeded: "超出同时下载%{number}个文件数量限制" error_entry_project_does_not_match_current_project: 入口项目与当前项目不匹配 notice_folder_created: 文件夹创建完毕 error_folder_creation_failed: 文件夹创建失败 @@ -40,7 +40,7 @@ zh: notice_folder_deleted: 文件夹已删除 error_folder_is_not_empty: 非空文件夹 error_folder_title_is_already_used: 标题已经被使用 - notice_folder_details_were_saved: 文件夹详细信息已保存 + notice_folder_details_were_saved: 文件夹详细信息已保存 error_file_is_locked: Folder is locked error_file_is_locked: 文件被锁定 notice_file_deleted: 文件已删除 @@ -81,8 +81,8 @@ zh: title_waiting_for_approval: 待批准 title_approved: 已批准 title_unlock_file: 解除锁定允许其他成员修改 - title_lock_file: 锁定以防其他成员修改 - title_download_checked: zip归档下载所选 + title_lock_file: 锁定以防其他成员修改 + title_download_checked: zip归档下载所选 title_send_checked_by_email: 电子邮件发送所选 link_user_preferences: 您的文档管理系统项目偏好设定 heading_send_documents_by_email: 电子邮件发送文档 @@ -93,8 +93,8 @@ zh: label_email_documents: 文档 label_email_body: 正文 label_email_send: 发送 - title_notifications_active: 通知处于有效状态 - label_upload: 上传 + title_notifications_active: 通知处于有效状态 + label_upload: 上传 heading_new_folder: 新建文件夹 label_title: 标题 label_description: 描述 @@ -113,9 +113,9 @@ zh: label_created: 创建 label_changed: 修改 info_changed_by_user: "%{changed} by" - label_filename: 文件名 + label_filename: 文件名 label_mime: Mime - label_size: 大小 + label_size: 大小 heading_new_revision: 新修订 option_version_same: Same option_version_minor: Minor @@ -150,7 +150,7 @@ zh: permission_user_preferences: 用户偏好设定 permission_view_dmsf_files: 查看文档 permission_folder_manipulation: 文件夹操作 - permission_file_manipulation: 文件操作 + permission_file_manipulation: 文件操作 permission_force_file_unlock: 强制文件解锁 permission_manage_workflows: Manage workflows permission_file_delete: Delete documents @@ -163,7 +163,7 @@ zh: error_user_has_not_right_delete_folder: 用户没有权限删除文件夹 error_user_has_not_right_delete_file: 用户没有权限删除文件 notice_entries_deleted: 条目已删除 - warning_some_entries_were_not_deleted: "某些条目未被删除: %{entries}" + warning_some_entries_were_not_deleted: "某些条目未被删除: %{entries}" title_delete_checked: 删除选中 title_items: items title_filename_for_download: 用于下载或zip归档的文件名 @@ -182,7 +182,7 @@ zh: label_dmsf_updated: DMSF updated label_dmsf_downloaded: DMSF document downloaded title_total_size_of_all_files: 文件夹所有文件总大小 - project_module_dmsf: 文档管家 + project_module_dmsf: 文档管家 warning_no_project_to_copy_file_to: No project to copy file to comment_copied_from: "Copied from %{source}" notice_file_copied: File copied @@ -199,7 +199,7 @@ zh: title_copy: Copy error_folder_cannot_be_copied: "Folder can't be copied" notice_folder_copied: Folder copied - + error_max_email_filesize_exceeded: "You've exceeded the maximum filesize for sending via email. (%{number} MB)" note_maximum_email_filesize: Limits maximum filesize that can be sent via email. 0 means unlimited. Number is in MB. label_maximum_email_filesize: Maximum email attachment size @@ -221,8 +221,8 @@ zh: select_option_webdav_readonly: Read-only select_option_webdav_readwrite: Read/Write label_webdav_strategy: Webdav strategy - note_webdav_strategy: Enables the administrator to decide if webdav is a read-only or read-write platform for end users. - + note_webdav_strategy: Enables the administrator to decide if webdav is a read-only or read-write platform for end users. + error_unable_delete_dmsf_workflow: Unable to delete the workflow error_empty_note: "The note can't be empty" error_workflow_assign: An error occured while assigning @@ -246,22 +246,22 @@ zh: label_dmsf_workflow_add_approver: "Add a new approver with a logical function:" label_or: or label_action: Action - label_note: Note + label_note: Note title_none: None title_rejection: Rejection title_delegation: Delegation title_assignment: Assignment - title_start: Start + title_start: Start title_dmsf_workflow_log: Approval Workflow Log title_assigned: Assigned title_approval: Approval title_rejected: Rejected dmsf_and: AND dmsf_or: OR - dmsf_new_step: New step + dmsf_new_step: New step message_dmsf_wokflow_note: Your note... info_revision: "r%{rev}" - link_workflow: Workflow + link_workflow: Workflow notice_workflow_started: Approval workflow successfully started text_email_subject_approved: approved text_email_subject_rejected: rejected @@ -274,14 +274,14 @@ zh: text_email_finished_delegated: "The approval workflow '%{name}' assigned to '%{filename}' document has just been delegated because of '%{notice}' and you are expected to do an approval in the current approval step." text_email_finished_step: "The approval workflow '%{name}' assigned to '%{filename}' document has just finished one of the approval steps and you are expected to do an approval in the next approval step." text_email_finished_step_short: "The approval workflow '%{name}' assigned to '%{filename}' document has just finished one of the approval steps." - text_email_started: "The approval workflow '%{name}' assigned to '%{filename}' document has just been started and you are expected to do an approval in the current approval step." + text_email_started: "The approval workflow '%{name}' assigned to '%{filename}' document has just been started and you are expected to do an approval in the current approval step." text_email_to_proceed: To proceed click on the check box icon next to the document in text_email_to_see_history: To see the approval history click on the workflow status of the document in text_email_to_see_status: To see the current status of the approval workflow click on the workflow status the document in label_my_open_approvals: My open approvals label_my_locked_documents: My locked documents - - title_create_link: Create a symbolic link + + title_create_link: Create a symbolic link label_link_from: Link from label_link_to: Link to label_notifications_on: Notifications on @@ -289,25 +289,25 @@ zh: 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_source_project: Source project + text_email_doc_updated_subject: Documents updated text_email_doc_updated: has just actualized documents of text_email_doc_follows: as follows text_email_doc_deleted_subject: Documents deleted text_email_doc_deleted: has just deleted documents of label_links_only: links only - + 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 @@ -315,19 +315,21 @@ zh: notice_dmsf_link_restored: The link has been successfully restored title_restore_checked: Restore checked error_parent_folder: "The parent folder doesn't exist" - + error_resource_or_parent_locked: Unable to complete lock - resource (or parent) is locked error_parent_locked: Unable to complete lock - resource parent is locked error_resource_locked: Unable to complete lock - resource is locked - error_lock_exclusively: unable to lock exclusively an already-locked resource - error_unlock_parent_locked: Unlock failed - resource parent is locked - + error_lock_exclusively: unable to lock exclusively an already-locked resource + error_unlock_parent_locked: Unlock failed - resource parent is locked + + field_dmsf_tree_view: Navigate folders in a tree + my: blocks: locked_documents: Locked documents 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. label_classic: Classic - label_drag_drop: "Drag&Drop" \ No newline at end of file + label_drag_drop: "Drag&Drop" diff --git a/lib/redmine_dmsf.rb b/lib/redmine_dmsf.rb index d5d273c0..96a378dc 100644 --- a/lib/redmine_dmsf.rb +++ b/lib/redmine_dmsf.rb @@ -29,6 +29,7 @@ require 'redmine_dmsf/patches/custom_fields_helper_patch' require 'redmine_dmsf/patches/acts_as_customizable' require 'redmine_dmsf/patches/project_patch' require 'redmine_dmsf/patches/project_tabs_extended' +require 'redmine_dmsf/patches/user_preference_patch' # Load up classes that make up our WebDAV solution ontop of DAV4Rack require 'redmine_dmsf/webdav/no_parse' @@ -49,9 +50,10 @@ require 'redmine_dmsf/errors/dmsf_lock_error.rb' require 'redmine_dmsf/errors/dmsf_zip_max_file_error.rb' # Hooks -require 'redmine_dmsf/hooks/view_projects_form_hook' -require 'redmine_dmsf/hooks/base_view_hooks' -require 'redmine_dmsf/hooks/search_controller_hooks' +require 'redmine_dmsf/hooks/views/view_projects_form_hook' +require 'redmine_dmsf/hooks/views/base_view_hooks' +require 'redmine_dmsf/hooks/controllers/search_controller_hooks' +require 'redmine_dmsf/hooks/views/my_account_view_hooks' # Macros require 'redmine_dmsf/macros' diff --git a/lib/redmine_dmsf/hooks/search_controller_hooks.rb b/lib/redmine_dmsf/hooks/controllers/search_controller_hooks.rb similarity index 100% rename from lib/redmine_dmsf/hooks/search_controller_hooks.rb rename to lib/redmine_dmsf/hooks/controllers/search_controller_hooks.rb diff --git a/lib/redmine_dmsf/hooks/base_view_hooks.rb b/lib/redmine_dmsf/hooks/views/base_view_hooks.rb similarity index 100% rename from lib/redmine_dmsf/hooks/base_view_hooks.rb rename to lib/redmine_dmsf/hooks/views/base_view_hooks.rb diff --git a/lib/redmine_dmsf/hooks/views/my_account_view_hooks.rb b/lib/redmine_dmsf/hooks/views/my_account_view_hooks.rb new file mode 100644 index 00000000..2de1da4d --- /dev/null +++ b/lib/redmine_dmsf/hooks/views/my_account_view_hooks.rb @@ -0,0 +1,39 @@ +# encoding: utf-8 +# +# Redmine plugin for Document Management System "Features" +# +# Copyright (C) 2011-16 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. + +module RedmineDmsf + module Hooks + include Redmine::Hook + + class MyAccountViewHook < Redmine::Hook::ViewListener + + def view_my_account_preferences(context={}) + if context.is_a?(Hash) && context[:user] + context[:controller].send( + :render_to_string, { + :partial => 'hooks/redmine_dmsf/view_my_account', + :locals => context + }).html_safe + end + end + + end + end +end \ No newline at end of file diff --git a/lib/redmine_dmsf/hooks/view_projects_form_hook.rb b/lib/redmine_dmsf/hooks/views/view_projects_form_hook.rb similarity index 100% rename from lib/redmine_dmsf/hooks/view_projects_form_hook.rb rename to lib/redmine_dmsf/hooks/views/view_projects_form_hook.rb diff --git a/lib/redmine_dmsf/patches/user_preference_patch.rb b/lib/redmine_dmsf/patches/user_preference_patch.rb new file mode 100644 index 00000000..2c424db3 --- /dev/null +++ b/lib/redmine_dmsf/patches/user_preference_patch.rb @@ -0,0 +1,39 @@ +# encoding: utf-8 +# +# Redmine plugin for Document Management System "Features" +# +# Copyright (C) 2011-16 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. + +module DmsfUserPreference + def self.included(base) + base.send(:include, InstanceMethods) + end + + module InstanceMethods + + def dmsf_tree_view + self[:dmsf_tree_view] || '0' + end + + def dmsf_tree_view=(value) + self[:dmsf_tree_view] = value + end + end + +end + +UserPreference.send(:include, DmsfUserPreference) From c49881dbf914d0d4ab04fac1dbeb4fa9f9bc56e9 Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Thu, 21 Apr 2016 14:16:07 +0200 Subject: [PATCH 54/94] nautilus-like folders-files list view #252 --- app/controllers/dmsf_controller.rb | 264 ++++++++-------- app/controllers/dmsf_files_controller.rb | 22 +- app/controllers/dmsf_files_copy_controller.rb | 42 +-- .../dmsf_folders_copy_controller.rb | 24 +- app/controllers/dmsf_links_controller.rb | 8 +- app/controllers/dmsf_upload_controller.rb | 4 +- app/controllers/dmsf_workflows_controller.rb | 7 +- app/helpers/dmsf_helper.rb | 59 +++- app/models/dmsf_file.rb | 41 ++- app/models/dmsf_file_revision.rb | 34 +-- app/models/dmsf_file_revision_access.rb | 20 +- app/models/dmsf_file_revision_custom_field.rb | 20 +- app/models/dmsf_folder.rb | 88 +++--- app/models/dmsf_link.rb | 28 +- app/models/dmsf_lock.rb | 5 +- app/models/dmsf_mailer.rb | 54 ++-- app/models/dmsf_upload.rb | 54 ++-- app/models/dmsf_workflow_step.rb | 24 +- app/models/dmsf_workflow_step_action.rb | 32 +- app/models/dmsf_workflow_step_assignment.rb | 12 +- app/views/dmsf/_dir.html.erb | 53 ++-- app/views/dmsf/_file.html.erb | 90 +++--- app/views/dmsf/_list_view.erb | 118 ++++++++ app/views/dmsf/_tree_view.erb | 138 +++++++++ app/views/dmsf/_url.html.erb | 18 +- app/views/dmsf/show.html.erb | 116 ++----- app/views/dmsf_files/show.html.erb | 102 +++---- app/views/dmsf_files_copy/new.html.erb | 12 +- app/views/dmsf_links/_form.html.erb | 42 +-- app/views/dmsf_mailer/send_documents.html.erb | 37 ++- app/views/dmsf_mailer/send_documents.text.erb | 29 +- .../workflow_notification.html.erb | 14 +- .../workflow_notification.text.erb | 6 +- .../redmine_dmsf/_view_my_account.html.erb | 22 ++ .../redmine_dmsf/_view_projects_form.html.erb | 12 +- .../my/blocks/_locked_documents.html.erb | 36 +-- app/views/my/blocks/_open_approvals.html.erb | 38 +-- assets/images/bullet_arrow_down.png | Bin 0 -> 220 bytes assets/javascripts/redmine_dmsf.js | 117 ++++++++ .../stylesheets/{img => images}/loading.gif | Bin .../stylesheets/{img => images}/plupload.png | Bin .../plupload/jquery.ui.plupload.css | 4 +- .../{dmsf.css => redmine_dmsf.css} | 25 +- config/locales/cs.yml | 28 +- config/routes.rb | 24 +- db/migrate/06_dmsf_1_2_0.rb | 14 +- ...1013102501_remove_project_from_revision.rb | 16 +- lib/dmsf_zip.rb | 32 +- .../hooks/views/base_view_hooks.rb | 19 +- lib/redmine_dmsf/lockable.rb | 20 +- lib/redmine_dmsf/macros.rb | 68 ++--- lib/redmine_dmsf/patches/project_patch.rb | 6 +- lib/redmine_dmsf/webdav/dmsf_resource.rb | 166 +++++------ test/functional/dmsf_controller_test.rb | 118 +++++--- .../dmsf_workflow_controller_test.rb | 282 +++++++++--------- test/integration/dmsf_webdav_get_test.rb | 38 +-- test/integration/dmsf_webdav_put_test.rb | 48 +-- test/unit/dmsf_file_test.rb | 78 ++--- 58 files changed, 1639 insertions(+), 1189 deletions(-) create mode 100644 app/views/dmsf/_list_view.erb create mode 100644 app/views/dmsf/_tree_view.erb create mode 100644 assets/images/bullet_arrow_down.png create mode 100644 assets/javascripts/redmine_dmsf.js rename assets/stylesheets/{img => images}/loading.gif (100%) rename assets/stylesheets/{img => images}/plupload.png (100%) rename assets/stylesheets/{dmsf.css => redmine_dmsf.css} (88%) diff --git a/app/controllers/dmsf_controller.rb b/app/controllers/dmsf_controller.rb index d929af8e..a6cf2e40 100644 --- a/app/controllers/dmsf_controller.rb +++ b/app/controllers/dmsf_controller.rb @@ -26,13 +26,13 @@ class DmsfController < ApplicationController before_filter :find_project before_filter :authorize before_filter :find_folder, :except => [:new, :create, :edit_root, :save_root] - before_filter :find_parent, :only => [:new, :create] - + before_filter :find_parent, :only => [:new, :create] + accept_api_auth :show, :create 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) @@ -40,112 +40,117 @@ class DmsfController < ApplicationController @force_file_unlock_allowed = User.current.allowed_to?(:force_file_unlock, @project) @workflows_available = DmsfWorkflow.where(['project_id = ? OR project_id IS NULL', @project.id]).count > 0 @file_approval_allowed = User.current.allowed_to?(:file_approval, @project) + @tree_view = (User.current.pref[:dmsf_tree_view] == '1') && (!%w(atom xml json).include?(params[:format])) - unless @folder - if params[:custom_field_id].present? && params[:custom_value].present? - @subfolders = [] - DmsfFolder.where(:project_id => @project.id).visible.each do |f| - f.custom_field_values.each do |v| - if v.custom_field_id == params[:custom_field_id].to_i - if v.custom_field.compare_values?(v.value, params[:custom_value]) - @subfolders << f - break - end - end - end - end - @files = [] - DmsfFile.where(:project_id => @project.id).visible.each do |f| - r = f.last_revision - if r - r.custom_field_values.each do |v| - if v.custom_field_id == params[:custom_field_id].to_i - if v.custom_field.compare_values?(v.value, params[:custom_value]) - @files << f - break - end - end - end - end - end - @dir_links = [] - DmsfLink.where(:project_id => @project.id, :target_type => DmsfFolder.model_name.to_s).visible.each do |l| - l.target_folder.custom_field_values.each do |v| - if v.custom_field_id == params[:custom_field_id].to_i - if v.custom_field.compare_values?(v.value, params[:custom_value]) - @dir_links << l - break - end - end - end - end - @file_links = [] - DmsfLink.where(:project_id => @project.id, :target_type => DmsfFile.model_name.to_s).visible.each do |l| - r = l.target_file.last_revision if l.target_file - if r - r.custom_field_values.each do |v| - if v.custom_field_id == params[:custom_field_id].to_i - if v.custom_field.compare_values?(v.value, params[:custom_value]) - @file_links << l - break - end - end - end - end - end - @url_links = [] - DmsfLink.where(:project_id => @project.id, :target_type => 'DmsfUrl').visible.each do |l| - r = l.target_file.last_revision if l.target_file - if r - r.custom_field_values.each do |v| - if v.custom_field_id == params[:custom_field_id].to_i - if v.custom_field.compare_values?(v.value, params[:custom_value]) - @file_links << l - break - end - end - end - end - end - else - @subfolders = @project.dmsf_folders.visible - @files = @project.dmsf_files.visible - @dir_links = @project.folder_links.visible - @file_links = @project.file_links.visible - @url_links = @project.url_links.visible - end + if @tree_view @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 - @file_links = @folder.file_links.visible - @url_links = @folder.url_links.visible - @locked_for_user = @folder.locked_for_user? + unless @folder + if params[:custom_field_id].present? && params[:custom_value].present? + @subfolders = [] + DmsfFolder.where(:project_id => @project.id).visible.each do |f| + f.custom_field_values.each do |v| + if v.custom_field_id == params[:custom_field_id].to_i + if v.custom_field.compare_values?(v.value, params[:custom_value]) + @subfolders << f + break + end + end + end + end + @files = [] + DmsfFile.where(:project_id => @project.id).visible.each do |f| + r = f.last_revision + if r + r.custom_field_values.each do |v| + if v.custom_field_id == params[:custom_field_id].to_i + if v.custom_field.compare_values?(v.value, params[:custom_value]) + @files << f + break + end + end + end + end + end + @dir_links = [] + DmsfLink.where(:project_id => @project.id, :target_type => DmsfFolder.model_name.to_s).visible.each do |l| + l.target_folder.custom_field_values.each do |v| + if v.custom_field_id == params[:custom_field_id].to_i + if v.custom_field.compare_values?(v.value, params[:custom_value]) + @dir_links << l + break + end + end + end + end + @file_links = [] + DmsfLink.where(:project_id => @project.id, :target_type => DmsfFile.model_name.to_s).visible.each do |l| + r = l.target_file.last_revision if l.target_file + if r + r.custom_field_values.each do |v| + if v.custom_field_id == params[:custom_field_id].to_i + if v.custom_field.compare_values?(v.value, params[:custom_value]) + @file_links << l + break + end + end + end + end + end + @url_links = [] + DmsfLink.where(:project_id => @project.id, :target_type => 'DmsfUrl').visible.each do |l| + r = l.target_file.last_revision if l.target_file + if r + r.custom_field_values.each do |v| + if v.custom_field_id == params[:custom_field_id].to_i + if v.custom_field.compare_values?(v.value, params[:custom_value]) + @file_links << l + break + end + end + end + end + end + else + @subfolders = @project.dmsf_folders.visible + @files = @project.dmsf_files.visible + @dir_links = @project.folder_links.visible + @file_links = @project.file_links.visible + @url_links = @project.url_links.visible + end + @locked_for_user = false + else + + if @folder.deleted? + render_404 + return + end + + @subfolders = @folder.dmsf_folders.visible + @files = @folder.dmsf_files.visible + @dir_links = @folder.folder_links.visible + @file_links = @folder.file_links.visible + @url_links = @folder.url_links.visible + @locked_for_user = @folder.locked_for_user? + end end @ajax_upload_size = Setting.plugin_redmine_dmsf['dmsf_max_ajax_upload_filesize'].present? ? Setting.plugin_redmine_dmsf['dmsf_max_ajax_upload_filesize'] : 100 # Trash - @trash_visible = @folder_manipulation_allowed && @file_manipulation_allowed && + @trash_visible = @folder_manipulation_allowed && @file_manipulation_allowed && @file_delete_allowed && !@locked_for_user && !@folder @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? - + respond_to do |format| format.html { render :layout => !request.xhr? } format.api end - end + end def trash @folder_manipulation_allowed = User.current.allowed_to? :folder_manipulation, @project @@ -188,7 +193,7 @@ class DmsfController < ApplicationController (params[:email_entries].present? || params[:download_entries].present?) selected_dir_links.each do |id| link = DmsfLink.find_by_id id - selected_folders << link.target_id if link && !selected_folders.include?(link.target_id.to_s) + selected_folders << link.target_id if link && !selected_folders.include?(link.target_id.to_s) end end @@ -196,10 +201,10 @@ class DmsfController < ApplicationController (params[:email_entries].present? || params[:download_entries].present?) selected_file_links.each do |id| link = DmsfLink.find_by_id id - selected_files << link.target_id if link && !selected_files.include?(link.target_id.to_s) + selected_files << link.target_id if link && !selected_files.include?(link.target_id.to_s) end end - + if params[:email_entries].present? email_entries(selected_folders, selected_files) elsif params[:restore_entries].present? @@ -213,7 +218,7 @@ class DmsfController < ApplicationController redirect_to :back else download_entries(selected_folders, selected_files) - end + end rescue FileNotFound render_404 rescue DmsfAccessError @@ -221,7 +226,7 @@ class DmsfController < ApplicationController rescue Exception => e flash[:error] = e.message Rails.logger.error e.message - redirect_to :back + redirect_to :back end def tag_changed @@ -236,7 +241,7 @@ class DmsfController < ApplicationController end end - def entries_email + def entries_email if params[:email][:to].strip.blank? flash.now[:error] = l(:error_email_to_must_be_entered) render :action => 'email_entries' @@ -255,68 +260,68 @@ class DmsfController < ApplicationController render :action => 'edit' end - def create + def create @folder = DmsfFolder.new @folder.title = params[:dmsf_folder][:title].strip @folder.description = params[:dmsf_folder][:description].strip @folder.dmsf_folder_id = params[:dmsf_folder][:dmsf_folder_id] @folder.project = @project @folder.user = User.current - + # Custom fields if params[:dmsf_folder][:custom_field_values].present? params[:dmsf_folder][:custom_field_values].each_with_index do |v, i| @folder.custom_field_values[i].value = v[1] end end - + saved = @folder.save - + respond_to do |format| format.js - format.api { - unless saved - render_validation_errors(@folder) - end + format.api { + unless saved + render_validation_errors(@folder) + end } - format.html { + format.html { if saved - flash[:notice] = l(:notice_folder_created) + 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 + end def edit - @parent = @folder.folder + @parent = @folder.dmsf_folder @pathfolder = copy_folder(@folder) @force_file_unlock_allowed = User.current.allowed_to?(:force_file_unlock, @project) end def save - unless params[:dmsf_folder] + unless params[:dmsf_folder] redirect_to dmsf_folder_path(:id => @project, :folder_id => @folder) return end - @pathfolder = copy_folder(@folder) + @pathfolder = copy_folder(@folder) @folder.title = params[:dmsf_folder][:title].strip @folder.description = params[:dmsf_folder][:description].strip @folder.dmsf_folder_id = params[:dmsf_folder][:dmsf_folder_id] - + # Custom fields if params[:dmsf_folder][:custom_field_values].present? params[:dmsf_folder][:custom_field_values].each_with_index do |v, i| @folder.custom_field_values[i].value = v[1] end end - + if @folder.save - flash[:notice] = l(:notice_folder_details_were_saved) + flash[:notice] = l(:notice_folder_details_were_saved) redirect_to dmsf_folder_path(:id => @project, :folder_id => @folder) else render :action => 'edit' @@ -333,7 +338,7 @@ class DmsfController < ApplicationController if commit redirect_to :back else - redirect_to dmsf_folder_path(:id => @project, :folder_id => @folder.folder) + redirect_to dmsf_folder_path(:id => @project, :folder_id => @folder.dmsf_folder) end end @@ -353,7 +358,7 @@ class DmsfController < ApplicationController if params[:project] @project.dmsf_description = params[:project][:dmsf_description] if @project.save - flash[:notice] = l(:notice_folder_details_were_saved) + flash[:notice] = l(:notice_folder_details_were_saved) else flash[:error] = @project.errors.full_messages.to_sentence end @@ -445,11 +450,11 @@ class DmsfController < ApplicationController end zip.files.each do |f| - log_activity(f, 'emailing zip') + log_activity(f, 'emailing zip') audit = DmsfFileRevisionAccess.new audit.user = User.current - audit.revision = f.last_revision - audit.action = DmsfFileRevisionAccess::EmailAction + audit.dmsf_file_revision = f.last_revision + audit.action = DmsfFileRevisionAccess::EmailAction audit.save! end @@ -472,11 +477,11 @@ class DmsfController < ApplicationController zip_entries(zip, selected_folders, selected_files) zip.files.each do |f| - log_activity(f, 'download zip') + log_activity(f, 'download zip') audit = DmsfFileRevisionAccess.new audit.user = User.current - audit.revision = f.last_revision - audit.action = DmsfFileRevisionAccess::DownloadAction + audit.dmsf_file_revision = f.last_revision + audit.action = DmsfFileRevisionAccess::DownloadAction audit.save! end @@ -484,7 +489,7 @@ class DmsfController < ApplicationController :filename => filename_for_content_disposition("#{@project.name}-#{DateTime.now.strftime('%y%m%d%H%M%S')}.zip"), :type => 'application/zip', :disposition => 'attachment') - rescue Exception + rescue Exception raise ensure zip.close if zip @@ -497,7 +502,7 @@ class DmsfController < ApplicationController selected_folders.each do |selected_folder_id| folder = DmsfFolder.visible.find_by_id selected_folder_id if folder - zip.add_folder(folder, member, (folder.folder.dmsf_path_str if folder.folder)) + zip.add_folder(folder, member, (folder.dmsf_folder.dmsf_path_str if folder.dmsf_folder)) else raise FileNotFound end @@ -505,14 +510,14 @@ class DmsfController < ApplicationController end if selected_files && selected_files.is_a?(Array) selected_files.each do |selected_file_id| - file = DmsfFile.visible.find_by_id selected_file_id + file = DmsfFile.visible.find_by_id selected_file_id unless file && file.last_revision && File.exists?(file.last_revision.disk_file) raise FileNotFound end unless (file.project == @project) || User.current.allowed_to?(:view_dmsf_files, file.project) raise DmsfAccessError end - zip.add_file(file, member, (file.folder.dmsf_path_str if file.folder)) if file + zip.add_file(file, member, (file.dmsf_folder.dmsf_path_str if file.dmsf_folder)) if file end end max_files = Setting.plugin_redmine_dmsf['dmsf_max_file_download'].to_i @@ -565,6 +570,7 @@ class DmsfController < ApplicationController if folder unless folder.delete commit flash[:error] = folder.errors.full_messages.to_sentence + return end elsif !commit raise FileNotFound @@ -624,7 +630,7 @@ class DmsfController < ApplicationController rescue DmsfAccessError render_403 rescue ActiveRecord::RecordNotFound - render_404 + render_404 end def find_parent @@ -648,5 +654,5 @@ class DmsfController < ApplicationController :to, :zipped_content, :email, :cc, :subject, :zipped_content => [], :files => []) 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 213ecdf1..a7135962 100644 --- a/app/controllers/dmsf_files_controller.rb +++ b/app/controllers/dmsf_files_controller.rb @@ -39,14 +39,14 @@ class DmsfFilesController < ApplicationController @revision = @file.last_revision else @revision = DmsfFileRevision.find(params[:download].to_i) - raise DmsfAccessError if @revision.file != @file + raise DmsfAccessError if @revision.dmsf_file != @file end - check_project(@revision.file) + check_project(@revision.dmsf_file) raise ActionController::MissingFile if @file.deleted? log_activity('downloaded') access = DmsfFileRevisionAccess.new access.user = User.current - access.revision = @revision + access.dmsf_file_revision = @revision access.action = DmsfFileRevisionAccess::DownloadAction access.save! member = Member.where(:user_id => User.current.id, :project_id => @file.project.id).first @@ -71,14 +71,14 @@ class DmsfFilesController < ApplicationController @revision = @file.last_revision else @revision = DmsfFileRevision.find(params[:download].to_i) - raise DmsfAccessError if @revision.file != @file + raise DmsfAccessError if @revision.dmsf_file != @file end - check_project(@revision.file) - raise ActionController::MissingFile if @revision.file.deleted? + check_project(@revision.dmsf_file) + raise ActionController::MissingFile if @revision.dmsf_file.deleted? log_activity('downloaded') access = DmsfFileRevisionAccess.new access.user = User.current - access.revision = @revision + access.dmsf_file_revision = @revision access.action = DmsfFileRevisionAccess::DownloadAction access.save! member = Member.where(:user_id => User.current.id, :project_id => @file.project.id).first @@ -99,7 +99,7 @@ class DmsfFilesController < ApplicationController @revision = @file.last_revision @file_delete_allowed = User.current.allowed_to?(:file_delete, @project) @file_manipulation_allowed = User.current.allowed_to?(:file_manipulation, @project) - @revision_pages = Paginator.new @file.revisions.visible.count, params['per_page'] ? params['per_page'].to_i : 25, params['page'] + @revision_pages = Paginator.new @file.dmsf_file_revisions.visible.count, params['per_page'] ? params['per_page'].to_i : 25, params['page'] respond_to do |format| format.html { @@ -120,7 +120,7 @@ class DmsfFilesController < ApplicationController revision.description = params[:dmsf_file_revision][:description] revision.comment = params[:dmsf_file_revision][:comment] - revision.file = @file + revision.dmsf_file = @file last_revision = @file.last_revision revision.source_revision = last_revision revision.user = User.current @@ -233,7 +233,7 @@ class DmsfFilesController < ApplicationController if commit redirect_to :back else - redirect_to dmsf_folder_path(:id => @project, :folder_id => @file.folder) + redirect_to dmsf_folder_path(:id => @project, :folder_id => @file.dmsf_folder) end end @@ -318,7 +318,7 @@ class DmsfFilesController < ApplicationController def find_revision @revision = DmsfFileRevision.visible.find params[:id] - @file = @revision.file + @file = @revision.dmsf_file @project = @file.project rescue ActiveRecord::RecordNotFound render_404 diff --git a/app/controllers/dmsf_files_copy_controller.rb b/app/controllers/dmsf_files_copy_controller.rb index 85b8af9b..85d2c60a 100644 --- a/app/controllers/dmsf_files_copy_controller.rb +++ b/app/controllers/dmsf_files_copy_controller.rb @@ -18,9 +18,9 @@ class DmsfFilesCopyController < ApplicationController unloadable - + menu_item :dmsf - + before_filter :find_file before_filter :authorize @@ -34,13 +34,13 @@ class DmsfFilesCopyController < ApplicationController else @target_project ||= DmsfFile.allowed_target_projects_on_copy[0] end - + @target_folder = DmsfFolder.visible.find(params[:target_folder_id]) unless params[:target_folder_id].blank? - @target_folder ||= @file.folder if @target_project == @project - + @target_folder ||= @file.dmsf_folder if @target_project == @project + render :layout => !request.xhr? end - + def create @target_project = DmsfFile.allowed_target_projects_on_copy.detect {|p| p.id.to_s == params[:target_project_id]} if params[:target_project_id] unless @target_project @@ -49,18 +49,18 @@ class DmsfFilesCopyController < ApplicationController end @target_folder = DmsfFolder.visible.find_by_id(params[:target_folder_id]) unless params[:target_folder_id].blank? if @target_folder && (@target_folder.project != @target_project) - raise DmsfAccessError, l(:error_entry_project_does_not_match_current_project) + raise DmsfAccessError, l(:error_entry_project_does_not_match_current_project) end - if (@target_folder && @target_folder == @file.folder) || - (@target_folder.nil? && @file.folder.nil? && @target_project == @file.project) + if (@target_folder && @target_folder == @file.dmsf_folder) || + (@target_folder.nil? && @file.dmsf_folder.nil? && @target_project == @file.project) flash[:error] = l(:error_target_folder_same) redirect_to :action => 'new', :id => @file, :target_project_id => @target_project, :target_folder_id => @target_folder return end new_file = @file.copy_to(@target_project, @target_folder) - + unless new_file.errors.empty? flash[:error] = "#{l(:error_file_cannot_be_copied)}: #{new_file.errors.full_messages.join(', ')}" redirect_to :action => 'new', :id => @file, :target_project_id => @target_project, :target_folder_id => @target_folder @@ -68,8 +68,8 @@ class DmsfFilesCopyController < ApplicationController end flash[:notice] = l(:notice_file_copied) - log_activity(new_file, 'was copied (is copy)') - + log_activity(new_file, 'was copied (is copy)') + redirect_to dmsf_file_path(new_file) end @@ -81,11 +81,11 @@ class DmsfFilesCopyController < ApplicationController end @target_folder = DmsfFolder.visible.find(params[:target_folder_id]) unless params[:target_folder_id].blank? if @target_folder && @target_folder.project != @target_project - raise DmsfAccessError, l(:error_entry_project_does_not_match_current_project) + raise DmsfAccessError, l(:error_entry_project_does_not_match_current_project) end - if (@target_folder && @target_folder == @file.folder) || - (@target_folder.nil? && @file.folder.nil? && @target_project == @file.project) + if (@target_folder && @target_folder == @file.dmsf_folder) || + (@target_folder.nil? && @file.dmsf_folder.nil? && @target_project == @file.project) flash[:error] = l(:error_target_folder_same) redirect_to :action => 'new', :id => @file, :target_project_id => @target_project, :target_folder_id => @target_folder return @@ -98,15 +98,15 @@ class DmsfFilesCopyController < ApplicationController end @file.reload - + flash[:notice] = l(:notice_file_moved) - log_activity(@file, 'was moved (is copy)') - + log_activity(@file, 'was moved (is copy)') + redirect_to dmsf_file_path(@file) - 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}" end @@ -115,5 +115,5 @@ private @file = DmsfFile.visible.find(params[:id]) @project = @file.project end - + end diff --git a/app/controllers/dmsf_folders_copy_controller.rb b/app/controllers/dmsf_folders_copy_controller.rb index 3b53ae82..e3d685bd 100644 --- a/app/controllers/dmsf_folders_copy_controller.rb +++ b/app/controllers/dmsf_folders_copy_controller.rb @@ -18,9 +18,9 @@ class DmsfFoldersCopyController < ApplicationController unloadable - + menu_item :dmsf - + before_filter :find_folder before_filter :authorize @@ -32,10 +32,10 @@ class DmsfFoldersCopyController < ApplicationController else @target_project ||= DmsfFolder.allowed_target_projects_on_copy[0] end - + @target_folder = DmsfFolder.visible.find(params[:target_folder_id]) unless params[:target_folder_id].blank? - @target_folder ||= @folder.folder if @target_project == @project - + @target_folder ||= @folder.dmsf_folder if @target_project == @project + render :layout => !request.xhr? end @@ -47,18 +47,18 @@ class DmsfFoldersCopyController < ApplicationController end @target_folder = DmsfFolder.visible.find(params[:target_folder_id]) unless params[:target_folder_id].blank? if !@target_folder.nil? && @target_folder.project != @target_project - raise DmsfAccessError, l(:error_entry_project_does_not_match_current_project) + raise DmsfAccessError, l(:error_entry_project_does_not_match_current_project) end - if (@target_folder && @target_folder == @folder.folder) || - (@target_folder.nil? && @folder.folder.nil? && @target_project == @folder.project) + if (@target_folder && @target_folder == @folder.dmsf_folder) || + (@target_folder.nil? && @folder.dmsf_folder.nil? && @target_project == @folder.project) flash[:error] = l(:error_target_folder_same) redirect_to :action => 'new', :id => @folder, :target_project_id => @target_project, :target_folder_id => @target_folder return end new_folder = @folder.copy_to(@target_project, @target_folder) - + unless new_folder.errors.empty? flash[:error] = "#{l(:error_folder_cannot_be_copied)}: #{new_folder.errors.full_messages.join(', ')}" redirect_to :action => 'new', :id => @folder, :target_project_id => @target_project, :target_folder_id => @target_folder @@ -66,10 +66,10 @@ class DmsfFoldersCopyController < ApplicationController end new_folder.reload - + flash[:notice] = l(:notice_folder_copied) log_activity(new_folder, 'was copied (is copy)') - + redirect_to dmsf_folder_path(:id => @target_project, :folder_id => new_folder) end @@ -83,5 +83,5 @@ class DmsfFoldersCopyController < ApplicationController @folder = DmsfFolder.visible.find(params[:id]) @project = @folder.project end - + end diff --git a/app/controllers/dmsf_links_controller.rb b/app/controllers/dmsf_links_controller.rb index ca03b680..519a57f6 100644 --- a/app/controllers/dmsf_links_controller.rb +++ b/app/controllers/dmsf_links_controller.rb @@ -26,12 +26,12 @@ class DmsfLinksController < ApplicationController before_filter :find_link_project before_filter :authorize - def new + def new @dmsf_link = DmsfLink.new @dmsf_link.project_id = params[:project_id] if params[:dmsf_link].present? - # Reload + # Reload @dmsf_link.dmsf_folder_id = params[:dmsf_link][:dmsf_folder_id] @dmsf_file_id = params[:dmsf_link][:dmsf_file_id] @type = params[:dmsf_link][:type] @@ -53,7 +53,7 @@ class DmsfLinksController < ApplicationController if file folder = DmsfFolder.find_by_id params[:dmsf_link][:target_folder_id] - if (folder && (folder.project_id == @dmsf_link.target_project_id) && folder.files.include?(file)) || folder.nil? + if (folder && (folder.project_id == @dmsf_link.target_project_id) && folder.dmsf_files.include?(file)) || folder.nil? @dmsf_link.name = file.title end end @@ -111,7 +111,7 @@ class DmsfLinksController < ApplicationController @dmsf_link.target_type = DmsfFolder.model_name.to_s end @dmsf_link.name = params[:dmsf_link][:name] - + if @dmsf_link.save flash[:notice] = l(:notice_successful_create) redirect_to dmsf_folder_path(:id => @project.id, :folder_id => @dmsf_link.dmsf_folder_id) diff --git a/app/controllers/dmsf_upload_controller.rb b/app/controllers/dmsf_upload_controller.rb index f472fdb5..ef32c92c 100644 --- a/app/controllers/dmsf_upload_controller.rb +++ b/app/controllers/dmsf_upload_controller.rb @@ -149,7 +149,7 @@ class DmsfUploadController < ApplicationController file = DmsfFile.new file.project = @project file.name = name - file.folder = @folder + file.dmsf_folder = @folder file.notification = Setting.plugin_redmine_dmsf[:dmsf_default_notifications].present? new_revision.minor_version = 0 new_revision.major_version = 0 @@ -172,7 +172,7 @@ class DmsfUploadController < ApplicationController commited_disk_filepath = "#{DmsfHelper.temp_dir}/#{commited_file[:disk_filename].gsub(/[\/\\]/,'')}" - new_revision.file = file + new_revision.dmsf_file = file new_revision.user = User.current new_revision.name = name new_revision.title = commited_file[:title] diff --git a/app/controllers/dmsf_workflows_controller.rb b/app/controllers/dmsf_workflows_controller.rb index b22f570a..d2f2d9f1 100644 --- a/app/controllers/dmsf_workflows_controller.rb +++ b/app/controllers/dmsf_workflows_controller.rb @@ -47,10 +47,9 @@ class DmsfWorkflowsController < ApplicationController revision = DmsfFileRevision.find_by_id params[:dmsf_file_revision_id] if revision if @dmsf_workflow.try_finish revision, action, (params[:step_action].to_i / 10) - file = DmsfFile.joins(:revisions).where(:dmsf_file_revisions => {:id => revision.id}).first - if file + if revision.dmsf_file begin - file.unlock! true + revision.dmsf_file.unlock! true rescue DmsfLockError => e flash[:info] = e.message end @@ -389,7 +388,7 @@ private @project = @dmsf_workflow.project else # Global workflow revision = DmsfFileRevision.find_by_id params[:dmsf_file_revision_id] - @project = revision.file.project if revision && revision.file + @project = revision.dmsf_file.project if revision && revision.dmsf_file end else if params[:dmsf_workflow] diff --git a/app/helpers/dmsf_helper.rb b/app/helpers/dmsf_helper.rb index 8d7d66ab..893a6a1a 100644 --- a/app/helpers/dmsf_helper.rb +++ b/app/helpers/dmsf_helper.rb @@ -1,5 +1,5 @@ # encoding: utf-8 -# +# # Redmine plugin for Document Management System "Features" # # Copyright (C) 2011 Vít Jonáš @@ -36,7 +36,7 @@ module DmsfHelper end "#{timestamp}_#{filename}" end - + def self.sanitize_filename(filename) # get only the filename, not the whole path just_filename = File.basename(filename.gsub('\\\\', '/')) @@ -49,10 +49,10 @@ module DmsfHelper extension = $1 if just_filename =~ %r{(\.[a-zA-Z0-9]+)$} just_filename = Digest::MD5.hexdigest(just_filename) << extension end - + just_filename end - + def self.filetype_css(filename) extension = File.extname(filename) extension = extension[1, extension.length-1] @@ -63,20 +63,55 @@ module DmsfHelper end end - def plugin_asset_path(plugin, asset_type, source) - return "#{Redmine::Utils.relative_url_root}/plugin_assets/#{plugin}/#{asset_type}/#{source}" + def plugin_asset_path(plugin, asset_type, source) + return "#{Redmine::Utils.relative_url_root}/plugin_assets/#{plugin}/#{asset_type}/#{source}" end def self.to_time(obj) #Right, enough of bugs, let's try a better approach here. - return if !obj + return unless obj return obj.to_time(ActiveRecord::Base.default_timezone) if obj.is_a?(String) # Why can't Mysql::Time conform to time object? - without a utc? method it breaks redmine's - # rendering method, so we convert it to string, and back into time - not the most efficient + # rendering method, so we convert it to string, and back into time - not the most efficient # of methods - however seems functional. Not sure if MySQL - return obj.to_s.to_time(ActiveRecord::Base.default_timezone) if obj.class.name == 'Mysql::Time' - return obj - end - + (obj.class.name == 'Mysql::Time') ? obj.to_s.to_time(ActiveRecord::Base.default_timezone) : obj + end + + def self.dmsf_tree(parent, obj, tree = nil) + tree ||= [] + # Folders && files && links + nodes = obj.dmsf_folders.visible + obj.dmsf_links.visible + obj.dmsf_files.visible + # Alphabetical and type sort + nodes.sort! do |x, y| + if ((x.is_a?(DmsfFolder) || (x.is_a?(DmsfLink) && x.is_folder?)) && + (y.is_a?(DmsfFile) || (y.is_a?(DmsfLink) && y.is_file?))) + -1 + elsif ((x.is_a?(DmsfFile) || (x.is_a?(DmsfLink) && x.is_file?)) && + (y.is_a?(DmsfFolder) || (y.is_a?(DmsfLink) && y.is_folder?))) + 1 + else + x.title.downcase <=> y.title.downcase + end + end + # Create the tree + nodes.each do |node| + local_parent = node.dmsf_folder + level = 0 + while local_parent && (local_parent != parent) + level += 1 + local_parent = local_parent.dmsf_folder + end + position = tree.size + # Files of the same parent and on the same level have the same position + if position > 0 && tree[-1][1] == level && + (tree[-1][0].is_a?(DmsfFile) || (tree[-1][0].is_a?(DmsfLink) && tree[-1][0].is_file?)) + position = tree[-1][2] + end + tree << [node, level, position] + self.dmsf_tree(parent, node, tree) if node.is_a?(DmsfFolder) + end + tree + end + end diff --git a/app/models/dmsf_file.rb b/app/models/dmsf_file.rb index 7ce97d85..3ac69a4a 100644 --- a/app/models/dmsf_file.rb +++ b/app/models/dmsf_file.rb @@ -33,17 +33,15 @@ class DmsfFile < ActiveRecord::Base include RedmineDmsf::Lockable belongs_to :project - belongs_to :folder, :class_name => 'DmsfFolder', :foreign_key => 'dmsf_folder_id' + belongs_to :dmsf_folder belongs_to :deleted_by_user, :class_name => 'User', :foreign_key => 'deleted_by_user_id' - has_many :revisions, -> { order("#{DmsfFileRevision.table_name}.major_version DESC, #{DmsfFileRevision.table_name}.minor_version DESC, #{DmsfFileRevision.table_name}.updated_at DESC") }, - :class_name => 'DmsfFileRevision', :foreign_key => 'dmsf_file_id', + has_many :dmsf_file_revisions, -> { order("#{DmsfFileRevision.table_name}.major_version DESC, #{DmsfFileRevision.table_name}.minor_version DESC, #{DmsfFileRevision.table_name}.updated_at DESC") }, :dependent => :destroy has_many :locks, -> { where(entity_type: 0).order("#{DmsfLock.table_name}.updated_at DESC") }, :class_name => 'DmsfLock', :foreign_key => 'entity_id', :dependent => :destroy has_many :referenced_links, -> { where target_type: DmsfFile.model_name.to_s}, :class_name => 'DmsfLink', :foreign_key => 'target_id', :dependent => :destroy - accepts_nested_attributes_for :revisions, :locks, :referenced_links, :project STATUS_DELETED = 1 STATUS_ACTIVE = 0 @@ -58,7 +56,7 @@ class DmsfFile < ActiveRecord::Base validate :validates_name_uniqueness def validates_name_uniqueness - existing_file = DmsfFile.visible.find_file_by_name(self.project, self.folder, self.name) + existing_file = DmsfFile.visible.find_file_by_name(self.project, self.dmsf_folder, self.name) errors.add(:name, l('activerecord.errors.messages.taken')) unless existing_file.nil? || existing_file.id == self.id end @@ -96,11 +94,12 @@ class DmsfFile < ActiveRecord::Base @@storage_path = nil def self.storage_path - path = Setting.plugin_redmine_dmsf['dmsf_storage_directory'].strip if Setting.plugin_redmine_dmsf['dmsf_storage_directory'].present? + return @@storage_path if @@storage_path.present? + path = Setting.plugin_redmine_dmsf['dmsf_storage_directory'] path = Pathname(Redmine::Configuration['attachments_storage_path']).join('dmsf') if path.blank? && Redmine::Configuration['attachments_storage_path'].present? path = Rails.root.join('files/dmsf').to_s if path.blank? - DmsfFile.storage_path = path if path != @@storage_path - @@storage_path + path.strip if path + path end # Lets introduce a write for storage path, that way we can also @@ -123,7 +122,7 @@ class DmsfFile < ActiveRecord::Base def last_revision unless @last_revision - @last_revision = self.deleted? ? self.revisions.first : self.revisions.visible.first + @last_revision = self.deleted? ? self.dmsf_file_revisions.first : self.dmsf_file_revisions.visible.first end @last_revision end @@ -144,7 +143,7 @@ class DmsfFile < ActiveRecord::Base end begin # Revisions and links of a deleted file SHOULD be deleted too - self.revisions.each { |r| r.delete(commit, true) } + self.dmsf_file_revisions.each { |r| r.delete(commit, true) } if commit self.destroy else @@ -160,11 +159,11 @@ class DmsfFile < ActiveRecord::Base end def restore - if self.dmsf_folder_id && (self.folder.nil? || self.folder.deleted?) + if self.dmsf_folder_id && (self.dmsf_folder.nil? || self.dmsf_folder.deleted?) errors[:base] << l(:error_parent_folder) return false end - self.revisions.each { |r| r.restore } + self.dmsf_file_revisions.each { |r| r.restore } self.deleted = STATUS_ACTIVE self.deleted_by_user = nil save @@ -191,7 +190,7 @@ class DmsfFile < ActiveRecord::Base end def dmsf_path - path = self.folder.nil? ? [] : self.folder.dmsf_path + path = self.dmsf_folder ? self.dmsf_folder.dmsf_path : [] path.push(self) path end @@ -202,8 +201,8 @@ class DmsfFile < ActiveRecord::Base def notify? return true if self.notification - return true if folder && folder.notify? - return true if !folder && self.project.dmsf_notification + return true if self.dmsf_folder && delf.dmsf_folder.notify? + return true if !self.dmsf_folder && self.project.dmsf_notification return false end @@ -236,7 +235,7 @@ class DmsfFile < ActiveRecord::Base # If the target project differs from the source project we must physically move the disk files if self.project != project - self.revisions.all.each do |rev| + self.dmsf_file_revisions.all.each do |rev| if File.exist? rev.disk_file(self.project) FileUtils.mv rev.disk_file(self.project), rev.disk_file(project) end @@ -244,9 +243,9 @@ class DmsfFile < ActiveRecord::Base end self.project = project - self.folder = folder + self.dmsf_folder = folder new_revision = self.last_revision.clone - new_revision.file = self + new_revision.dmsf_file = self new_revision.comment = l(:comment_moved_from, :source => "#{self.project.identifier}:#{self.dmsf_path_str}") new_revision.custom_values = [] @@ -261,7 +260,7 @@ class DmsfFile < ActiveRecord::Base # If the target project differs from the source project we must physically move the disk files if self.project != project - self.revisions.all.each do |rev| + self.dmsf_file_revisions.all.each do |rev| if File.exist? rev.disk_file(self.project) FileUtils.cp rev.disk_file(self.project), rev.disk_file(project) end @@ -269,14 +268,14 @@ class DmsfFile < ActiveRecord::Base end file = DmsfFile.new - file.folder = folder + file.dmsf_folder = folder file.project = project file.name = self.name file.notification = Setting.plugin_redmine_dmsf['dmsf_default_notifications'].present? if file.save && self.last_revision new_revision = self.last_revision.clone - new_revision.file = file + new_revision.dmsf_file = file new_revision.comment = l(:comment_copied_from, :source => "#{self.project.identifier}: #{self.dmsf_path_str}") new_revision.custom_values = [] diff --git a/app/models/dmsf_file_revision.rb b/app/models/dmsf_file_revision.rb index b11a257b..13d04d8b 100644 --- a/app/models/dmsf_file_revision.rb +++ b/app/models/dmsf_file_revision.rb @@ -21,14 +21,12 @@ class DmsfFileRevision < ActiveRecord::Base unloadable - belongs_to :file, :class_name => 'DmsfFile', :foreign_key => 'dmsf_file_id' + belongs_to :dmsf_file belongs_to :source_revision, :class_name => 'DmsfFileRevision', :foreign_key => 'source_dmsf_file_revision_id' belongs_to :user - belongs_to :folder, :class_name => 'DmsfFolder', :foreign_key => 'dmsf_folder_id' belongs_to :deleted_by_user, :class_name => 'User', :foreign_key => 'deleted_by_user_id' - has_many :access, :class_name => 'DmsfFileRevisionAccess', :foreign_key => 'dmsf_file_revision_id', :dependent => :destroy + has_many :dmsf_file_revision_access, :dependent => :destroy has_many :dmsf_workflow_step_assignment, :dependent => :destroy - accepts_nested_attributes_for :access, :dmsf_workflow_step_assignment, :file, :user STATUS_DELETED = 1 STATUS_ACTIVE = 0 @@ -37,8 +35,8 @@ class DmsfFileRevision < ActiveRecord::Base scope :deleted, -> { where(:deleted => STATUS_DELETED) } acts_as_customizable - acts_as_event :title => Proc.new {|o| "#{l(:label_dmsf_updated)}: #{o.file.dmsf_path_str}"}, - :url => Proc.new {|o| {:controller => 'dmsf_files', :action => 'show', :id => o.file}}, + acts_as_event :title => Proc.new {|o| "#{l(:label_dmsf_updated)}: #{o.dmsf_file.dmsf_path_str}"}, + :url => Proc.new {|o| {:controller => 'dmsf_files', :action => 'show', :id => o.dmsf_file}}, :datetime => Proc.new {|o| o.updated_at }, :description => Proc.new {|o| o.comment }, :author => Proc.new {|o| o.user } @@ -58,11 +56,11 @@ class DmsfFileRevision < ActiveRecord::Base :message => l(:error_contains_invalid_character) def project - self.file.project if self.file + self.dmsf_file.project if self.dmsf_file end def folder - self.file.folder if self.file + self.dmsf_file.dmsf_folder if self.dmsf_file end def self.remove_extension(filename) @@ -74,11 +72,11 @@ class DmsfFileRevision < ActiveRecord::Base end def delete(commit = false, force = true) - if self.file.locked_for_user? + if self.dmsf_file.locked_for_user? errors[:base] << l(:error_file_is_locked) return false end - if !commit && (!force && (self.file.revisions.length <= 1)) + if !commit && (!force && (self.dmsf_file.dmsf_file_revisions.length <= 1)) errors[:base] << l(:error_at_least_one_revision_must_be_present) return false end @@ -122,7 +120,7 @@ class DmsfFileRevision < ActiveRecord::Base # custom SQL into a temporary object # def access_grouped - access.select('user_id, COUNT(*) AS count, MIN(created_at) AS first_at, MAX(created_at) AS last_at').group('user_id') + self.dmsf_file_revision_access.select('user_id, COUNT(*) AS count, MIN(created_at) AS first_at, MAX(created_at) AS last_at').group('user_id') end def version @@ -130,9 +128,9 @@ class DmsfFileRevision < ActiveRecord::Base end def disk_file(project = nil) - project = self.file.project unless project + project = self.dmsf_file.project unless project storage_base = DmsfFile.storage_path.dup - if self.file && project + if self.dmsf_file && project project_base = project.identifier.gsub(/[^\w\.\-]/,'_') storage_base << "/p_#{project_base}" end @@ -149,7 +147,7 @@ class DmsfFileRevision < ActiveRecord::Base def clone new_revision = DmsfFileRevision.new - new_revision.file = self.file + new_revision.dmsf_file = self.dmsf_file new_revision.disk_filename = self.disk_filename new_revision.size = self.size new_revision.mime_type = self.mime_type @@ -223,13 +221,13 @@ class DmsfFileRevision < ActiveRecord::Base end def new_storage_filename - raise DmsfAccessError, 'File id is not set' unless self.file.id + raise DmsfAccessError, 'File id is not set' unless self.dmsf_file.id filename = DmsfHelper.sanitize_filename(self.name) timestamp = DateTime.now.strftime("%y%m%d%H%M%S") - while File.exist?(File.join(DmsfFile.storage_path, "#{timestamp}_#{self.file.id}_#{filename}")) + while File.exist?(File.join(DmsfFile.storage_path, "#{timestamp}_#{self.dmsf_file.id}_#{filename}")) timestamp.succ! end - "#{timestamp}_#{self.file.id}_#{filename}" + "#{timestamp}_#{self.dmsf_file.id}_#{filename}" end def copy_file_content(open_file) @@ -262,7 +260,7 @@ class DmsfFileRevision < ActiveRecord::Base format.sub!('%f', filename) format.sub!('%d', self.updated_at.strftime('%Y%m%d%H%M%S')) format.sub!('%v', self.version) - format.sub!('%i', self.file.id.to_s) + format.sub!('%i', self.dmsf_file.id.to_s) format.sub!('%r', self.id.to_s) format + ext end diff --git a/app/models/dmsf_file_revision_access.rb b/app/models/dmsf_file_revision_access.rb index 0136fa78..bdaf493e 100644 --- a/app/models/dmsf_file_revision_access.rb +++ b/app/models/dmsf_file_revision_access.rb @@ -1,3 +1,5 @@ +# encoding: utf-8 +# # Redmine plugin for Document Management System "Features" # # Copyright (C) 2011 Vít Jonáš @@ -18,22 +20,22 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. class DmsfFileRevisionAccess < ActiveRecord::Base + unloadable - belongs_to :revision, :class_name => 'DmsfFileRevision', :foreign_key => 'dmsf_file_revision_id' + belongs_to :dmsf_file_revision belongs_to :user delegate :project, :to => :revision, :allow_nil => false - delegate :file, :to => :revision, :allow_nil => false - accepts_nested_attributes_for :user, :revision + delegate :file, :to => :revision, :allow_nil => false DownloadAction = 0 EmailAction = 1 - acts_as_event :title => Proc.new {|o| "#{l(:label_dmsf_downloaded)}: #{o.file.dmsf_path_str}"}, - :url => Proc.new {|o| {:controller => 'dmsf_files', :action => 'show', :id => o.file}}, + acts_as_event :title => Proc.new {|o| "#{l(:label_dmsf_downloaded)}: #{o.dmsf_file.dmsf_path_str}"}, + :url => Proc.new {|o| {:controller => 'dmsf_files', :action => 'show', :id => o.dmsf_file}}, :datetime => Proc.new {|o| o.updated_at }, - :description => Proc.new {|o| o.revision.comment }, - :author => Proc.new {|o| o.user } - + :description => Proc.new {|o| o.dmsf_file_revision.comment }, + :author => Proc.new {|o| o.user } + acts_as_activity_provider :type => 'dmsf_file_revision_accesses', :timestamp => "#{DmsfFileRevisionAccess.table_name}.updated_at", :author_key => "#{DmsfFileRevisionAccess.table_name}.user_id", @@ -43,6 +45,6 @@ class DmsfFileRevisionAccess < ActiveRecord::Base "INNER JOIN #{DmsfFileRevision.table_name} ON #{DmsfFileRevisionAccess.table_name}.dmsf_file_revision_id = #{DmsfFileRevision.table_name}.id " + "INNER JOIN #{DmsfFile.table_name} ON #{DmsfFileRevision.table_name}.dmsf_file_id = #{DmsfFile.table_name}.id " + "INNER JOIN #{Project.table_name} ON #{DmsfFile.table_name}.project_id = #{Project.table_name}.id"). - where("#{DmsfFile.table_name}.deleted = ?", DmsfFile::STATUS_ACTIVE) + where("#{DmsfFile.table_name}.deleted = ?", DmsfFile::STATUS_ACTIVE) end diff --git a/app/models/dmsf_file_revision_custom_field.rb b/app/models/dmsf_file_revision_custom_field.rb index 2793b424..e9a12d8f 100644 --- a/app/models/dmsf_file_revision_custom_field.rb +++ b/app/models/dmsf_file_revision_custom_field.rb @@ -1,7 +1,9 @@ +# encoding: utf-8 +# # Redmine plugin for Document Management System "Features" # -# Copyright (C) 2011 Vít Jonáš -# Copyright (C) 2013 Karel Picman +# Copyright (C) 2011 Vít Jonáš +# Copyright (C) 2011-16 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,18 +19,18 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -class DmsfFileRevisionCustomField < CustomField - +class DmsfFileRevisionCustomField < CustomField + def type_name :menu_dmsf end - + def compare_values?(x, y) if x.is_a?(Array) && y.is_a?(Array) && !y.empty? - x.include? y[0] - else + x.include? y[0] + else x == y - end + end end - + end \ No newline at end of file diff --git a/app/models/dmsf_folder.rb b/app/models/dmsf_folder.rb index 484bd4f4..4c68e0a2 100644 --- a/app/models/dmsf_folder.rb +++ b/app/models/dmsf_folder.rb @@ -25,43 +25,41 @@ class DmsfFolder < ActiveRecord::Base include RedmineDmsf::Lockable cattr_reader :invalid_characters - @@invalid_characters = /\A[^\/\\\?":<>]*\z/ + @@invalid_characters = /\A[^\/\\\?":<>]*\z/ belongs_to :project - belongs_to :folder, :class_name => 'DmsfFolder', :foreign_key => 'dmsf_folder_id' + belongs_to :dmsf_folder belongs_to :deleted_by_user, :class_name => 'User', :foreign_key => 'deleted_by_user_id' belongs_to :user - has_many :subfolders, :class_name => 'DmsfFolder', :foreign_key => 'dmsf_folder_id', - :dependent => :destroy - has_many :files, :class_name => 'DmsfFile', :foreign_key => 'dmsf_folder_id', - :dependent => :destroy + has_many :dmsf_folders, :dependent => :destroy + has_many :dmsf_files, :dependent => :destroy 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' }, :class_name => 'DmsfLink', :foreign_key => 'dmsf_folder_id', :dependent => :destroy has_many :url_links, -> { where :target_type => 'DmsfUrl' }, :class_name => 'DmsfLink', :foreign_key => 'dmsf_folder_id', :dependent => :destroy + has_many :dmsf_links, :dependent => :destroy 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 - + STATUS_DELETED = 1 STATUS_ACTIVE = 0 - + scope :visible, -> { where(:deleted => STATUS_ACTIVE) } scope :deleted, -> { where(:deleted => STATUS_DELETED) } 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) - + :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}}, @@ -69,11 +67,11 @@ class DmsfFolder < ActiveRecord::Base :author => Proc.new {|o| o.user } validates :title, :presence => true - validates_uniqueness_of :title, :scope => [:dmsf_folder_id, :project_id, :deleted], + validates_uniqueness_of :title, :scope => [:dmsf_folder_id, :project_id, :deleted], conditions: -> { where(:deleted => STATUS_ACTIVE) } validates_format_of :title, :with => @@invalid_characters, :message => l(:error_contains_invalid_character) - validate :check_cycle + validate :check_cycle before_create :default_values def default_values @@ -87,13 +85,13 @@ class DmsfFolder < ActiveRecord::Base def check_cycle folders = [] - self.subfolders.each {|f| folders.push(f)} - folders.each do |folder| - if folder == self.folder + self.dmsf_folders.each {|f| folders.push(f)} + self.dmsf_folders.each do |folder| + if folder == self.dmsf_folder errors.add(:folder, l(:error_create_cycle_in_folder_dependency)) return false end - folder.subfolders.each {|f| folders.push(f)} + folder.dmsf_folders.each {|f| folders.push(f)} end return true end @@ -110,10 +108,10 @@ class DmsfFolder < ActiveRecord::Base if self.locked? errors[:base] << l(:error_folder_is_locked) return false - elsif !self.subfolders.visible.empty? || !self.files.visible.empty? + elsif !self.dmsf_folders.visible.empty? || !self.dmsf_files.visible.empty? errors[:base] << l(:error_folder_is_not_empty) return false - end + end if commit self.destroy else @@ -122,16 +120,16 @@ class DmsfFolder < ActiveRecord::Base self.save end end - + def deleted? self.deleted == STATUS_DELETED end def restore - if self.dmsf_folder_id && (self.folder.nil? || self.folder.deleted?) + if self.dmsf_folder_id && (self.dmsf_folder.nil? || self.dmsf_folder.deleted?) errors[:base] << l(:error_parent_folder) return false - end + end self.deleted = STATUS_ACTIVE self.deleted_by_user = nil self.save @@ -142,7 +140,7 @@ class DmsfFolder < ActiveRecord::Base path = [] while folder path.unshift(folder) - folder = folder.folder + folder = folder.dmsf_folder end path end @@ -155,8 +153,8 @@ class DmsfFolder < ActiveRecord::Base def notify? return true if self.notification - return true if folder && folder.notify? - return true if !folder && self.project.dmsf_notification + return true if self.dmsf_folder && self.dmsf_folder.notify? + return true if !self.dmsf_folder && self.project.dmsf_notification return false end @@ -197,21 +195,21 @@ class DmsfFolder < ActiveRecord::Base end def deep_file_count - file_count = self.files.visible.count - self.subfolders.visible.each {|subfolder| file_count += subfolder.deep_file_count} - file_count + self.file_links.visible.count + file_count = self.dmsf_files.visible.count + self.dmsf_folders.visible.each { |subfolder| file_count += subfolder.deep_file_count } + file_count + self.file_links.visible.count + self.url_links.visible.count end def deep_folder_count - folder_count = self.subfolders.visible.count - self.subfolders.visible.each {|subfolder| folder_count += subfolder.deep_folder_count} + folder_count = self.dmsf_folders.visible.count + self.dmsf_folders.visible.each { |subfolder| folder_count += subfolder.deep_folder_count } folder_count + self.folder_links.visible.count end def deep_size size = 0 - self.files.visible.each {|file| size += file.size} - self.subfolders.visible.each {|subfolder| size += subfolder.deep_size} + self.dmsf_files.visible.each {|file| size += file.size} + self.dmsf_folders.visible.each {|subfolder| size += subfolder.deep_size} size end @@ -228,7 +226,7 @@ class DmsfFolder < ActiveRecord::Base def copy_to(project, folder) new_folder = DmsfFolder.new - new_folder.folder = folder ? folder : nil + new_folder.dmsf_folder = folder ? folder : nil new_folder.project = folder ? folder.project : project new_folder.title = self.title new_folder.description = self.description @@ -241,11 +239,11 @@ class DmsfFolder < ActiveRecord::Base return new_folder unless new_folder.save - self.files.visible.each do |f| + self.dmsf_files.visible.each do |f| f.copy_to project, new_folder end - self.subfolders.visible.each do |s| + self.dmsf_folders.visible.each do |s| s.copy_to project, new_folder end @@ -268,13 +266,13 @@ class DmsfFolder < ActiveRecord::Base def available_custom_fields DmsfFileRevisionCustomField.all end - + def modified last_update = updated_at - subfolders.each do |subfolder| + dmsf_folders.each do |subfolder| last_update = subfolder.updated_at if subfolder.updated_at > last_update end - files.each do |file| + dmsf_files.each do |file| last_update = file.updated_at if file.updated_at > last_update end folder_links.each do |folder_link| @@ -288,20 +286,20 @@ class DmsfFolder < ActiveRecord::Base end last_update end - + # Number of items in the folder def items - subfolders.visible.count + - files.visible.count + + dmsf_folders.visible.count + + dmsf_files.visible.count + folder_links.visible.count + file_links.visible.count + url_links.visible.count - end - + end + private def self.directory_subtree(tree, folder, level, current_folder) - folder.subfolders.visible.each do |subfolder| + folder.dmsf_folders.visible.each do |subfolder| unless subfolder == current_folder tree.push(["#{'...' * level}#{subfolder.title}", subfolder.id]) directory_subtree(tree, subfolder, level + 1, current_folder) diff --git a/app/models/dmsf_link.rb b/app/models/dmsf_link.rb index d0fa4152..1e67e143 100644 --- a/app/models/dmsf_link.rb +++ b/app/models/dmsf_link.rb @@ -29,9 +29,9 @@ class DmsfLink < ActiveRecord::Base validates :name, :presence => true validates_length_of :name, :maximum => 255 - validates_length_of :external_url, :maximum => 255 + validates_length_of :external_url, :maximum => 255 validate :validate_url - + def validate_url if self.target_type == 'DmsfUrl' begin @@ -40,17 +40,17 @@ class DmsfLink < ActiveRecord::Base else errors.add :external_url, :invalid end - rescue URI::InvalidURIError + rescue URI::InvalidURIError errors.add :external_url, :invalid - end + end end end - + STATUS_DELETED = 1 STATUS_ACTIVE = 0 - + scope :visible, -> { where(:deleted => STATUS_ACTIVE) } - scope :deleted, -> { where(:deleted => STATUS_DELETED) } + scope :deleted, -> { where(:deleted => STATUS_DELETED) } def target_folder_id if self.target_type == DmsfFolder.model_name.to_s @@ -97,9 +97,9 @@ class DmsfLink < ActiveRecord::Base end def path - if self.target_type == DmsfFile.model_name.to_s + if self.target_type == DmsfFile.model_name.to_s path = self.target_file.dmsf_path.map { |element| element.is_a?(DmsfFile) ? element.name : element.title }.join('/') if self.target_file - else + 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 @@ -133,7 +133,7 @@ class DmsfLink < ActiveRecord::Base end def restore - if self.dmsf_folder_id && (self.folder.nil? || self.folder.deleted?) + if self.dmsf_folder_id && (self.dmsf_folder.nil? || self.dmsf_folder.deleted?) errors[:base] << l(:error_parent_folder) return false end @@ -142,4 +142,12 @@ class DmsfLink < ActiveRecord::Base save(:validate => false) end + def is_folder? + self.target_type == 'DmsfFolder' + end + + def is_file? + !is_folder? + end + end \ No newline at end of file diff --git a/app/models/dmsf_lock.rb b/app/models/dmsf_lock.rb index e1873c7d..13bc6b8c 100644 --- a/app/models/dmsf_lock.rb +++ b/app/models/dmsf_lock.rb @@ -29,10 +29,9 @@ class DmsfLock < ActiveRecord::Base belongs_to :user # At the moment apparently we're only supporting a write lock? - as_enum :lock_type, [:type_write, :type_other] as_enum :lock_scope, [:scope_exclusive, :scope_shared] - + # We really loosely bind the value in the belongs_to above # here we just ensure the data internal to the model is correct # to ensure everything lists fine - it's the same as a join @@ -73,5 +72,5 @@ class DmsfLock < ActiveRecord::Base def self.find_by_param(*args) self.find(*args) end - + end \ No newline at end of file diff --git a/app/models/dmsf_mailer.rb b/app/models/dmsf_mailer.rb index 500f4362..666cc3b7 100644 --- a/app/models/dmsf_mailer.rb +++ b/app/models/dmsf_mailer.rb @@ -23,74 +23,74 @@ require 'mailer' class DmsfMailer < Mailer layout 'mailer' - + def files_updated(user, project, files) - if user && project && files.count > 0 + if user && project && files.count > 0 files = files.select { |file| file.notify? } - redmine_headers 'Project' => project.identifier if project + redmine_headers 'Project' => project.identifier if project @files = files @project = project message_id project set_language_if_valid user.language - mail :to => user.mail, + mail :to => user.mail, :subject => "[#{@project.name} - #{l(:menu_dmsf)}] #{l(:text_email_doc_updated_subject)}" end end - + def files_deleted(user, project, files) - if user && files.count > 0 + if user && files.count > 0 files = files.select { |file| file.notify? } - redmine_headers 'Project' => project.identifier if project + redmine_headers 'Project' => project.identifier if project @files = files @project = project message_id project set_language_if_valid user.language - mail :to => user.mail, + mail :to => user.mail, :subject => "[#{@project.name} - #{l(:menu_dmsf)}] #{l(:text_email_doc_deleted_subject)}" end end - - def send_documents(project, user, email_params) - zipped_content_data = open(email_params[:zipped_content], 'rb') { |io| io.read } + + def send_documents(project, user, email_params) + zipped_content_data = open(email_params[:zipped_content], 'rb') { |io| io.read } redmine_headers 'Project' => project.identifier if project @body = email_params[:body] @links_only = email_params[:links_only] @folders = email_params[:folders] - @files = email_params[:files] + @files = email_params[:files] unless @links_only == '1' attachments['Documents.zip'] = { :content_type => 'application/zip', :content => zipped_content_data } - end - mail :to => email_params[:to], :cc => email_params[:cc], + end + mail :to => email_params[:to], :cc => email_params[:cc], :subject => email_params[:subject], :from => user.mail end - + def workflow_notification(user, workflow, revision, subject_id, text1_id, text2_id, notice = nil) if user && workflow && revision - if revision.file && revision.file.project - @project = revision.file.project + if revision.dmsf_file && revision.dmsf_file.project + @project = revision.dmsf_file.project redmine_headers 'Project' => @project.identifier end set_language_if_valid user.language @user = user message_id workflow @workflow = workflow - @revision = revision - @text1 = l(text1_id, :name => workflow.name, :filename => revision.file.name, :notice => notice) + @revision = revision + @text1 = l(text1_id, :name => workflow.name, :filename => revision.dmsf_file.name, :notice => notice) @text2 = l(text2_id) @notice = notice - mail :to => user.mail, + mail :to => user.mail, :subject => "[#{@project.name} - #{l(:field_label_dmsf_workflow)}] #{@workflow.name} #{l(subject_id)}" end - end - + end + def self.get_notify_users(project, files = nil) if files notify_files = files.select { |file| file.notify? } return [] if notify_files.empty? - end + end notify_members = project.members notify_members = notify_members.select do |notify_member| - notify_user = notify_member.user + notify_user = notify_member.user if notify_user == User.current && notify_user.pref.no_self_notified false else @@ -105,13 +105,13 @@ class DmsfMailer < Mailer else false end - else + else notify_member.dmsf_mail_notification end end - end + end notify_members.collect { |m| m.user }.uniq end - + end \ No newline at end of file diff --git a/app/models/dmsf_upload.rb b/app/models/dmsf_upload.rb index a151de50..ff1efd06 100644 --- a/app/models/dmsf_upload.rb +++ b/app/models/dmsf_upload.rb @@ -1,8 +1,10 @@ +# encoding: utf-8 +# # Redmine plugin for Document Management System "Features" # # Copyright (C) 2011 Vít Jonáš # Copyright (C) 2012 Daniel Munn -# Copyright (C) 2011-14 Karel Pičman +# Copyright (C) 2011-16 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,63 +21,67 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. class DmsfUpload - attr_accessor :name + attr_accessor :name attr_accessor :disk_filename attr_reader :size attr_accessor :mime_type attr_accessor :title - attr_accessor :description + attr_accessor :description attr_accessor :comment attr_accessor :major_version attr_accessor :minor_version - attr_accessor :locked + attr_accessor :locked attr_accessor :workflow - attr_accessor :custom_values - + attr_accessor :custom_values + def disk_file "#{DmsfHelper.temp_dir}/#{self.disk_filename}" - end - - def self.create_from_uploaded_attachment(project, folder, uploaded_file) - a = Attachment.find_by_token(uploaded_file[:token]) + end + + def self.create_from_uploaded_attachment(project, folder, uploaded_file) + a = Attachment.find_by_token(uploaded_file[:token]) if a uploaded = { :disk_filename => DmsfHelper.temp_filename(a.filename), :content_type => a.content_type, :original_filename => a.filename, :comment => uploaded_file[:description] - } + } FileUtils.mv(a.diskfile, "#{DmsfHelper.temp_dir}/#{uploaded[:disk_filename]}") - a.destroy + a.destroy upload = DmsfUpload.new(project, folder, uploaded) else - Rails.logger.error "An attachment not found by its token: #{uploaded_file[:token]}" + Rails.logger.error "An attachment not found by its token: #{uploaded_file[:token]}" end upload end - + def initialize(project, folder, uploaded) @name = uploaded[:original_filename] - + file = DmsfFile.find_file_by_name(project, folder, @name) unless file link = DmsfLink.find_link_by_file_name(project, folder, @name) file = link.target_file if link end - + @disk_filename = uploaded[:disk_filename] @mime_type = uploaded[:content_type] - @size = File.size(disk_file) - + @size = File.size(disk_file) + if file.nil? || file.last_revision.nil? @title = DmsfFileRevision.filename_to_title(@name) @description = uploaded[:comment] @major_version = 0 @minor_version = 0 - @workflow = nil - @custom_values = DmsfFileRevision.new(:file => DmsfFile.new(:project => @project)).custom_field_values + @workflow = nil + file = DmsfFile.new + file.project = @project + revision = DmsfFileRevision.new + revision.dmsf_file = file + @custom_values = revision.custom_field_values else - last_revision = file.last_revision + last_revision = file.last_revision @title = last_revision.title if last_revision.description.present? @description = last_revision.description @@ -86,7 +92,7 @@ class DmsfUpload @major_version = last_revision.major_version @minor_version = last_revision.minor_version @workflow = last_revision.workflow - @custom_values = Array.new(file.last_revision.custom_values) + @custom_values = Array.new(file.last_revision.custom_values) # Add default value for CFs not existing present_custom_fields = file.last_revision.custom_values.collect(&:custom_field).uniq @@ -96,8 +102,8 @@ class DmsfUpload end end end - + @locked = file && file.locked_for_user? end - + end \ No newline at end of file diff --git a/app/models/dmsf_workflow_step.rb b/app/models/dmsf_workflow_step.rb index 8ce5c857..63e903ad 100644 --- a/app/models/dmsf_workflow_step.rb +++ b/app/models/dmsf_workflow_step.rb @@ -2,7 +2,7 @@ # # Redmine plugin for Document Management System "Features" # -# Copyright (C) 2011-15 Karel Pičman +# Copyright (C) 2011-16 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,8 +25,8 @@ class DmsfWorkflowStep < ActiveRecord::Base validates :step, :presence => true validates :user_id, :presence => true validates :operator, :presence => true - validates_uniqueness_of :user_id, :scope => [:dmsf_workflow_id, :step] - + validates_uniqueness_of :user_id, :scope => [:dmsf_workflow_id, :step] + OPERATOR_OR = 0 OPERATOR_AND = 1 @@ -37,7 +37,7 @@ class DmsfWorkflowStep < ActiveRecord::Base def user User.find(user_id) end - + def assign(dmsf_file_revision_id) step_assignment = DmsfWorkflowStepAssignment.new( :dmsf_workflow_step_id => id, @@ -45,20 +45,20 @@ class DmsfWorkflowStep < ActiveRecord::Base :dmsf_file_revision_id => dmsf_file_revision_id) step_assignment.save end - + def is_finished?(dmsf_file_revision_id) self.dmsf_workflow_step_assignments.each do |assignment| if assignment.dmsf_file_revision_id == dmsf_file_revision_id - if assignment.dmsf_workflow_step_actions.empty? + if assignment.dmsf_workflow_step_actions.empty? return false - end - assignment.dmsf_workflow_step_actions.each do |act| - return false unless act.is_finished? end - end - end + assignment.dmsf_workflow_step_actions.each do |act| + return false unless act.is_finished? + end + end + end end - + def copy_to(workflow) new_step = self.dup new_step.dmsf_workflow_id = workflow.id diff --git a/app/models/dmsf_workflow_step_action.rb b/app/models/dmsf_workflow_step_action.rb index f5d8c69e..0725c362 100644 --- a/app/models/dmsf_workflow_step_action.rb +++ b/app/models/dmsf_workflow_step_action.rb @@ -2,7 +2,7 @@ # # Redmine plugin for Document Management System "Features" # -# Copyright (C) 2011-15 Karel Pičman +# Copyright (C) 2011-16 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,37 +19,35 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. class DmsfWorkflowStepAction < ActiveRecord::Base - - belongs_to :dmsf_workflow_step_assignment + + belongs_to :dmsf_workflow_step_assignment validates :dmsf_workflow_step_assignment_id, :presence => true validates :action, :presence => true validates :note, :presence => true, :unless => lambda { self.action == DmsfWorkflowStepAction::ACTION_APPROVE } validates :author_id, :presence => true - validates_uniqueness_of :dmsf_workflow_step_assignment_id, :scope => [:action], :unless => lambda {self.action == DmsfWorkflowStepAction::ACTION_DELEGATE} - - attr_accessible :dmsf_workflow_step_assignment_id, :action, :note - + validates_uniqueness_of :dmsf_workflow_step_assignment_id, :scope => [:action], :unless => lambda {self.action == DmsfWorkflowStepAction::ACTION_DELEGATE} + ACTION_APPROVE = 1 ACTION_REJECT = 2 ACTION_DELEGATE = 3 ACTION_ASSIGN = 4 ACTION_START = 5 - + def initialize(*args) super - self.author_id = User.current.id if User.current + self.author_id = User.current.id if User.current end - + def self.is_finished?(action) action == DmsfWorkflowStepAction::ACTION_APPROVE || action == DmsfWorkflowStepAction::ACTION_REJECT end - + def is_finished? DmsfWorkflowStepAction.is_finished? self.action end - + def self.action_str(action) if action case action.to_i @@ -66,7 +64,7 @@ class DmsfWorkflowStepAction < ActiveRecord::Base end end end - + def self.action_type_str(action) if action case action.to_i @@ -83,12 +81,12 @@ class DmsfWorkflowStepAction < ActiveRecord::Base end end end - + def self.workflow_str(action) if action - case action.to_i + case action.to_i when ACTION_REJECT - l(:title_rejected) + l(:title_rejected) when ACTION_ASSIGN l(:title_assigned) when ACTION_START, ACTION_DELEGATE, ACTION_APPROVE @@ -98,5 +96,5 @@ class DmsfWorkflowStepAction < ActiveRecord::Base end end end - + end \ No newline at end of file diff --git a/app/models/dmsf_workflow_step_assignment.rb b/app/models/dmsf_workflow_step_assignment.rb index cda7d8f3..30ffbcd5 100644 --- a/app/models/dmsf_workflow_step_assignment.rb +++ b/app/models/dmsf_workflow_step_assignment.rb @@ -2,7 +2,7 @@ # # Redmine plugin for Document Management System "Features" # -# Copyright (C) 2011-15 Karel Pičman +# Copyright (C) 2011-16 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 @@ -22,17 +22,15 @@ class DmsfWorkflowStepAssignment < ActiveRecord::Base belongs_to :dmsf_workflow_step belongs_to :user belongs_to :dmsf_file_revision - has_many :dmsf_workflow_step_actions, :dependent => :destroy - validates :dmsf_workflow_step_id, :dmsf_file_revision_id, :presence => true + has_many :dmsf_workflow_step_actions, :dependent => :destroy + validates :dmsf_workflow_step_id, :dmsf_file_revision_id, :presence => true validates_uniqueness_of :dmsf_workflow_step_id, :scope => [:dmsf_file_revision_id] - - attr_accessible :dmsf_workflow_step_id, :user_id, :dmsf_file_revision_id - + def add?(dmsf_file_revision_id) if self.dmsf_file_revision_id == dmsf_file_revision_id add = true self.dmsf_workflow_step_actions.each do |action| - if action.is_finished? + if action.is_finished? add = false break end diff --git a/app/views/dmsf/_dir.html.erb b/app/views/dmsf/_dir.html.erb index 56da2dbc..2a12f9a0 100644 --- a/app/views/dmsf/_dir.html.erb +++ b/app/views/dmsf/_dir.html.erb @@ -21,12 +21,15 @@ %> <% locked_for_user = subfolder && subfolder.locked_for_user? %> -<% locked = subfolder && subfolder.locked? %> +<% locked = subfolder && subfolder.locked? %> -
    - + - @@ -65,7 +68,7 @@ <% if subfolder.unlockable? %> <%= link_to(image_tag('unlock.png', :plugin => 'redmine_dmsf'), unlock_dmsf_path(:id => project, :folder_id => subfolder), - :title => l(:title_unlock_file)) %> + :title => l(:title_unlock_file)) %> <% else %> <% end %> @@ -77,11 +80,11 @@ <% else %> <% end %> - <% if (subfolder && subfolder.notification) || (!subfolder && project.dmsf_notification) %> + <% if (subfolder && subfolder.notification) || (!subfolder && project.dmsf_notification) %> <%= link_to(image_tag('notify.png', :plugin => 'redmine_dmsf'), notify_deactivate_dmsf_path(:id => project, :folder_id => subfolder), :title => l(:title_notifications_active_deactivate)) %> - <% else %> + <% else %> <%= link_to(image_tag('notifynot.png', :plugin => 'redmine_dmsf'), notify_activate_dmsf_path(:id => project, :folder_id => subfolder), :title => l(:title_notifications_not_active_activate)) %> @@ -89,24 +92,24 @@ <% if link %> <%= link_to(image_tag('delete.png'), dmsf_link_path(link), :data => {:confirm => l(:text_are_you_sure)}, - :method => :delete, :title => l(:title_delete)) %> + :method => :delete, :title => l(:title_delete)) %> <% else %> <%= link_to(image_tag('delete.png'), delete_dmsf_path(:id => project, :folder_id => subfolder), - :data => {:confirm => l(:text_are_you_sure)}, - :title => l(:title_delete)) %> - <% end %> - <% else %> + :data => {:confirm => l(:text_are_you_sure)}, + :title => l(:title_delete)) %> + <% end %> + <% else %> <% if (!locked_for_user || @force_file_unlock_allowed) && subfolder.unlockable? %> <%= link_to(image_tag('unlock.png', :plugin => 'redmine_dmsf'), unlock_dmsf_path(:id => project, :folder_id => subfolder), - :title => l(:title_unlock_file)) %> - <% end %> - <% end %> + :title => l(:title_unlock_file)) %> + <% end %> + <% end %> <% end %> - + - \ No newline at end of file + diff --git a/app/views/dmsf/_file.html.erb b/app/views/dmsf/_file.html.erb index 84d6341e..c39fceaf 100644 --- a/app/views/dmsf/_file.html.erb +++ b/app/views/dmsf/_file.html.erb @@ -22,9 +22,12 @@ <% wf = DmsfWorkflow.find_by_id(file.last_revision.dmsf_workflow_id) %> - - - - + - \ No newline at end of file + diff --git a/app/views/dmsf/_list_view.erb b/app/views/dmsf/_list_view.erb new file mode 100644 index 00000000..6ef48f67 --- /dev/null +++ b/app/views/dmsf/_list_view.erb @@ -0,0 +1,118 @@ +<% +# encoding: utf-8 +# +# Redmine plugin for Document Management System "Features" +# +# Copyright (C) 2011-16 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_step) %> <%= l(:label_dmsf_workflow_approval_plural) %><%= l(:button_sort)%><%= 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 %>  + + <% stps = @dmsf_workflow.dmsf_workflow_steps.collect{|s| (s.step == i) ? s : nil}.compact %> + <% stps.each_with_index do |step, j| %> + <% if (j != 0) || (stps.count > 1) %> + <%= 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) %> -
    <%= check_box_tag(name, id, false, +<%= check_box_tag(name, id, false, :title => l(:title_check_for_zip_download_or_email), :id => "subfolder_#{id}") %> - <%= link_to(h(title), + + <% if @tree_view %> + > + <% end %> + <%= link_to(h(title), dmsf_folder_path(:id => project, :folder_id => subfolder), :class => 'icon icon-folder') %> <% if link %> @@ -34,22 +37,22 @@ <% else %>
    [<%= subfolder.items %>]
    <% end %> -
    <%= format_time(subfolder.modified) if subfolder %> +<%= format_time(subfolder.modified) if subfolder %> <% if locked_for_user %> - <% if subfolder.lock.reverse[0].user %> + <% if subfolder.lock.reverse[0].user %> <%= link_to(image_tag(link ? 'locked_gray.png' : 'locked.png', :plugin => 'redmine_dmsf'), user_path(subfolder.lock.reverse[0].user), :title => l(:title_locked_by_user, :user => subfolder.lock.reverse[0].user)) %> - <% else %> - <%= content_tag(:span, image_tag(link ? 'locked_gray.png' : 'locked.png', :plugin => 'redmine_dmsf'), + <% else %> + <%= content_tag(:span, image_tag(link ? 'locked_gray.png' : 'locked.png', :plugin => 'redmine_dmsf'), :title => l(:notice_account_unknown_email)) %> <% end %> - <% elsif locked %> - <%= content_tag(:span, image_tag(link ? 'lockedbycurrent_gray.png' : 'lockedbycurrent.png', :plugin => 'redmine_dmsf'), + <% elsif locked %> + <%= content_tag(:span, image_tag(link ? 'lockedbycurrent_gray.png' : 'lockedbycurrent.png', :plugin => 'redmine_dmsf'), :title => l(:title_locked_by_you)) %> - <% end %> + <% end %> 0<%= position %> 0 <%= subfolder.modified.to_i if subfolder %>00<%= check_box_tag(name, id, false, +<%= check_box_tag(name, id, false, :title => l(:title_check_for_zip_download_or_email), :id => "file_#{id}") %> + <% if @tree_view %> + > + <% end %> <% file_view_url = url_for({:controller => :dmsf_files, :action => 'view', :id => file}) %> <%= link_to(h(title), file_view_url, @@ -33,32 +36,33 @@ :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_view_url}") %>
    <%= h(link ? link.path : file.display_name) %>
    + <%= ''.html_safe if @tree_view %>
    <%= number_to_human_size(file.last_revision.size) %> <%= format_time(file.last_revision.updated_at) %> <% if file.locked_for_user? %> - <% if file.lock.reverse[0].user %> + <% if file.lock.reverse[0].user %> <%= link_to(image_tag(link ? 'locked_gray.png' : 'locked.png', :plugin => 'redmine_dmsf'), user_path(file.lock.reverse[0].user), :title => l(:title_locked_by_user, :user => file.lock.reverse[0].user)) %> - <% else %> - <%= content_tag(:span, image_tag(link ? 'locked_gray.png' : 'locked.png', :plugin => 'redmine_dmsf'), + <% else %> + <%= content_tag(:span, image_tag(link ? 'locked_gray.png' : 'locked.png', :plugin => 'redmine_dmsf'), :title => l(:notice_account_unknown_email)) %> <% end %> - <% elsif file.locked? %> - <%= content_tag(:span, image_tag(link ? 'lockedbycurrent_gray.png' : 'lockedbycurrent.png', :plugin => 'redmine_dmsf'), + <% elsif file.locked? %> + <%= content_tag(:span, image_tag(link ? 'lockedbycurrent_gray.png' : 'lockedbycurrent.png', :plugin => 'redmine_dmsf'), :title => l(:title_locked_by_you)) %> <% end %> <%= file.last_revision.version %> + <% if wf && @file_approval_allowed %> <%= link_to( file.last_revision.workflow_str(false), log_dmsf_workflow_path( - :project_id => project.id, - :id => wf.id, + :project_id => project.id, + :id => wf.id, :dmsf_file_revision_id => file.last_revision.id), :title => DmsfWorkflow.assignments_to_users_str(wf.next_assignments(file.last_revision.id)), :remote => true) %> @@ -67,7 +71,7 @@ <% end %> <%= h(file.last_revision.user) %> + <% if @file_manipulation_allowed %> <%= link_to(image_tag('filedetails.png', :plugin => 'redmine_dmsf'), dmsf_file_path(:id => file), @@ -76,7 +80,7 @@ <% if !file.locked? %> <%= link_to(image_tag('lock.png', :plugin => 'redmine_dmsf'), lock_dmsf_files_path(:id => file), - :title => l(:title_lock_file)) %> + :title => l(:title_lock_file)) %> <% elsif file.unlockable? %> <%= link_to(image_tag('unlock.png', :plugin => 'redmine_dmsf'), unlock_dmsf_files_path(:id => file), @@ -84,97 +88,97 @@ <% else %> <% end %> - <% if file.notification %> + <% if file.notification %> <%= link_to(image_tag('notify.png', :plugin => 'redmine_dmsf'), notify_deactivate_dmsf_files_path(:id => file), :title => l(:title_notifications_active_deactivate)) %> - <% else %> + <% else %> <%= link_to(image_tag('notifynot.png', :plugin => 'redmine_dmsf'), notify_activate_dmsf_files_path(:id => file), :title => l(:title_notifications_not_active_activate)) %> <% end %> <% if link %> - <%= link_to(image_tag('delete.png'), - dmsf_link_path(link), - :data => {:confirm => l(:text_are_you_sure)}, + <%= link_to(image_tag('delete.png'), + dmsf_link_path(link), + :data => {:confirm => l(:text_are_you_sure)}, :method => :delete, :title => l(:title_delete)) %> <% else %> - <% if @file_delete_allowed %> - <%= link_to(image_tag('delete.png'), - dmsf_file_path(:id => file), - :data => {:confirm => l(:text_are_you_sure)}, + <% if @file_delete_allowed %> + <%= link_to(image_tag('delete.png'), + dmsf_file_path(:id => file), + :data => {:confirm => l(:text_are_you_sure)}, :method => :delete, :title => l(:title_delete)) unless file.locked_for_user? %> <% else %> <% end %> - <% end %> - <% else %> + <% end %> + <% else %> <% if @force_file_unlock_allowed && file.unlockable? %> <%= link_to(image_tag('unlock.png', :plugin => 'redmine_dmsf'), unlock_dmsf_files_path(:id => file), :title => l(:title_unlock_file))%> <% else %> - <% end %> + <% end %> <% end %> <% end %> <% if @file_approval_allowed %> <% case file.last_revision.workflow %> - <% when DmsfWorkflow::STATE_WAITING_FOR_APPROVAL %> + <% when DmsfWorkflow::STATE_WAITING_FOR_APPROVAL %> <% if wf %> <% assignments = wf.next_assignments(file.last_revision.id) %> <% index = assignments.find_index{|assignment| assignment.user_id == User.current.id} if assignments %> <% if index %> <%= link_to(image_tag('waiting_for_approval.png', :plugin => 'redmine_dmsf'), action_dmsf_workflow_path( - :project_id => project.id, - :id => wf.id, + :project_id => project.id, + :id => wf.id, :dmsf_workflow_step_assignment_id => assignments[index].id, :dmsf_file_revision_id => file.last_revision.id), - :title => l(:title_waiting_for_approval), + :title => l(:title_waiting_for_approval), :remote => true) %> <% else %> <%= content_tag(:span, image_tag('waiting_for_approval.png', :plugin => 'redmine_dmsf'), :title => "#{l(:label_dmsf_wokflow_action_approve)} #{l(:label_dmsf_wokflow_action_reject)} #{l(:label_dmsf_wokflow_action_delegate)}") %> <% end %> - <% else %> + <% else %> <%= content_tag(:span, image_tag('waiting_for_approval.png', :plugin => 'redmine_dmsf'), :title => "#{l(:label_dmsf_wokflow_action_approve)} #{l(:label_dmsf_wokflow_action_reject)} #{l(:label_dmsf_wokflow_action_delegate)}") %> <% end %> - <% when DmsfWorkflow::STATE_APPROVED %> - <%= content_tag(:span, image_tag('approved.png', :plugin => 'redmine_dmsf'), + <% when DmsfWorkflow::STATE_APPROVED %> + <%= content_tag(:span, image_tag('approved.png', :plugin => 'redmine_dmsf'), :title => l(:title_approved)) %> <% when DmsfWorkflow::STATE_ASSIGNED %> - <% if User.current && (file.last_revision.dmsf_workflow_assigned_by == User.current.id) && wf %> + <% if User.current && (file.last_revision.dmsf_workflow_assigned_by == User.current.id) && wf %> <%= link_to(image_tag('assigned.png', :plugin => 'redmine_dmsf'), - start_dmsf_workflow_path( - :id => file.last_revision.dmsf_workflow_id, + start_dmsf_workflow_path( + :id => file.last_revision.dmsf_workflow_id, :dmsf_file_revision_id => file.last_revision.id), :title => l(:label_dmsf_wokflow_action_start)) %> - <% else %> - <%= content_tag(:span, image_tag('assigned.png', :plugin => 'redmine_dmsf'), + <% else %> + <%= content_tag(:span, image_tag('assigned.png', :plugin => 'redmine_dmsf'), title => l(:label_dmsf_wokflow_action_start)) %> <% end %> - <% when DmsfWorkflow::STATE_REJECTED %> - <%= content_tag(:span, image_tag('rejected.png', :plugin => 'redmine_dmsf'), + <% when DmsfWorkflow::STATE_REJECTED %> + <%= content_tag(:span, image_tag('rejected.png', :plugin => 'redmine_dmsf'), :title => l(:title_rejected)) %> <% else %> <% if @workflows_available && !file.locked_for_user? %> <%= link_to(image_tag('none.png', :plugin => 'redmine_dmsf'), assign_dmsf_workflow_path( - :project_id => project.id, + :project_id => project.id, :dmsf_file_revision_id => file.last_revision.id), - :title => l(:label_dmsf_wokflow_action_assign), - :remote => true) %> + :title => l(:label_dmsf_wokflow_action_assign), + :remote => true) %> <% end %> - <% end %> + <% end %> <% end %> 1<%= position %> <%= file.last_revision.size %> <%= file.last_revision.updated_at.to_i %><%= file.last_revision.iversion %><%= file.last_revision.iversion %>
    + + + + + + + + + + + + + + + + + + <% @subfolders.each do |subfolder| %> + + <%= render(:partial => 'dir', + :locals => { + :project => @project, + :subfolder => subfolder, + :link => nil, + :id => subfolder.id, + :name => 'subfolders[]', + :title => subfolder.title, + :position => 0 }) %> + + <% end %> + <% @dir_links.each do |link| %> + <% unless link.target_project %> + <% Rails.logger.error "Error: dmsf_link id #{link.id} has no target!" %> + <% next %> + <% end %> + + <%= render(:partial => 'dir', + :locals => { + :project => link.target_project, + :subfolder => link.target_folder, + :link => link, + :id => link.id, + :name => 'dir_links[]', + :title => link.name, + :position => 0}) %> + + <% end %> + <% @files.each do |file| %> + <% unless file.last_revision %> + <% Rails.logger.error "Error: dmsf_file id #{file.id} has no revision!" %> + <% next %> + <% end %> + + <%= render(:partial => 'file', :locals => { + :project => @project, + :file => file, + :link => nil, + :id => file.id, + :name => 'files[]', + :title => file.title, + :position => 1 }) %> + + <% end %> + <% @file_links.each do |link| %> + <% unless link.target_file.last_revision %> + <% Rails.logger.error "Error: dmsf_file id #{link.target_id} has no revision!" %> + <% next %> + <% end %> + + <%= render(:partial => 'file', :locals => { + :project => link.target_project, + :file => link.target_file, + :link => link, + :id => link.id, + :name => 'file_links[]', + :title => link.name, + :position => 1}) %> + + <% end %> + <% @url_links.each do |link| %> + + <%= render(:partial => 'url', :locals => { + :project => link.target_project, + :file => link.target_file, + :link => link, + :id => link.id, + :name => 'file_links[]', + :title => link.name, + :position => 1}) %> + + <% end %> + +
    + + <%= l(:link_title) %><%= l(:link_size) %><%= l(:link_modified) %><%= l(:link_ver) %><%= l(:link_workflow) %><%= l(:link_author) %>
    diff --git a/app/views/dmsf/_tree_view.erb b/app/views/dmsf/_tree_view.erb new file mode 100644 index 00000000..8949ad52 --- /dev/null +++ b/app/views/dmsf/_tree_view.erb @@ -0,0 +1,138 @@ +<% +# encoding: utf-8 +# +# Redmine plugin for Document Management System "Features" +# +# Copyright (C) 2011-16 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. +%> + + + + + + + + + + + + + + + + + + + + <% parent = @folder ? @folder : @project %> + <% DmsfHelper.dmsf_tree(parent, parent).each do |obj, level, position| %> + <% classes = "dmsf_tree idnt-#{level}" %> + <% if obj.is_a?(DmsfFolder) && ((obj.dmsf_folders.visible.count > 0) || (obj.dmsf_files.visible.count > 0) || (obj.dmsf_links.visible.count > 0)) %> + <% classes += ' idnt dmsf_collapsed' %> + <% id = "id='#{obj.id}span'".html_safe %> + <% onclick = "onclick=\"dmsf_toggle('#{obj.id}','#{obj.id}span')\"" %> + <% else %> + <% classes += ' dmsf_child' %> + <% onclick = '' %> + <% end %> + <% classes += ' dmsf_hidden' if obj.dmsf_folder && obj.dmsf_folder != @folder %> + <% parent = obj.dmsf_folder %> + <% while parent %> + <% classes += " #{parent.id}" %> + <% parent = parent.dmsf_folder %> + <% end %> + + <% if obj.is_a? DmsfFolder %> + class="dir <%= classes %>"> + <%= render(:partial => 'dir', + :locals => { + :project => @project, + :subfolder => obj, + :link => nil, + :id => obj.id, + :name => 'subfolders[]', + :title => obj.title, + :onclick => onclick, + :position => position}) %> + + <% elsif obj.is_a?(DmsfLink) && (obj.target_type == 'DmsfFolder') %> + <% unless obj.target_project %> + <% Rails.logger.error "Error: dmsf_link id #{obj.id} has no target!" %> + <% next %> + <% end %> + class="dmsf_gray <%= classes %>"> + <%= render(:partial => 'dir', + :locals => { + :project => obj.target_project, + :subfolder => obj.target_folder, + :link => obj, + :id => obj.id, + :name => 'dir_links[]', + :title => obj.name, + :onclick => onclick, + :position => position}) %> + + <% elsif obj.is_a?(DmsfFile) %> + <% unless obj.last_revision %> + <% Rails.logger.error "Error: dmsf_file id #{obj.id} has no revision!" %> + <% next %> + <% end %> + class="file <%= classes %>"> + <%= render(:partial => 'file', :locals => { + :project => @project, + :file => obj, + :link => nil, + :id => obj.id, + :name => 'files[]', + :title => obj.title, + :onclick => onclick, + :position => position}) %> + + <% elsif obj.is_a?(DmsfLink) && (obj.target_type == 'DmsfFile') %> + <% unless obj.target_file.last_revision %> + <% Rails.logger.error "Error: dmsf_file id #{obj.target_id} has no revision!" %> + <% next %> + <% end %> + class="dmsf_gray <%= classes %>"> + <%= render(:partial => 'file', :locals => { + :project => obj.target_project, + :file => obj.target_file, + :link => obj, + :id => obj.id, + :name => 'file_links[]', + :title => obj.name, + :onclick => onclick, + :position => position}) %> + + <% elsif obj.is_a?(DmsfLink) && (obj.target_type == 'DmsfUrl') %> + class="dmsf_gray <%= classes %>"> + <%= render(:partial => 'url', :locals => { + :project => obj.target_project, + :file => obj.target_file, + :link => obj, + :id => obj.id, + :name => 'file_links[]', + :title => obj.name, + :onclick => onclick, + :position => position}) %> + + <% end %> + <% end %> + +
    + + <%= l(:link_title) %><%= l(:link_size) %><%= l(:link_modified) %><%= l(:link_ver) %><%= l(:link_workflow) %><%= l(:link_author) %>
    diff --git a/app/views/dmsf/_url.html.erb b/app/views/dmsf/_url.html.erb index 7816bab0..cafab4a4 100644 --- a/app/views/dmsf/_url.html.erb +++ b/app/views/dmsf/_url.html.erb @@ -22,13 +22,17 @@ + <% if @tree_view %> + > + <% end %> <%= link_to(h(title), link.external_url, :target => '_blank', :class => 'icon dmsf_icon-link') %> -
    +
    <%= link.external_url %>
    + <%= ''.html_safe if @tree_view %> <%= format_time(link.updated_at) %> @@ -39,17 +43,17 @@ - <% if @file_manipulation_allowed %> - <%= link_to(image_tag('delete.png'), + <% if @file_manipulation_allowed %> + <%= link_to(image_tag('delete.png'), dmsf_link_path(link), :data => {:confirm => l(:text_are_you_sure)}, :method => :delete, :title => l(:title_delete)) %> - <% else %> + <% else %> - <% end %> + <% end %> -1 +<%= position %> link.updated_at.to_i - \ No newline at end of file + diff --git a/app/views/dmsf/show.html.erb b/app/views/dmsf/show.html.erb index c218b2c8..190945f5 100644 --- a/app/views/dmsf/show.html.erb +++ b/app/views/dmsf/show.html.erb @@ -101,97 +101,11 @@
    <% end %>
    - - - - - - - - - - - - - - - - - - - <% @subfolders.each do |subfolder| %> - - <%= render(:partial => 'dir', - :locals => { - :project => @project, - :subfolder => subfolder, - :link => nil, - :id => subfolder.id, - :name => 'subfolders[]', - :title => subfolder.title }) %> - - <% end %> - <% @dir_links.each do |link| %> - <% unless link.target_project %> - <% Rails.logger.error "Error: dmsf_link id #{link.id} has no target!" %> - <% next %> + <% if @tree_view %> + <%= render(:partial => 'tree_view') %> + <% else %> + <%= render(:partial => 'list_view') %> <% end %> - - <%= render(:partial => 'dir', - :locals => { - :project => link.target_project, - :subfolder => link.target_folder, - :link => link, - :id => link.id, - :name => 'dir_links[]', - :title => link.name }) %> - - <% end %> - <% @files.each do |file| %> - <% unless file.last_revision %> - <% Rails.logger.error "Error: dmsf_file id #{file.id} has no revision!" %> - <% next %> - <% end %> - - <%= render(:partial => 'file', :locals => { - :project => @project, - :file => file, - :link => nil, - :id => file.id, - :name => 'files[]', - :title => file.title }) %> - - <% end %> - <% @file_links.each do |link| %> - <% unless link.target_file.last_revision %> - <% Rails.logger.error "Error: dmsf_file id #{link.target_id} has no revision!" %> - <% next %> - <% end %> - - <%= render(:partial => 'file', :locals => { - :project => link.target_project, - :file => link.target_file, - :link => link, - :id => link.id, - :name => 'file_links[]', - :title => link.name }) %> - - <% end %> - <% @url_links.each do |link| %> - - <%= render(:partial => 'url', :locals => { - :project => link.target_project, - :file => link.target_file, - :link => link, - :id => link.id, - :name => 'file_links[]', - :title => link.name }) %> - - <% end %> - -
    - - <%= l(:link_title) %><%= l(:link_size) %><%= l(:link_modified) %><%= l(:link_ver) %><%= l(:link_workflow) %><%= l(:link_author) %>
    <% end %> @@ -228,15 +142,15 @@ $('#browser').dataTable({ 'bJQueryUI': true, 'oLanguage': { - 'sUrl': "<%= plugin_asset_path(:redmine_dmsf, 'javascripts', sUrl) %>" + 'sUrl': "<%= plugin_asset_path(:redmine_dmsf, 'javascripts', sUrl) %>" }, - 'bAutoWidth': false, + 'bAutoWidth': false, 'bPaginate': false, - 'aaSorting': [[1,'asc']], - 'aaSortingFixed': [[8,'asc']], + 'aaSorting': [[1, 'asc']], + 'aaSortingFixed': [[ 8, 'asc']], 'aoColumnDefs': [ - { 'bSearchable': false, 'aTargets': [0, 7, 8, 9] }, - { 'bSortable': false, 'aTargets': [0, 7, 8] }, + { 'bSearchable': false, 'aTargets': [0, 7, 8, 9, 10, 11] }, + { 'bSortable': false, 'aTargets': [0, 7] }, { 'iDataSort': 9, 'aTargets': [ 2 ] }, { 'iDataSort': 10, 'aTargets': [ 3 ] }, { 'iDataSort': 11, 'aTargets': [ 4 ] } @@ -245,7 +159,15 @@ jQuery('div.dmsf_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 + @url_links.count %>"; + <% if @tree_view %> + <% if @folder %> + return "<%= "#{l(:label_number_of_folders)}: #{@folder.deep_folder_count} #{l(:label_number_of_documents)}: #{@folder.deep_file_count}" %>"; + <% else %> + return "<%= "#{l(:label_number_of_folders)}: #{DmsfFolder.visible.where(:project_id => @project.id).count + DmsfLink.visible.where(:project_id => @project.id, :target_type => 'DmsfFolder').count}, #{l(:label_number_of_documents)}: #{DmsfFile.visible.where(:project_id => @project.id).count + DmsfLink.visible.where(:project_id => @project.id, :target_type => 'DmsfFile').count + DmsfLink.visible.where(:project_id => @project.id, :target_type => 'DmsfUrl').count}" %>"; + <% end %> + <% else %> + return "<%= "#{l(:label_number_of_folders)}: #{@subfolders.count + @dir_links.count}, #{l(:label_number_of_documents)}: #{@files.count + @file_links.count + @url_links.count}" %>"; + <% end %> } }); diff --git a/app/views/dmsf_files/show.html.erb b/app/views/dmsf_files/show.html.erb index 27241caa..973a773b 100644 --- a/app/views/dmsf_files/show.html.erb +++ b/app/views/dmsf_files/show.html.erb @@ -27,27 +27,27 @@
    <% if User.current.allowed_to?(:file_manipulation, @project) %> <% unless @file.locked_for_user? %> - <% unless @file.locked? %> + <% unless @file.locked? %> <%= link_to(l(:button_lock), lock_dmsf_files_path(:id => @file), :title => l(:title_lock_file), :class => 'icon dmsf_icon-lock') %> <% else %> <%= link_to_if(@file.unlockable?, l(:button_unlock), unlock_dmsf_files_path(:id => @file), - :title => l(:title_unlock_file), :class => 'icon dmsf_icon-unlock') %> + :title => l(:title_unlock_file), :class => 'icon dmsf_icon-unlock') %> <% end %> - <% if @file.notification %> + <% if @file.notification %> <%= link_to(l(:label_notifications_off), notify_deactivate_dmsf_files_path(:id => @file), :title => l(:title_notifications_active_deactivate), :class => 'icon dmsf_icon-notification-on') %> - <% else %> + <% else %> <%= link_to(l(:label_notifications_on), notify_activate_dmsf_files_path(:id => @file), :title => l(:title_notifications_not_active_activate), :class => 'icon dmsf_icon-notification-off') %> <% end %> - <%= link_to(l(:label_link_to), - new_dmsf_link_path(:project_id => @project.id, :dmsf_folder_id => @file.folder ? @file.folder.id : nil, :dmsf_file_id => @file.id, :type => 'link_to'), - :title => l(:title_create_link), + <%= link_to(l(:label_link_to), + new_dmsf_link_path(:project_id => @project.id, :dmsf_folder_id => @file.dmsf_folder ? @file.dmsf_folder.id : nil, :dmsf_file_id => @file.id, :type => 'link_to'), + :title => l(:title_create_link), :class => 'icon dmsf_icon-link') %> <%= link_to("#{l(:button_copy)}/#{l(:button_move)}", copy_file_path(:id => @file), :title => l(:title_copy), :class => 'icon icon-copy') %> @@ -55,13 +55,13 @@ <% else %> <% if User.current.allowed_to?(:force_file_unlock, @project) %> <%= link_to_if(@file.unlockable?, l(:button_unlock), unlock_dmsf_files_path(:id => @file), - :title => l(:title_unlock_file), :class => 'icon dmsf_icon-unlock')%> - <% end %> - <% end %> + :title => l(:title_unlock_file), :class => 'icon dmsf_icon-unlock')%> + <% end %> + <% end %> <% end %>
    -<%= render(:partial => '/dmsf/path', +<%= render(:partial => '/dmsf/path', :locals => {:folder => @file.folder, :filename => @file.title, :title => nil}) %> <% if User.current.allowed_to?(:file_manipulation, @file.project) && !@file.locked_for_user? %> @@ -76,13 +76,13 @@

    <%= l(:heading_revisions) %>

    -<% @file.revisions.visible[@revision_pages.offset, @revision_pages.per_page].each do |revision| %> -
    -
    +<% @file.dmsf_file_revisions.visible[@revision_pages.offset, @revision_pages.per_page].each do |revision| %> +
    +
    -
    - <%= link_to_function image_tag('rev_downloads.png', :plugin => 'redmine_dmsf'), - "$('#revision_access-#{revision.id}').toggle()", +
    + <%= link_to_function image_tag('rev_downloads.png', :plugin => 'redmine_dmsf'), + "$('#revision_access-#{revision.id}').toggle()", :title => l(:title_download_entries) %> <%= link_to image_tag('rev_download.png', :plugin => 'redmine_dmsf'), dmsf_file_path(@file, :download => revision), @@ -90,8 +90,8 @@ <%= link_to image_tag('rev_delete.png', :plugin => 'redmine_dmsf'), delete_revision_path(revision), :data => {:confirm => l(:text_are_you_sure)}, - :title => l(:title_delete_revision) if @file_delete_allowed && (@file.revisions.visible.count > 1) %> -
    + :title => l(:title_delete_revision) if @file_delete_allowed && (@file.dmsf_file_revisions.visible.count > 1) %> +
    <%= 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)) %> @@ -103,29 +103,29 @@

    <%= label_tag('', l(:label_title)) %> - <%= h(revision.title) %> + <%= h(revision.title) %>

    <%= label_tag('', l(:label_file)) %> - <%= ("#{h(revision.file.folder.dmsf_path_str)}/") if revision.file.folder %><%= h(revision.name) %> + <%= ("#{h(revision.dmsf_file.dmsf_folder.dmsf_path_str)}/") if revision.dmsf_file.dmsf_folder %><%= h(revision.name) %>

    <% if revision.description.present? %>

    - <%= label_tag('', l(:label_description)) %> + <%= label_tag('', l(:label_description)) %>

    <%= textilizable(revision.description) %> -
    +

    <% end %>
    -
    +

    <%= label_tag('', l(:label_version)) %> - <%= revision.major_version %>.<%= revision.minor_version %> + <%= revision.major_version %>.<%= revision.minor_version %>

    <%= label_tag('', l(:link_workflow)) %> @@ -133,44 +133,44 @@ <% if wf %> <%= "#{wf.name} - " %> <%= link_to(revision.workflow_str(false), - log_dmsf_workflow_path(:project_id => @project.id, + log_dmsf_workflow_path(:project_id => @project.id, :id => wf.id, :dmsf_file_revision_id => revision.id), :title => DmsfWorkflow.assignments_to_users_str(wf.next_assignments(revision.id)), :remote => true) %> <% else %> <%= revision.workflow_str(true) %> - <% end %> + <% end %>

    -
    +

    <%= label_tag('', l(:label_mime)) %> <%= h(revision.mime_type) %>

    <%= label_tag('', l(:label_size)) %> - <%= number_to_human_size(revision.size) %> + <%= number_to_human_size(revision.size) %>

    -
    -
    - <%= render 'dmsf/custom_fields', :object => revision %> +
    +
    + <%= render 'dmsf/custom_fields', :object => revision %> <% if revision.comment.present? %>

    - <%= label_tag('', l(:label_comment)) %> + <%= label_tag('', l(:label_comment)) %>

    <%= textilizable(revision.comment) %> -
    +

    <% end %> -
    " style="display:none"> - <%= render(:partial => 'revision_access', :locals => {:revision => revision}) if User.current.allowed_to?(:file_manipulation, @file.project) %> -
    +
    " style="display:none"> + <%= render(:partial => 'revision_access', :locals => {:revision => revision}) if User.current.allowed_to?(:file_manipulation, @file.project) %> +
    -
    -
    +
    +
    <% end %> -<%= pagination_links_full @revision_pages, @file.revisions.visible.count %> +<%= pagination_links_full @revision_pages, @file.dmsf_file_revisions.visible.count %> <% url = 'jquery.dataTables/en.json' @@ -186,16 +186,16 @@ $('a.delete-entry').click(function(event) { if(!window.confirm('<%= l(:text_are_you_sure) %>')) { - event.preventDefault(); + event.preventDefault(); } }); $('#file_upload').change(function() { if($("input[name='version']:checked").val() == '0') { - $('#fileMinorVersionRadio').prop('checked', true); + $('#fileMinorVersionRadio').prop('checked', true); } - $('#fileSameVersionRadio').prop('disabled', true); - }); + $('#fileSameVersionRadio').prop('disabled', true); + }); $('#newRevisionFormContentToggle').click(function() { if($('#newRevisionFormContent').is(':visible')) { @@ -205,24 +205,24 @@ else { $(this).text('[-]'); $('#newRevisionFormContent').show(); - } + } }); $('.access-table').dataTable({ 'bJQueryUI': true, 'oLanguage': { - 'sUrl': '/plugin_assets/<%= :redmine_dmsf %>/javascripts/<%= url %>' + 'sUrl': '/plugin_assets/<%= :redmine_dmsf %>/javascripts/<%= url %>' } }); <% if @revision.valid? && @file.valid? %> <% end %> -<% content_for :header_tags do %> - <%= stylesheet_link_tag 'jquery.dataTables/jquery-ui.dataTables.css', :plugin => 'redmine_dmsf' %> - <%= javascript_include_tag 'jquery.dataTables/jquery.dataTables.min.js', :plugin => 'redmine_dmsf' %> +<% content_for :header_tags do %> + <%= stylesheet_link_tag 'jquery.dataTables/jquery-ui.dataTables.css', :plugin => 'redmine_dmsf' %> + <%= javascript_include_tag 'jquery.dataTables/jquery.dataTables.min.js', :plugin => 'redmine_dmsf' %> <% end %> \ No newline at end of file diff --git a/app/views/dmsf_files_copy/new.html.erb b/app/views/dmsf_files_copy/new.html.erb index dd1b3270..09c80ea0 100644 --- a/app/views/dmsf_files_copy/new.html.erb +++ b/app/views/dmsf_files_copy/new.html.erb @@ -24,17 +24,17 @@ <% html_title(l(:dmsf)) %> -<%= render(:partial => '/dmsf/path', - :locals => {:folder => @file.folder, :filename => @file.title, +<%= render(:partial => '/dmsf/path', + :locals => {:folder => @file.dmsf_folder, :filename => @file.title, :title => "#{l(:button_copy)}/#{l(:button_move)}"}) %> <% if DmsfFile.allowed_target_projects_on_copy.present? %> <%= form_tag({:action => 'create', :id => @file}, :id => 'copyForm') do |f| %>
    -

    +

    <%= label_tag('target_project_id', l(:field_target_project)) %> <%= select_tag('target_project_id', - project_tree_options_for_select(DmsfFile.allowed_target_projects_on_copy, + project_tree_options_for_select(DmsfFile.allowed_target_projects_on_copy, :selected => @target_project)) %>

    @@ -46,7 +46,7 @@

    <%= submit_tag(l(:button_copy)) %> - <% if User.current.allowed_to?(:file_manipulation, @project) %> + <% if User.current.allowed_to?(:file_manipulation, @project) %> <%= submit_tag(l(:button_move), :id => 'move_button') %> <% end %>

    @@ -60,7 +60,7 @@ }); $('#target_project_id').change(function () { - $('#content').load("<%= url_for(:action => 'new') %>", $('#copyForm').serialize()); + $('#content').load("<%= url_for(:action => 'new') %>", $('#copyForm').serialize()); }); $('#target_project_id').select2(); diff --git a/app/views/dmsf_links/_form.html.erb b/app/views/dmsf_links/_form.html.erb index d59727ad..de487e25 100644 --- a/app/views/dmsf_links/_form.html.erb +++ b/app/views/dmsf_links/_form.html.erb @@ -24,11 +24,11 @@ <% if @dmsf_file_id %> <% file = DmsfFile.find_by_id @dmsf_file_id%> - <% title = file.title if file %> + <% title = file.title if file %> <% end %> -<%= render(:partial => '/dmsf/path', - :locals => {:folder => @dmsf_link.folder, :filename => nil, +<%= render(:partial => '/dmsf/path', + :locals => {:folder => @dmsf_link.folder, :filename => nil, :title => (@type == 'link_from') ? l(:label_link_from) : l(:label_link_to) }) %> <%= labelled_form_for @dmsf_link do |f| %> @@ -43,12 +43,12 @@ <%= radio_button_tag(:external_link, 'false', @link_external == false) %> <%= l(:label_internal) %>
    <%= radio_button_tag(:external_link, 'true', @link_external) %> <%= l(:label_external) %>

    - <% end %> + <% end %> + <% end %> + -

    - <%= f.text_field :name, :required => true, :label => l(:label_link_name) %> -

    + +

    + <%= f.text_field :name, :required => true, :label => l(:label_link_name) %> +

    <%= f.submit l(:button_create) %>

    <% end %> - \ No newline at end of file + \ No newline at end of file diff --git a/app/views/dmsf_mailer/send_documents.html.erb b/app/views/dmsf_mailer/send_documents.html.erb index ef98e05a..ac8f1eeb 100644 --- a/app/views/dmsf_mailer/send_documents.html.erb +++ b/app/views/dmsf_mailer/send_documents.html.erb @@ -1,8 +1,11 @@ -<%# Redmine plugin for Document Management System "Features" +<% +# encoding: utf-8 +# +# Redmine plugin for Document Management System "Features" # # Copyright (C) 2011 Vít Jonáš # Copyright (C) 2012 Daniel Munn -# Copyright (C) 2011-15 Karel Pičman +# Copyright (C) 2011-16 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 @@ -20,24 +23,30 @@ <%= textilizable(@body) %> -<% if @links_only == '1' %> +<% if @links_only == '1' %> + <% folders = [] %> + <% files = [] %> <% if @folders.present? %> <% JSON.parse(@folders).each do |id| %> <% folder = DmsfFolder.find_by_id id %> - <% if folder %> - <% folder.folder_tree.each do |name, i| %> + <% if folder %> + <% folder.folder_tree.each do |name, i| %> <% dir = DmsfFolder.find_by_id i %> - <% if dir %> + <% if dir && !folders.include?(dir) %>
    <%= link_to(h(dir.dmsf_path_str), dmsf_folder_path(:id => dir.project_id, :folder_id => dir.id, :only_path => false)) %>

    - <% dir.files.each do |file| %> - <%= link_to(h(file.title), dmsf_file_url(file)) %> -  (<%= link_to(h(file.name), dmsf_file_url(file, :download => '')) %>) -
    + <% dir.dmsf_files.each do |file| %> + <% unless files.include?(file) %> + <%= link_to(h(file.title), dmsf_file_url(file)) %> +  (<%= link_to(h(file.name), dmsf_file_url(file, :download => '')) %>) +
    + <% files << file %> + <% end %> <% end %> + <% folders << dir %> <% end %> - <% end %> + <% end %> <% end %> <% end %> <% end %> @@ -45,11 +54,13 @@
    <% JSON.parse(@files).each do |id| %> <% file = DmsfFile.find_by_id id %> - <% if file %> + <% if file && !files.include?(file) %> <%= link_to(h(file.title), dmsf_file_url(file)) %>  (<%= link_to(h(file.name), dmsf_file_url(file, :download => '')) %>)
    + <% files << file %> <% end %> <% end %> <% end %> -<% end %> \ No newline at end of file +<% end %> + \ No newline at end of file diff --git a/app/views/dmsf_mailer/send_documents.text.erb b/app/views/dmsf_mailer/send_documents.text.erb index 62f4baec..b4f4f23d 100644 --- a/app/views/dmsf_mailer/send_documents.text.erb +++ b/app/views/dmsf_mailer/send_documents.text.erb @@ -1,8 +1,11 @@ -<%# Redmine plugin for Document Management System "Features" +<% +# encoding: utf-8 +# +# Redmine plugin for Document Management System "Features" # # Copyright (C) 2011 Vít Jonáš # Copyright (C) 2012 Daniel Munn -# Copyright (C) 2011-15 Karel Pičman +# Copyright (C) 2011-16 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 @@ -20,29 +23,35 @@ <%= @body %> -<% if @links_only == '1' %> +<% if @links_only == '1' %> + <% folders = [] %> + <% files = [] %> <% if @folders.present? %> <% JSON.parse(@folders).each do |id| %> <% folder = DmsfFolder.find_by_id id %> - <% if folder %> - <% folder.folder_tree.each do |name, i| %> + <% if folder && !folders.include?(folder) %> + <% folder.folder_tree.each do |name, i| %> <% dir = DmsfFolder.find_by_id i %> <% if dir %> <%= dir.dmsf_path_str %> - <% dir.files.each do |file| %> - <%= dmsf_file_url(file, :download => '') %> + <% dir.dmsf_files.each do |file| %> + <% unless files.include?(file) %> + <%= dmsf_file_url(file, :download => '') %> + <% files << file %> + <% end %> <% end %> <% end %> - <% end %> + <% end %> <% end %> <% end %> <% end %> <% if @files.present? %> <% JSON.parse(@files).each do |id| %> <% file = DmsfFile.find_by_id id %> - <% if file %> + <% if file && !files.include?(file) %> <%= dmsf_file_url(file, :download => '') %> + <% files << file %> <% end %> <% end %> <% end %> -<% end %> \ No newline at end of file +<% end %> diff --git a/app/views/dmsf_mailer/workflow_notification.html.erb b/app/views/dmsf_mailer/workflow_notification.html.erb index 911c9ee1..47078947 100644 --- a/app/views/dmsf_mailer/workflow_notification.html.erb +++ b/app/views/dmsf_mailer/workflow_notification.html.erb @@ -17,16 +17,16 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.%>

    <%= @user.name %>,

    -

    +

    <%= @text1 %>

    - <%= @text2 %> - <% unless @revision.file.folder %> - <%= link_to l(:link_documents), - dmsf_folder_url(:id => @revision.file.project) %> + <%= @text2 %> + <% unless @revision.dmsf_file.dmsf_folder %> + <%= link_to l(:link_documents), + dmsf_folder_url(:id => @revision.dmsf_file.project) %> <% else %> - <%= link_to @revision.file.folder.title, - dmsf_folder_url(:id => @revision.file.project, :folder_id => @revision.file.folder) %> + <%= link_to @revision.dmsf_file.dmsf_folder.title, + dmsf_folder_url(:id => @revision.dmsf_file.project, :folder_id => @revision.dmsf_file.dmsf_folder) %> <% end %>.

    \ No newline at end of file diff --git a/app/views/dmsf_mailer/workflow_notification.text.erb b/app/views/dmsf_mailer/workflow_notification.text.erb index f2e104bd..9a36d147 100644 --- a/app/views/dmsf_mailer/workflow_notification.text.erb +++ b/app/views/dmsf_mailer/workflow_notification.text.erb @@ -18,8 +18,8 @@ <%= @user.name %>, <%= @text1 %> -<% unless @revision.file.folder %> - <%= @text2 %> <%= dmsf_folder_url(:id => @revision.file.project) %>. +<% unless @revision.dmsf_file.dmsf_folder %> + <%= @text2 %> <%= dmsf_folder_url(:id => @revision.dmsf_file.project) %>. <% else %> - <%= @text2 %> <%= dmsf_folder_url(:id => @revision.file.project, :folder_id => @revision.file.folder) %>. + <%= @text2 %> <%= dmsf_folder_url(:id => @revision.dmsf_file.project, :folder_id => @revision.dmsf_file.dmsf_folder) %>. <% end %> \ No newline at end of file diff --git a/app/views/hooks/redmine_dmsf/_view_my_account.html.erb b/app/views/hooks/redmine_dmsf/_view_my_account.html.erb index d32164f6..c179abbb 100644 --- a/app/views/hooks/redmine_dmsf/_view_my_account.html.erb +++ b/app/views/hooks/redmine_dmsf/_view_my_account.html.erb @@ -1,3 +1,25 @@ +<%# +# encoding: utf-8 +# +# Redmine plugin for Document Management System "Features" +# +# Copyright (C) 2011-16 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. +%> + <%= labelled_fields_for :pref, @user.pref do |pref_fields| %>

    <%= pref_fields.check_box :dmsf_tree_view %>

    <% end %> \ No newline at end of file diff --git a/app/views/hooks/redmine_dmsf/_view_projects_form.html.erb b/app/views/hooks/redmine_dmsf/_view_projects_form.html.erb index f5112d14..a9b28fc9 100644 --- a/app/views/hooks/redmine_dmsf/_view_projects_form.html.erb +++ b/app/views/hooks/redmine_dmsf/_view_projects_form.html.erb @@ -1,8 +1,11 @@ -<%# Redmine plugin for Document Management System "Features" +<%# +# encoding: utf-8 +# +# Redmine plugin for Document Management System "Features" # # Copyright (C) 2011 Vít Jonáš # Copyright (C) 2012 Daniel Munn -# Copyright (C) 2011-14 Karel Pičman +# Copyright (C) 2011-16 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 @@ -16,11 +19,12 @@ # # 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 @project.new_record? && @source_project %>

    - +

    <% end %> diff --git a/app/views/my/blocks/_locked_documents.html.erb b/app/views/my/blocks/_locked_documents.html.erb index 3615e774..eb6086f9 100644 --- a/app/views/my/blocks/_locked_documents.html.erb +++ b/app/views/my/blocks/_locked_documents.html.erb @@ -29,9 +29,9 @@ <%= form_tag({}) do %> - + - + @@ -40,19 +40,19 @@ + + :class => 'icon icon-folder') %> + <% end %> @@ -60,19 +60,19 @@ - + + <% end %> diff --git a/app/views/my/blocks/_open_approvals.html.erb b/app/views/my/blocks/_open_approvals.html.erb index 5727c0f9..a61c499a 100644 --- a/app/views/my/blocks/_open_approvals.html.erb +++ b/app/views/my/blocks/_open_approvals.html.erb @@ -24,11 +24,11 @@ 'LEFT JOIN dmsf_workflow_step_actions ON dmsf_workflow_step_assignments.id = dmsf_workflow_step_actions.dmsf_workflow_step_assignment_id').where( 'dmsf_workflow_step_assignments.user_id = ? AND dmsf_workflow_step_actions.id IS NULL', @user.id).all %> <% assignments = Array.new %> -<% all_assignments.each do |assignment| %> - <% if assignment.dmsf_file_revision.file.last_revision && - !assignment.dmsf_file_revision.file.last_revision.deleted? && - (assignment.dmsf_file_revision.workflow == DmsfWorkflow::STATE_WAITING_FOR_APPROVAL) && - (assignment.dmsf_file_revision == assignment.dmsf_file_revision.file.last_revision) %> +<% all_assignments.each do |assignment| %> + <% if assignment.dmsf_file_revision.dmsf_file.last_revision && + !assignment.dmsf_file_revision.dmsf_file.last_revision.deleted? && + (assignment.dmsf_file_revision.workflow == DmsfWorkflow::STATE_WAITING_FOR_APPROVAL) && + (assignment.dmsf_file_revision == assignment.dmsf_file_revision.dmsf_file.last_revision) %> <% assignments << assignment %> <% end %> <% end %> @@ -37,7 +37,7 @@ <%= form_tag({}) do %>
    <%=l(:field_project)%><%=l(:label_document)%>/<%=l(:field_folder)%><%=l(:label_document)%>/<%=l(:field_folder)%> <%=l(:field_folder)%>
    <%= link_to_project(folder.project) %> - <%= link_to(h(folder.title), {:controller => 'dmsf', :action => 'show', :id => folder.project, :folder_id => folder}, - :class => 'icon icon-folder') %> - - <% if folder.folder %> - <%= link_to(h(folder.folder.title), - {:controller => 'dmsf', :action => 'show', :id => folder.project, :folder_id => folder.folder}) %> + <% if folder.dmsf_folder %> + <%= link_to(h(folder.dmsf_folder.title), + {:controller => 'dmsf', :action => 'show', :id => folder.project, :folder_id => folder.dmsf_folder}) %> <% else %> - <%= link_to(l(:link_documents), {:controller => 'dmsf', :action => 'show', :id=> folder.project }) %> - <% end %> + <%= link_to(l(:link_documents), {:controller => 'dmsf', :action => 'show', :id=> folder.project }) %> + <% end %>
    <%= link_to_project(file.project) %> - - <%= link_to(h(file.title), + + <%= link_to(h(file.title), {:controller => 'dmsf_files', :action => :show, :id => file }, :class => "icon icon-file #{DmsfHelper.filetype_css(file.name)}") %> - - <% if file.folder %> - <%= link_to(h(file.folder.title), + <% if file.dmsf_folder %> + <%= link_to(h(file.dmsf_folder.title), {:controller => 'dmsf', :action => 'show', :id => file.project, :folder_id => file.folder}) %> <% else %> - <%= link_to(l(:link_documents), {:controller => 'dmsf', :action => 'show', :id=> file.project }) %> - <% end %> + <%= link_to(l(:link_documents), {:controller => 'dmsf', :action => 'show', :id=> file.project }) %> + <% end %>
    - + @@ -48,29 +48,29 @@ <% assignments.each do |assignment| %> diff --git a/assets/images/bullet_arrow_down.png b/assets/images/bullet_arrow_down.png new file mode 100644 index 0000000000000000000000000000000000000000..cb819a7d15b8ebb6f2c96b67d92315e2bb723fed GIT binary patch literal 220 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf6n3BBRT^Rni_n+Ah4nJ za0`PlBg3pY5H=O_6ID&+^S}73tV}CLb;wUjv*T7lmDD=kT)>oVPw@YICk*= zeu+AxKM6n1edv97`@54K59|H?hQ%KZHW(Hd{7L#zA>7RwKV|>@#K+H4Dh_`5@cX+$ z3zM6RgYU#2W%Fl0zWDvU)MUBD&;thobU*(6#Q!+6e}e2u3y~%%2I(!TEH8u9LGEYp MboFyt=akR{0DDwV761SM literal 0 HcmV?d00001 diff --git a/assets/javascripts/redmine_dmsf.js b/assets/javascripts/redmine_dmsf.js new file mode 100644 index 00000000..21cf3308 --- /dev/null +++ b/assets/javascripts/redmine_dmsf.js @@ -0,0 +1,117 @@ +/* encoding: utf-8 +* +* Redmine plugin for Document Management System "Features" +* +* Copyright (C) 2011-16 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. +*/ + +/* Function to allow the projects to show up as a tree */ +function dmsf_toggle(EL, PM) +{ + var els = document.getElementsByTagName('tr'); + var elsLen = els.length; + var pattern = new RegExp("(^|\\s)" + EL + "(\\s|$)"); + var cpattern = new RegExp('span'); + var expand = new RegExp('dmsf_expanded'); + var collapse = new RegExp('dmsf_collapsed'); + var hide = new RegExp('dmsf_hidden'); + var spanid = PM; + var classid = new RegExp('junk'); + var oddeventoggle = 0; + + for(i = 0; i < elsLen; i++) + { + if(cpattern.test(els[i].id)) + { + var tmpspanid = spanid; + var tmpclassid = classid; + + spanid = els[i].id; + classid = spanid; + classid = classid.match(/(\w+)span/)[1]; + classid = new RegExp(classid); + + if(tmpclassid.test(els[i].className) && (tmpspanid.toString() !== PM.toString())) + { + if(collapse.test(document.getElementById(tmpspanid).className)) + { + spanid = tmpspanid; + classid = tmpclassid; + } + } + } + + if(pattern.test(els[i].className)) + { + var cnames = els[i].className; + + cnames = cnames.replace(/dmsf_hidden/g,''); + + if(expand.test(document.getElementById(PM).className)) + { + cnames += ' dmsf_hidden'; + } + else + { + if((spanid.toString() !== PM.toString()) && (classid.test(els[i].className))) + { + if(collapse.test(document.getElementById(spanid).className)) + { + cnames += ' dmsf_hidden'; + } + } + } + + els[i].className = cnames; + } + + if(!(hide.test(els[i].className))) + { + var cnames = els[i].className; + + cnames = cnames.replace(/odd/g,''); + cnames = cnames.replace(/even/g,''); + + if(oddeventoggle === 0) + { + cnames += ' odd'; + } + else + { + cnames += ' even'; + } + + oddeventoggle ^= 1; + els[i].className = cnames; + } + } + + if (collapse.test(document.getElementById(PM).className)) + { + var cnames = document.getElementById(PM).className; + + cnames = cnames.replace(/dmsf_collapsed/,'dmsf_expanded'); + document.getElementById(PM).className = cnames; + } + else + { + var cnames = document.getElementById(PM).className; + + cnames = cnames.replace(/dmsf_expanded/,'dmsf_collapsed'); + document.getElementById(PM).className = cnames; + } +} diff --git a/assets/stylesheets/img/loading.gif b/assets/stylesheets/images/loading.gif similarity index 100% rename from assets/stylesheets/img/loading.gif rename to assets/stylesheets/images/loading.gif diff --git a/assets/stylesheets/img/plupload.png b/assets/stylesheets/images/plupload.png similarity index 100% rename from assets/stylesheets/img/plupload.png rename to assets/stylesheets/images/plupload.png diff --git a/assets/stylesheets/plupload/jquery.ui.plupload.css b/assets/stylesheets/plupload/jquery.ui.plupload.css index e46a3f17..3b9cd1bd 100644 --- a/assets/stylesheets/plupload/jquery.ui.plupload.css +++ b/assets/stylesheets/plupload/jquery.ui.plupload.css @@ -43,7 +43,7 @@ .plupload_logo { width: 40px; height: 40px; - background: url('../img/plupload.png') no-repeat 0 0; + background: url('../images/plupload.png') no-repeat 0 0; position: absolute; top: 8px; left: 8px; @@ -119,7 +119,7 @@ } .plupload_thumb_loading { - background: #eee url(../img/loading.gif) center no-repeat; + background: #eee url(../images/loading.gif) center no-repeat; } .plupload_thumb_loading .plupload_file_dummy, diff --git a/assets/stylesheets/dmsf.css b/assets/stylesheets/redmine_dmsf.css similarity index 88% rename from assets/stylesheets/dmsf.css rename to assets/stylesheets/redmine_dmsf.css index de445d49..ede409dd 100644 --- a/assets/stylesheets/dmsf.css +++ b/assets/stylesheets/redmine_dmsf.css @@ -20,7 +20,6 @@ */ /* DMSF table.list modifications */ - table.dmsf_list th.dmsf_th { border: none; } @@ -222,4 +221,26 @@ tr.dmsf_gray .icon-file.application-x-gzip { background-image: url(../images/fil /* Search results */ dt.dmsf-file { background-image: url(../../../images/document.png); } -dt.dmsf-folder { background-image: url(../../../images/folder.png); } \ No newline at end of file +dt.dmsf-folder { background-image: url(../../../images/folder.png); } + +/* DMSF tree view */ +tr.dmsf_hidden { display:none; } +tr.dmsf_tree span.dmsf_expander { cursor: pointer; } +tr.dmsf_tree.dmsf_expanded td.dmsf_title span { + background: url(../images/bullet_arrow_down.png) no-repeat 0 50%; + padding-left: 16px; +} +tr.dmsf_tree.dmsf_child td.dmsf_title span { padding-left: 16px; } +tr.dmsf_tree.dmsf_collapsed td.dmsf_title span { + background: url(../../../images/bullet_arrow_right.png) no-repeat 0 50%; + padding-left: 16px; +} +tr.dmsf_tree.idnt-1 td.dmsf_title {padding-left: 1.5em;} +tr.dmsf_tree.idnt-2 td.dmsf_title {padding-left: 2em;} +tr.dmsf_tree.idnt-3 td.dmsf_title {padding-left: 2.5em;} +tr.dmsf_tree.idnt-4 td.dmsf_title {padding-left: 3em;} +tr.dmsf_tree.idnt-5 td.dmsf_title {padding-left: 3.5em;} +tr.dmsf_tree.idnt-6 td.dmsf_title {padding-left: 4em;} +tr.dmsf_tree.idnt-7 td.dmsf_title {padding-left: 4.5em;} +tr.dmsf_tree.idnt-8 td.dmsf_title {padding-left: 5em;} +tr.dmsf_tree.idnt-9 td.dmsf_title {padding-left: 5.5em;} diff --git a/config/locales/cs.yml b/config/locales/cs.yml index 5576b09b..3fc3c650 100644 --- a/config/locales/cs.yml +++ b/config/locales/cs.yml @@ -26,7 +26,7 @@ cs: 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 + error_email_to_must_be_entered: Musí být zadán příjemce warning_file_already_locked: Soubor je již zamčen notice_file_locked: Soubor byl zamčen warning_file_not_locked: Soubor není zamčen @@ -34,14 +34,14 @@ cs: error_only_user_that_locked_file_can_unlock_it: Soubor může být odemčen pouze uživatelem, který ho zamkl error_max_files_exceeded: "Limit pro %{number} najednou stažených souborů je překročen" error_entry_project_does_not_match_current_project: Zadaný projekt neodpovídá aktuálnímu projektu - notice_folder_created: Adresář byl vytvořen + notice_folder_created: Složka byla vytvořena error_folder_creation_failed: Vytváření složky selhalo error_folder_title_must_be_entered: Musí být zadán název - notice_folder_deleted: Adresář byl smazán - error_folder_is_not_empty: Adresář není prázdný + notice_folder_deleted: Složka byla smazána + error_folder_is_not_empty: Složka není prázdná error_folder_title_is_already_used: Název již existuje notice_folder_details_were_saved: Detaily složky byly uloženy - error_folder_is_locked: Adresář je zamčen + error_folder_is_locked: Složka je zamčena error_file_is_locked: Soubor je zamčen notice_file_deleted: Soubor byl smazán error_at_least_one_revision_must_be_present: Musí existovat alespoň jedna revize @@ -181,18 +181,18 @@ cs: heading_access_last: Poslední 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 + title_total_size_of_all_files: Celková velikost všech souborů ve složce project_module_dmsf: DMSF warning_no_project_to_copy_file_to: Neexistuje projekt, do kterého můžete kopírovat comment_copied_from: "Zkopírováno z %{source}" notice_file_copied: Soubor zkopírován notice_file_moved: Soubor přesunut field_target_project: Cílový projekt - field_target_folder: Cílový adresář + field_target_folder: Cílová složka title_copy_or_move: Kopírovat/Přesunout 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_target_folder_same: Cílový složka a projekt jsou stejné jako aktuální error_file_cannot_be_moved: Soubor nemůže být přesunut error_file_cannot_be_copied: Soubor nemůže být zkopírován warning_no_project_to_copy_folder_to: Neexistuje projekt, do kterého můžete kopírovat @@ -205,7 +205,7 @@ cs: label_maximum_email_filesize: Maximální velikost souboru emailu header_minimum_filesize: Chyba souboru. error_minimum_filesize: "Soubor %{file} má nulovou velikost a nebude přiložen." - parent_directory: Nadřazený adresář + parent_directory: Nadřazená složka note_webdav: "Webdav je po aktivaci k dispozici na %{protocol}://%{domain}/dmsf/webdav/[identifikátor projektu]" label_webdav: Webdav functionalita label_dmsf_plural: "Kopíruj dokumenty a složky (%{files} souborů v %{folders} složkách)" @@ -292,8 +292,8 @@ cs: label_link_name: Název odkazu label_link_external_url: URL - label_target_folder: Cílový adresář - label_source_folder: Zdrojový adresář + label_target_folder: Cílová složka + label_source_folder: Zdrojová složka label_target_project: Cílový projekt label_source_project: Zdrojový projekt @@ -311,10 +311,10 @@ cs: link_trash_bin: Koš title_restore: Obnovit notice_dmsf_file_restored: Document byl úspěšně obnoven - notice_dmsf_folder_restored: Adresář byl úspěšně obnoven + notice_dmsf_folder_restored: Složka byla úspěšně obnovena notice_dmsf_link_restored: Odkaz byl úspěšně obnoven title_restore_checked: Obnov vybrané - error_parent_folder: Nadřazený adresář neexistuje + error_parent_folder: Nadřazená složka neexistuje error_resource_or_parent_locked: Nelze zamknout - zdrojový nebo nadřazený objekt je zamčený error_parent_locked: Nelze zamknout - nadřazený objekt je zamčený @@ -322,7 +322,7 @@ cs: error_lock_exclusively: Nelze zamknout již zamčený objekt error_unlock_parent_locked: Nelze odemknout - nadřazený objekt je zamčený - field_dmsf_tree_view: Navigate folders in a tree + field_dmsf_tree_view: Zobrazit složky jako stromovou strukturu my: blocks: diff --git a/config/routes.rb b/config/routes.rb index 6f940c06..d1d962a7 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -42,8 +42,8 @@ RedmineApp::Application.routes.draw do get '/projects/:id/dmsf/unlock', :controller => 'dmsf', :action => 'unlock', :as => 'unlock_dmsf' get '/projects/:id/dmsf/', :controller => 'dmsf', :action => 'show', :as => 'dmsf_folder' get '/projects/:id/dmsf/new', :controller => 'dmsf', :action => 'new', :as => 'new_dmsf' - get '/projects/:id/dmsf/edit', :controller=> 'dmsf', :action => 'edit', :as => 'edit_dmsf' - get '/projects/:id/dmsf/edit/root', :controller=> 'dmsf', :action => 'edit_root', :as => 'edit_root_dmsf' + get '/projects/:id/dmsf/edit', :controller => 'dmsf', :action => 'edit', :as => 'edit_dmsf' + get '/projects/:id/dmsf/edit/root', :controller => 'dmsf', :action => 'edit_root', :as => 'edit_root_dmsf' get '/projects/:id/dmsf/trash', :controller => 'dmsf', :action => 'trash', :as => 'trash_dmsf' get '/projects/:id/dmsf/restore', :controller => 'dmsf', :action => 'restore', :as => 'restore_dmsf' @@ -63,7 +63,7 @@ RedmineApp::Application.routes.draw do post '/projects/:id/dmsf/upload', :controller => 'dmsf_upload', :action => 'upload' post '/projects/:id/dmsf/upload/commit', :controller => 'dmsf_upload', :action => 'commit_files' post '/projects/:id/dmsf/commit', :controller => 'dmsf_upload', :action => 'commit' - + # # dmsf_files controller # /dmsf/files/ @@ -78,7 +78,7 @@ RedmineApp::Application.routes.draw do get '/dmsf/files/:id/download', :controller => 'dmsf_files', :action => 'show', :download => '' # Otherwise will not route nil download param get '/dmsf/files/:id/download/:download', :controller => 'dmsf_files', :action => 'show', :as => 'download_revision' get '/dmsf/files/:id/view', :controller => 'dmsf_files', :action => 'view' - get '/dmsf/files/:id', :controller => 'dmsf_files', :action => 'show', :as => 'dmsf_file' + get '/dmsf/files/:id', :controller => 'dmsf_files', :action => 'show', :as => 'dmsf_file' delete '/dmsf/files/:id', :controller => 'dmsf_files', :action => 'delete' get '/dmsf/files/:id/restore', :controller => 'dmsf_files', :action => 'restore', :as => 'restore_dmsf_file' @@ -118,8 +118,8 @@ RedmineApp::Application.routes.draw do :controller_class => RedmineDmsf::Webdav::Controller, :log_to => Rails.logger ), :at => '/dmsf/webdav' - - # Approval workflow + + # Approval workflow resources :dmsf_workflows do member do get 'autocomplete_for_user' @@ -132,16 +132,16 @@ RedmineApp::Application.routes.draw do 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 - + match 'dmsf_workflows/:id/edit', :controller => 'dmsf_workflows', :action => 'reorder_steps', :id => /\d+/, :via => :put + # Links resources :dmsf_links do member do get 'restore' - end - end - + end + end + end \ No newline at end of file diff --git a/db/migrate/06_dmsf_1_2_0.rb b/db/migrate/06_dmsf_1_2_0.rb index 522706ca..f2099274 100644 --- a/db/migrate/06_dmsf_1_2_0.rb +++ b/db/migrate/06_dmsf_1_2_0.rb @@ -17,7 +17,7 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. class Dmsf120 < ActiveRecord::Migration - + class DmsfFileRevision < ActiveRecord::Base belongs_to :file, :class_name => 'DmsfFile', :foreign_key => 'dmsf_file_id' belongs_to :source_revision, :class_name => 'DmsfFileRevision', :foreign_key => 'source_dmsf_file_revision_id' @@ -26,22 +26,22 @@ class Dmsf120 < ActiveRecord::Migration belongs_to :deleted_by_user, :class_name => 'User', :foreign_key => 'deleted_by_user_id' belongs_to :project end - + def self.up add_column :dmsf_file_revisions, :project_id, :integer, :null => true - + DmsfFileRevision.find_each do |revision| - if revision.file - revision.project_id = revision.file.project.id + if revision.dmsf_file + revision.project_id = revision.dmsf_file.project.id revision.save end end - + change_column :dmsf_file_revisions, :project_id, :integer, :null => false end def self.down - remove_column :dmsf_file_revisions, :project_id + remove_column :dmsf_file_revisions, :project_id end end diff --git a/db/migrate/20141013102501_remove_project_from_revision.rb b/db/migrate/20141013102501_remove_project_from_revision.rb index 109513ef..2277a3e0 100644 --- a/db/migrate/20141013102501_remove_project_from_revision.rb +++ b/db/migrate/20141013102501_remove_project_from_revision.rb @@ -19,19 +19,19 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. class RemoveProjectFromRevision < ActiveRecord::Migration - def up - remove_column :dmsf_file_revisions, :project_id + def up + remove_column :dmsf_file_revisions, :project_id end - + def down add_column :dmsf_file_revisions, :project_id, :integer, :null => true - + DmsfFileRevision.find_each do |revision| - if revision.file - revision.project_id = revision.file.project_id + if revision.dmsf_file + revision.project_id = revision.dmsf_file.project_id revision.save end - end + end end - + end \ No newline at end of file diff --git a/lib/dmsf_zip.rb b/lib/dmsf_zip.rb index 088148f1..5b66858a 100644 --- a/lib/dmsf_zip.rb +++ b/lib/dmsf_zip.rb @@ -30,6 +30,7 @@ class DmsfZip @zip.chmod(0644) @zip_file = Zip::OutputStream.new(@zip.path) @files = [] + @folders = [] end def finish @@ -43,24 +44,29 @@ class DmsfZip end def add_file(file, member, root_path = nil) - string_path = file.folder.nil? ? '' : "#{file.folder.dmsf_path_str}/" - string_path = string_path[(root_path.length + 1) .. string_path.length] if root_path - string_path += file.formatted_name(member ? member.title_format : nil) - @zip_file.put_next_entry(string_path) - File.open(file.last_revision.disk_file, 'rb') do |f| - while (buffer = f.read(8192)) - @zip_file.write(buffer) + unless @files.include?(file) + string_path = file.dmsf_folder.nil? ? '' : "#{file.dmsf_folder.dmsf_path_str}/" + string_path = string_path[(root_path.length + 1) .. string_path.length] if root_path + string_path += file.formatted_name(member ? member.title_format : nil) + @zip_file.put_next_entry(string_path) + File.open(file.last_revision.disk_file, 'rb') do |f| + while (buffer = f.read(8192)) + @zip_file.write(buffer) + end end + @files << file end - @files << file end def add_folder(folder, member, root_path = nil) - string_path = "#{folder.dmsf_path_str}/" - string_path = string_path[(root_path.length + 1) .. string_path.length] if root_path - @zip_file.put_next_entry(string_path) - folder.subfolders.visible.each { |subfolder| self.add_folder(subfolder, root_path) } - folder.files.visible.each { |file| self.add_file(file, member, root_path) } + unless @folders.include?(folder) + string_path = "#{folder.dmsf_path_str}/" + string_path = string_path[(root_path.length + 1) .. string_path.length] if root_path + @zip_file.put_next_entry(string_path) + @folders << folder + folder.dmsf_folders.visible.each { |subfolder| self.add_folder(subfolder, root_path) } + folder.dmsf_files.visible.each { |file| self.add_file(file, member, root_path) } + end end end \ No newline at end of file diff --git a/lib/redmine_dmsf/hooks/views/base_view_hooks.rb b/lib/redmine_dmsf/hooks/views/base_view_hooks.rb index 384375d3..0eff651c 100644 --- a/lib/redmine_dmsf/hooks/views/base_view_hooks.rb +++ b/lib/redmine_dmsf/hooks/views/base_view_hooks.rb @@ -20,16 +20,17 @@ module RedmineDmsf module Hooks - include Redmine::Hook - - class DmsfViewListener < Redmine::Hook::ViewListener - - def view_layouts_base_html_head(context={}) - "\n".html_safe + stylesheet_link_tag('dmsf', :plugin => :redmine_dmsf) + + include Redmine::Hook + + class DmsfViewListener < Redmine::Hook::ViewListener + + def view_layouts_base_html_head(context={}) + "\n".html_safe + stylesheet_link_tag('redmine_dmsf.css', :plugin => :redmine_dmsf) + "\n".html_safe + stylesheet_link_tag('select2.min.css', :plugin => :redmine_dmsf) + - "\n".html_safe + javascript_include_tag('select2.min.js', :plugin => :redmine_dmsf) - end - + "\n".html_safe + javascript_include_tag('select2.min.js', :plugin => :redmine_dmsf) + + "\n".html_safe + javascript_include_tag('redmine_dmsf.js', :plugin => :redmine_dmsf) + end + end end end \ No newline at end of file diff --git a/lib/redmine_dmsf/lockable.rb b/lib/redmine_dmsf/lockable.rb index a2433b3f..abedcdd0 100644 --- a/lib/redmine_dmsf/lockable.rb +++ b/lib/redmine_dmsf/lockable.rb @@ -4,7 +4,7 @@ # # Copyright (C) 2011 Vít Jonáš # Copyright (C) 2012 Daniel Munn -# Copyright (C) 2011-15 Karel Pičman +# Copyright (C) 2011-16 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 @@ -38,17 +38,17 @@ module RedmineDmsf end end if tree - ret = ret | (folder.locks.empty? ? folder.lock : folder.locks) unless folder.nil? + ret = ret | (self.dmsf_folder.locks.empty? ? self.dmsf_folder.lock : self.dmsf_folder.locks) if dmsf_folder end return ret end - def lock! scope = :scope_exclusive, type = :type_write, expire = nil + def lock!(scope = :scope_exclusive, type = :type_write, expire = nil) # Raise a lock error if entity is locked, but its not at resource level existing = locks(false) raise DmsfLockError.new(l(:error_resource_or_parent_locked)) if self.locked? && existing.empty? unless existing.empty? - if existing[0].lock_scope == :scope_shared && scope == :scope_shared + if (existing[0].lock_scope == :scope_shared) && (scope == :scope_shared) # RFC states if an item is exclusively locked and another lock is attempted we reject # if the item is shared locked however, we can always add another lock to it if self.folder.locked? @@ -79,20 +79,20 @@ module RedmineDmsf return false unless self.locked? existing = self.lock(true) # If its empty its a folder that's locked (not root) - (existing.empty? || (!self.folder.nil? && self.folder.locked?)) ? false : true + (existing.empty? || (!self.dmsf_folder.nil? && self.dmsf_folder.locked?)) ? false : true end # # By using the path upwards, surely this would be quicker? def locked_for_user? return false unless locked? - b_shared = nil - self.dmsf_path.each do |entity| + b_shared = nil + self.dmsf_path.each do |entity| locks = entity.locks || entity.lock(false) next if locks.empty? locks.each do |lock| next if lock.expired? # In case we're in between updates - if (lock.lock_scope == :scope_exclusive && b_shared.nil?) + if (lock.lock_scope == :scope_exclusive && b_shared.nil?) return true if (!lock.user) || (lock.user.id != User.current.id) else b_shared = true if b_shared.nil? @@ -103,11 +103,11 @@ module RedmineDmsf end false end - + def unlock!(force_file_unlock_allowed = false) raise DmsfLockError.new(l(:warning_file_not_locked)) unless self.locked? existing = self.lock(true) - if existing.empty? || (!self.folder.nil? && self.folder.locked?) #If its empty its a folder thats locked (not root) + if existing.empty? || (!self.dmsf_folder.nil? && self.dmsf_folder.locked?) #If its empty its a folder thats locked (not root) raise DmsfLockError.new(l(:error_unlock_parent_locked)) else # If entity is locked to you, you aren't the lock originator (or named in a shared lock) so deny action diff --git a/lib/redmine_dmsf/macros.rb b/lib/redmine_dmsf/macros.rb index 9826a29f..51b333c4 100644 --- a/lib/redmine_dmsf/macros.rb +++ b/lib/redmine_dmsf/macros.rb @@ -19,30 +19,30 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Redmine::WikiFormatting::Macros.register do - + # dmsf - link to a document desc "Wiki link to DMSF file:\n\n" + "{{dmsf(file_id [, title [, revision_id]])}}\n\n" + - "_file_id_ / _revision_id_ can be found in the link for file/revision download." + "_file_id_ / _revision_id_ can be found in the link for file/revision download." macro :dmsf do |obj, args| raise ArgumentError if args.length < 1 # Requires file id - file = DmsfFile.visible.find args[0].strip + file = DmsfFile.visible.find args[0].strip if args[2].blank? revision = file.last_revision else revision = DmsfFileRevision.find(args[2]) - if revision.file != file + if revision.dmsf_file != file raise ActiveRecord::RecordNotFound end - end - if User.current && User.current.allowed_to?(:view_dmsf_files, file.project) + end + if User.current && User.current.allowed_to?(:view_dmsf_files, file.project) file_view_url = url_for(:controller => :dmsf_files, :action => 'view', :id => file, :download => args[2]) return link_to(h(args[1] ? args[1] : file.title), file_view_url, :target => '_blank', :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_view_url}") - else + else raise l(:notice_not_authorized) end end @@ -50,48 +50,48 @@ Redmine::WikiFormatting::Macros.register do # dmsff - link to a folder desc "Wiki link to DMSF folder:\n\n" + "{{dmsff(folder_id [, title])}}\n\n" + - "_folder_id_ may be missing. _folder_id_ can be found in the 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), dmsf_folder_url(@project) - else + else folder = DmsfFolder.visible.find args[0].strip - if User.current && User.current.allowed_to?(:view_dmsf_folders, folder.project) + if User.current && User.current.allowed_to?(:view_dmsf_folders, folder.project) return link_to h(args[1] ? args[1] : folder.title), dmsf_folder_url(folder.project, :folder_id => folder) - else + else raise l(:notice_not_authorized) end - end - end + end + end # dmsfd - link to a document's description desc "Wiki link to DMSF document description:\n\n" + "{{dmsfd(file_id)}}\n\n" + - "_file_id_ can be found in the document's details." + "_file_id_ can be found in the document's details." macro :dmsfd do |obj, args| raise ArgumentError if args.length < 1 # Requires file id file = DmsfFile.visible.find args[0].strip - if User.current && User.current.allowed_to?(:view_dmsf_files, file.project) + if User.current && User.current.allowed_to?(:view_dmsf_files, file.project) return textilizable(file.description) - else + else raise l(:notice_not_authorized) - end - end + end + end # dmsft - link to a document's content preview desc "Wiki link to DMSF document's content preview:\n\n" + "{{dmsft(file_id)}}\n\n" + - "_file_id_ can be found in the document's details." + "_file_id_ can be found in the document's details." macro :dmsft do |obj, args| raise ArgumentError if args.length < 2 # Requires file id and lines number file = DmsfFile.visible.find args[0].strip - if User.current && User.current.allowed_to?(:view_dmsf_files, file.project) - return file.preview(args[1].strip).gsub("\n", '
    ').html_safe - else + if User.current && User.current.allowed_to?(:view_dmsf_files, file.project) + return file.preview(args[1].strip).gsub("\n", '
    ').html_safe + else raise l(:notice_not_authorized) - end - end + end + end # dmsf_image - link to an image desc "Wiki DMSF image:\n\n" + @@ -108,11 +108,11 @@ Redmine::WikiFormatting::Macros.register do width = options[:width] height = options[:height] if file = DmsfFile.find_by_id(file_id) - unless User.current && User.current.allowed_to?(:view_dmsf_files, file.project) + unless User.current && User.current.allowed_to?(:view_dmsf_files, file.project) raise l(:notice_not_authorized) end raise 'Not supported image format' unless file.image? - url = url_for(:controller => :dmsf_files, :action => 'view', :id => file) + url = url_for(:controller => :dmsf_files, :action => 'view', :id => file) if size && size.include?('%') image_tag(url, :alt => file.title, :width => size, :height => size) elsif height @@ -142,11 +142,11 @@ Redmine::WikiFormatting::Macros.register do width = options[:width] height = options[:height] if file = DmsfFile.find_by_id(file_id) - unless User.current && User.current.allowed_to?(:view_dmsf_files, file.project) + unless User.current && User.current.allowed_to?(:view_dmsf_files, file.project) raise l(:notice_not_authorized) end raise 'Not supported image format' unless file.image? - url = url_for(:controller => :dmsf_files, :action => 'view', :id => file) + url = url_for(:controller => :dmsf_files, :action => 'view', :id => file) file_view_url = url_for(:controller => :dmsf_files, :action => 'view', :id => file, :download => args[2]) if size && size.include?("%") img = image_tag(url, :alt => file.title, :width => size, :height => size) @@ -167,20 +167,20 @@ Redmine::WikiFormatting::Macros.register do raise "Document ID #{file_id} not found" end end - + # dmsfw - link to a document's approval workflow status desc "Wiki link to DMSF document's approval workflow status:\n\n" + "{{dmsfw(file_id)}}\n\n" + - "_file_id_ can be found in the document's details." + "_file_id_ can be found in the document's details." macro :dmsfw do |obj, args| raise ArgumentError if args.length < 1 # Requires file id file = DmsfFile.visible.find args[0].strip - if User.current && User.current.allowed_to?(:view_dmsf_files, file.project) + if User.current && User.current.allowed_to?(:view_dmsf_files, file.project) raise ActiveRecord::RecordNotFound unless file.last_revision return file.last_revision.workflow_str(false) - else + else raise l(:notice_not_authorized) - end - end + end + end end \ No newline at end of file diff --git a/lib/redmine_dmsf/patches/project_patch.rb b/lib/redmine_dmsf/patches/project_patch.rb index 578fd1a5..0e387417 100644 --- a/lib/redmine_dmsf/patches/project_patch.rb +++ b/lib/redmine_dmsf/patches/project_patch.rb @@ -31,7 +31,7 @@ module RedmineDmsf base.class_eval do unloadable alias_method_chain :copy, :dmsf - + has_many :dmsf_files, -> { where dmsf_folder_id: nil}, :class_name => 'DmsfFile', :foreign_key => 'project_id', :dependent => :destroy has_many :dmsf_folders, -> {where dmsf_folder_id: nil}, @@ -43,7 +43,9 @@ module RedmineDmsf has_many :file_links, -> { where dmsf_folder_id: nil, target_type: 'DmsfFile' }, :class_name => 'DmsfLink', :foreign_key => 'project_id', :dependent => :destroy has_many :url_links, -> { where dmsf_folder_id: nil, target_type: 'DmsfUrl' }, - :class_name => 'DmsfLink', :foreign_key => 'project_id', :dependent => :destroy + :class_name => 'DmsfLink', :foreign_key => 'project_id', :dependent => :destroy + has_many :dmsf_links, -> { where dmsf_folder_id: nil }, + :class_name => 'DmsfLink', :foreign_key => 'project_id', :dependent => :destroy end end diff --git a/lib/redmine_dmsf/webdav/dmsf_resource.rb b/lib/redmine_dmsf/webdav/dmsf_resource.rb index 833e70d5..6996ddb9 100644 --- a/lib/redmine_dmsf/webdav/dmsf_resource.rb +++ b/lib/redmine_dmsf/webdav/dmsf_resource.rb @@ -29,7 +29,7 @@ module RedmineDmsf def setup @skip_alias |= [ :folder, :file ] end - + # Here we hook into the fact that resources can have a pre-execution routine run for them # Our sole job here is to ensure that any write functionality is restricted to relevent configuration before do |resource, method_name| @@ -49,15 +49,15 @@ module RedmineDmsf unless @childern @children = [] if collection? - folder.subfolders.select(:title).visible.map do |p| + folder.dmsf_folders.select(:title).visible.map do |p| @children.push child(p.title) end - folder.files.select(:name).visible.map do |p| + folder.dmsf_files.select(:name).visible.map do |p| @children.push child(p.name) end end end - @children + @children end # Does the object exist? @@ -65,7 +65,7 @@ module RedmineDmsf # - 2012-06-15: Only if you're allowed to browse the project # - 2012-06-18: Issue #5, ensure item is only listed if project is enabled for dmsf def exist? - return project && project.module_enabled?('dmsf') && (folder || file) && + return project && project.module_enabled?('dmsf') && (folder || file) && (User.current.admin? || User.current.allowed_to?(:view_dmsf_folders, project)) end @@ -74,38 +74,38 @@ module RedmineDmsf folder.present? # No need to check if entity exists, as false is returned if entity does not exist anyways end - # Check if current entity is a folder and return DmsfFolder object if found (nil if not) + # Check if current entity is a folder and return DmsfFolder object if found (nil if not) def folder unless @folder - return nil unless project # If the project doesn't exist, this entity can't exist + return nil unless project # If the project doesn't exist, this entity can't exist # 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 + # 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 hierarchy, I can't yet - # see a practical need for it - folders = DmsfFolder.visible.where(:project_id => project.id, :title => basename).order('title ASC').to_a + # see a practical need for it + 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) folders.delete_if { |x| '/' + x.dmsf_path_str != projectless_path } return nil unless folders.length > 0 @folder = folders[0] - else + else if (('/' + folders[0].dmsf_path_str) == projectless_path) - @folder = folders[0] + @folder = folders[0] end end end @folder - end + end # Check if current entity exists as a file (DmsfFile), and returns corresponding object if found (nil otherwise) - # Currently has a dual search approach (depending on if parent can be determined) - def file + # Currently has a dual search approach (depending on if parent can be determined) + def file unless @file return nil unless project # Again if entity project is nil, it cannot exist in context of this object # Hunt for files parent path f = false - if (parent.projectless_path != '/') - f = parent.folder if parent.folder + if (parent.projectless_path != '/') + f = parent.dmsf_folder if parent.dmsf_folder else f = nil end @@ -114,16 +114,16 @@ module RedmineDmsf # DMSF file search by name. @file = DmsfFile.visible.find_file_by_name(project, f, basename) else - # If folder is false, means it couldn't pick up parent, - # as such its probably fine to bail out, however we'll + # 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').to_a - files.delete_if { |x| File.dirname('/' + x.dmsf_path_str) != File.dirname(projectless_path) } - @file = files[0] if files.length > 0 + files.delete_if { |x| File.dirname('/' + x.dmsf_path_str) != File.dirname(projectless_path) } + @file = files[0] if files.length > 0 end end @file - end + end # Return the content type of file # will return inode/directory for any collections, and appropriate for File entities @@ -187,7 +187,7 @@ module RedmineDmsf end OK end - + # Process incoming MKCOL request # # Create a DmsfFolder at location requested, only if parent is a folder (or root) @@ -199,12 +199,12 @@ module RedmineDmsf return MethodNotAllowed if exist? # If we already exist, why waste the time trying to save? parent_folder = nil if (parent.projectless_path != '/') - return Conflict unless parent.folder - parent_folder = parent.folder.id - end + return Conflict unless parent.dmsf_folder + parent_folder = parent.dmsf_folder.id + end f = DmsfFolder.new f.title = basename - f.dmsf_folder_id = parent_folder + f.dmsf_folder_id = parent_folder f.project = project f.user = User.current f.save ? OK : Conflict @@ -217,7 +217,7 @@ module RedmineDmsf # # should be of entity to be deleted, we simply follow the Dmsf entity method # for deletion and return of appropriate status based on outcome. - def delete + def delete if file raise Forbidden unless User.current.admin? || User.current.allowed_to?(:file_delete, project) file.delete(false) ? NoContent : Conflict @@ -235,13 +235,13 @@ module RedmineDmsf # 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 - resource = dest.is_a?(ResourceProxy) ? dest.resource : dest - + # 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 @@ -254,37 +254,37 @@ module RedmineDmsf 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 + return PreconditionFailed unless parent.exist? && parent.dmsf_folder + folder.dmsf_folder_id = parent.dmsf_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) + raise Forbidden unless User.current.admin? || + User.current.allowed_to?(:folder_manipulation, project) || + User.current.allowed_to?(:folder_manipulation, resource.project) if dest.exist? - methodNotAllowed + 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 - return PreconditionFailed unless parent.exist? && parent.folder - f = parent.folder + return PreconditionFailed unless parent.exist? && parent.dmsf_folder + f = parent.dmsf_folder end return PreconditionFailed unless exist? && file 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] + # Update Revision and names of file [We can link to old physical resource, as it's not changed] if file.last_revision - file.last_revision.name = resource.basename + file.last_revision.name = resource.basename file.last_revision.title = DmsfFileRevision.filename_to_title(resource.basename) end - file.name = resource.basename + file.name = resource.basename # Save Changes (file.last_revision.save! && file.save!) ? Created : PreconditionFailed @@ -307,9 +307,9 @@ module RedmineDmsf end return PreconditionFailed if !resource.is_a?(DmsfResource) || resource.project.nil? || resource.project.id == 0 - + parent = resource.parent - + if collection? # Current object is a folder, so now we need to figure out information about Destination return MethodNotAllowed if(dest.exist?) @@ -320,7 +320,7 @@ module RedmineDmsf # View folders on destination project :view_dmsf_folders # View files on the source project :view_dmsf_files # View fodlers on the source project :view_dmsf_folders - raise Forbidden unless User.current.admin? || + raise Forbidden unless User.current.admin? || (User.current.allowed_to?(:folder_manipulation, resource.project) && User.current.allowed_to?(:view_dmsf_folders, resource.project) && User.current.allowed_to?(:view_dmsf_files, project) && @@ -351,12 +351,12 @@ module RedmineDmsf f = nil else return PreconditionFailed unless parent.exist? && parent.folder - f = parent.folder + f = parent.dmsf_folder end return PreconditionFailed unless exist? && file return InternalServerError unless file.copy_to(resource.project, f) - # Update Revision and names of file [We can link to old physical resource, as it's not changed] + # 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 @@ -368,7 +368,7 @@ module RedmineDmsf # Lock Check # Check for the existence of locks - # At present as deletions of folders are not recursive, we do not need to extend + # At present as deletions of folders are not recursive, we do not need to extend # this to cover every file, just queried def lock_check(lock_scope = nil) if file @@ -381,7 +381,7 @@ module RedmineDmsf # Lock def lock(args) return Conflict unless (parent.projectless_path == '/' || parent_exists?) - lock_check(args[:scope]) + lock_check(args[:scope]) unless self.exist? Rails.logger.warn "Path doesn't exist: #{@path}" return super @@ -392,7 +392,7 @@ module RedmineDmsf raise DAV4Rack::LockFailure.new("Failed to lock: #{@path}") else # If scope and type are not defined, the only thing we can - # logically assume is that the lock is being refreshed (office loves + # logically assume is that the lock is being refreshed (office loves # to do this for example, so we do a few checks, try to find the lock # and ultimately extend it, otherwise we return Conflict for any failure if (!args[:scope] && !args[:type]) #Perhaps a lock refresh @@ -459,19 +459,19 @@ module RedmineDmsf raise Forbidden end - # HTTP POST request. - def put(request, response) - raise BadRequest if collection? - raise Forbidden unless User.current.admin? || User.current.allowed_to?(:file_manipulation, project) - - # Ignore Mac OS X resource forks and special Windows files. + # HTTP POST request. + def put(request, response) + raise BadRequest if collection? + raise Forbidden unless User.current.admin? || User.current.allowed_to?(:file_manipulation, project) + + # Ignore Mac OS X resource forks and special Windows files. if basename.match(/^\._/i) || basename.match(/^Thumbs.db$/i) Rails.logger.info "#{basename} ignored" return NoContent end new_revision = DmsfFileRevision.new - + if exist? # We're over-writing something, so ultimately a new revision f = file last_revision = file.last_revision @@ -486,21 +486,21 @@ module RedmineDmsf f = DmsfFile.new f.project = project f.name = basename - f.folder = parent.folder + f.dmsf_folder = parent.folder f.notification = !Setting.plugin_redmine_dmsf['dmsf_default_notifications'].blank? new_revision.minor_version = 0 new_revision.major_version = 0 end - - new_revision.file = f + + new_revision.dmsf_file = f new_revision.user = User.current new_revision.name = basename new_revision.title = DmsfFileRevision.filename_to_title(basename) new_revision.description = nil - new_revision.comment = nil + new_revision.comment = nil new_revision.increase_version(1, true) new_revision.mime_type = Redmine::MimeType.of(new_revision.name) - + # Phusion passenger does not have a method "length" in its model # however, includes a size method - so we instead use reflection # to determine best approach to problem @@ -511,15 +511,15 @@ module RedmineDmsf else new_revision.size = request.content_length # Bad Guess end - - # Ignore Mac OS X resource forks and special Windows files. + + # Ignore Mac OS X resource forks and special Windows files. unless new_revision.size > 0 Rails.logger.info "#{basename} #{new_revision.size}b ignored" - return Created + return Created end - + raise InternalServerError unless new_revision.valid? && f.save - + new_revision.disk_filename = new_revision.new_storage_filename if new_revision.save @@ -543,13 +543,13 @@ module RedmineDmsf case element[:name] when 'supportedlock' supportedlock - when 'lockdiscovery' + when 'lockdiscovery' lockdiscovery - else + else super end end - + # Available properties def properties %w(creationdate displayname getlastmodified getetag resourcetype getcontenttype getcontentlength supportedlock lockdiscovery).collect do |prop| @@ -559,7 +559,7 @@ module RedmineDmsf private # Prepare file for download using Rack functionality: - # Download (see RedmineDmsf::Webdav::Download) extends Rack::File to allow single-file + # Download (see RedmineDmsf::Webdav::Download) extends Rack::File to allow single-file # implementation of service for request, which allows for us to pipe a single file through # also best-utilising DAV4Rack's implementation. def download @@ -567,28 +567,28 @@ module RedmineDmsf # If there is no range (start of ranged download, or direct download) then we log the # file access, so we can properly keep logged information - if @request.env['HTTP_RANGE'].nil? + if @request.env['HTTP_RANGE'].nil? access = DmsfFileRevisionAccess.new access.user = User.current - access.revision = file.last_revision + access.dmsf_file_revision = file.last_revision access.action = DmsfFileRevisionAccess::DownloadAction access.save! end Download.new(file.last_revision.disk_file) end - + # As the name suggests, we're returning lock recovery information for requested resource def lockdiscovery x = Nokogiri::XML::DocumentFragment.parse '' entity = file || folder return nil unless entity.locked? - if entity.folder && entity.folder.locked? - locks = entity.lock.reverse[0].folder.locks(false) # longwinded way of getting base items locks + if entity.dmsf_folder && entity.dmsf_folder.locked? + locks = entity.lock.reverse[0].dmsf_folder.locks(false) # longwinded way of getting base items locks else locks = entity.lock(false) end - + Nokogiri::XML::Builder.with(x) do |doc| doc.lockdiscovery { locks.each do |lock| @@ -601,8 +601,8 @@ module RedmineDmsf else doc.shared end - } - doc.depth lock.folder.nil? ? '0' : 'infinity' + } + doc.depth lock.folder.nil? ? '0' : 'infinity' doc.owner lock.user.to_s if lock.expires_at.nil? doc.timeout 'Infinite' @@ -623,7 +623,7 @@ module RedmineDmsf end x end - + # As the name suggests, we're returning locks supported by our implementation def supportedlock x = Nokogiri::XML::DocumentFragment.parse '' diff --git a/test/functional/dmsf_controller_test.rb b/test/functional/dmsf_controller_test.rb index ad21abb7..e6872de2 100644 --- a/test/functional/dmsf_controller_test.rb +++ b/test/functional/dmsf_controller_test.rb @@ -20,14 +20,14 @@ require File.expand_path('../../test_helper', __FILE__) -class DmsfControllerTest < RedmineDmsf::Test::TestCase - include Redmine::I18n - - fixtures :users, :email_addresses, :dmsf_folders, :custom_fields, - :custom_values, :projects, :roles, :members, :member_roles, :dmsf_links, +class DmsfControllerTest < RedmineDmsf::Test::TestCase + include Redmine::I18n + + fixtures :users, :email_addresses, :dmsf_folders, :custom_fields, + :custom_values, :projects, :roles, :members, :member_roles, :dmsf_links, :dmsf_files, :dmsf_file_revisions - def setup + def setup @project = Project.find_by_id 1 assert_not_nil @project @project.enable_module! :dmsf @@ -39,11 +39,12 @@ class DmsfControllerTest < RedmineDmsf::Test::TestCase @folder_link1 = DmsfLink.find_by_id 1 @role = Role.find_by_id 1 @custom_field = CustomField.find_by_id 21 - @custom_value = CustomValue.find_by_id 21 + @custom_value = CustomValue.find_by_id 21 + @user_member = User.find_by_id 2 User.current = nil - @request.session[:user_id] = 2 + @request.session[:user_id] = @user_member.id end - + def test_truth assert_kind_of Project, @project assert_kind_of DmsfFolder, @folder1 @@ -54,16 +55,17 @@ class DmsfControllerTest < RedmineDmsf::Test::TestCase assert_kind_of DmsfLink, @folder_link1 assert_kind_of Role, @role assert_kind_of CustomField, @custom_field - assert_kind_of CustomValue, @custom_value + assert_kind_of CustomValue, @custom_value + assert_kind_of User, @user_member end - + def test_edit_folder_forbidden - # Missing permissions + # Missing permissions get :edit, :id => @project, :folder_id => @folder1 - assert_response :forbidden + assert_response :forbidden end - - def test_edit_folder_allowed + + def test_edit_folder_allowed # Permissions OK @role.add_permission! :view_dmsf_folders @role.add_permission! :folder_manipulation @@ -72,58 +74,58 @@ class DmsfControllerTest < RedmineDmsf::Test::TestCase assert_select 'label', { :text => @custom_field.name } assert_select 'option', { :value => @custom_value.value } end - + def test_trash_forbidden # Missing permissions get :trash, :id => @project - assert_response :forbidden + assert_response :forbidden end - - def test_trash_allowed + + def test_trash_allowed # Permissions OK @role.add_permission! :file_delete get :trash, :id => @project assert_response :success assert_select 'h2', { :text => l(:link_trash_bin) } end - + def test_delete_forbidden # Missing permissions get :delete, :id => @project, :folder_id => @folder1.id, :commit => false assert_response :forbidden end - + def test_delete_not_empty # Permissions OK but the folder is not empty @role.add_permission! :folder_manipulation get :delete, :id => @project, :folder_id => @folder1.id, :commit => false - assert_response :redirect + assert_response :redirect assert_include l(:error_folder_is_not_empty), flash[:error] end - + def test_delete_locked # Permissions OK but the folder is locked @role.add_permission! :folder_manipulation get :delete, :id => @project, :folder_id => @folder2.id, :commit => false - assert_response :redirect + assert_response :redirect assert_include l(:error_folder_is_locked), flash[:error] end - + def test_delete_ok - # Empty and not locked folder + # Empty and not locked folder @role.add_permission! :folder_manipulation get :delete, :id => @project, :folder_id => @folder4.id, :commit => false - assert_response :redirect + assert_response :redirect end - - def test_restore_forbidden - # Missing permissions + + def test_restore_forbidden + # Missing permissions @folder4.deleted = 1 @folder4.save get :restore, :id => @project, :folder_id => @folder4.id assert_response :forbidden end - + def test_restore_ok # Permissions OK @request.env['HTTP_REFERER'] = trash_dmsf_path(:id => @project.id) @@ -131,49 +133,67 @@ class DmsfControllerTest < RedmineDmsf::Test::TestCase @folder4.deleted = 1 @folder4.save get :restore, :id => @project, :folder_id => @folder4.id - assert_response :redirect + assert_response :redirect end - + def test_delete_restore_entries_forbidden # Missing permissions - get :entries_operation, :id => @project, :delete_entries => 'Delete', - :subfolders => [@folder1.id.to_s], :files => [@file1.id.to_s], + get :entries_operation, :id => @project, :delete_entries => 'Delete', + :subfolders => [@folder1.id.to_s], :files => [@file1.id.to_s], :dir_links => [@folder_link1.id.to_s], :file_links => [@file_link2.id.to_s] - assert_response :forbidden + assert_response :forbidden end - + def test_delete_restore_not_empty - # Permissions OK but the folder is not empty - @request.env['HTTP_REFERER'] = dmsf_folder_path(:id => @project.id) + # Permissions OK but the folder is not empty + @request.env['HTTP_REFERER'] = dmsf_folder_path(:id => @project.id) @role.add_permission! :view_dmsf_files - get :entries_operation, :id => @project, :delete_entries => 'Delete', - :subfolders => [@folder1.id.to_s], :files => [@file1.id.to_s], + get :entries_operation, :id => @project, :delete_entries => 'Delete', + :subfolders => [@folder1.id.to_s], :files => [@file1.id.to_s], :dir_links => [@folder_link1.id.to_s], :file_links => [@file_link2.id.to_s] assert_response :redirect assert_equal flash[:error].to_s, l(:error_folder_is_not_empty) end - + def test_delete_restore_entries_ok # Permissions OK @request.env['HTTP_REFERER'] = dmsf_folder_path(:id => @project.id) @role.add_permission! :view_dmsf_files flash[:error] = nil - get :entries_operation, :id => @project, :delete_entries => 'Delete', - :subfolders => [], :files => [@file1.id.to_s], + get :entries_operation, :id => @project, :delete_entries => 'Delete', + :subfolders => [], :files => [@file1.id.to_s], :dir_links => [], :file_links => [@file_link2.id.to_s] assert_response :redirect assert_nil flash[:error] end - + def test_restore_entries # Restore @role.add_permission! :view_dmsf_files - @request.env['HTTP_REFERER'] = trash_dmsf_path(:id => @project.id) - get :entries_operation, :id => @project, :restore_entries => 'Restore', - :subfolders => [], :files => [@file1.id.to_s], + @request.env['HTTP_REFERER'] = trash_dmsf_path(:id => @project.id) + get :entries_operation, :id => @project, :restore_entries => 'Restore', + :subfolders => [], :files => [@file1.id.to_s], :dir_links => [], :file_links => [@file_link2.id.to_s] assert_response :redirect assert_nil flash[:error] end - + + def test_show + @role.add_permission! :view_dmsf_files + @role.add_permission! :view_dmsf_folders + get :show, :id => @project.id + assert_response :success + assert_select 'tr.dmsf_tree', :count => 0 + end + + def test_show_tree_view + @role.add_permission! :view_dmsf_files + @role.add_permission! :view_dmsf_folders + @user_member.pref[:dmsf_tree_view] = '1' + @user_member.preference.save + get :show, :id => @project.id + assert_response :success + assert_select 'tr.dmsf_tree' + end + end \ No newline at end of file diff --git a/test/functional/dmsf_workflow_controller_test.rb b/test/functional/dmsf_workflow_controller_test.rb index ab220685..07e3360c 100644 --- a/test/functional/dmsf_workflow_controller_test.rb +++ b/test/functional/dmsf_workflow_controller_test.rb @@ -22,15 +22,15 @@ require File.expand_path('../../test_helper', __FILE__) class DmsfWorkflowsControllerTest < RedmineDmsf::Test::TestCase include Redmine::I18n - - fixtures :users, :email_addresses, :dmsf_workflows, :dmsf_workflow_steps, - :projects, :roles, :members, :member_roles, :dmsf_workflow_step_assignments, + + fixtures :users, :email_addresses, :dmsf_workflows, :dmsf_workflow_steps, + :projects, :roles, :members, :member_roles, :dmsf_workflow_step_assignments, :dmsf_file_revisions, :dmsf_files - + def setup - @user_admin = User.find_by_id 1 # Redmine admin - @user_member = User.find_by_id 2 # John Smith - manager - @user_non_member = User.find_by_id 3 # Dave Lopper + @user_admin = User.find_by_id 1 # Redmine admin + @user_member = User.find_by_id 2 # John Smith - manager + @user_non_member = User.find_by_id 3 # Dave Lopper @role_manager = Role.find_by_name('Manager') @role_manager.add_permission! :file_manipulation @role_manager.add_permission! :manage_workflows @@ -39,22 +39,22 @@ class DmsfWorkflowsControllerTest < RedmineDmsf::Test::TestCase @wfs2 = DmsfWorkflowStep.find_by_id 2 # step 2 @wfs3 = DmsfWorkflowStep.find_by_id 3 # step 1 @wfs4 = DmsfWorkflowStep.find_by_id 4 # step 2 - @wfs5 = DmsfWorkflowStep.find_by_id 5 # step 3 - @project1 = Project.find_by_id 1 + @wfs5 = DmsfWorkflowStep.find_by_id 5 # step 3 + @project1 = Project.find_by_id 1 @project1.enable_module! :dmsf @wf1 = DmsfWorkflow.find_by_id 1 @wf3 = DmsfWorkflow.find_by_id 3 @wfsa2 = DmsfWorkflowStepAssignment.find_by_id 2 @revision1 = DmsfFileRevision.find_by_id 1 @revision2 = DmsfFileRevision.find_by_id 2 - @revision3 = DmsfFileRevision.find_by_id 3 + @revision3 = DmsfFileRevision.find_by_id 3 @file1 = DmsfFile.find_by_id 1 @file2 = DmsfFile.find_by_id 2 @request.env['HTTP_REFERER'] = dmsf_folder_path(:id => @project1.id) User.current = nil - @request.session[:user_id] = @user_member.id + @request.session[:user_id] = @user_member.id end - + def test_truth assert_kind_of User, @user_admin assert_kind_of User, @user_member @@ -65,7 +65,7 @@ class DmsfWorkflowsControllerTest < RedmineDmsf::Test::TestCase assert_kind_of DmsfWorkflowStep, @wfs3 assert_kind_of DmsfWorkflowStep, @wfs4 assert_kind_of DmsfWorkflowStep, @wfs5 - assert_kind_of Project, @project1 + assert_kind_of Project, @project1 assert_kind_of DmsfWorkflow, @wf1 assert_kind_of DmsfWorkflow, @wf3 assert_kind_of DmsfWorkflowStepAssignment, @wfsa2 @@ -75,50 +75,50 @@ class DmsfWorkflowsControllerTest < RedmineDmsf::Test::TestCase assert_kind_of DmsfFile, @file1 assert_kind_of DmsfFile, @file2 end - + def test_authorize_admin # Admin @request.session[:user_id] = @user_admin.id get :index assert_response :success - assert_template 'index' + assert_template 'index' end - + def test_authorize_member # Non member - @request.session[:user_id] = @user_non_member.id + @request.session[:user_id] = @user_non_member.id get :index, :project_id => @project1.id - assert_response :forbidden + assert_response :forbidden end - - def test_authorize_administration + + def test_authorize_administration # Administration get :index - assert_response :forbidden + assert_response :forbidden end - - def test_authorize_projects - # Project + + def test_authorize_projects + # Project get :index, :project_id => @project1.id assert_response :success assert_template 'index' end - + def test_authorize_manage_workflows_forbidden # Without permissions @role_manager.remove_permission! :manage_workflows get :index, :project_id => @project1.id assert_response :forbidden end - + def test_authorization_file_approval_ok - @role_manager.add_permission! :file_approval + @role_manager.add_permission! :file_approval @revision2.dmsf_workflow_id = @wf1.id get :start, :id => @revision2.dmsf_workflow_id, :dmsf_file_revision_id => @revision2.id assert_response :redirect end - + def test_authorization_file_approval_forbidden @role_manager.remove_permission! :file_approval @revision2.dmsf_workflow_id = @wf1.id @@ -126,43 +126,43 @@ class DmsfWorkflowsControllerTest < RedmineDmsf::Test::TestCase :dmsf_file_revision_id => @revision2.id assert_response :forbidden end - + def test_authorization_no_module - # Without the module + # Without the module @role_manager.add_permission! :file_manipulation - @project1.disable_module!(:dmsf) + @project1.disable_module!(:dmsf) get :index, :project_id => @project1.id - assert_response :forbidden + assert_response :forbidden end - + def test_index_administration @request.session[:user_id] = @user_admin.id get :index assert_response :success assert_template 'index' end - - def test_index_project + + def test_index_project get :index, :project_id => @project1.id assert_response :success assert_template 'index' end - def test_new + def test_new get :new, :project_id => @project1.id assert_response :success assert_template 'new' - end - + end + def test_lock put :update, :id => @wf1.id, :dmsf_workflow => { :status => DmsfWorkflow::STATUS_LOCKED } @wf1.reload assert @wf1.locked?, "#{@wf1.name} status is #{@wf1.status}" end - + def test_unlock @request.session[:user_id] = @user_admin.id - put :update, :id => @wf3.id, :dmsf_workflow => { :status => DmsfWorkflow::STATUS_ACTIVE } + put :update, :id => @wf3.id, :dmsf_workflow => { :status => DmsfWorkflow::STATUS_ACTIVE } @wf3.reload assert @wf3.active?, "#{@wf3.name} status is #{@wf3.status}" end @@ -170,43 +170,43 @@ class DmsfWorkflowsControllerTest < RedmineDmsf::Test::TestCase def test_show get :show, :id => @wf1.id assert_response :success - assert_template 'show' + assert_template 'show' end - - def test_create - assert_difference 'DmsfWorkflow.count', +1 do - post :create, :dmsf_workflow => {:name => 'wf4'}, :project_id => @project1.id - end - assert_redirected_to settings_project_path(@project1, :tab => 'dmsf_workflow') + + def test_create + assert_difference 'DmsfWorkflow.count', +1 do + post :create, :dmsf_workflow => {:name => 'wf4', :project_id => @project1.id} + end + assert_redirected_to settings_project_path(@project1, :tab => 'dmsf_workflow') end - - def test_update + + def test_update put :update, :id => @wf1.id, :dmsf_workflow => {:name => 'wf1a'} @wf1.reload - assert_equal 'wf1a', @wf1.name + assert_equal 'wf1a', @wf1.name end - - def test_destroy + + def test_destroy assert_difference 'DmsfWorkflow.count', -1 do delete :destroy, :id => @wf1.id end - assert_redirected_to settings_project_path(@project1, :tab => 'dmsf_workflow') - assert_equal 0, DmsfWorkflowStep.where(:dmsf_workflow_id => @wf1.id).all.count + assert_redirected_to settings_project_path(@project1, :tab => 'dmsf_workflow') + assert_equal 0, DmsfWorkflowStep.where(:dmsf_workflow_id => @wf1.id).all.count end - - def test_add_step - assert_difference 'DmsfWorkflowStep.count', +1 do + + def test_add_step + assert_difference 'DmsfWorkflowStep.count', +1 do post :add_step, :commit => l(:dmsf_or), :step => 1, :id => @wf1.id, :user_ids => [@user_non_member.id] end assert_response :success - ws = DmsfWorkflowStep.order('id DESC').first + ws = DmsfWorkflowStep.order('id DESC').first assert_equal @wf1.id, ws.dmsf_workflow_id assert_equal 1, ws.step assert_equal @user_non_member.id, ws.user_id assert_equal DmsfWorkflowStep::OPERATOR_OR, ws.operator end - - def test_remove_step + + def test_remove_step n = DmsfWorkflowStep.where(:dmsf_workflow_id => @wf1.id, :step => 1).count assert_difference 'DmsfWorkflowStep.count', -n do delete :remove_step, :step => @wfs1.id, :id => @wf1.id @@ -215,158 +215,158 @@ class DmsfWorkflowsControllerTest < RedmineDmsf::Test::TestCase ws = DmsfWorkflowStep.where(:dmsf_workflow_id => @wf1.id).order('id ASC').first assert_equal 1, ws.step end - - def test_reorder_steps_to_lower + + def test_reorder_steps_to_lower put :reorder_steps, :step => 1, :id => @wf1.id, :workflow_step => {:move_to => 'lower'} - assert_response :success + assert_response :success @wfs1.reload @wfs2.reload @wfs3.reload @wfs4.reload - @wfs5.reload + @wfs5.reload assert_equal 1, @wfs2.step assert_equal 1, @wfs3.step assert_equal 2, @wfs1.step assert_equal 2, @wfs4.step assert_equal 3, @wfs5.step end - - def test_reorder_steps_to_lowest + + def test_reorder_steps_to_lowest put :reorder_steps, :step => 1, :id => @wf1.id, :workflow_step => {:move_to => 'lowest'} - assert_response :success + assert_response :success @wfs1.reload @wfs2.reload @wfs3.reload @wfs4.reload - @wfs5.reload - assert_equal 1, @wfs2.step + @wfs5.reload + assert_equal 1, @wfs2.step assert_equal 1, @wfs3.step assert_equal 2, @wfs5.step assert_equal 3, @wfs1.step assert_equal 3, @wfs4.step - end - - def test_reorder_steps_to_higher + end + + def test_reorder_steps_to_higher put :reorder_steps, :step => 3, :id => @wf1.id, :workflow_step => {:move_to => 'higher'} - assert_response :success + assert_response :success @wfs1.reload @wfs2.reload @wfs3.reload @wfs4.reload - @wfs5.reload + @wfs5.reload assert_equal 1, @wfs1.step assert_equal 1, @wfs4.step assert_equal 2, @wfs5.step - assert_equal 3, @wfs2.step - assert_equal 3, @wfs3.step + assert_equal 3, @wfs2.step + assert_equal 3, @wfs3.step end - - def test_reorder_steps_to_highest + + def test_reorder_steps_to_highest put :reorder_steps, :step => 3, :id => @wf1.id, :workflow_step => {:move_to => 'highest'} - assert_response :success + assert_response :success @wfs1.reload @wfs2.reload @wfs3.reload @wfs4.reload - @wfs5.reload + @wfs5.reload assert_equal 1, @wfs5.step assert_equal 2, @wfs1.step assert_equal 2, @wfs4.step - assert_equal 3, @wfs2.step - assert_equal 3, @wfs3.step + assert_equal 3, @wfs2.step + assert_equal 3, @wfs3.step end - - def test_action_approve - post( - :new_action, - :commit => l(:button_submit), - :id => @wf1.id, - :dmsf_workflow_step_assignment_id => @wfsa2.id, + + def test_action_approve + post( + :new_action, + :commit => l(:button_submit), + :id => @wf1.id, + :dmsf_workflow_step_assignment_id => @wfsa2.id, :dmsf_file_revision_id => @revision1.id, :step_action => DmsfWorkflowStepAction::ACTION_APPROVE, :user_id => nil, - :note => '') + :note => '') assert_redirected_to dmsf_folder_path(:id => @project1.id) assert DmsfWorkflowStepAction.where( - :dmsf_workflow_step_assignment_id => @wfsa2.id, - :action => DmsfWorkflowStepAction::ACTION_APPROVE).first - end - - def test_action_reject - post( - :new_action, - :commit => l(:button_submit), - :id => @wf1.id, - :dmsf_workflow_step_assignment_id => @wfsa2.id, - :dmsf_file_revision_id => @revision2.id, - :step_action => DmsfWorkflowStepAction::ACTION_REJECT, - :note => 'Rejected because...') - assert_response :redirect - assert DmsfWorkflowStepAction.where( - :dmsf_workflow_step_assignment_id => @wfsa2.id, - :action => DmsfWorkflowStepAction::ACTION_REJECT).first + :dmsf_workflow_step_assignment_id => @wfsa2.id, + :action => DmsfWorkflowStepAction::ACTION_APPROVE).first end - def test_action + def test_action_reject + post( + :new_action, + :commit => l(:button_submit), + :id => @wf1.id, + :dmsf_workflow_step_assignment_id => @wfsa2.id, + :dmsf_file_revision_id => @revision2.id, + :step_action => DmsfWorkflowStepAction::ACTION_REJECT, + :note => 'Rejected because...') + assert_response :redirect + assert DmsfWorkflowStepAction.where( + :dmsf_workflow_step_assignment_id => @wfsa2.id, + :action => DmsfWorkflowStepAction::ACTION_REJECT).first + end + + def test_action xhr( :get, :action, - :project_id => @project1.id, - :id => @wf1.id, + :project_id => @project1.id, + :id => @wf1.id, :dmsf_workflow_step_assignment_id => @wfsa2.id, :dmsf_file_revision_id => @revision2.id, - :title => l(:title_waiting_for_approval)) + :title => l(:title_waiting_for_approval)) assert_response :success assert_match /ajax-modal/, response.body assert_template 'action' end - - def test_new_action_delegate - post( - :new_action, - :commit => l(:button_submit), - :id => @wf1.id, - :dmsf_workflow_step_assignment_id => @wfsa2.id, + + def test_new_action_delegate + post( + :new_action, + :commit => l(:button_submit), + :id => @wf1.id, + :dmsf_workflow_step_assignment_id => @wfsa2.id, :dmsf_file_revision_id => @revision2.id, :step_action => @user_admin.id * 10, - :note => 'Delegated because...') + :note => 'Delegated because...') assert_redirected_to dmsf_folder_path(:id => @project1.id) assert DmsfWorkflowStepAction.where( - :dmsf_workflow_step_assignment_id => @wfsa2.id, - :action => DmsfWorkflowStepAction::ACTION_DELEGATE).first + :dmsf_workflow_step_assignment_id => @wfsa2.id, + :action => DmsfWorkflowStepAction::ACTION_DELEGATE).first @wfsa2.reload - assert_equal @wfsa2.user_id, @user_admin.id + assert_equal @wfsa2.user_id, @user_admin.id end - - def test_assign - xhr( - :get, - :assign, + + def test_assign + xhr( + :get, + :assign, :project_id => @project1.id, - :id => @wf1.id, - :dmsf_file_revision_id => @revision1.id, + :id => @wf1.id, + :dmsf_file_revision_id => @revision1.id, :title => l(:label_dmsf_wokflow_action_assign)) assert_response :success assert_match /ajax-modal/, response.body - assert_template 'assign' + assert_template 'assign' end - + def test_start - @revision2.dmsf_workflow_id = @wf1.id + @revision2.dmsf_workflow_id = @wf1.id get :start, :id => @revision2.dmsf_workflow_id,:dmsf_file_revision_id => @revision2.id assert_redirected_to dmsf_folder_path(:id => @project1.id) end - - def test_assignment - post( - :assignment, - :commit => l(:button_submit), - :id => @wf1.id, - :dmsf_workflow_id => @wf1.id, + + def test_assignment + post( + :assignment, + :commit => l(:button_submit), + :id => @wf1.id, + :dmsf_workflow_id => @wf1.id, :dmsf_file_revision_id => @revision2.id, :action => 'assignment', - :project_id => @project1.id) - assert_response :redirect + :project_id => @project1.id) + assert_response :redirect end end diff --git a/test/integration/dmsf_webdav_get_test.rb b/test/integration/dmsf_webdav_get_test.rb index a3ad1353..20295236 100644 --- a/test/integration/dmsf_webdav_get_test.rb +++ b/test/integration/dmsf_webdav_get_test.rb @@ -23,7 +23,7 @@ require File.expand_path('../../test_helper', __FILE__) class DmsfWebdavGetTest < RedmineDmsf::Test::IntegrationTest - fixtures :projects, :users, :email_addresses, :members, :member_roles, :roles, + fixtures :projects, :users, :email_addresses, :members, :member_roles, :roles, :enabled_modules, :dmsf_folders, :dmsf_files, :dmsf_file_revisions def setup @@ -33,12 +33,12 @@ class DmsfWebdavGetTest < RedmineDmsf::Test::IntegrationTest @project2 = Project.find_by_id 2 @role = Role.find_by_id 1 # Manager Setting.plugin_redmine_dmsf['dmsf_webdav'] = '1' - Setting.plugin_redmine_dmsf['dmsf_webdav_strategy'] = 'WEBDAV_READ_WRITE' + Setting.plugin_redmine_dmsf['dmsf_webdav_strategy'] = 'WEBDAV_READ_WRITE' DmsfFile.storage_path = File.expand_path '../../fixtures/files', __FILE__ User.current = nil end - - def test_truth + + def test_truth assert_kind_of Project, @project1 assert_kind_of Project, @project2 assert_kind_of Role, @role @@ -67,26 +67,26 @@ class DmsfWebdavGetTest < RedmineDmsf::Test::IntegrationTest def test_should_not_list_non_dmsf_enabled_project get '/dmsf/webdav', nil, @jsmith - assert_response :success + assert_response :success assert response.body.match(@project2.name).nil?, "Unexpected find of project #{@project2.name} in return data" end def test_should_return_status_404_when_project_does_not_exist @project1.enable_module! :dmsf # Flag module enabled - get '/dmsf/webdav/project_does_not_exist', nil, @jsmith + get '/dmsf/webdav/project_does_not_exist', nil, @jsmith assert_response :missing end def test_should_return_status_404_when_dmsf_not_enabled get "/dmsf/webdav/#{@project2.identifier}", nil, @jsmith assert_response :missing - end + end def test_download_file_from_dmsf_enabled_project - #@project1.enable_module! :dmsf # Flag module enabled - get "/dmsf/webdav/#{@project1.identifier}/test.txt", nil, @admin + #@project1.enable_module! :dmsf # Flag module enabled + get "/dmsf/webdav/#{@project1.identifier}/test.txt", nil, @admin assert_response :success - assert_equal response.body, '1234', + assert_equal response.body, '1234', "File downloaded with unexpected contents: '#{response.body}'" end @@ -95,19 +95,19 @@ class DmsfWebdavGetTest < RedmineDmsf::Test::IntegrationTest assert_response :success folder = DmsfFolder.find_by_id 1 assert folder - assert response.body.match(folder.title), + assert response.body.match(folder.title), "Expected to find #{folder.title} in return data" file = DmsfFile.find_by_id 1 assert file - assert response.body.match(file.name), + assert response.body.match(file.name), "Expected to find #{file.name} in return data" end - def test_user_assigned_to_project_dmsf_module_not_enabled + def test_user_assigned_to_project_dmsf_module_not_enabled get "/dmsf/webdav/#{@project1.identifier}", nil, @jsmith assert_response :missing end - + def test_user_assigned_to_project_folder_forbidden @project2.enable_module! :dmsf # Flag module enabled get "/dmsf/webdav/#{@project1.identifier}", nil, @jsmith @@ -121,21 +121,21 @@ class DmsfWebdavGetTest < RedmineDmsf::Test::IntegrationTest get "/dmsf/webdav/#{@project1.identifier}", nil, @jsmith assert_response :success end - + def test_user_assigned_to_project_file_forbidden @project1.enable_module! :dmsf # Flag module enabled - @role.add_permission! :view_dmsf_folders + @role.add_permission! :view_dmsf_folders get "/dmsf/webdav/#{@project1.identifier}/test.txt", nil, @jsmith assert_response :forbidden end - + def test_user_assigned_to_project_file_ok @project1.enable_module! :dmsf # Flag module enabled @role.add_permission! :view_dmsf_folders - @role.add_permission! :view_dmsf_files + @role.add_permission! :view_dmsf_files get "/dmsf/webdav/#{@project1.identifier}/test.txt", nil, @jsmith assert_response :success - assert_equal response.body, '1234', + assert_equal response.body, '1234', "File downloaded with unexpected contents: '#{response.body}'" end diff --git a/test/integration/dmsf_webdav_put_test.rb b/test/integration/dmsf_webdav_put_test.rb index 2ddc10df..f0f603bf 100644 --- a/test/integration/dmsf_webdav_put_test.rb +++ b/test/integration/dmsf_webdav_put_test.rb @@ -24,7 +24,7 @@ require 'fileutils' class DmsfWebdavPutTest < RedmineDmsf::Test::IntegrationTest - fixtures :projects, :users, :email_addresses, :members, :member_roles, :roles, + fixtures :projects, :users, :email_addresses, :members, :member_roles, :roles, :enabled_modules, :dmsf_folders, :dmsf_files, :dmsf_file_revisions def setup @@ -36,13 +36,13 @@ class DmsfWebdavPutTest < RedmineDmsf::Test::IntegrationTest @jsmith = credentials 'jsmith' @project1 = Project.find_by_id 1 @project2 = Project.find_by_id 2 - @role = Role.find 1 # + @role = Role.find 1 # Setting.plugin_redmine_dmsf['dmsf_webdav'] = '1' Setting.plugin_redmine_dmsf['dmsf_webdav_strategy'] = 'WEBDAV_READ_WRITE' super end - def teardown + def teardown # Delete our tmp folder begin FileUtils.rm_rf DmsfFile.storage_path @@ -50,8 +50,8 @@ class DmsfWebdavPutTest < RedmineDmsf::Test::IntegrationTest error e.message end end - - def test_truth + + def test_truth assert_kind_of Project, @project1 assert_kind_of Project, @project2 assert_kind_of Role, @role @@ -61,7 +61,7 @@ class DmsfWebdavPutTest < RedmineDmsf::Test::IntegrationTest put '/dmsf/webdav' assert_response 401 end - + def test_put_denied_unless_authenticated put "/dmsf/webdav/#{@project1.identifier}" assert_response 401 @@ -113,43 +113,43 @@ class DmsfWebdavPutTest < RedmineDmsf::Test::IntegrationTest put "/dmsf/webdav/#{@project1.identifier}/test-1234.txt", '1234', @jsmith.merge!({:content_type => :text}) assert_response 409 # We don't hold the permission view_dmsf_folders, and thus project 2 doesn't exist to us. end - + def test_put_failed_when_no_file_manipulation_permission @project1.enable_module! :dmsf # Flag module enabled - @role.add_permission! :view_dmsf_folders + @role.add_permission! :view_dmsf_folders put "/dmsf/webdav/#{@project1.identifier}/test-1234.txt", '1234', @jsmith.merge!({:content_type => :text}) assert_response :forbidden # We don't hold the permission file_manipulation - so we're unable to do anything with files end - + def test_put_failed_when_no_view_dmsf_folders_permission - @project1.enable_module! :dmsf # Flag module enabled + @project1.enable_module! :dmsf # Flag module enabled @role.add_permission! :file_manipulation # Check we don't have write access even if we do have the file_manipulation permission put "/dmsf/webdav/#{@project1.identifier}/test-1234.txt", '1234', @jsmith.merge!({:content_type => :text}) assert_response 409 # We don't hold the permission view_dmsf_folders, and thus project 2 doesn't exist to us. # Lets check for our file file = DmsfFile.find_file_by_name @project1, nil, 'test-1234.txt' - assert_nil file, 'File test-1234 was found in projects dmsf folder.' + assert_nil file, 'File test-1234 was found in projects dmsf folder.' end def test_put_succeeds_for_non_admin_with_correct_permissions - @project1.enable_module! :dmsf # Flag module enabled + @project1.enable_module! :dmsf # Flag module enabled @role.add_permission! :view_dmsf_folders - @role.add_permission! :file_manipulation + @role.add_permission! :file_manipulation put "/dmsf/webdav/#{@project1.identifier}/test-1234.txt", '1234', @jsmith.merge!({:content_type => :text}) assert_response :success # 201 - Now we have permissions # Lets check for our file file = DmsfFile.find_file_by_name @project1, nil, 'test-1234.txt' - assert file, 'File test-1234 was not found in projects dmsf folder.' + assert file, 'File test-1234 was not found in projects dmsf folder.' end def test_put_writes_revision_successfully_for_unlocked_file - @project1.enable_module! :dmsf #Flag module enabled + @project1.enable_module! :dmsf #Flag module enabled @role.add_permission! :view_dmsf_folders @role.add_permission! :file_manipulation file = DmsfFile.find_file_by_name @project1, nil, 'test.txt' assert_not_nil file, 'test.txt file not found' - assert_difference 'file.revisions.count', +1 do + assert_difference 'file.dmsf_file_revisions.count', +1 do put "/dmsf/webdav/#{@project1.identifier}/test.txt", '1234', @jsmith.merge!({:content_type => :text}) assert_response :success # 201 - Created end @@ -163,10 +163,10 @@ class DmsfWebdavPutTest < RedmineDmsf::Test::IntegrationTest assert !User.current.anonymous?, 'Current user is not anonymous' file = DmsfFile.find_file_by_name @project1, nil, 'test.txt' assert file.lock!, "File failed to be locked by #{User.current.name}" - assert_no_difference 'file.revisions.count' do + assert_no_difference 'file.dmsf_file_revisions.count' do put "/dmsf/webdav/#{@project1.identifier}/test.txt", '1234', @jsmith.merge!({:content_type => :text}) assert_response 423 # Locked - end + end end def test_put_fails_revision_when_file_is_locked_and_user_is_administrator @@ -177,10 +177,10 @@ class DmsfWebdavPutTest < RedmineDmsf::Test::IntegrationTest assert !User.current.anonymous?, 'Current user is not anonymous' file = DmsfFile.find_file_by_name @project1, nil, 'test.txt' assert file.lock!, "File failed to be locked by #{User.current.name}" - assert_no_difference 'file.revisions.count' do + assert_no_difference 'file.dmsf_file_revisions.count' do put "/dmsf/webdav/#{@project1.identifier}/test.txt", '1234', @admin.merge!({:content_type => :text}) - assert_response 423 # Locked - end + assert_response 423 # Locked + end end def test_put_accepts_revision_when_file_is_locked_and_user_is_same_as_lock_holder @@ -191,10 +191,10 @@ class DmsfWebdavPutTest < RedmineDmsf::Test::IntegrationTest assert !User.current.anonymous?, 'Current user is not anonymous' file = DmsfFile.find_file_by_name @project1, nil, 'test.txt' assert file.lock!, "File failed to be locked by #{User.current.name}" - assert_difference 'file.revisions.count', +1 do + assert_difference 'file.dmsf_file_revisions.count', +1 do put "/dmsf/webdav/#{@project1.identifier}/test.txt", '1234', @jsmith.merge!({:content_type => :text}) assert_response :success # 201 - Created - end + end end - + end \ No newline at end of file diff --git a/test/unit/dmsf_file_test.rb b/test/unit/dmsf_file_test.rb index b113250c..0e1c9299 100644 --- a/test/unit/dmsf_file_test.rb +++ b/test/unit/dmsf_file_test.rb @@ -22,44 +22,44 @@ require File.expand_path('../../test_helper', __FILE__) class DmsfFileTest < RedmineDmsf::Test::UnitTest - fixtures :projects, :users, :email_addresses, :dmsf_folders, :dmsf_files, - :dmsf_file_revisions, :roles, :members, :member_roles, :dmsf_locks, + fixtures :projects, :users, :email_addresses, :dmsf_folders, :dmsf_files, + :dmsf_file_revisions, :roles, :members, :member_roles, :dmsf_locks, :dmsf_links - + def setup @admin = User.find_by_id 1 - @jsmith = User.find_by_id 2 + @jsmith = User.find_by_id 2 @project1 = Project.find_by_id 1 @file1 = DmsfFile.find_by_id 1 @file2 = DmsfFile.find_by_id 2 @file3 = DmsfFile.find_by_id 3 @file4 = DmsfFile.find_by_id 4 - @file5 = DmsfFile.find_by_id 5 + @file5 = DmsfFile.find_by_id 5 User.current = nil end - + def test_truth assert_kind_of User, @admin - assert_kind_of User, @jsmith + assert_kind_of User, @jsmith assert_kind_of Project, @project1 assert_kind_of DmsfFile, @file1 assert_kind_of DmsfFile, @file2 assert_kind_of DmsfFile, @file3 assert_kind_of DmsfFile, @file4 assert_kind_of DmsfFile, @file5 - end + end def test_project_file_count_differs_from_project_visibility_count assert_not_same(@project1.dmsf_files.count, @project1.dmsf_files.visible.count) end - def test_project_dmsf_file_listing_contains_deleted_items - assert @project1.dmsf_files.index{ |f| f.deleted? }, - 'Expected at least one deleted item in ' + def test_project_dmsf_file_listing_contains_deleted_items + assert @project1.dmsf_files.index{ |f| f.deleted? }, + 'Expected at least one deleted item in ' end - def test_project_dmsf_file_visible_listing_contains_no_deleted_items - assert @project1.dmsf_files.visible.index{ |f| f.deleted? }.nil?, + def test_project_dmsf_file_visible_listing_contains_no_deleted_items + assert @project1.dmsf_files.visible.index{ |f| f.deleted? }.nil?, 'There is a deleted file, this was unexpected' end @@ -71,68 +71,68 @@ class DmsfFileTest < RedmineDmsf::Test::UnitTest assert @file4.locked?, "#{@file4.name} is not locked" end - def test_file_with_folder_up_heirarchy_locked_is_reported_as_locked + def test_file_with_folder_up_heirarchy_locked_is_reported_as_locked assert @file5.locked?, "#{@file5.name} is not locked" end def test_file_locked_is_not_locked_for_user_who_locked User.current = @admin @file1.lock! - assert !@file1.locked_for_user?, + assert !@file1.locked_for_user?, "#{@file1.name} is locked for #{User.current.name}" - @file1.unlock! + @file1.unlock! end - + def test_file_locked_is_locked_for_user_who_didnt_lock User.current = @admin @file1.lock! User.current = @jsmith - assert @file1.locked_for_user?, + assert @file1.locked_for_user?, "#{@file1.name} is locked for #{User.current.name}" User.current = @admin - @file1.unlock! + @file1.unlock! end - + def test_file_with_no_locks_reported_unlocked assert !@file1.locked? end - - def test_delete_restore - assert_equal 1, @file4.revisions.visible.count + + def test_delete_restore + assert_equal 1, @file4.dmsf_file_revisions.visible.count assert_equal 2, @file4.referenced_links.visible.count end - + def test_delete User.current = @admin - @file4.folder.unlock! + @file4.dmsf_folder.unlock! assert @file4.delete(false), @file4.errors.full_messages.to_sentence assert @file4.deleted?, "File #{@file4.name} is not deleted" - assert_equal 0, @file4.revisions.visible.count + assert_equal 0, @file4.dmsf_file_revisions.visible.count # Links should not be deleted assert_equal 2, @file4.referenced_links.visible.count - @file4.folder.lock! + @file4.dmsf_folder.lock! end - + def test_restore User.current = @admin - @file4.folder.unlock! + @file4.dmsf_folder.unlock! assert @file4.delete(false), @file4.errors.full_messages.to_sentence - @file4.restore + @file4.restore assert !@file4.deleted?, "File #{@file4} hasn't been restored" - assert_equal 1, @file4.revisions.visible.count + assert_equal 1, @file4.dmsf_file_revisions.visible.count assert_equal 2, @file4.referenced_links.visible.count - @file4.folder.lock! + @file4.dmsf_folder.lock! end - - def test_destroy + + def test_destroy User.current = @admin - @file4.folder.unlock! - assert_equal 1, @file4.revisions.visible.count + @file4.dmsf_folder.unlock! + assert_equal 1, @file4.dmsf_file_revisions.visible.count assert_equal 2, @file4.referenced_links.visible.count - @file4.delete true - assert_equal 0, @file4.revisions.count + @file4.delete true + assert_equal 0, @file4.dmsf_file_revisions.count assert_equal 0, @file4.referenced_links.count - @file4.folder.lock! + @file4.dmsf_folder.lock! end end \ No newline at end of file From 8507ee9b09a2748ae6fd6243b3be239af157e896 Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Thu, 21 Apr 2016 14:56:47 +0200 Subject: [PATCH 55/94] The same version feature doen't work as expected #526 --- app/controllers/dmsf_files_controller.rb | 22 +++++++----------- app/controllers/dmsf_upload_controller.rb | 2 +- app/models/dmsf_file_revision.rb | 27 ++++++++++------------- app/views/dmsf_files/show.html.erb | 2 +- lib/redmine_dmsf/webdav/dmsf_resource.rb | 8 +++---- 5 files changed, 26 insertions(+), 35 deletions(-) diff --git a/app/controllers/dmsf_files_controller.rb b/app/controllers/dmsf_files_controller.rb index a7135962..c67b988e 100644 --- a/app/controllers/dmsf_files_controller.rb +++ b/app/controllers/dmsf_files_controller.rb @@ -128,24 +128,18 @@ class DmsfFilesController < ApplicationController revision.major_version = last_revision.major_version revision.minor_version = last_revision.minor_version version = params[:version].to_i + if version == 3 + revision.major_version = params[:custom_version_major].to_i + revision.minor_version = params[:custom_version_minor].to_i + else + revision.increase_version(version) + end file_upload = params[:file_upload] unless file_upload - revision.disk_filename = last_revision.disk_filename - if version == 3 - revision.major_version = params[:custom_version_major].to_i - revision.minor_version = params[:custom_version_minor].to_i - else - revision.increase_version(version, false) - end - revision.mime_type = last_revision.mime_type revision.size = last_revision.size + revision.disk_filename = last_revision.disk_filename + revision.mime_type = last_revision.mime_type else - if version == 3 - revision.major_version = params[:custom_version_major].to_i - revision.minor_version = params[:custom_version_minor].to_i - else - revision.increase_version(version, true) - end revision.size = file_upload.size revision.disk_filename = revision.new_storage_filename revision.mime_type = Redmine::MimeType.of(file_upload.original_filename) diff --git a/app/controllers/dmsf_upload_controller.rb b/app/controllers/dmsf_upload_controller.rb index ef32c92c..290c65f0 100644 --- a/app/controllers/dmsf_upload_controller.rb +++ b/app/controllers/dmsf_upload_controller.rb @@ -183,7 +183,7 @@ class DmsfUploadController < ApplicationController new_revision.major_version = commited_file[:custom_version_major].to_i new_revision.minor_version = commited_file[:custom_version_minor].to_i else - new_revision.increase_version(version, true) + new_revision.increase_version(version) end new_revision.mime_type = Redmine::MimeType.of(new_revision.name) new_revision.size = File.size(commited_disk_filepath) diff --git a/app/models/dmsf_file_revision.rb b/app/models/dmsf_file_revision.rb index 13d04d8b..5d22f1ec 100644 --- a/app/models/dmsf_file_revision.rb +++ b/app/models/dmsf_file_revision.rb @@ -200,23 +200,20 @@ class DmsfFileRevision < ActiveRecord::Base wf.assign(self.id) if wf && self.id end - def increase_version(version_to_increase, new_content) - if new_content - self.minor_version = case version_to_increase - when 2 then 0 - else self.minor_version + 1 - end - else - self.minor_version = case version_to_increase - when 1 then self.minor_version + 1 - when 2 then 0 - else self.minor_version - end + def increase_version(version_to_increase) + self.minor_version = case version_to_increase + when 1 + self.minor_version + 1 + when 2 + 0 + else + self.minor_version end - self.major_version = case version_to_increase - when 2 then self.major_version + 1 - else self.major_version + when 2 + self.major_version + 1 + else + major_version end end diff --git a/app/views/dmsf_files/show.html.erb b/app/views/dmsf_files/show.html.erb index 973a773b..ba2d2746 100644 --- a/app/views/dmsf_files/show.html.erb +++ b/app/views/dmsf_files/show.html.erb @@ -62,7 +62,7 @@ <%= render(:partial => '/dmsf/path', - :locals => {:folder => @file.folder, :filename => @file.title, :title => nil}) %> + :locals => {:folder => @file.dmsf_folder, :filename => @file.title, :title => nil}) %> <% if User.current.allowed_to?(:file_manipulation, @file.project) && !@file.locked_for_user? %> <%= error_messages_for('file') %> diff --git a/lib/redmine_dmsf/webdav/dmsf_resource.rb b/lib/redmine_dmsf/webdav/dmsf_resource.rb index 6996ddb9..e5873ad0 100644 --- a/lib/redmine_dmsf/webdav/dmsf_resource.rb +++ b/lib/redmine_dmsf/webdav/dmsf_resource.rb @@ -105,7 +105,7 @@ module RedmineDmsf # Hunt for files parent path f = false if (parent.projectless_path != '/') - f = parent.dmsf_folder if parent.dmsf_folder + f = parent.folder if parent.folder else f = nil end @@ -199,8 +199,8 @@ module RedmineDmsf return MethodNotAllowed if exist? # If we already exist, why waste the time trying to save? parent_folder = nil if (parent.projectless_path != '/') - return Conflict unless parent.dmsf_folder - parent_folder = parent.dmsf_folder.id + return Conflict unless parent.folder + parent_folder = parent.folder.id end f = DmsfFolder.new f.title = basename @@ -498,7 +498,7 @@ module RedmineDmsf new_revision.title = DmsfFileRevision.filename_to_title(basename) new_revision.description = nil new_revision.comment = nil - new_revision.increase_version(1, true) + new_revision.increase_version(1) new_revision.mime_type = Redmine::MimeType.of(new_revision.name) # Phusion passenger does not have a method "length" in its model From 52f245e913490498fdfcbe077d641176cb4389ed Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Fri, 22 Apr 2016 12:46:30 +0200 Subject: [PATCH 56/94] Add MD5 of each revision in the detail view of documents #527 --- app/controllers/dmsf_files_controller.rb | 6 ++ app/controllers/dmsf_upload_controller.rb | 1 + app/helpers/dmsf_helper.rb | 11 --- app/models/dmsf_file_revision.rb | 22 +++++ .../dmsf_files/_revision_access.html.erb | 20 ++--- app/views/dmsf_files/show.html.erb | 80 +++++++++---------- .../20160421150501_add_digest_to_revision.rb | 29 +++++++ lib/tasks/dmsf_create_digests.rake | 63 +++++++++++++++ 8 files changed, 170 insertions(+), 62 deletions(-) create mode 100644 db/migrate/20160421150501_add_digest_to_revision.rb create mode 100644 lib/tasks/dmsf_create_digests.rake diff --git a/app/controllers/dmsf_files_controller.rb b/app/controllers/dmsf_files_controller.rb index c67b988e..441bc5bf 100644 --- a/app/controllers/dmsf_files_controller.rb +++ b/app/controllers/dmsf_files_controller.rb @@ -139,10 +139,16 @@ class DmsfFilesController < ApplicationController revision.size = last_revision.size revision.disk_filename = last_revision.disk_filename revision.mime_type = last_revision.mime_type + if last_revision.digest.blank? + revision.digest = DmsfFileRevision.create_digest last_revision.disk_file + else + revision.digest = last_revision.digest + end else revision.size = file_upload.size revision.disk_filename = revision.new_storage_filename revision.mime_type = Redmine::MimeType.of(file_upload.original_filename) + revision.digest = DmsfFileRevision.create_digest file_upload.path end # Custom fields diff --git a/app/controllers/dmsf_upload_controller.rb b/app/controllers/dmsf_upload_controller.rb index 290c65f0..fdfc835c 100644 --- a/app/controllers/dmsf_upload_controller.rb +++ b/app/controllers/dmsf_upload_controller.rb @@ -187,6 +187,7 @@ class DmsfUploadController < ApplicationController end new_revision.mime_type = Redmine::MimeType.of(new_revision.name) new_revision.size = File.size(commited_disk_filepath) + new_revision.digest = DmsfFileRevision.create_digest commited_disk_filepath # Need to save file first to generate id for it in case of creation. # File id is needed to properly generate revision disk filename diff --git a/app/helpers/dmsf_helper.rb b/app/helpers/dmsf_helper.rb index 893a6a1a..a5f33c6c 100644 --- a/app/helpers/dmsf_helper.rb +++ b/app/helpers/dmsf_helper.rb @@ -67,17 +67,6 @@ module DmsfHelper return "#{Redmine::Utils.relative_url_root}/plugin_assets/#{plugin}/#{asset_type}/#{source}" end - def self.to_time(obj) - #Right, enough of bugs, let's try a better approach here. - return unless obj - return obj.to_time(ActiveRecord::Base.default_timezone) if obj.is_a?(String) - - # Why can't Mysql::Time conform to time object? - without a utc? method it breaks redmine's - # rendering method, so we convert it to string, and back into time - not the most efficient - # of methods - however seems functional. Not sure if MySQL - (obj.class.name == 'Mysql::Time') ? obj.to_s.to_time(ActiveRecord::Base.default_timezone) : obj - end - def self.dmsf_tree(parent, obj, tree = nil) tree ||= [] # Folders && files && links diff --git a/app/models/dmsf_file_revision.rb b/app/models/dmsf_file_revision.rb index 5d22f1ec..c628fc82 100644 --- a/app/models/dmsf_file_revision.rb +++ b/app/models/dmsf_file_revision.rb @@ -19,6 +19,8 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +require 'digest/md5' + class DmsfFileRevision < ActiveRecord::Base unloadable belongs_to :dmsf_file @@ -159,6 +161,7 @@ class DmsfFileRevision < ActiveRecord::Base new_revision.source_revision = self new_revision.user = User.current new_revision.name = self.name + new_revision.digest = self.digest new_revision end @@ -262,4 +265,23 @@ class DmsfFileRevision < ActiveRecord::Base format + ext end + def self.create_digest(path) + begin + Digest::MD5.file(path).hexdigest + rescue Exception => e + Rails.logger.error e.message + nil + end + end + + def create_digest + begin + self.digest = Digest::MD5.file(self.disk_file).hexdigest + true + rescue Exception => e + Rails.logger.error e.message + false + end + end + end \ No newline at end of file diff --git a/app/views/dmsf_files/_revision_access.html.erb b/app/views/dmsf_files/_revision_access.html.erb index a4a736fb..e2dd74f4 100644 --- a/app/views/dmsf_files/_revision_access.html.erb +++ b/app/views/dmsf_files/_revision_access.html.erb @@ -22,23 +22,23 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. %> -
    <%=l(:field_project)%> <%=l(:field_label_dmsf_workflow)%> <%=l(:label_document)%>
    - <% if assignment.dmsf_file_revision.file.project %> - <%= link_to_project(assignment.dmsf_file_revision.file.project) %> + <% if assignment.dmsf_file_revision.dmsf_file.project %> + <%= link_to_project(assignment.dmsf_file_revision.dmsf_file.project) %> <% end %> - <% if assignment.dmsf_workflow_step && assignment.dmsf_workflow_step.dmsf_workflow %> - <%= link_to(h(assignment.dmsf_workflow_step.dmsf_workflow.name), - edit_dmsf_workflow_path(assignment.dmsf_workflow_step.dmsf_workflow)) %> + <% if assignment.dmsf_workflow_step && assignment.dmsf_workflow_step.dmsf_workflow %> + <%= link_to(h(assignment.dmsf_workflow_step.dmsf_workflow.name), + edit_dmsf_workflow_path(assignment.dmsf_workflow_step.dmsf_workflow)) %> <% end %> - <% if assignment.dmsf_file_revision && assignment.dmsf_file_revision.file %> - <%= link_to(h(assignment.dmsf_file_revision.title), - {:controller => 'dmsf_files', :action => :show, :id => assignment.dmsf_file_revision.file }) %> + <% if assignment.dmsf_file_revision && assignment.dmsf_file_revision.dmsf_file %> + <%= link_to(h(assignment.dmsf_file_revision.title), + {:controller => 'dmsf_files', :action => :show, :id => assignment.dmsf_file_revision.dmsf_file }) %> <% end %> <% if assignment.dmsf_file_revision %> - <% if assignment.dmsf_file_revision.file.folder %> - <%= link_to(h(assignment.dmsf_file_revision.file.folder.title), - {:controller => 'dmsf', :action => 'show', :id => assignment.dmsf_file_revision.file.project, :folder_id => assignment.dmsf_file_revision.file.folder}) %> - <% elsif assignment.dmsf_file_revision.file.project %> - <%= link_to(l(:link_documents), {:controller => 'dmsf', :action => 'show', :id => assignment.dmsf_file_revision.file.project }) %> + <% if assignment.dmsf_file_revision.dmsf_file.dmsf_folder %> + <%= link_to(h(assignment.dmsf_file_revision.dmsf_file.dmsf_folder.title), + {:controller => 'dmsf', :action => 'show', :id => assignment.dmsf_file_revision.dmsf_file.project, :folder_id => assignment.dmsf_file_revision.dmsf_file.dmsf_folder}) %> + <% elsif assignment.dmsf_file_revision.dmsf_file.project %> + <%= link_to(l(:link_documents), {:controller => 'dmsf', :action => 'show', :id => assignment.dmsf_file_revision.dmsf_file.project }) %> <% end %> <% end %>
    +
    - - - - + + + + <% revision.access_grouped.each do |access| %> - - - - + + + + <% end %> -
    <%= l(:field_user) %><%= l(:heading_access_downloads_emails) %><%= l(:heading_access_first) %><%= l(:heading_access_last) %><%= l(:field_user) %><%= l(:heading_access_downloads_emails) %><%= l(:heading_access_first) %><%= l(:heading_access_last) %>
    <%= link_to_user(access.user) %><%= access['count'] %><%= format_time(DmsfHelper::to_time(access.first_at)) %><%= format_time(DmsfHelper::to_time(access.last_at)) %><%= link_to_user(access.user) %><%= access.count %><%= format_time(access.first_at) %><%= format_time(access.last_at) %>
    \ No newline at end of file + diff --git a/app/views/dmsf_files/show.html.erb b/app/views/dmsf_files/show.html.erb index ba2d2746..cd0f9484 100644 --- a/app/views/dmsf_files/show.html.erb +++ b/app/views/dmsf_files/show.html.erb @@ -98,70 +98,68 @@ <%= link_to(revision.user.name, user_path(revision.user)) if revision.user %> -
    -
    +
    +

    <%= label_tag('', l(:label_title)) %> <%= h(revision.title) %>

    -
    -
    -

    - <%= label_tag('', l(:label_file)) %> - <%= ("#{h(revision.dmsf_file.dmsf_folder.dmsf_path_str)}/") if revision.dmsf_file.dmsf_folder %><%= h(revision.name) %> -

    -
    -
    - <% if revision.description.present? %> -

    - <%= label_tag('', l(:label_description)) %> -

    - <%= textilizable(revision.description) %> -
    -

    - <% end %> -
    -
    + <% if revision.description.present? %> +

    + <%= label_tag('', l(:label_description)) %> +

    + <%= textilizable(revision.description) %> +
    +

    + <% end %>

    <%= label_tag('', l(:label_version)) %> <%= revision.major_version %>.<%= revision.minor_version %>

    - <%= label_tag('', l(:link_workflow)) %> - <% wf = DmsfWorkflow.find_by_id(revision.dmsf_workflow_id) %> - <% if wf %> + <%= label_tag('', l(:label_size)) %> + <%= number_to_human_size(revision.size) %> +

    + <% wf = DmsfWorkflow.find_by_id(revision.dmsf_workflow_id) %> + <% if wf %> +

    + <%= label_tag('', l(:link_workflow)) %> <%= "#{wf.name} - " %> <%= link_to(revision.workflow_str(false), log_dmsf_workflow_path(:project_id => @project.id, :id => wf.id, :dmsf_file_revision_id => revision.id), :title => DmsfWorkflow.assignments_to_users_str(wf.next_assignments(revision.id)), :remote => true) %> - <% else %> - <%= revision.workflow_str(true) %> - <% end %> -

    +

    + <% end %> + <% if revision.comment.present? %> +

    + <%= label_tag('', l(:label_comment)) %> +

    + <%= textilizable(revision.comment) %> +
    +

    + <% end %>
    +

    + <%= label_tag('', l(:label_file)) %> + <%= ("#{h(revision.dmsf_file.dmsf_folder.dmsf_path_str)}/") if revision.dmsf_file.dmsf_folder %><%= h(revision.name) %> +

    <%= label_tag('', l(:label_mime)) %> <%= h(revision.mime_type) %>

    -

    - <%= label_tag('', l(:label_size)) %> - <%= number_to_human_size(revision.size) %> -

    + <% if revision.digest.present? %> +

    + <%= label_tag('', 'MD5') %> + <%= revision.digest %> +

    + <% end %> + <%= render 'dmsf/custom_fields', :object => revision %>
    - <%= render 'dmsf/custom_fields', :object => revision %> - <% if revision.comment.present? %> -

    - <%= label_tag('', l(:label_comment)) %> -

    - <%= textilizable(revision.comment) %> -
    -

    - <% end %>
    " style="display:none"> <%= render(:partial => 'revision_access', :locals => {:revision => revision}) if User.current.allowed_to?(:file_manipulation, @file.project) %>
    @@ -208,7 +206,7 @@ } }); - $('.access-table').dataTable({ + $('.dmsf_list').dataTable({ 'bJQueryUI': true, 'oLanguage': { 'sUrl': '/plugin_assets/<%= :redmine_dmsf %>/javascripts/<%= url %>' diff --git a/db/migrate/20160421150501_add_digest_to_revision.rb b/db/migrate/20160421150501_add_digest_to_revision.rb new file mode 100644 index 00000000..a6babd0d --- /dev/null +++ b/db/migrate/20160421150501_add_digest_to_revision.rb @@ -0,0 +1,29 @@ +# encoding: utf-8 +# +# Redmine plugin for Document Management System "Features" +# +# Copyright (C) 2011-16 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 AddDigestToRevision < ActiveRecord::Migration + def up + add_column :dmsf_file_revisions, :digest, :string, :limit => 40, :default => '', :null => false + end + + def down + remove_column :dmsf_file_revisions, :digest + end +end diff --git a/lib/tasks/dmsf_create_digests.rake b/lib/tasks/dmsf_create_digests.rake new file mode 100644 index 00000000..54a92118 --- /dev/null +++ b/lib/tasks/dmsf_create_digests.rake @@ -0,0 +1,63 @@ +# encoding: utf-8 +# +# Redmine plugin for Document Management System "Features" +# +# Copyright (C) 2011-16 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. + +desc <<-END_DESC +DMSF maintenance task + * Create missing MD5 digest for all file revisions + +Available options: + *dry_run - test, no changes to he database + +Example: + bundle exec rake redmine:dmsf_create_digests RAILS_ENV="production" + bundle exec rake redmine:dmsf_create_digests dry_run=1 RAILS_ENV="production" +END_DESC + +namespace :redmine do + task :dmsf_create_digests => :environment do + m = DmsfDigest.new + m.create_digests + end +end + +class DmsfDigest + + def initialize + @dry_run = ENV['dry_run'] + end + + def create_digests + revisions = DmsfFileRevision.where("digest IS NULL OR digest = ''").all + count = revisions.count + n = 0 + revisions.each_with_index do |rev, i| + if rev.create_digest + rev.save unless @dry_run + n += 1 + end + # Progress bar + print "\r#{i * 100 / count}%" + end + print "\r100%\n" + # Result + puts "#{n}/#{DmsfFileRevision.count} revisions updated." + end + +end From c1918b6f779a937db0f71fcc4f04405925315a23 Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Mon, 25 Apr 2016 12:48:42 +0200 Subject: [PATCH 57/94] nautilus-like folders-files list view #252 --- app/models/dmsf_file.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/dmsf_file.rb b/app/models/dmsf_file.rb index 3ac69a4a..c8f77f95 100644 --- a/app/models/dmsf_file.rb +++ b/app/models/dmsf_file.rb @@ -316,7 +316,7 @@ class DmsfFile < ActiveRecord::Base results = [] - scope = self.visible.joins(:project, :revisions) + scope = self.visible.joins(:project, :dmsf_file_revisions) scope = scope.limit(options[:limit]) unless options[:limit].blank? scope = scope.where(limit_options) unless limit_options.blank? scope = scope.where(project_conditions.join(' AND ')) @@ -330,7 +330,7 @@ class DmsfFile < ActiveRecord::Base Setting.plugin_redmine_dmsf['dmsf_index_database'].strip, lang) database = Xapian::Database.new(databasepath) rescue Exception => e - Rails.logger.warn 'REDMAIN_XAPIAN ERROR: Xapian database is not properly set or initiated or is corrupted.' + Rails.logger.warn "REDMAIN_XAPIAN ERROR: Xapian database is not properly set, initiated or it's corrupted." Rails.logger.warn e.message end From d819670f0c869d641d51f56f4cb395dc25b11ab4 Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Mon, 25 Apr 2016 13:51:07 +0200 Subject: [PATCH 58/94] nautilus-like folders-files list view #252 --- dmsf_user_guide.odt | Bin 1143049 -> 1142885 bytes lib/tasks/dmsf_create_digests.rake | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/dmsf_user_guide.odt b/dmsf_user_guide.odt index 8ca6d1bfc7eedeecd1036de41e88023340c62eb8..2560a8742b6bced835cd62e0c96c8750639ff6d2 100644 GIT binary patch delta 40773 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 wQ=%=xI2 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 delta 41258 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�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 V0=QEFK}*5I8*F@zxWVAn{{Xjjb Date: Mon, 25 Apr 2016 14:29:23 +0200 Subject: [PATCH 59/94] Tagging filter in sub-folders and tree view --- app/controllers/dmsf_controller.rb | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/app/controllers/dmsf_controller.rb b/app/controllers/dmsf_controller.rb index a6cf2e40..1d871963 100644 --- a/app/controllers/dmsf_controller.rb +++ b/app/controllers/dmsf_controller.rb @@ -40,13 +40,14 @@ class DmsfController < ApplicationController @force_file_unlock_allowed = User.current.allowed_to?(:force_file_unlock, @project) @workflows_available = DmsfWorkflow.where(['project_id = ? OR project_id IS NULL', @project.id]).count > 0 @file_approval_allowed = User.current.allowed_to?(:file_approval, @project) - @tree_view = (User.current.pref[:dmsf_tree_view] == '1') && (!%w(atom xml json).include?(params[:format])) - + tag = params[:custom_field_id].present? && params[:custom_value].present? + @tree_view = (User.current.pref[:dmsf_tree_view] == '1') && (!%w(atom xml json).include?(params[:format])) && !tag + @folder = nil if tag if @tree_view @locked_for_user = false else unless @folder - if params[:custom_field_id].present? && params[:custom_value].present? + if tag @subfolders = [] DmsfFolder.where(:project_id => @project.id).visible.each do |f| f.custom_field_values.each do |v| @@ -120,12 +121,10 @@ class DmsfController < ApplicationController end @locked_for_user = false else - if @folder.deleted? render_404 return end - @subfolders = @folder.dmsf_folders.visible @files = @folder.dmsf_files.visible @dir_links = @folder.folder_links.visible From d4fd440603ef835041b8296d8293796ae8f3f2a5 Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Mon, 25 Apr 2016 15:31:44 +0200 Subject: [PATCH 60/94] Wrong odd/even background style in the dmsf tree --- app/views/dmsf/_tree_view.erb | 7 ++++++- assets/stylesheets/redmine_dmsf.css | 2 ++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/app/views/dmsf/_tree_view.erb b/app/views/dmsf/_tree_view.erb index 8949ad52..dd467356 100644 --- a/app/views/dmsf/_tree_view.erb +++ b/app/views/dmsf/_tree_view.erb @@ -51,7 +51,12 @@ <% classes += ' dmsf_child' %> <% onclick = '' %> <% end %> - <% classes += ' dmsf_hidden' if obj.dmsf_folder && obj.dmsf_folder != @folder %> + <%if obj.dmsf_folder && obj.dmsf_folder != @folder %> + <% classes += ' dmsf_hidden' %> + <% else %> + <%# Force odd/even backgroung style for visible items %> + <% classes += " dmsf_#{cycle('odd', 'even')}" %> + <% end %> <% parent = obj.dmsf_folder %> <% while parent %> <% classes += " #{parent.id}" %> diff --git a/assets/stylesheets/redmine_dmsf.css b/assets/stylesheets/redmine_dmsf.css index ede409dd..38dbb2e8 100644 --- a/assets/stylesheets/redmine_dmsf.css +++ b/assets/stylesheets/redmine_dmsf.css @@ -244,3 +244,5 @@ tr.dmsf_tree.idnt-6 td.dmsf_title {padding-left: 4em;} tr.dmsf_tree.idnt-7 td.dmsf_title {padding-left: 4.5em;} tr.dmsf_tree.idnt-8 td.dmsf_title {padding-left: 5em;} tr.dmsf_tree.idnt-9 td.dmsf_title {padding-left: 5.5em;} +.dmsf_odd {background-color:#f6f7f8;} +.dmsf_even {background-color: #fff;} \ No newline at end of file From c9706bf19b19c738c174ee15c18f664426c747ce Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Tue, 26 Apr 2016 12:14:19 +0200 Subject: [PATCH 61/94] A project approval workflow is created as a global one when copying --- app/views/dmsf_workflows/new.html.erb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/views/dmsf_workflows/new.html.erb b/app/views/dmsf_workflows/new.html.erb index 3fccb993..a161906f 100644 --- a/app/views/dmsf_workflows/new.html.erb +++ b/app/views/dmsf_workflows/new.html.erb @@ -45,8 +45,7 @@ \ No newline at end of file + From 8582997d0250987b277cee74c3b2c86481c7af0c Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Wed, 27 Apr 2016 10:51:34 +0200 Subject: [PATCH 62/94] nautilus-like folders-files list view #252 --- app/models/dmsf_file_revision_access.rb | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/app/models/dmsf_file_revision_access.rb b/app/models/dmsf_file_revision_access.rb index bdaf493e..cb3ad307 100644 --- a/app/models/dmsf_file_revision_access.rb +++ b/app/models/dmsf_file_revision_access.rb @@ -24,17 +24,16 @@ class DmsfFileRevisionAccess < ActiveRecord::Base unloadable belongs_to :dmsf_file_revision belongs_to :user - delegate :project, :to => :revision, :allow_nil => false - delegate :file, :to => :revision, :allow_nil => false + delegate :dmsf_file, :to => :revision, :allow_nil => false DownloadAction = 0 EmailAction = 1 - acts_as_event :title => Proc.new {|o| "#{l(:label_dmsf_downloaded)}: #{o.dmsf_file.dmsf_path_str}"}, - :url => Proc.new {|o| {:controller => 'dmsf_files', :action => 'show', :id => o.dmsf_file}}, - :datetime => Proc.new {|o| o.updated_at }, - :description => Proc.new {|o| o.dmsf_file_revision.comment }, - :author => Proc.new {|o| o.user } + acts_as_event :title => Proc.new {|ra| "#{l(:label_dmsf_downloaded)}: #{ra.dmsf_file.dmsf_path_str}"}, + :url => Proc.new {|ra| {:controller => 'dmsf_files', :action => 'show', :id => ra.dmsf_file}}, + :datetime => Proc.new {|ra| ra.updated_at }, + :description => Proc.new {|ra| ra.dmsf_file_revision.comment }, + :author => Proc.new {|ra| ra.user } acts_as_activity_provider :type => 'dmsf_file_revision_accesses', :timestamp => "#{DmsfFileRevisionAccess.table_name}.updated_at", @@ -46,5 +45,4 @@ class DmsfFileRevisionAccess < ActiveRecord::Base "INNER JOIN #{DmsfFile.table_name} ON #{DmsfFileRevision.table_name}.dmsf_file_id = #{DmsfFile.table_name}.id " + "INNER JOIN #{Project.table_name} ON #{DmsfFile.table_name}.project_id = #{Project.table_name}.id"). where("#{DmsfFile.table_name}.deleted = ?", DmsfFile::STATUS_ACTIVE) - end From 542b9aa217e561457d6b22abaedc5065915e5ccf Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Wed, 27 Apr 2016 11:01:48 +0200 Subject: [PATCH 63/94] nautilus-like folders-files list view #252 --- app/models/dmsf_file_revision_access.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/models/dmsf_file_revision_access.rb b/app/models/dmsf_file_revision_access.rb index cb3ad307..61d8a87f 100644 --- a/app/models/dmsf_file_revision_access.rb +++ b/app/models/dmsf_file_revision_access.rb @@ -24,7 +24,8 @@ class DmsfFileRevisionAccess < ActiveRecord::Base unloadable belongs_to :dmsf_file_revision belongs_to :user - delegate :dmsf_file, :to => :revision, :allow_nil => false + delegate :dmsf_file, :to => :dmsf_file_revision, :allow_nil => false + delegate :project, :to => :dmsf_file, :allow_nil => false DownloadAction = 0 EmailAction = 1 From 39f3a92e46bb52410172808b34840e18cefaa012 Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Mon, 2 May 2016 10:31:43 +0200 Subject: [PATCH 64/94] NoParse class removed --- config/routes.rb | 3 +- lib/redmine_dmsf.rb | 1 - lib/redmine_dmsf/webdav/no_parse.rb | 48 ----------------------------- 3 files changed, 1 insertion(+), 51 deletions(-) delete mode 100644 lib/redmine_dmsf/webdav/no_parse.rb diff --git a/config/routes.rb b/config/routes.rb index d1d962a7..8f86500c 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -110,8 +110,7 @@ RedmineApp::Application.routes.draw do get '/dmsf/folders/:id/copy', :controller => 'dmsf_folders_copy', :action => 'new', :as => 'copy_folder' # - # DAV4Rack implementation of Webdav [note: if changing path you'll need to update lib/redmine_dmsf/webdav/no_parse.rb also] - # /dmsf/webdav + # DAV4Rack implementation of Webdav mount DAV4Rack::Handler.new( :root_uri_path => "#{Redmine::Utils::relative_url_root}/dmsf/webdav", :resource_class => RedmineDmsf::Webdav::ResourceProxy, diff --git a/lib/redmine_dmsf.rb b/lib/redmine_dmsf.rb index 96a378dc..5b9718b2 100644 --- a/lib/redmine_dmsf.rb +++ b/lib/redmine_dmsf.rb @@ -32,7 +32,6 @@ require 'redmine_dmsf/patches/project_tabs_extended' require 'redmine_dmsf/patches/user_preference_patch' # Load up classes that make up our WebDAV solution ontop of DAV4Rack -require 'redmine_dmsf/webdav/no_parse' require 'redmine_dmsf/webdav/base_resource' require 'redmine_dmsf/webdav/controller' require 'redmine_dmsf/webdav/dmsf_resource' diff --git a/lib/redmine_dmsf/webdav/no_parse.rb b/lib/redmine_dmsf/webdav/no_parse.rb deleted file mode 100644 index 4c1edf7a..00000000 --- a/lib/redmine_dmsf/webdav/no_parse.rb +++ /dev/null @@ -1,48 +0,0 @@ -# encoding: utf-8 -# -# Redmine plugin for Document Management System "Features" -# -# Copyright (C) 2012 Daniel Munn -# Copyright (C) 2011-16 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. - -module RedmineDmsf - class NoParse - def initialize(app, options={}) - @app = app - @urls = options[:urls] - end - - def call(env) - if env['REQUEST_METHOD'] == 'PUT' && env.has_key?('CONTENT_TYPE') then - if (@urls.dup.delete_if {|x| !env['PATH_INFO'].starts_with? x}.length > 0) then - Rails.logger.info "RedmineDmsf::NoParse prevented mime parsing for PUT #{env['PATH_INFO']}" - env['CONTENT_TYPE'] = 'text/plain' - end - end - @app.call(env) - end - - end -end - -# Todo: -# 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 => ["#{Redmine::Utils::relative_url_root}/dmsf/webdav"]) \ No newline at end of file From 8645fd257d5ff07222e3219909c376a8237be715 Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Wed, 4 May 2016 13:38:34 +0200 Subject: [PATCH 65/94] new WF command fails in the administration --- app/views/dmsf_workflows/new.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/dmsf_workflows/new.html.erb b/app/views/dmsf_workflows/new.html.erb index a161906f..51c89660 100644 --- a/app/views/dmsf_workflows/new.html.erb +++ b/app/views/dmsf_workflows/new.html.erb @@ -45,7 +45,7 @@ From b4744efa998a667e9c349f1edf764d68c673f76e Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Fri, 6 May 2016 09:24:02 +0200 Subject: [PATCH 66/94] :label_version => :label_dmsf_version --- app/views/dmsf/_list_view.erb | 2 +- app/views/dmsf/_tree_view.erb | 2 +- app/views/dmsf/trash.html.erb | 2 +- app/views/dmsf_files/_file_new_revision.html.erb | 2 +- app/views/dmsf_files/show.html.erb | 2 +- app/views/dmsf_mailer/files_deleted.html.erb | 2 +- app/views/dmsf_mailer/files_deleted.text.erb | 2 +- app/views/dmsf_mailer/files_updated.html.erb | 2 +- app/views/dmsf_mailer/files_updated.text.erb | 2 +- app/views/dmsf_upload/_upload_file.html.erb | 2 +- app/views/dmsf_upload/_upload_file_locked.html.erb | 2 +- config/locales/cs.yml | 1 + config/locales/de.yml | 1 + config/locales/en.yml | 1 + config/locales/es.yml | 1 + config/locales/fr.yml | 1 + config/locales/ja.yml | 1 + config/locales/pl.yml | 1 + config/locales/pt-BR.yml | 1 + config/locales/ru.yml | 1 + config/locales/sl.yml | 1 + config/locales/zh-TW.yml | 1 + config/locales/zh.yml | 1 + 23 files changed, 23 insertions(+), 11 deletions(-) diff --git a/app/views/dmsf/_list_view.erb b/app/views/dmsf/_list_view.erb index 6ef48f67..c89f9113 100644 --- a/app/views/dmsf/_list_view.erb +++ b/app/views/dmsf/_list_view.erb @@ -29,7 +29,7 @@ <%= l(:link_title) %> <%= l(:link_size) %> <%= l(:link_modified) %> - <%= l(:link_ver) %> + <%= l(:link_ver) %> <%= l(:link_workflow) %> <%= l(:link_author) %> diff --git a/app/views/dmsf/_tree_view.erb b/app/views/dmsf/_tree_view.erb index dd467356..6c8aed60 100644 --- a/app/views/dmsf/_tree_view.erb +++ b/app/views/dmsf/_tree_view.erb @@ -29,7 +29,7 @@ <%= l(:link_title) %> <%= l(:link_size) %> <%= l(:link_modified) %> - <%= l(:link_ver) %> + <%= l(:link_ver) %> <%= l(:link_workflow) %> <%= l(:link_author) %> diff --git a/app/views/dmsf/trash.html.erb b/app/views/dmsf/trash.html.erb index 9b9a232c..9d1af75e 100644 --- a/app/views/dmsf/trash.html.erb +++ b/app/views/dmsf/trash.html.erb @@ -53,7 +53,7 @@ <%= l(:link_title) %> <%= l(:link_size) %> <%= l(:link_modified) %> - <%= l(:link_ver) %> + <%= l(:link_ver) %> <%= l(:link_workflow) %> <%= l(:link_author) %> diff --git a/app/views/dmsf_files/_file_new_revision.html.erb b/app/views/dmsf_files/_file_new_revision.html.erb index 215efc29..27b38162 100644 --- a/app/views/dmsf_files/_file_new_revision.html.erb +++ b/app/views/dmsf_files/_file_new_revision.html.erb @@ -48,7 +48,7 @@

    - <%= label_tag('version_0', l(:label_version)) %> + <%= label_tag('version_0', l(:label_dmsf_version)) %> <%= radio_button_tag('version', 0, @revision.version == @file.last_revision.version) %> <%= @file.last_revision.major_version %>.<%= @file.last_revision.minor_version %> <%= l(:option_version_same) %>
    diff --git a/app/views/dmsf_files/show.html.erb b/app/views/dmsf_files/show.html.erb index cd0f9484..191d1d9d 100644 --- a/app/views/dmsf_files/show.html.erb +++ b/app/views/dmsf_files/show.html.erb @@ -114,7 +114,7 @@

    <% end %>

    - <%= label_tag('', l(:label_version)) %> + <%= label_tag('', l(:label_dmsf_version)) %> <%= revision.major_version %>.<%= revision.minor_version %>

    diff --git a/app/views/dmsf_mailer/files_deleted.html.erb b/app/views/dmsf_mailer/files_deleted.html.erb index 6000d864..7e780433 100644 --- a/app/views/dmsf_mailer/files_deleted.html.erb +++ b/app/views/dmsf_mailer/files_deleted.html.erb @@ -24,7 +24,7 @@

    <%= h(file.dmsf_path_str) %> (<%= file.name %>) <% if file.last_revision %> - , <%= number_to_human_size(file.last_revision.size) %>, <%= l(:label_version) %> <%= file.last_revision.major_version %>.<%= file.last_revision.minor_version %> + , <%= number_to_human_size(file.last_revision.size) %>, <%= l(:label_dmsf_version) %> <%= file.last_revision.major_version %>.<%= file.last_revision.minor_version %> <% end %>

    <% end %> \ No newline at end of file diff --git a/app/views/dmsf_mailer/files_deleted.text.erb b/app/views/dmsf_mailer/files_deleted.text.erb index a6cd02a0..bfd77ff6 100644 --- a/app/views/dmsf_mailer/files_deleted.text.erb +++ b/app/views/dmsf_mailer/files_deleted.text.erb @@ -22,6 +22,6 @@ <% @files.each do |file| %> <%= h(file.dmsf_path_str) %> (<%= file.name %>) <% if file.last_revision %> - , <%= number_to_human_size(file.last_revision.size) %>, <%= l(:label_version) %> <%= file.last_revision.major_version %>.<%= file.last_revision.minor_version %> + , <%= number_to_human_size(file.last_revision.size) %>, <%= l(:label_dmsf_version) %> <%= file.last_revision.major_version %>.<%= file.last_revision.minor_version %> <% end %> <% end %> \ No newline at end of file diff --git a/app/views/dmsf_mailer/files_updated.html.erb b/app/views/dmsf_mailer/files_updated.html.erb index d3e2a999..cd366281 100644 --- a/app/views/dmsf_mailer/files_updated.html.erb +++ b/app/views/dmsf_mailer/files_updated.html.erb @@ -26,7 +26,7 @@ dmsf_file_url(file, :download => '')) %> (<%= file.name %>), <%= number_to_human_size(file.last_revision.size) %>, - <%= l(:label_version) %> <%= file.last_revision.major_version %>.<%= file.last_revision.minor_version %>, + <%= l(:label_dmsf_version) %> <%= file.last_revision.major_version %>.<%= file.last_revision.minor_version %>, <%= "#{file.last_revision.workflow_str(true)}," if file.last_revision.workflow_str(true) != l(:title_none) %> <%= link_to(l(:link_details, :title => h(file.title)), dmsf_file_url(file)) %> diff --git a/app/views/dmsf_mailer/files_updated.text.erb b/app/views/dmsf_mailer/files_updated.text.erb index 899d3b71..de0d8d9f 100644 --- a/app/views/dmsf_mailer/files_updated.text.erb +++ b/app/views/dmsf_mailer/files_updated.text.erb @@ -23,7 +23,7 @@ <% @files.each do |file| %> <%= h(file.dmsf_path_str) %> (<%= file.name %>), <%= number_to_human_size(file.last_revision.size) %>, - <%= l(:label_version) %> <%= file.last_revision.major_version %>.<%= file.last_revision.minor_version %>, + <%= l(:label_dmsf_version) %> <%= file.last_revision.major_version %>.<%= file.last_revision.minor_version %>, <%= "#{file.last_revision.workflow_str(true)}," if file.last_revision.workflow_str(true) != l(:title_none) %> <%= dmsf_file_url(file) %> <% if file.last_revision.comment.present? %> diff --git a/app/views/dmsf_upload/_upload_file.html.erb b/app/views/dmsf_upload/_upload_file.html.erb index c7fb7edb..a7c27f1c 100644 --- a/app/views/dmsf_upload/_upload_file.html.erb +++ b/app/views/dmsf_upload/_upload_file.html.erb @@ -46,7 +46,7 @@

    - <%= label_tag("commited_files[#{i}][version]_minor", l(:label_version)) %> + <%= label_tag("commited_files[#{i}][version]_minor", l(:label_dmsf_version)) %> <%= radio_button_tag("commited_files[#{i}][version]", 1, true) %> <%= upload.major_version %>.<%= upload.minor_version + 1 %> <%= l(:option_version_minor) %>
    diff --git a/app/views/dmsf_upload/_upload_file_locked.html.erb b/app/views/dmsf_upload/_upload_file_locked.html.erb index 6abdf98a..97a52432 100644 --- a/app/views/dmsf_upload/_upload_file_locked.html.erb +++ b/app/views/dmsf_upload/_upload_file_locked.html.erb @@ -51,7 +51,7 @@

    - <%= label_tag('', l(:label_version)) %> + <%= label_tag('', l(:label_dmsf_version)) %> <%= upload.major_version %>.<%= upload.minor_version %>

    diff --git a/config/locales/cs.yml b/config/locales/cs.yml index 3fc3c650..36a9f54f 100644 --- a/config/locales/cs.yml +++ b/config/locales/cs.yml @@ -323,6 +323,7 @@ cs: error_unlock_parent_locked: Nelze odemknout - nadřazený objekt je zamčený field_dmsf_tree_view: Zobrazit složky jako stromovou strukturu + label_dmsf_version: Verze my: blocks: diff --git a/config/locales/de.yml b/config/locales/de.yml index bdeef3e3..75cf3cee 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -323,6 +323,7 @@ de: error_unlock_parent_locked: Unlock failed - resource parent is locked field_dmsf_tree_view: Navigate folders in a tree + label_dmsf_version: Version my: blocks: diff --git a/config/locales/en.yml b/config/locales/en.yml index 0f6e1140..4a472eb6 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -323,6 +323,7 @@ en: error_unlock_parent_locked: Unlock failed - resource parent is locked field_dmsf_tree_view: Navigate folders in a tree + label_dmsf_version: Version my: blocks: diff --git a/config/locales/es.yml b/config/locales/es.yml index ad698efd..bd89668f 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -323,6 +323,7 @@ es: error_unlock_parent_locked: Unlock failed - resource parent is locked field_dmsf_tree_view: Navigate folders in a tree + label_dmsf_version: Versión my: blocks: diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 62e19416..f2a9ed32 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -323,6 +323,7 @@ fr: error_unlock_parent_locked: Unlock failed - resource parent is locked field_dmsf_tree_view: Navigate folders in a tree + label_dmsf_version: Version my: blocks: diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 47f2ebcb..349dc8a4 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -323,6 +323,7 @@ ja: error_unlock_parent_locked: Unlock failed - resource parent is locked field_dmsf_tree_view: Navigate folders in a tree + label_dmsf_version: バージョン my: blocks: diff --git a/config/locales/pl.yml b/config/locales/pl.yml index ee77e5a1..f23c8bfd 100644 --- a/config/locales/pl.yml +++ b/config/locales/pl.yml @@ -323,6 +323,7 @@ pl: error_unlock_parent_locked: Unlock failed - resource parent is locked field_dmsf_tree_view: Navigate folders in a tree + label_dmsf_version: Wersja my: blocks: diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml index 25c15dad..70cee525 100644 --- a/config/locales/pt-BR.yml +++ b/config/locales/pt-BR.yml @@ -323,6 +323,7 @@ pt-BR: error_unlock_parent_locked: Unlock failed - resource parent is locked field_dmsf_tree_view: Navigate folders in a tree + label_dmsf_version: Versão my: blocks: diff --git a/config/locales/ru.yml b/config/locales/ru.yml index 0cb33f61..4db75444 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -323,6 +323,7 @@ ru: error_unlock_parent_locked: Разблокировка не удалась - родительская запись заблокирована field_dmsf_tree_view: Navigate folders in a tree + label_dmsf_version: Версия my: blocks: diff --git a/config/locales/sl.yml b/config/locales/sl.yml index e477eb1f..46dff91c 100644 --- a/config/locales/sl.yml +++ b/config/locales/sl.yml @@ -323,6 +323,7 @@ sl: error_unlock_parent_locked: Unlock failed - resource parent is locked field_dmsf_tree_view: Navigate folders in a tree + label_dmsf_version: Verzija my: blocks: diff --git a/config/locales/zh-TW.yml b/config/locales/zh-TW.yml index 2ef6fcc9..f879ae03 100644 --- a/config/locales/zh-TW.yml +++ b/config/locales/zh-TW.yml @@ -323,6 +323,7 @@ zh-TW: error_unlock_parent_locked: Unlock failed - resource parent is locked field_dmsf_tree_view: Navigate folders in a tree + label_dmsf_version: 版本 my: blocks: diff --git a/config/locales/zh.yml b/config/locales/zh.yml index 5ef12d8f..07af3617 100644 --- a/config/locales/zh.yml +++ b/config/locales/zh.yml @@ -323,6 +323,7 @@ zh: error_unlock_parent_locked: Unlock failed - resource parent is locked field_dmsf_tree_view: Navigate folders in a tree + label_dmsf_version: 版本 my: blocks: From 0aa82d3ac3b320593b1734495c157a9f5c14faf0 Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Wed, 11 May 2016 14:32:29 +0200 Subject: [PATCH 67/94] My page block headers are not localized --- app/views/my/blocks/_locked_documents.html.erb | 8 +++++--- app/views/my/blocks/_open_approvals.html.erb | 4 ++-- config/locales/cs.yml | 6 ++---- config/locales/de.yml | 6 ++---- config/locales/en.yml | 6 ++---- config/locales/es.yml | 6 ++---- config/locales/fr.yml | 6 ++---- config/locales/ja.yml | 6 ++---- config/locales/pl.yml | 6 ++---- config/locales/pt-BR.yml | 6 ++---- config/locales/ru.yml | 6 ++---- config/locales/sl.yml | 6 ++---- config/locales/zh-TW.yml | 6 ++---- config/locales/zh.yml | 6 ++---- 14 files changed, 31 insertions(+), 53 deletions(-) diff --git a/app/views/my/blocks/_locked_documents.html.erb b/app/views/my/blocks/_locked_documents.html.erb index eb6086f9..4e6d150c 100644 --- a/app/views/my/blocks/_locked_documents.html.erb +++ b/app/views/my/blocks/_locked_documents.html.erb @@ -1,7 +1,9 @@ -<%# +<% +# encoding: utf-8 +# # Redmine plugin for Document Management System "Features" # -# Copyright (C) 2013 Karel Picman +# Copyright (C) 2011-16 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 @@ -24,7 +26,7 @@ <% files = DmsfFile.joins( 'JOIN dmsf_locks ON dmsf_files.id = dmsf_locks.entity_id').where( 'dmsf_locks.entity_type' => 0, 'dmsf_locks.user_id' => @user.id).all %> -

    <%= l(:label_my_locked_documents)%> (<%= folders.count %>/<%= files.count %>)

    +

    <%= l(:locked_documents)%> (<%= folders.count %>/<%= files.count %>)

    <% if folders.any? || files.any?%> <%= form_tag({}) do %> diff --git a/app/views/my/blocks/_open_approvals.html.erb b/app/views/my/blocks/_open_approvals.html.erb index a61c499a..83874721 100644 --- a/app/views/my/blocks/_open_approvals.html.erb +++ b/app/views/my/blocks/_open_approvals.html.erb @@ -1,4 +1,4 @@ -<%# +<% # encoding: utf-8 # # Redmine plugin for Document Management System "Features" @@ -32,7 +32,7 @@ <% assignments << assignment %> <% end %> <% end %> -

    <%= l(:label_my_open_approvals)%> (<%= assignments.count %>)

    +

    <%= l(:open_approvals)%> (<%= assignments.count %>)

    <% if assignments.any? %> <%= form_tag({}) do %>
    diff --git a/config/locales/cs.yml b/config/locales/cs.yml index 36a9f54f..fdfed8fe 100644 --- a/config/locales/cs.yml +++ b/config/locales/cs.yml @@ -325,10 +325,8 @@ cs: field_dmsf_tree_view: Zobrazit složky jako stromovou strukturu label_dmsf_version: Verze - my: - blocks: - locked_documents: Zamčené dokumenty - open_approvals: Procesy ke schválení + locked_documents: Zamčené dokumenty + open_approvals: Procesy ke schválení label_maximum_ajax_upload_filesize: Maximální velikost souboru nahratelná přes AJAX note_maximum_ajax_upload_filesize: Omezuje velikost souboru, který může být nahrán přes standardní rozhraní AJAX, jinak se použije standardní rozhraní Redminu. Číslo je v MB. diff --git a/config/locales/de.yml b/config/locales/de.yml index 75cf3cee..6ae07e6b 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -325,10 +325,8 @@ de: field_dmsf_tree_view: Navigate folders in a tree label_dmsf_version: Version - my: - blocks: - locked_documents: Gesperrte Dateien - open_approvals: Offene Genehmigungs-Workflows + locked_documents: Gesperrte Dateien + open_approvals: Offene Genehmigungs-Workflows label_maximum_ajax_upload_filesize: Maximale Dateigröße für den Upload via AJAX note_maximum_ajax_upload_filesize: Maximale Dateigröße für den Upload über die AJAX-Schnittstelle. Für größere Dateien muss der Standard-Uploader von Redmine verwendet werden. Angabe in MB. diff --git a/config/locales/en.yml b/config/locales/en.yml index 4a472eb6..ef60fa49 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -325,10 +325,8 @@ en: field_dmsf_tree_view: Navigate folders in a tree label_dmsf_version: Version - my: - blocks: - locked_documents: Locked documents - openap_provals: Open approvals + locked_documents: Locked documents + 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. diff --git a/config/locales/es.yml b/config/locales/es.yml index bd89668f..22189389 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -325,10 +325,8 @@ es: field_dmsf_tree_view: Navigate folders in a tree label_dmsf_version: Versión - my: - blocks: - locked_documents: Documentos bloqueados - open_approvals: Aprobaciones abiertas + locked_documents: Documentos bloqueados + open_approvals: Aprobaciones abiertas label_maximum_ajax_upload_filesize: "El máximo tamaño de archivo para subir por AJAX" note_maximum_ajax_upload_filesize: "El límite máximo de tamaño de archivo que puede ser subido por la interfaz AJAX estandar, de lo contrario se debe utilizar el formulario estandar de Redmine. El número es en MB." diff --git a/config/locales/fr.yml b/config/locales/fr.yml index f2a9ed32..7023ab65 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -325,10 +325,8 @@ fr: field_dmsf_tree_view: Navigate folders in a tree label_dmsf_version: Version - my: - blocks: - locked_documents: Documents verrouillés - open_approvals: Approbations en attente + locked_documents: Documents verrouillés + 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." diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 349dc8a4..28be9da0 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -325,10 +325,8 @@ ja: field_dmsf_tree_view: Navigate folders in a tree label_dmsf_version: バージョン - my: - blocks: - locked_documents: ロック中 - open_approvals: 未承認 + locked_documents: ロック中 + open_approvals: 未承認 label_maximum_ajax_upload_filesize: アップロードファイルサイズ上限 note_maximum_ajax_upload_filesize: アップロード可能なファイルサイズの上限。AjaxおよびRedmineの仕様に制限される(2ギガバイト程度までは確認済み)単位はMB。 diff --git a/config/locales/pl.yml b/config/locales/pl.yml index f23c8bfd..58792b10 100644 --- a/config/locales/pl.yml +++ b/config/locales/pl.yml @@ -325,10 +325,8 @@ pl: field_dmsf_tree_view: Navigate folders in a tree label_dmsf_version: Wersja - my: - blocks: - locked_documents: Dokumenty zablokowane - openap_provals: Otwarte procesy akceptacji + locked_documents: Dokumenty zablokowane + open_approvals: Otwarte procesy akceptacji 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. diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml index 70cee525..b6f50757 100644 --- a/config/locales/pt-BR.yml +++ b/config/locales/pt-BR.yml @@ -325,10 +325,8 @@ pt-BR: field_dmsf_tree_view: Navigate folders in a tree label_dmsf_version: Versão - my: - blocks: - locked_documents: Documentos bloqueados - open_approvals: Aprovações abertas + locked_documents: Documentos bloqueados + open_approvals: Aprovações abertas 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. diff --git a/config/locales/ru.yml b/config/locales/ru.yml index 4db75444..b529588d 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -325,10 +325,8 @@ ru: field_dmsf_tree_view: Navigate folders in a tree label_dmsf_version: Версия - my: - blocks: - locked_documents: Заблокированные документы - open_approvals: Открытые согласования + locked_documents: Заблокированные документы + open_approvals: Открытые согласования label_maximum_ajax_upload_filesize: Максимальный размер файла, загружаемого посредством AJAX note_maximum_ajax_upload_filesize: Превышает максимальный размер файла, загружаемого посредством интерфейса AJAX, для загрузки можно использовать стандартную форму Redmine. Размер указан в MB. diff --git a/config/locales/sl.yml b/config/locales/sl.yml index 46dff91c..115bc31a 100644 --- a/config/locales/sl.yml +++ b/config/locales/sl.yml @@ -325,10 +325,8 @@ sl: field_dmsf_tree_view: Navigate folders in a tree label_dmsf_version: Verzija - my: - blocks: - locked_documents: Locked documents - open_approvals: Open approvals + locked_documents: Locked documents + 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. diff --git a/config/locales/zh-TW.yml b/config/locales/zh-TW.yml index f879ae03..ff8a7160 100644 --- a/config/locales/zh-TW.yml +++ b/config/locales/zh-TW.yml @@ -325,10 +325,8 @@ zh-TW: field_dmsf_tree_view: Navigate folders in a tree label_dmsf_version: 版本 - my: - blocks: - locked_documents: Locked documents - open_approvals: Open approvals + locked_documents: Locked documents + 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. diff --git a/config/locales/zh.yml b/config/locales/zh.yml index 07af3617..dabf6d85 100644 --- a/config/locales/zh.yml +++ b/config/locales/zh.yml @@ -325,10 +325,8 @@ zh: field_dmsf_tree_view: Navigate folders in a tree label_dmsf_version: 版本 - my: - blocks: - locked_documents: Locked documents - open_approvals: Open approvals + locked_documents: Locked documents + 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. From 22e4a5c31c9adda257b6d6fa42b571bb8913116c Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Fri, 13 May 2016 14:39:10 +0200 Subject: [PATCH 68/94] Plugin uninstallation fails --- db/migrate/20141015132701_remove_folder_from_revision.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/db/migrate/20141015132701_remove_folder_from_revision.rb b/db/migrate/20141015132701_remove_folder_from_revision.rb index 007fdd43..403116e4 100644 --- a/db/migrate/20141015132701_remove_folder_from_revision.rb +++ b/db/migrate/20141015132701_remove_folder_from_revision.rb @@ -27,8 +27,8 @@ class RemoveFolderFromRevision < ActiveRecord::Migration add_column :dmsf_file_revisions, :dmsf_folder_id, :integer, :null => true DmsfFileRevision.find_each do |revision| - if revision.file - revision.dmsf_folder_id = revision.file.dmsf_folder_id + if revision.dmsf_file + revision.dmsf_folder_id = revision.dmsf_file.dmsf_folder_id revision.save end end From 9b440fdea3026dda27ffc812d2a49cc292122750 Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Tue, 24 May 2016 10:37:10 +0200 Subject: [PATCH 69/94] Moving files into a sub-directory fails --- lib/redmine_dmsf/webdav/dmsf_resource.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/redmine_dmsf/webdav/dmsf_resource.rb b/lib/redmine_dmsf/webdav/dmsf_resource.rb index e5873ad0..215c307d 100644 --- a/lib/redmine_dmsf/webdav/dmsf_resource.rb +++ b/lib/redmine_dmsf/webdav/dmsf_resource.rb @@ -254,8 +254,8 @@ module RedmineDmsf if(parent.projectless_path == '/') #Project root folder.dmsf_folder_id = nil else - return PreconditionFailed unless parent.exist? && parent.dmsf_folder - folder.dmsf_folder_id = parent.dmsf_folder.id + return PreconditionFailed unless parent.exist? && parent.folder + folder.dmsf_folder_id = parent.folder.id end folder.title = resource.basename folder.save ? Created : PreconditionFailed @@ -273,8 +273,8 @@ module RedmineDmsf if(parent.projectless_path == '/') #Project root f = nil else - return PreconditionFailed unless parent.exist? && parent.dmsf_folder - f = parent.dmsf_folder + return PreconditionFailed unless parent.exist? && parent.folder + f = parent.folder end return PreconditionFailed unless exist? && file return InternalServerError unless file.move_to(resource.project, f) @@ -351,7 +351,7 @@ module RedmineDmsf f = nil else return PreconditionFailed unless parent.exist? && parent.folder - f = parent.dmsf_folder + f = parent.folder end return PreconditionFailed unless exist? && file return InternalServerError unless file.copy_to(resource.project, f) From 13c8db9eac832300936eb50165db7f50447f92aa Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Fri, 3 Jun 2016 10:23:22 +0200 Subject: [PATCH 70/94] Redirect to the roor after deleting entries in case of tree view --- app/controllers/dmsf_controller.rb | 18 ++++++++---------- app/controllers/dmsf_files_controller.rb | 8 +++++++- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/app/controllers/dmsf_controller.rb b/app/controllers/dmsf_controller.rb index 1d871963..9d06bc9b 100644 --- a/app/controllers/dmsf_controller.rb +++ b/app/controllers/dmsf_controller.rb @@ -27,6 +27,7 @@ class DmsfController < ApplicationController before_filter :authorize before_filter :find_folder, :except => [:new, :create, :edit_root, :save_root] before_filter :find_parent, :only => [:new, :create] + before_filter :tree_view, :only => [:delete, :show] accept_api_auth :show, :create @@ -41,7 +42,7 @@ class DmsfController < ApplicationController @workflows_available = DmsfWorkflow.where(['project_id = ? OR project_id IS NULL', @project.id]).count > 0 @file_approval_allowed = User.current.allowed_to?(:file_approval, @project) tag = params[:custom_field_id].present? && params[:custom_value].present? - @tree_view = (User.current.pref[:dmsf_tree_view] == '1') && (!%w(atom xml json).include?(params[:format])) && !tag + #@tree_view = (User.current.pref[:dmsf_tree_view] == '1') && (!%w(atom xml json).include?(params[:format])) && !tag @folder = nil if tag if @tree_view @locked_for_user = false @@ -334,7 +335,7 @@ class DmsfController < ApplicationController else flash[:error] = @folder.errors.full_messages.to_sentence end - if commit + if commit || @tree_view redirect_to :back else redirect_to dmsf_folder_path(:id => @project, :folder_id => @folder.dmsf_folder) @@ -640,18 +641,15 @@ class DmsfController < ApplicationController render_404 end + def tree_view + tag = params[:custom_field_id].present? && params[:custom_value].present? + @tree_view = (User.current.pref[:dmsf_tree_view] == '1') && (!%w(atom xml json).include?(params[:format])) && !tag + end + def copy_folder(folder) copy = folder.clone copy.id = folder.id copy end - private - - def e_params - params.fetch(:email, {}).permit( - :to, :zipped_content, :email, - :cc, :subject, :zipped_content => [], :files => []) - 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 441bc5bf..348516c8 100644 --- a/app/controllers/dmsf_files_controller.rb +++ b/app/controllers/dmsf_files_controller.rb @@ -27,6 +27,7 @@ class DmsfFilesController < ApplicationController before_filter :find_file, :except => [:delete_revision] before_filter :find_revision, :only => [:delete_revision] before_filter :authorize + before_filter :tree_view, :only => [:delete] accept_api_auth :show @@ -230,7 +231,7 @@ class DmsfFilesController < ApplicationController flash[:error] = @file.errors.full_messages.join(', ') end end - if commit + if commit || @tree_view redirect_to :back else redirect_to dmsf_folder_path(:id => @project, :folder_id => @file.dmsf_folder) @@ -330,4 +331,9 @@ class DmsfFilesController < ApplicationController end end + def tree_view + tag = params[:custom_field_id].present? && params[:custom_value].present? + @tree_view = (User.current.pref[:dmsf_tree_view] == '1') && (!%w(atom xml json).include?(params[:format])) && !tag + end + end \ No newline at end of file From 8ef03b026f30b1fb5c4980a92787b1a64c8013d5 Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Fri, 3 Jun 2016 10:45:21 +0200 Subject: [PATCH 71/94] Modified timestamps lost after migration #532 --- app/controllers/dmsf_controller.rb | 1 - lib/tasks/dmsf_convert_documents.rake | 5 +++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/controllers/dmsf_controller.rb b/app/controllers/dmsf_controller.rb index 9d06bc9b..bf724030 100644 --- a/app/controllers/dmsf_controller.rb +++ b/app/controllers/dmsf_controller.rb @@ -42,7 +42,6 @@ class DmsfController < ApplicationController @workflows_available = DmsfWorkflow.where(['project_id = ? OR project_id IS NULL', @project.id]).count > 0 @file_approval_allowed = User.current.allowed_to?(:file_approval, @project) tag = params[:custom_field_id].present? && params[:custom_value].present? - #@tree_view = (User.current.pref[:dmsf_tree_view] == '1') && (!%w(atom xml json).include?(params[:format])) && !tag @folder = nil if tag if @tree_view @locked_for_user = false diff --git a/lib/tasks/dmsf_convert_documents.rake b/lib/tasks/dmsf_convert_documents.rake index 595661a6..30f7a61e 100644 --- a/lib/tasks/dmsf_convert_documents.rake +++ b/lib/tasks/dmsf_convert_documents.rake @@ -31,6 +31,7 @@ Available options: Example: rake redmine:dmsf_convert_documents project=test RAILS_ENV="production" + rake redmine:dmsf_convert_documents project=test dry=true RAILS_ENV="production" END_DESC class DmsfConvertDocuments @@ -111,7 +112,7 @@ class DmsfConvertDocuments begin file = DmsfFile.new file.project = project - file.folder = folder + file.dmsf_folder = folder file.name = attachment.filename i = 1 @@ -142,7 +143,7 @@ class DmsfConvertDocuments end revision = DmsfFileRevision.new - revision.file = file + revision.dmsf_file = file revision.name = file.name revision.title = DmsfFileRevision.filename_to_title(attachment.filename) revision.description = attachment.description From a73a89d738f06c71ff35511afef8449306d7383c Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Mon, 6 Jun 2016 09:25:30 +0200 Subject: [PATCH 72/94] A wrong URL in My Page's block --- app/views/my/blocks/_locked_documents.html.erb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/views/my/blocks/_locked_documents.html.erb b/app/views/my/blocks/_locked_documents.html.erb index 4e6d150c..30762435 100644 --- a/app/views/my/blocks/_locked_documents.html.erb +++ b/app/views/my/blocks/_locked_documents.html.erb @@ -32,9 +32,9 @@
    - - - + + + @@ -71,7 +71,7 @@ diff --git a/app/views/dmsf/_file.html.erb b/app/views/dmsf/_file.html.erb index c39fceaf..3a1af275 100644 --- a/app/views/dmsf/_file.html.erb +++ b/app/views/dmsf/_file.html.erb @@ -33,7 +33,7 @@ file_view_url, :target => '_blank', :class => "icon icon-file #{DmsfHelper.filetype_css(file.name)}", - :title => l(:title_title_version_version_download, :title => h(file.title), :version => file.version), + :title => file.tooltip, 'data-downloadurl' => "#{file.last_revision.detect_content_type}:#{h(file.name)}:#{file_view_url}") %>
    <%= h(link ? link.path : file.display_name) %>
    <%= ''.html_safe if @tree_view %> diff --git a/app/views/dmsf/_file_trash.html.erb b/app/views/dmsf/_file_trash.html.erb index ae53ce6d..dea5acc3 100644 --- a/app/views/dmsf/_file_trash.html.erb +++ b/app/views/dmsf/_file_trash.html.erb @@ -24,7 +24,7 @@ :title => l(:title_check_for_restore_or_delete), :id => "file_#{id}") %> From 0327b1e6a30564ad7b15a0694d4d742c2aa3a9ae Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Tue, 14 Jun 2016 14:39:51 +0200 Subject: [PATCH 80/94] Show document description in mouseover or column --- app/models/dmsf_file.rb | 5 +---- app/views/dmsf/_file_trash.html.erb | 2 +- lib/redmine_dmsf/macros.rb | 4 ++-- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/app/models/dmsf_file.rb b/app/models/dmsf_file.rb index b9ce4b39..f638bbab 100644 --- a/app/models/dmsf_file.rb +++ b/app/models/dmsf_file.rb @@ -452,7 +452,7 @@ class DmsfFile < ActiveRecord::Base false end - def tooltip(download_info=true) + def tooltip text = '' if self.last_revision if self.last_revision.description.present? @@ -465,9 +465,6 @@ class DmsfFile < ActiveRecord::Base text += self.last_revision.comment end end - if text.blank? && download_info - text = l(:title_title_version_version_download, :title => self.title, :version => self.version) - end text.html_safe end diff --git a/app/views/dmsf/_file_trash.html.erb b/app/views/dmsf/_file_trash.html.erb index dea5acc3..1e8d9f93 100644 --- a/app/views/dmsf/_file_trash.html.erb +++ b/app/views/dmsf/_file_trash.html.erb @@ -24,7 +24,7 @@ :title => l(:title_check_for_restore_or_delete), :id => "file_#{id}") %> diff --git a/lib/redmine_dmsf/macros.rb b/lib/redmine_dmsf/macros.rb index 51b333c4..4cdfcf43 100644 --- a/lib/redmine_dmsf/macros.rb +++ b/lib/redmine_dmsf/macros.rb @@ -40,7 +40,7 @@ Redmine::WikiFormatting::Macros.register do return link_to(h(args[1] ? args[1] : file.title), file_view_url, :target => '_blank', - :title => l(:title_title_version_version_download, :title => h(file.title), :version => file.version), + :title => h(file.tooltip), 'data-downloadurl' => "#{file.last_revision.detect_content_type}:#{h(file.name)}:#{file_view_url}") else raise l(:notice_not_authorized) @@ -161,7 +161,7 @@ Redmine::WikiFormatting::Macros.register do end link_to(img, file_view_url, :target => '_blank', - :title => l(:title_title_version_version_download, :title => h(file.title), :version => file.version), + :title => h(file.tooltip), 'data-downloadurl' => "#{file.last_revision.detect_content_type}:#{h(file.name)}:#{file_view_url}") else raise "Document ID #{file_id} not found" From f6097b30e7bee3de03d4cd6ec663695ff6490019 Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Wed, 15 Jun 2016 08:37:06 +0200 Subject: [PATCH 81/94] undefined method 'html_safe' for nil:NilClass --- app/views/dmsf/_dir.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/dmsf/_dir.html.erb b/app/views/dmsf/_dir.html.erb index aa69aed9..4a136309 100644 --- a/app/views/dmsf/_dir.html.erb +++ b/app/views/dmsf/_dir.html.erb @@ -32,7 +32,7 @@ <%= link_to(h(title), dmsf_folder_path(:id => project, :folder_id => subfolder), :class => 'icon icon-folder', - :title => subfolder.description.html_safe) %> + :title => h(subfolder.description)) %> <% if link %>
    <%= link.path %>
    <% else %> From 38676e63d79bc928ae30adabeddac26e015013ee Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Mon, 20 Jun 2016 15:53:26 +0200 Subject: [PATCH 82/94] Migration error with Redmine 3.3 (1.5.7 devel) #538 --- db/migrate/20160222140401_approval_workflow_std_fields.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/migrate/20160222140401_approval_workflow_std_fields.rb b/db/migrate/20160222140401_approval_workflow_std_fields.rb index bed7ca9d..d96578ac 100644 --- a/db/migrate/20160222140401_approval_workflow_std_fields.rb +++ b/db/migrate/20160222140401_approval_workflow_std_fields.rb @@ -26,7 +26,7 @@ class ApprovalWorkflowStdFields < ActiveRecord::Migration # Set updated_on DmsfWorkflow.all.each(&:touch) # Set created_on and author_id - DmsfWorkflow.update_all 'created_on = updated_on, author_id = (select id from users where admin = 1 limit 1)' + DmsfWorkflow.update_all 'created_on = updated_on, author_id = (select id from users where admin limit 1)' end def self.down From 94c93e85691c946801878a6237e90e6c0183771b Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Tue, 19 Jul 2016 11:54:16 +0200 Subject: [PATCH 83/94] Fixing project copy #546 --- lib/redmine_dmsf/patches/project_patch.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/redmine_dmsf/patches/project_patch.rb b/lib/redmine_dmsf/patches/project_patch.rb index 0e387417..cae7be20 100644 --- a/lib/redmine_dmsf/patches/project_patch.rb +++ b/lib/redmine_dmsf/patches/project_patch.rb @@ -67,7 +67,7 @@ module RedmineDmsf project = project.is_a?(Project) ? project : Project.find(project) to_be_copied = %w(dmsf approval_workflows) - to_be_copied = to_be_copied & options[:only].to_a if options[:only].present? + to_be_copied = to_be_copied & Array.wrap(options[:only]) unless options[:only].nil? if save to_be_copied.each do |name| From 7fc3e49a76f6edcfe808485682e8a2641d7f9f1b Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Tue, 19 Jul 2016 14:04:32 +0200 Subject: [PATCH 84/94] Drag&Drop in approval workflow steps ordering --- app/controllers/dmsf_workflows_controller.rb | 5 +- app/models/dmsf_workflow.rb | 55 ++++---------------- app/views/dmsf_workflows/_steps.html.erb | 14 ++--- 3 files changed, 23 insertions(+), 51 deletions(-) diff --git a/app/controllers/dmsf_workflows_controller.rb b/app/controllers/dmsf_workflows_controller.rb index d2f2d9f1..98a988b9 100644 --- a/app/controllers/dmsf_workflows_controller.rb +++ b/app/controllers/dmsf_workflows_controller.rb @@ -338,12 +338,15 @@ class DmsfWorkflowsController < ApplicationController def reorder_steps if request.put? - unless @dmsf_workflow.reorder_steps(params[:step].to_i, params[:workflow_step][:move_to]) + unless @dmsf_workflow.reorder_steps(params[:step].to_i, params[:dmsf_workflow][:position].to_i) flash[:error] = l(:notice_cannot_renumber_steps) end end respond_to do |format| format.html + format.js { + render inline: "location.replace('#{dmsf_workflow_path(@dmsf_workflow)}');" + } end end diff --git a/app/models/dmsf_workflow.rb b/app/models/dmsf_workflow.rb index d4010777..ed358a99 100644 --- a/app/models/dmsf_workflow.rb +++ b/app/models/dmsf_workflow.rb @@ -19,7 +19,6 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. class DmsfWorkflow < ActiveRecord::Base - has_many :dmsf_workflow_steps, -> { order 'step ASC, operator DESC' }, :dependent => :destroy belongs_to :author, :class_name => 'User' @@ -89,51 +88,19 @@ class DmsfWorkflow < ActiveRecord::Base def reorder_steps(step, move_to) DmsfWorkflow.transaction do - case move_to - when 'highest' - unless step == 1 - dmsf_workflow_steps.each do |ws| - if ws.step < step - return false unless ws.update_attribute('step', ws.step + 1) - elsif ws.step == step - return false unless ws.update_attribute('step', 1) - end - end - end - when 'higher' - unless step == 1 - dmsf_workflow_steps.each do |ws| - if ws.step == step - 1 - return false unless ws.update_attribute('step', step) - elsif ws.step == step - return false unless ws.update_attribute('step', step - 1) - end - end - end - when 'lower' - unless step == dmsf_workflow_steps.collect{|s| s.step}.uniq.count - dmsf_workflow_steps.each do |ws| - if ws.step == step + 1 - return false unless ws.update_attribute('step', step) - elsif ws.step == step - return false unless ws.update_attribute('step', step + 1) - end - end - end - when 'lowest' - size = dmsf_workflow_steps.collect{|s| s.step}.uniq.count - unless step == size - dmsf_workflow_steps.each do |ws| - if ws.step > step - return false unless ws.update_attribute('step', ws.step - 1) - elsif ws.step == step - return false unless ws.update_attribute('step', size) - end - end + dmsf_workflow_steps.each do |ws| + if ws.step == step + return false unless ws.update_attribute('step', move_to) + elsif ws.step >= move_to && ws.step < step + # Move up + return false unless ws.update_attribute('step', ws.step + 1) + elsif ws.step <= move_to && ws.step > step + # Move down + return false unless ws.update_attribute('step', ws.step - 1) end + end end - end - return reload + return true end def delegates(q, dmsf_workflow_step_assignment_id, dmsf_file_revision_id) diff --git a/app/views/dmsf_workflows/_steps.html.erb b/app/views/dmsf_workflows/_steps.html.erb index ef3c2699..5d244c57 100644 --- a/app/views/dmsf_workflows/_steps.html.erb +++ b/app/views/dmsf_workflows/_steps.html.erb @@ -42,11 +42,11 @@

    <% steps = @dmsf_workflow.dmsf_workflow_steps.collect{|s| s.step}.uniq %> <% if steps.any? %> -
    <%=l(:field_project)%><%=l(:label_document)%>/<%=l(:field_folder)%><%=l(:field_folder)%><%= l(:field_project) %><%= l(:label_document) %>/<%= l(:field_folder) %><%= l(:field_folder) %>
    <% if file.dmsf_folder %> <%= link_to(h(file.dmsf_folder.title), - {:controller => 'dmsf', :action => 'show', :id => file.project, :folder_id => file.folder}) %> + {:controller => 'dmsf', :action => 'show', :id => file.project, :folder_id => file.dmsf_folder}) %> <% else %> <%= link_to(l(:link_documents), {:controller => 'dmsf', :action => 'show', :id=> file.project }) %> <% end %> From 6f582a498cfde7ec9142041aa33d5f67857d0d25 Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Tue, 7 Jun 2016 13:02:56 +0200 Subject: [PATCH 73/94] A wrong parametrs order on the validation --- app/models/dmsf_workflow.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/dmsf_workflow.rb b/app/models/dmsf_workflow.rb index 10997257..50013408 100644 --- a/app/models/dmsf_workflow.rb +++ b/app/models/dmsf_workflow.rb @@ -36,7 +36,7 @@ class DmsfWorkflow < ActiveRecord::Base if self.project_id if self.id if (DmsfWorkflow.where(['(project_id IS NULL OR (project_id = ? AND id != ?)) AND name = ?', - self.project_id, self.name, self.id]).count > 0) + self.project_id, self.id, self.name]).count > 0) errors.add(:name, l('activerecord.errors.messages.taken')) end else From ab52e6ae096edefd85eb5fffbb3225663823c06f Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Tue, 7 Jun 2016 15:09:22 +0200 Subject: [PATCH 74/94] A wrong parametrs order in the validation --- app/controllers/dmsf_controller.rb | 2 +- app/models/dmsf_workflow.rb | 8 ++++---- db/migrate/20160222140401_approval_workflow_std_fields.rb | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/controllers/dmsf_controller.rb b/app/controllers/dmsf_controller.rb index bf724030..ae1419f1 100644 --- a/app/controllers/dmsf_controller.rb +++ b/app/controllers/dmsf_controller.rb @@ -39,7 +39,7 @@ class DmsfController < ApplicationController @file_delete_allowed = User.current.allowed_to?(:file_delete, @project) @file_view_allowed = User.current.allowed_to?(:view_dmsf_files, @project) @force_file_unlock_allowed = User.current.allowed_to?(:force_file_unlock, @project) - @workflows_available = DmsfWorkflow.where(['project_id = ? OR project_id IS NULL', @project.id]).count > 0 + @workflows_available = DmsfWorkflow.where(['project_id = ? OR project_id IS NULL', @project.id]).exists? @file_approval_allowed = User.current.allowed_to?(:file_approval, @project) tag = params[:custom_field_id].present? && params[:custom_value].present? @folder = nil if tag diff --git a/app/models/dmsf_workflow.rb b/app/models/dmsf_workflow.rb index 50013408..d4010777 100644 --- a/app/models/dmsf_workflow.rb +++ b/app/models/dmsf_workflow.rb @@ -36,22 +36,22 @@ class DmsfWorkflow < ActiveRecord::Base if self.project_id if self.id if (DmsfWorkflow.where(['(project_id IS NULL OR (project_id = ? AND id != ?)) AND name = ?', - self.project_id, self.id, self.name]).count > 0) + self.project_id, self.id, self.name]).exists?) errors.add(:name, l('activerecord.errors.messages.taken')) end else if (DmsfWorkflow.where(['(project_id IS NULL OR project_id = ?) AND name = ?', - self.project_id, self.name]).count > 0) + self.project_id, self.name]).exists?) errors.add(:name, l('activerecord.errors.messages.taken')) end end else if self.id - if DmsfWorkflow.where(['name = ? AND id != ?', self.name, self.id]).count > 0 + if DmsfWorkflow.where(['name = ? AND id != ?', self.name, self.id]).exists? errors.add(:name, l('activerecord.errors.messages.taken')) end else - if DmsfWorkflow.where(:name => self.name).count > 0 + if DmsfWorkflow.where(:name => self.name).exists? errors.add(:name, l('activerecord.errors.messages.taken')) end end diff --git a/db/migrate/20160222140401_approval_workflow_std_fields.rb b/db/migrate/20160222140401_approval_workflow_std_fields.rb index e27bdbe2..bed7ca9d 100644 --- a/db/migrate/20160222140401_approval_workflow_std_fields.rb +++ b/db/migrate/20160222140401_approval_workflow_std_fields.rb @@ -24,7 +24,7 @@ class ApprovalWorkflowStdFields < ActiveRecord::Migration add_column :dmsf_workflows, :created_on, :datetime add_column :dmsf_workflows, :author_id, :integer # Set updated_on - DmsfWorkflow.all.each(&:save) + DmsfWorkflow.all.each(&:touch) # Set created_on and author_id DmsfWorkflow.update_all 'created_on = updated_on, author_id = (select id from users where admin = 1 limit 1)' end From 9e937525afd50b7334bf0eeea75e463b61cd9dbc Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Wed, 8 Jun 2016 16:55:04 +0200 Subject: [PATCH 75/94] Cannot download folders with sub folders #530 --- lib/dmsf_zip.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/dmsf_zip.rb b/lib/dmsf_zip.rb index 5b66858a..c19a6b16 100644 --- a/lib/dmsf_zip.rb +++ b/lib/dmsf_zip.rb @@ -64,7 +64,7 @@ class DmsfZip string_path = string_path[(root_path.length + 1) .. string_path.length] if root_path @zip_file.put_next_entry(string_path) @folders << folder - folder.dmsf_folders.visible.each { |subfolder| self.add_folder(subfolder, root_path) } + folder.dmsf_folders.visible.each { |subfolder| self.add_folder(subfolder, member, root_path) } folder.dmsf_files.visible.each { |file| self.add_file(file, member, root_path) } end end From 2fd955183d3c7aacbfdadd6bcae7218502d7782c Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Fri, 10 Jun 2016 09:33:01 +0200 Subject: [PATCH 76/94] Email notification permissions #533 --- app/models/dmsf_file.rb | 11 +++++++++++ app/models/dmsf_mailer.rb | 23 +++++++++++++++++++---- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/app/models/dmsf_file.rb b/app/models/dmsf_file.rb index c8f77f95..bd8b2a3c 100644 --- a/app/models/dmsf_file.rb +++ b/app/models/dmsf_file.rb @@ -441,4 +441,15 @@ class DmsfFile < ActiveRecord::Base end end + def owner?(user) + self.last_revision && (self.last_revision.user == user) + end + + def involved?(user) + self.dmsf_file_revisions.each do |file_revision| + return true if file_revision.user == user + end + false + end + end diff --git a/app/models/dmsf_mailer.rb b/app/models/dmsf_mailer.rb index 666cc3b7..1f06bd48 100644 --- a/app/models/dmsf_mailer.rb +++ b/app/models/dmsf_mailer.rb @@ -88,8 +88,7 @@ class DmsfMailer < Mailer notify_files = files.select { |file| file.notify? } return [] if notify_files.empty? end - notify_members = project.members - notify_members = notify_members.select do |notify_member| + notify_members = project.members.select do |notify_member| notify_user = notify_member.user if notify_user == User.current && notify_user.pref.no_self_notified false @@ -100,8 +99,24 @@ class DmsfMailer < Mailer true when 'selected' notify_member.mail_notification? - when 'only_my_events', 'only_owner' - notify_user.allowed_to?(:file_manipulation, project) + when 'only_my_events' + author = false + files.each do |file| + if file.involved?(notify_user) + author = true + break + end + end + author + when 'only_owner', 'only_assigned' + author = false + files.each do |file| + if file.owner?(notify_user) + author = true + break + end + end + author else false end From 8238a4f33f5120f46af079ce7cddac98c5fe4f3a Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Fri, 10 Jun 2016 13:58:20 +0200 Subject: [PATCH 77/94] Expired locks --- app/controllers/dmsf_files_controller.rb | 16 ++++++++++++---- 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/pt-BR.yml | 2 +- config/locales/ru.yml | 2 +- config/locales/sl.yml | 2 +- config/locales/zh-TW.yml | 2 +- config/locales/zh.yml | 2 +- lib/redmine_dmsf/lockable.rb | 2 +- 13 files changed, 24 insertions(+), 16 deletions(-) diff --git a/app/controllers/dmsf_files_controller.rb b/app/controllers/dmsf_files_controller.rb index 348516c8..6a5b6a7e 100644 --- a/app/controllers/dmsf_files_controller.rb +++ b/app/controllers/dmsf_files_controller.rb @@ -254,8 +254,12 @@ class DmsfFilesController < ApplicationController if @file.locked? flash[:warning] = l(:warning_file_already_locked) else - @file.lock! - flash[:notice] = l(:notice_file_locked) + begin + @file.lock! + flash[:notice] = l(:notice_file_locked) + rescue Exception => e + flash[:error] = e.message + end end redirect_to :back end @@ -265,8 +269,12 @@ class DmsfFilesController < ApplicationController flash[:warning] = l(:warning_file_not_locked) else if @file.locks[0].user == User.current || User.current.allowed_to?(:force_file_unlock, @file.project) - @file.unlock! - flash[:notice] = l(:notice_file_unlocked) + begin + @file.unlock! + flash[:notice] = l(:notice_file_unlocked) + rescue Exception => e + flash[:error] = e.message + end else flash[:error] = l(:error_only_user_that_locked_file_can_unlock_it) end diff --git a/config/locales/de.yml b/config/locales/de.yml index 6ae07e6b..7a13db07 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -319,7 +319,7 @@ de: error_resource_or_parent_locked: Unable to complete lock - resource (or parent) is locked error_parent_locked: Unable to complete lock - resource parent is locked error_resource_locked: Unable to complete lock - resource is locked - error_lock_exclusively: unable to lock exclusively an already-locked resource + error_lock_exclusively: Unable to lock exclusively an already-locked resource error_unlock_parent_locked: Unlock failed - resource parent is locked field_dmsf_tree_view: Navigate folders in a tree diff --git a/config/locales/en.yml b/config/locales/en.yml index ef60fa49..9722dcc2 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -319,7 +319,7 @@ en: error_resource_or_parent_locked: Unable to complete lock - resource (or parent) is locked error_parent_locked: Unable to complete lock - resource parent is locked error_resource_locked: Unable to complete lock - resource is locked - error_lock_exclusively: unable to lock exclusively an already-locked resource + error_lock_exclusively: Unable to lock exclusively an already-locked resource error_unlock_parent_locked: Unlock failed - resource parent is locked field_dmsf_tree_view: Navigate folders in a tree diff --git a/config/locales/es.yml b/config/locales/es.yml index 22189389..faa35823 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -319,7 +319,7 @@ es: error_resource_or_parent_locked: Unable to complete lock - resource (or parent) is locked error_parent_locked: Unable to complete lock - resource parent is locked error_resource_locked: Unable to complete lock - resource is locked - error_lock_exclusively: unable to lock exclusively an already-locked resource + error_lock_exclusively: Unable to lock exclusively an already-locked resource error_unlock_parent_locked: Unlock failed - resource parent is locked field_dmsf_tree_view: Navigate folders in a tree diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 7023ab65..0132ed4c 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -319,7 +319,7 @@ fr: error_resource_or_parent_locked: Unable to complete lock - resource (or parent) is locked error_parent_locked: Unable to complete lock - resource parent is locked error_resource_locked: Unable to complete lock - resource is locked - error_lock_exclusively: unable to lock exclusively an already-locked resource + error_lock_exclusively: Unable to lock exclusively an already-locked resource error_unlock_parent_locked: Unlock failed - resource parent is locked field_dmsf_tree_view: Navigate folders in a tree diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 28be9da0..5ae5ccf4 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -319,7 +319,7 @@ ja: error_resource_or_parent_locked: Unable to complete lock - resource (or parent) is locked error_parent_locked: Unable to complete lock - resource parent is locked error_resource_locked: Unable to complete lock - resource is locked - error_lock_exclusively: unable to lock exclusively an already-locked resource + error_lock_exclusively: Unable to lock exclusively an already-locked resource error_unlock_parent_locked: Unlock failed - resource parent is locked field_dmsf_tree_view: Navigate folders in a tree diff --git a/config/locales/pl.yml b/config/locales/pl.yml index 58792b10..3e8b56b6 100644 --- a/config/locales/pl.yml +++ b/config/locales/pl.yml @@ -319,7 +319,7 @@ pl: error_resource_or_parent_locked: Unable to complete lock - resource (or parent) is locked error_parent_locked: Unable to complete lock - resource parent is locked error_resource_locked: Unable to complete lock - resource is locked - error_lock_exclusively: unable to lock exclusively an already-locked resource + error_lock_exclusively: Unable to lock exclusively an already-locked resource error_unlock_parent_locked: Unlock failed - resource parent is locked field_dmsf_tree_view: Navigate folders in a tree diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml index b6f50757..a436ba1a 100644 --- a/config/locales/pt-BR.yml +++ b/config/locales/pt-BR.yml @@ -319,7 +319,7 @@ pt-BR: error_resource_or_parent_locked: Unable to complete lock - resource (or parent) is locked error_parent_locked: Unable to complete lock - resource parent is locked error_resource_locked: Unable to complete lock - resource is locked - error_lock_exclusively: unable to lock exclusively an already-locked resource + error_lock_exclusively: Unable to lock exclusively an already-locked resource error_unlock_parent_locked: Unlock failed - resource parent is locked field_dmsf_tree_view: Navigate folders in a tree diff --git a/config/locales/ru.yml b/config/locales/ru.yml index b529588d..e282cf65 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -319,7 +319,7 @@ ru: error_resource_or_parent_locked: Невозможно выполнить блокировку - ресурс (или родительская запись) заблокированы error_parent_locked: Невозможно выполнить блокировку - родительская запись заблокирована error_resource_locked: Невозможно выполнить блокировку - ресурс заблокирован - error_lock_exclusively: невозможно эксклюзивно заблокировать уже забловированный ресурс + error_lock_exclusively: Невозможно эксклюзивно заблокировать уже забловированный ресурс error_unlock_parent_locked: Разблокировка не удалась - родительская запись заблокирована field_dmsf_tree_view: Navigate folders in a tree diff --git a/config/locales/sl.yml b/config/locales/sl.yml index 115bc31a..cf71701b 100644 --- a/config/locales/sl.yml +++ b/config/locales/sl.yml @@ -319,7 +319,7 @@ sl: error_resource_or_parent_locked: Unable to complete lock - resource (or parent) is locked error_parent_locked: Unable to complete lock - resource parent is locked error_resource_locked: Unable to complete lock - resource is locked - error_lock_exclusively: unable to lock exclusively an already-locked resource + error_lock_exclusively: Unable to lock exclusively an already-locked resource error_unlock_parent_locked: Unlock failed - resource parent is locked field_dmsf_tree_view: Navigate folders in a tree diff --git a/config/locales/zh-TW.yml b/config/locales/zh-TW.yml index ff8a7160..002c846b 100644 --- a/config/locales/zh-TW.yml +++ b/config/locales/zh-TW.yml @@ -319,7 +319,7 @@ zh-TW: error_resource_or_parent_locked: Unable to complete lock - resource (or parent) is locked error_parent_locked: Unable to complete lock - resource parent is locked error_resource_locked: Unable to complete lock - resource is locked - error_lock_exclusively: unable to lock exclusively an already-locked resource + error_lock_exclusively: Unable to lock exclusively an already-locked resource error_unlock_parent_locked: Unlock failed - resource parent is locked field_dmsf_tree_view: Navigate folders in a tree diff --git a/config/locales/zh.yml b/config/locales/zh.yml index dabf6d85..d5aac430 100644 --- a/config/locales/zh.yml +++ b/config/locales/zh.yml @@ -319,7 +319,7 @@ zh: error_resource_or_parent_locked: Unable to complete lock - resource (or parent) is locked error_parent_locked: Unable to complete lock - resource parent is locked error_resource_locked: Unable to complete lock - resource is locked - error_lock_exclusively: unable to lock exclusively an already-locked resource + error_lock_exclusively: Unable to lock exclusively an already-locked resource error_unlock_parent_locked: Unlock failed - resource parent is locked field_dmsf_tree_view: Navigate folders in a tree diff --git a/lib/redmine_dmsf/lockable.rb b/lib/redmine_dmsf/lockable.rb index abedcdd0..66640a7c 100644 --- a/lib/redmine_dmsf/lockable.rb +++ b/lib/redmine_dmsf/lockable.rb @@ -45,7 +45,7 @@ module RedmineDmsf def lock!(scope = :scope_exclusive, type = :type_write, expire = nil) # Raise a lock error if entity is locked, but its not at resource level - existing = locks(false) + existing = lock(false) raise DmsfLockError.new(l(:error_resource_or_parent_locked)) if self.locked? && existing.empty? unless existing.empty? if (existing[0].lock_scope == :scope_shared) && (scope == :scope_shared) From 529296215ae5004351ea1ca3926c9d08fa6017ea Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Mon, 13 Jun 2016 13:05:25 +0200 Subject: [PATCH 78/94] webdav: Error -36 on OSX #531 --- lib/redmine_dmsf/webdav/dmsf_resource.rb | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/lib/redmine_dmsf/webdav/dmsf_resource.rb b/lib/redmine_dmsf/webdav/dmsf_resource.rb index 215c307d..74e21a10 100644 --- a/lib/redmine_dmsf/webdav/dmsf_resource.rb +++ b/lib/redmine_dmsf/webdav/dmsf_resource.rb @@ -465,7 +465,7 @@ module RedmineDmsf raise Forbidden unless User.current.admin? || User.current.allowed_to?(:file_manipulation, project) # Ignore Mac OS X resource forks and special Windows files. - if basename.match(/^\._/i) || basename.match(/^Thumbs.db$/i) + if basename.match(/^\._/i) || basename.match(/^\.DS_Store$/i) || basename.match(/^Thumbs.db$/i) Rails.logger.info "#{basename} ignored" return NoContent end @@ -511,13 +511,7 @@ module RedmineDmsf else new_revision.size = request.content_length # Bad Guess end - - # Ignore Mac OS X resource forks and special Windows files. - unless new_revision.size > 0 - Rails.logger.info "#{basename} #{new_revision.size}b ignored" - return Created - end - + raise InternalServerError unless new_revision.valid? && f.save new_revision.disk_filename = new_revision.new_storage_filename From 2a68400a89947680eadd4d86b53a828df6e3b825 Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Mon, 13 Jun 2016 13:52:54 +0200 Subject: [PATCH 79/94] Show document description in mouseover or column #529 --- app/models/dmsf_file.rb | 19 +++++++++++++++++++ app/views/dmsf/_dir.html.erb | 3 ++- app/views/dmsf/_dir_trash.html.erb | 29 ++++++++++++++++++++--------- app/views/dmsf/_file.html.erb | 2 +- app/views/dmsf/_file_trash.html.erb | 2 +- 5 files changed, 43 insertions(+), 12 deletions(-) diff --git a/app/models/dmsf_file.rb b/app/models/dmsf_file.rb index bd8b2a3c..b9ce4b39 100644 --- a/app/models/dmsf_file.rb +++ b/app/models/dmsf_file.rb @@ -452,4 +452,23 @@ class DmsfFile < ActiveRecord::Base false end + def tooltip(download_info=true) + text = '' + if self.last_revision + if self.last_revision.description.present? + text = self.last_revision.description + end + if self.last_revision.comment.present? + if text.present? + text += ' ' + end + text += self.last_revision.comment + end + end + if text.blank? && download_info + text = l(:title_title_version_version_download, :title => self.title, :version => self.version) + end + text.html_safe + end + end diff --git a/app/views/dmsf/_dir.html.erb b/app/views/dmsf/_dir.html.erb index 2a12f9a0..aa69aed9 100644 --- a/app/views/dmsf/_dir.html.erb +++ b/app/views/dmsf/_dir.html.erb @@ -31,7 +31,8 @@ <% end %> <%= link_to(h(title), dmsf_folder_path(:id => project, :folder_id => subfolder), - :class => 'icon icon-folder') %> + :class => 'icon icon-folder', + :title => subfolder.description.html_safe) %> <% if link %>
    <%= link.path %>
    <% else %> diff --git a/app/views/dmsf/_dir_trash.html.erb b/app/views/dmsf/_dir_trash.html.erb index 5747d3d1..3a742703 100644 --- a/app/views/dmsf/_dir_trash.html.erb +++ b/app/views/dmsf/_dir_trash.html.erb @@ -24,7 +24,7 @@ :title => l(:title_check_for_restore_or_delete), :id => "subfolder_#{id}") %>
    <%= content_tag(:span, h(title), - :title => h(title), + :title => h(subfolder.description), :class => 'icon icon-folder') %> <% if link %>
    <%= link.path %>
    @@ -40,14 +40,25 @@
    <%= h(subfolder.user) %> - <% if @folder_manipulation_allowed %> - <%= link_to(image_tag('restore.png', :plugin => 'redmine_dmsf'), - restore_dmsf_path(:id => project, :folder_id => subfolder), - :title => l(:title_restore)) %> - <%= link_to(image_tag('rev_delete.png', :plugin => 'redmine_dmsf'), - delete_dmsf_path(:id => project, :folder_id => subfolder, :commit => 'yes'), - :data => {:confirm => l(:text_are_you_sure)}, - :title => l(:title_delete)) %> + <% if @folder_manipulation_allowed %> + <% if link %> + <%= link_to(image_tag('restore.png', :plugin => 'redmine_dmsf'), + restore_dmsf_link_path(:id => link), + :title => l(:title_restore)) %> + <%= link_to(image_tag('rev_delete.png', :plugin => 'redmine_dmsf'), + dmsf_link_path(:id => link, :commit => 'yes'), + :data => {:confirm => l(:text_are_you_sure)}, + :method => :delete, + :title => l(:title_delete)) %> + <% else # folder %> + <%= link_to(image_tag('restore.png', :plugin => 'redmine_dmsf'), + restore_dmsf_path(:id => project, :folder_id => subfolder), + :title => l(:title_restore)) %> + <%= link_to(image_tag('rev_delete.png', :plugin => 'redmine_dmsf'), + delete_dmsf_path(:id => project, :folder_id => subfolder, :commit => 'yes'), + :data => {:confirm => l(:text_are_you_sure)}, + :title => l(:title_delete)) %> + <% end %> <% end %> 0 <%= content_tag(:span, h(title), - :title => h(title), + :title => h(file.tooltip(false)), :class => "icon icon-file #{DmsfHelper.filetype_css(file.name)}") %>
    <%= h(link ? link.path : file.display_name) %>
    <%= content_tag(:span, h(title), - :title => h(file.tooltip(false)), + :title => h(file.tooltip), :class => "icon icon-file #{DmsfHelper.filetype_css(file.name)}") %>
    <%= h(link ? link.path : file.display_name) %>
    +
    - + @@ -62,10 +62,8 @@ <%= link_to_user step.user %> <% end %> - @@ -75,4 +73,8 @@ <% else %>

    <%= l(:label_no_data) %>

    <% end %> - \ No newline at end of file + + +<%= javascript_tag do %> + $(function() { $("table.steps tbody").positionedItems(); }); +<% end %> From 6ec5fe3719981f0fdf7a03bf44ba3d55ddca9786 Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Tue, 19 Jul 2016 14:28:02 +0200 Subject: [PATCH 85/94] Drag&Drop in approval workflow ordering --- app/views/dmsf_workflows/_steps.html.erb | 1 - 1 file changed, 1 deletion(-) diff --git a/app/views/dmsf_workflows/_steps.html.erb b/app/views/dmsf_workflows/_steps.html.erb index 5d244c57..ca2ee7ce 100644 --- a/app/views/dmsf_workflows/_steps.html.erb +++ b/app/views/dmsf_workflows/_steps.html.erb @@ -47,7 +47,6 @@ - <% steps.each do |i|%> From 111a27fc98d65678b17c4105e211bd2310f7166f Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Wed, 20 Jul 2016 07:25:34 +0200 Subject: [PATCH 86/94] Wrong tool-tip for dmsf macro #545 --- app/models/dmsf_file.rb | 16 ---------------- app/models/dmsf_file_revision.rb | 10 ++++++++++ app/views/dmsf/_file.html.erb | 2 +- app/views/dmsf/_file_trash.html.erb | 2 +- lib/redmine_dmsf/macros.rb | 4 ++-- 5 files changed, 14 insertions(+), 20 deletions(-) diff --git a/app/models/dmsf_file.rb b/app/models/dmsf_file.rb index f638bbab..bd8b2a3c 100644 --- a/app/models/dmsf_file.rb +++ b/app/models/dmsf_file.rb @@ -452,20 +452,4 @@ class DmsfFile < ActiveRecord::Base false end - def tooltip - text = '' - if self.last_revision - if self.last_revision.description.present? - text = self.last_revision.description - end - if self.last_revision.comment.present? - if text.present? - text += ' ' - end - text += self.last_revision.comment - end - end - text.html_safe - end - end diff --git a/app/models/dmsf_file_revision.rb b/app/models/dmsf_file_revision.rb index c628fc82..310e4174 100644 --- a/app/models/dmsf_file_revision.rb +++ b/app/models/dmsf_file_revision.rb @@ -284,4 +284,14 @@ class DmsfFileRevision < ActiveRecord::Base end end + def tooltip + text = '' + text = self.description if self.description.present? + if self.comment.present? + text += ' ' if text.present? + text += self.comment + end + text.html_safe + end + end \ No newline at end of file diff --git a/app/views/dmsf/_file.html.erb b/app/views/dmsf/_file.html.erb index 3a1af275..951b8e01 100644 --- a/app/views/dmsf/_file.html.erb +++ b/app/views/dmsf/_file.html.erb @@ -33,7 +33,7 @@ file_view_url, :target => '_blank', :class => "icon icon-file #{DmsfHelper.filetype_css(file.name)}", - :title => file.tooltip, + :title => file.last_revision.try(:tooltip), 'data-downloadurl' => "#{file.last_revision.detect_content_type}:#{h(file.name)}:#{file_view_url}") %>
    <%= h(link ? link.path : file.display_name) %>
    <%= ''.html_safe if @tree_view %> diff --git a/app/views/dmsf/_file_trash.html.erb b/app/views/dmsf/_file_trash.html.erb index 1e8d9f93..bd84be1f 100644 --- a/app/views/dmsf/_file_trash.html.erb +++ b/app/views/dmsf/_file_trash.html.erb @@ -24,7 +24,7 @@ :title => l(:title_check_for_restore_or_delete), :id => "file_#{id}") %>
    diff --git a/lib/redmine_dmsf/macros.rb b/lib/redmine_dmsf/macros.rb index 4cdfcf43..51aaa514 100644 --- a/lib/redmine_dmsf/macros.rb +++ b/lib/redmine_dmsf/macros.rb @@ -40,7 +40,7 @@ Redmine::WikiFormatting::Macros.register do return link_to(h(args[1] ? args[1] : file.title), file_view_url, :target => '_blank', - :title => h(file.tooltip), + :title => h(revision.tooltip), 'data-downloadurl' => "#{file.last_revision.detect_content_type}:#{h(file.name)}:#{file_view_url}") else raise l(:notice_not_authorized) @@ -161,7 +161,7 @@ Redmine::WikiFormatting::Macros.register do end link_to(img, file_view_url, :target => '_blank', - :title => h(file.tooltip), + :title => h(file.last_revision.try(:tooltip)), 'data-downloadurl' => "#{file.last_revision.detect_content_type}:#{h(file.name)}:#{file_view_url}") else raise "Document ID #{file_id} not found" From 192edd6984ec0c711b72182bb356b27ffbf7c031 Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Wed, 20 Jul 2016 08:14:32 +0200 Subject: [PATCH 87/94] Link from commbo box sorting #542 --- app/models/dmsf_folder.rb | 4 ++-- lib/redmine_dmsf/patches/project_patch.rb | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/models/dmsf_folder.rb b/app/models/dmsf_folder.rb index 4c68e0a2..b7a8ff36 100644 --- a/app/models/dmsf_folder.rb +++ b/app/models/dmsf_folder.rb @@ -32,9 +32,9 @@ class DmsfFolder < ActiveRecord::Base belongs_to :deleted_by_user, :class_name => 'User', :foreign_key => 'deleted_by_user_id' belongs_to :user - has_many :dmsf_folders, :dependent => :destroy + has_many :dmsf_folders, -> { order(:title) }, :dependent => :destroy has_many :dmsf_files, :dependent => :destroy - has_many :folder_links, -> { where :target_type => 'DmsfFolder' }, + has_many :folder_links, -> { where(:target_type => 'DmsfFolder').order(:name) }, :class_name => 'DmsfLink', :foreign_key => 'dmsf_folder_id', :dependent => :destroy has_many :file_links, -> { where :target_type => 'DmsfFile' }, :class_name => 'DmsfLink', :foreign_key => 'dmsf_folder_id', :dependent => :destroy diff --git a/lib/redmine_dmsf/patches/project_patch.rb b/lib/redmine_dmsf/patches/project_patch.rb index cae7be20..b0e35423 100644 --- a/lib/redmine_dmsf/patches/project_patch.rb +++ b/lib/redmine_dmsf/patches/project_patch.rb @@ -32,9 +32,9 @@ module RedmineDmsf unloadable alias_method_chain :copy, :dmsf - has_many :dmsf_files, -> { where dmsf_folder_id: nil}, + has_many :dmsf_files, -> { where(dmsf_folder_id: nil).order(:name) }, :class_name => 'DmsfFile', :foreign_key => 'project_id', :dependent => :destroy - has_many :dmsf_folders, -> {where dmsf_folder_id: nil}, + has_many :dmsf_folders, -> { where(dmsf_folder_id: nil).order(:title) }, :class_name => 'DmsfFolder', :foreign_key => 'project_id', :dependent => :destroy has_many :dmsf_workflows, :dependent => :destroy From e7a6ae5ddd4c14cb0c2d42be221d306de2c8a36d Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Wed, 20 Jul 2016 09:51:25 +0200 Subject: [PATCH 88/94] Approval workflow email notifications #544 --- app/controllers/dmsf_workflows_controller.rb | 4 ++-- app/models/dmsf_mailer.rb | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/controllers/dmsf_workflows_controller.rb b/app/controllers/dmsf_workflows_controller.rb index 98a988b9..9646388b 100644 --- a/app/controllers/dmsf_workflows_controller.rb +++ b/app/controllers/dmsf_workflows_controller.rb @@ -56,7 +56,7 @@ class DmsfWorkflowsController < ApplicationController end if revision.workflow == DmsfWorkflow::STATE_APPROVED # Just approved - recipients = DmsfMailer.get_notify_users(@project) + recipients = DmsfMailer.get_notify_users(@project, [revision.dmsf_file]) recipients.each do |user| DmsfMailer.workflow_notification( user, @@ -78,7 +78,7 @@ class DmsfWorkflowsController < ApplicationController recipients = @dmsf_workflow.participiants recipients.push User.find_by_id revision.dmsf_workflow_assigned_by recipients.uniq! - recipients = recipients & DmsfMailer.get_notify_users(@project) + recipients = recipients & DmsfMailer.get_notify_users(@project, [revision.dmsf_file]) recipients.each do |user| DmsfMailer.workflow_notification( user, diff --git a/app/models/dmsf_mailer.rb b/app/models/dmsf_mailer.rb index 1f06bd48..70877fd8 100644 --- a/app/models/dmsf_mailer.rb +++ b/app/models/dmsf_mailer.rb @@ -83,8 +83,8 @@ class DmsfMailer < Mailer end end - def self.get_notify_users(project, files = nil) - if files + def self.get_notify_users(project, files = []) + if files.present? notify_files = files.select { |file| file.notify? } return [] if notify_files.empty? end From c7f6a760c17b37f26444b4da0fa26c923dd21dd6 Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Thu, 28 Jul 2016 09:41:53 +0200 Subject: [PATCH 89/94] Permissions of the uploaded file --- app/controllers/dmsf_upload_controller.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/controllers/dmsf_upload_controller.rb b/app/controllers/dmsf_upload_controller.rb index fdfc835c..628b38d1 100644 --- a/app/controllers/dmsf_upload_controller.rb +++ b/app/controllers/dmsf_upload_controller.rb @@ -61,14 +61,16 @@ class DmsfUploadController < ApplicationController return end @disk_filename = DmsfHelper.temp_filename(@tempfile.original_filename) + target = "#{DmsfHelper.temp_dir}/#{@disk_filename}" begin - FileUtils.cp @tempfile.path, "#{DmsfHelper.temp_dir}/#{@disk_filename}" + FileUtils.cp @tempfile.path, target + FileUtils.chmod 'u=wr,g=r', target rescue Exception => e Rails.logger.error e.message end - if File.size("#{DmsfHelper.temp_dir}/#{@disk_filename}") <= 0 + if File.size(target) <= 0 begin - File.delete "#{DmsfHelper.temp_dir}/#{@disk_filename}" + File.delete target rescue Exception => e Rails.logger.error e.message end From 4f6e5fee5d1ab4fbb0c27403ec56d7b5195ddf6d Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Thu, 28 Jul 2016 10:52:00 +0200 Subject: [PATCH 90/94] JQeury datatable not load correct language file #554 --- app/views/dmsf/show.html.erb | 4 +- assets/javascripts/jquery.dataTables/it.json | 17 + config/locales/it.yml | 334 +++++++++++++++++++ 3 files changed, 353 insertions(+), 2 deletions(-) create mode 100644 assets/javascripts/jquery.dataTables/it.json create mode 100644 config/locales/it.yml diff --git a/app/views/dmsf/show.html.erb b/app/views/dmsf/show.html.erb index 190945f5..cdf73cb4 100644 --- a/app/views/dmsf/show.html.erb +++ b/app/views/dmsf/show.html.erb @@ -128,8 +128,8 @@ <% - sUrl = "jquery.dataTables/#{I18n.locale.to_s.downcase}.json" - sUrl = 'jquery.dataTables/en.json' unless File.exist?(sUrl) + sUrl = 'jquery.dataTables/en.json' + sUrl = "jquery.dataTables/#{I18n.locale.to_s.downcase}.json" if I18n.locale && !I18n.locale.to_s.match(/^en.*/) %> <% content_for :header_tags do %> diff --git a/assets/javascripts/jquery.dataTables/it.json b/assets/javascripts/jquery.dataTables/it.json new file mode 100644 index 00000000..fc342725 --- /dev/null +++ b/assets/javascripts/jquery.dataTables/it.json @@ -0,0 +1,17 @@ +{ + "sProcessing": "Elaborazione...", + "sLengthMenu": "Visualizza _MENU_ elementi", + "sZeroRecords": "La ricerca non ha portato alcun risultato o nessun elemento è presente", + "sInfo": "Vista da _START_ a _END_ di _TOTAL_ elementi", + "sInfoEmpty": "Vista da 0 a 0 di 0 elementi", + "sInfoFiltered": "(filtrati da _MAX_ elementi totali)", + "sInfoPostFix": "", + "sSearch": "Cerca:", + "sUrl": "", + "oPaginate": { + "sFirst": "Inizio", + "sPrevious": "Precedente", + "sNext": "Successivo", + "sLast": "Fine" + } +} diff --git a/config/locales/it.yml b/config/locales/it.yml new file mode 100644 index 00000000..34c3a8b4 --- /dev/null +++ b/config/locales/it.yml @@ -0,0 +1,334 @@ +# encoding: utf-8 +# +# Redmine plugin for Document Management System "Features" +# +# Copyright (C) 2011 Vít Jonáš +# Copyright (C) 2012 Daniel Munn +# 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. + +it: # Italian strings thx 2 Matteo Arceci! + dmsf: DMSF + label_dmsf_file_plural: Documenti + label_dmsf_file_revision_plural: Revisioni al documento + label_dmsf_file_revision_access_plural: Accessi al documento + warning_no_entries_selected: Nessun documento selezionato + error_email_to_must_be_entered: Deve essere inserito l'indirizzo email + warning_file_already_locked: Documento già bloccato + notice_file_locked: Documento bloccato + warning_file_not_locked: Documento non bloccato + notice_file_unlocked: Documento sbloccato + error_only_user_that_locked_file_can_unlock_it: Solo chi ha bloccato il documento può sbloccarlo + error_max_files_exceeded: "Superato il limite di %{number} documenti per il download simultaneo" + error_entry_project_does_not_match_current_project: "Il progetto non corrisponde al progetto corrente" + notice_folder_created: Cartella creata + error_folder_creation_failed: Creazione cartella fallita + error_folder_title_must_be_entered: Deve essere inserito il titolo + notice_folder_deleted: Cartella cancellata + error_folder_is_not_empty: La cartella non è vuota + error_folder_title_is_already_used: Il titolo è già in uso + notice_folder_details_were_saved: I dettagli della cartella sono stati salvati + error_folder_is_locked: La cartella è bloccata + error_file_is_locked: Il documento è bloccato + notice_file_deleted: Il documento è stato cancellato + error_at_least_one_revision_must_be_present: Deve essere presente almeno una revisione + notice_revision_deleted: Revisione cancellata + warning_one_of_files_locked: Uno dei documenti è bloccato + notice_file_unlocked: Documento sbloccato + notice_file_revision_created: Revisione del documento creata + notice_your_preferences_were_saved: Le tue preferenze sono state salvate + notice_your_preferences_were_not_saved: Le tue preferenze non sono state salvate + warning_folder_notifications_already_activated: La notifica su cartella è già attiva + notice_folder_notifications_activated: La notifica su cartella è stata attivata + warning_folder_notifications_already_deactivated: La notifica su cartella è già disattiva + notice_folder_notifications_deactivated: La notifica su cartella è stat disattivata + warning_file_notifications_already_activated: La notifica su documento è già attiva + notice_file_notifications_activated: La notifica su documento è stata attivata + warning_file_notifications_already_deactivated: La notifica su documento è già disattiva + notice_file_notifications_deactivated: La notifica su documento è disattiva + link_details: "%{title} dettagli" + link_edit: "Modifica %{title}" + submit_create: Creato + link_create_folder: Creata cartella + title_check_uncheck_all_for_zip_download_or_email: Seleziona/Deseleziona tutto per lo zip, download o email + title_check_uncheck_all_for_restore_or_delete: Seleziona/Deseleziona tutto per ripristinare o cancellare + link_title: Nome documento + link_size: Dimensioni + link_modified: Modificato + link_ver: Ver. + link_author: Autore + title_check_for_zip_download_or_email: Seleziona per zip, download o email + title_check_for_restore_or_delete: Seleziona per ripristinare o cancellare + title_delete: Cancella + title_notifications_active_deactivate: "Notifiche attive: Disattiva" + title_notifications_not_active_activate: "Notifiche disattivate: Attiva" + title_title_version_version_download: "%{title} versione %{version} download" + title_locked_by_user: "Bloccato da %{user}" + title_locked_by_you: Bloccato da te + title_waiting_for_approval: In attesa di Approvazione + title_approved: Approvato + title_unlock_file: Sblocca per consentire modifiche degli altri membri + title_lock_file: Blocca per evitare modifiche degli altri membri + title_download_checked: Download selezionati in archivio Zip + title_send_checked_by_email: Selezionati spediti per email + link_user_preferences: Le tue preferenze DMSF di progetto + heading_send_documents_by_email: Documenti spediti per email + label_email_from: Da + label_email_to: A + label_email_cc: CC + label_email_subject: Oggetto + label_email_documents: Documenti + label_email_body: Corpo + label_email_send: Spedisci + title_notifications_active: Notifiche attive + label_upload: Carica + heading_new_folder: Nuova cartella + label_title: Titolo + label_description: Descrizione + submit_save: Salva + info_file_locked: Documento bloccato! + label_notifications: Notifiche + select_option_default: Default + select_option_deactivated: Disattivato + select_option_activated: Attivato + label_title_format: Titolo formattato + text_title_format: "Titolo del documento formattato per il download (%t - titolo, %d - data, %v - versione, %i - ID, %r - revisione). Esempio: %t_%v" + title_save_preferences: Salva preferenze + heading_revisions: Revisioni + title_download: Download + title_delete_revision: Cancella revisione + label_created: Creato + label_changed: Modificato + info_changed_by_user: "%{changed} da" + label_filename: Nome file + label_mime: Mime + label_size: Dimensioni + heading_new_revision: Nuova revisione + option_version_same: Stessa + option_version_minor: Miniore + option_version_major: Maggiore + option_version_custom: Personalizzata + label_new_content: Nuovo contenuto + label_maximum_files_upload: Limite di caricamento + note_maximum_number_of_files_uploaded: Limita il numero massimo di documenti caricati alla volta. 0 significa senza limiti. + label_maximum_files_download: Numero massimo di documenti scaricabili + note_maximum_number_of_files_downloaded: Limita il numero massimo di documenti scaricabili in archivio zip o spediti via email. 0 significa senza limiti. + label_file_storage_directory: Cartella dei documenti + label_index_database: Indice database + label_stemming_language: Linguaggio di Stemming + note_possible_values: Valori possibili + note_pass_none_to_disable_stemming: "passa 'nulla' per disabilitare lo stemming" + label_stem_strategy: Strategia dello Stem + option_stem_none: Stem nulla (default) + option_stem_some: Stem qualcosa + option_stem_all: Stem tutto + label_stemming_description: Questo controlla come il parser delle query applica l'algoritmo di Stemming. Il valore di default è STEM_NONE. I valori possibili sono + note_do_not_stem: "Non fare alcun stemming." + note_stem_some: Cerca per termini 'Stemmed' ad eccezione di quelli che iniziano con la lettera maiuscola, o sono seguiti da alcuni caratteri, o vengono utilizzati con gli operatori che hanno bisogno di informazioni di posizione. I termini 'Stemmed' hanno il prefisso 'Z'. + note_stem_all: "Cerca per termini 'Stemmed' di tutte le parole (nota: non viene aggiunto alcun prefisso 'Z')." + note_stemming_applied: Si noti che l'algoritmo di 'Stemming' viene applicato solo alle parole nei campi probabilistici - termini con filtro booleano non sono compresi. + label_default_notifications: Notifica di default dei documenti + heading_uploaded_files: Documenti caricati + submit_commit: Invia/Salva + link_documents: Documenti + permission_view_dmsf_file_revision_accesses: Visualizza i download nel flusso di attività + permission_view_dmsf_file_revisions: Visualizza le revisioni nel flusso di attività + permission_view_dmsf_folders: Sfoglia i documenti + permission_user_preferences: Preferenze + permission_view_dmsf_files: Visualizza i documenti + permission_folder_manipulation: Modifica la cartella + permission_file_manipulation: Modifica il documento + permission_force_file_unlock: Forza lo sblocco del documento + permission_manage_workflows: Gestisci i flussi di lavoro + permission_file_delete: Elimina i documenti + label_file: Documento + field_folder: Cartella + error_create_cycle_in_folder_dependency: crea un ciclo nella dipendenza della cartella + error_contains_invalid_character: contiene carattere(i) non validi + error_file_commit_require_uploaded_file: L'aggiornamento del documento richiede un documento già caricato in precedenza + warning_some_files_were_not_commited: "Alcuni documenti non sono stati aggiornati a causa di errori di validazione: %{files}" + error_user_has_not_right_delete_folder: "L'utente non ha i diritti per eliminare le cartelle" + error_user_has_not_right_delete_file: "L'utente non ha i diritti per eliminare il documento" + notice_entries_deleted: Voci eliminate + warning_some_entries_were_not_deleted: "Alcune voci non sono state eliminate: %{entries}" + title_delete_checked: Elimina i selezionati + title_items: elementi + title_filename_for_download: Nome file utilizzato per il download o nell'archivio Zip + label_number_of_folders: Cartelle + label_number_of_documents: Documenti + error_file_storage_directory_does_not_exist: "La cartella di archiviazione non esiste e non può essere creata" + error_file_can_not_be_created: "Il documento non può essere creato nella cartella di archiviazione" + error_wrong_zip_encoding: Errato encoding dello Zip + warning_xapian_not_available: Xapian non disponibile + menu_dmsf: Documenti + label_physical_file_delete: File fisico eliminato + user_is_not_project_member: Non sei un membro del progetto + heading_access_downloads_emails: Downloads/Emails + heading_access_first: Primo + heading_access_last: Ultimo + label_dmsf_updated: Aggiornato + label_dmsf_downloaded: Scaricato + title_total_size_of_all_files: Dimensione totale di tutti i files in questa cartella + project_module_dmsf: DMSF + warning_no_project_to_copy_file_to: Nessun progetto nel quale copiare il documento + comment_copied_from: "Copiato da %{source}" + notice_file_copied: Documento copiato + notice_file_moved: Documento spostato + field_target_project: Progetto di destinazione + field_target_folder: Cartella di destinazione + title_copy_or_move: Copia/Sposta + label_dmsf_folder_plural: Cartelle + comment_moved_from: "Spostato da %{source}" + error_target_folder_same: La cartella di destinazione ed il progetto sono gli stessi di adesso + error_file_cannot_be_moved: "Il documento non può essere spostato" + error_file_cannot_be_copied: "Il documento non può essere copiato" + warning_no_project_to_copy_folder_to: Nessun progetto dove copiare la cartella + title_copy: Copia + error_folder_cannot_be_copied: "La cartella non può essere copiata" + notice_folder_copied: Cartella copiata + + error_max_email_filesize_exceeded: "Hai superato la dimensione massima del file per l'invio tramite e-mail. (%{number} MB)" + note_maximum_email_filesize: Limiti di dimensione massima dei file che possono essere inviati via e-mail. 0 significa illimitato. Il numero è in MB. + label_maximum_email_filesize: Dimensione massima degli allegati delle e-mail + header_minimum_filesize: Errore file. + error_minimum_filesize: "Il file %{file} è 0 bytes e non sarà allegato." + parent_directory: Cartella superiore + note_webdav: "Una volta abilitato il Webdav può essere contattato sul percorso %{protocol}://%{domain}/dmsf/webdav/[project identifier]" + label_webdav: Funzionalità Webdav + label_dmsf_plural: "Copia documenti e cartelle (%{files} documenti in %{folders} cartelle)" + + warning_folder_already_locked: Questa cartella è già bloccata + notice_folder_locked: La cartella è stata bloccata + warning_folder_not_locked: Purtroppo la cartella non può essere bloccata + notice_folder_unlocked: La cartella è stata sbloccata + error_only_user_that_locked_folder_can_unlock_it: Non sei autorizzato a sbloccare questa cartella + title_unlock_folder: Sblocca per consentire modifiche agli altri membri + title_lock_folder: Blocca per evitare modifiche da parte di altri membri + + select_option_webdav_readonly: Sola lettura + select_option_webdav_readwrite: Lettura/Scrittura + label_webdav_strategy: Strategia Webdav + note_webdav_strategy: Abilita l'amministratore a decidere se Webdav è di sola lettura oppure lettura-scrittura per gli utenti finali. + + error_unable_delete_dmsf_workflow: Impossibile eliminare il flusso di lavoro + error_empty_note: "La nota non può essere vuota" + error_workflow_assign: C'è stato un errore durante l'assegnazione + error_cannot_start_workflow: "Il flusso di lavoro non può partire" + error_cannot_renumber_steps: "I passi non possono essere numerati" + label_dmsf_workflow_new: Nuova 'approvazione di flusso di lavoro' + field_label_dmsf_workflow: Approvazione di flusso di lavoro + field_label_dmsf_workflow_name: Nome per l'approvazione di flusso di lavoro + label_dmsf_workflow_plural: Approvazioni flusso di lavoro + label_dmsf_workflow_plural_num: Approvazioni flusso di lavoro (%{count}) + label_dmsf_workflow_step: Passo + label_dmsf_workflow_step_plural: Passi + label_dmsf_workflow_approval: Approvazione + label_dmsf_workflow_approval_plural: Approvazioni + label_dmsf: DMSF + label_dmsf_wokflow_action_approve: Approva + label_dmsf_wokflow_action_reject: Rifiuta + label_dmsf_wokflow_action_delegate: Delega a + label_dmsf_wokflow_action_assign: Assegna un 'approvazione di flusso di lavoro' + label_dmsf_wokflow_action_start: Inizia un flusso di lavoro + label_dmsf_workflow_add_approver: "Aggiungi un nuovo approvatore con funzioni logiche:" + label_or: oppure + label_action: Azione + label_note: Note + title_none: Nessuno + title_rejection: Rifiuto + title_delegation: Delega + title_assignment: Assegnazione + title_start: Inizio + title_dmsf_workflow_log: Log di approvazione di flusso di lavoro + title_assigned: Assegnato + title_approval: Approvazione + title_rejected: Rifiutato + dmsf_and: AND + dmsf_or: OR + dmsf_new_step: Nuovo passo + message_dmsf_wokflow_note: La tua nota... + info_revision: "r%{rev}" + link_workflow: Flusso di lavoro + notice_workflow_started: Approvazione di flusso di lavoro inizializzato + text_email_subject_approved: "Approvazione di flusso di lavoro %{name} approvato" + text_email_subject_rejected: "Approvazione di flusso di lavoro %{name} rifiutato" + text_email_subject_delegated: "Approvazione di flusso di lavoro %{name} delegato" + text_email_subject_requires_approval: "Approvazione di flusso di lavoro %{name} richiede la tua approvazione" + text_email_subject_updated: "Approvazione di flusso di lavoro %{name} aggiornato" + text_email_subject_started: "Approvazione di flusso di lavoro %{name} inizializzato" + text_email_finished_approved: "L'approvazione di flusso di lavoro '%{name}' assegnato a '%{filename}' è stata completata ed il documento è stato approvato." + text_email_finished_rejected: "L'approvazione di flusso di lavoro '%{name}' assegnato a '%{filename}' è stata completata ma il documento è stato rifiutato a causa di '%{notice}'." + text_email_finished_delegated: "L'approvazione di flusso di lavoro '%{name}' assegnato a '%{filename}' è stata delegata a causa di '%{notice}' e sei tenuto ad approvare nel passo corrente." + text_email_finished_step: "L'approvazione di flusso di lavoro '%{name}' assegnato a '%{filename}' ha completato uno dei passi di approvazione e sei tenuto ad approvare il prossimo passo." + text_email_finished_step_short: "L'approvazione di flusso di lavoro '%{name}' assegnato a '%{filename}' ha completato uno dei passi di approvazione." + text_email_started: "L'approvazione di flusso di lavoro '%{name}' assegnato a '%{filename}' è stato inizializzato e sei tenuto ad approvare il passo corrente." + text_email_to_proceed: Per proseguire clicca sull'icona checkbox a fianco del documento + text_email_to_see_history: Per vedere la storia di approvazione clicca sullo stato del flusso di lavoro del documento + text_email_to_see_status: Per vedere lo stato attuale del flusso di lavoro di approvazione clicca sullo stato del flusso di lavoro del documento + label_my_open_approvals: Le mie approvazioni aperte + label_my_locked_documents: I miei documenti bloccati + + title_create_link: Crea un collegamento + label_link_from: Collegamento da + label_link_to: Collegamento a + label_notifications_on: Attiva notifiche + label_notifications_off: Disattiva notifiche + field_target_file: Scarica sorgente + title_download_entries: Scarica documenti + label_external: Esterno + + label_link_name: Nome del collegamento + label_link_external_url: URL + label_target_folder: Cartella di destinazione + label_source_folder: Cartella sorgente + label_target_project: Progetto di destinazione + label_source_project: Progetto sorgente + + text_email_doc_updated_subject: "Documenti del progetto %{project} caricati" + text_email_doc_updated: ha documenti appena attualizzati + text_email_doc_follows: come segue + text_email_doc_deleted_subject: "Documenti del progetto %{project} cancellati" + text_email_doc_deleted: ha documenti appena eliminati + label_links_only: solo colegamenti + + label_display_notified_recipients: Visualizza i destinatari notificati + note_display_notified_recipients: L'utente sarà informato di tutti i destinatari appena inviato la notifica e-mail. + warning_email_notifications: "Notifica email inviata a %{to}" + + link_trash_bin: Cestino + title_restore: Ripristina + notice_dmsf_file_restored: Il documento è stato ripristinato correttamente + notice_dmsf_folder_restored: La cartella è stata ripristinata correttamente + notice_dmsf_link_restored: Il collegamento è stato ripristinato correttamente + title_restore_checked: Ripristina selezionati + error_parent_folder: "La cartella padre non esiste" + + error_resource_or_parent_locked: Impossibile completare il blocco - la risorsa (o superiore) è bloccata + error_parent_locked: Impossibile completare il blocco - la risorsa superiore è bloccata + error_resource_locked: Impossibile completare il blocco - la risorsa è bloccata + error_lock_exclusively: impossibile bloccare in modo esclusivo una risorsa già bloccata + error_unlock_parent_locked: Sblocco fallito - la risorsa superiore è bloccata + + field_dmsf_tree_view: Navigate folders in a tree + label_dmsf_version: Version + + locked_documents: Documenti bloccati + open_approvals: Approvazioni aperte + + label_maximum_ajax_upload_filesize: Dimensione massima dei documenti caricabili tramite AJAX + note_maximum_ajax_upload_filesize: Limita la dimensione massima dei documenti che possono essere caricati tramite interfaccia standard AJAX altrimenti sarà necessario utilizzare il modulo standard di Redmine. Il numero è espresso in MB. + label_classic: Classic + label_drag_drop: "Drag&Drop" From c8928d6691d72ed7a767b856e3d5ca01cd57c023 Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Fri, 29 Jul 2016 14:08:32 +0200 Subject: [PATCH 91/94] Upload title is a required field --- app/views/dmsf_upload/_upload_file.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/dmsf_upload/_upload_file.html.erb b/app/views/dmsf_upload/_upload_file.html.erb index a7c27f1c..9af33a30 100644 --- a/app/views/dmsf_upload/_upload_file.html.erb +++ b/app/views/dmsf_upload/_upload_file.html.erb @@ -28,7 +28,7 @@

    <%= label_tag("commited_files[#{i}][title]", l(:label_title)) %> - <%= text_field_tag("commited_files[#{i}][title]", upload.title, :size => 32) %> + <%= text_field_tag("commited_files[#{i}][title]", upload.title, :size => 32, :required => true) %>

    From fb7b2623afa896893e715e5fd9762d3e2d9c9ba2 Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Tue, 9 Aug 2016 09:35:49 +0200 Subject: [PATCH 92/94] NoMethodError: undefined method 'each' for nil:NilClass --- app/models/dmsf_mailer.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/models/dmsf_mailer.rb b/app/models/dmsf_mailer.rb index 70877fd8..bfe8e2b3 100644 --- a/app/models/dmsf_mailer.rb +++ b/app/models/dmsf_mailer.rb @@ -88,6 +88,7 @@ class DmsfMailer < Mailer notify_files = files.select { |file| file.notify? } return [] if notify_files.empty? end + return [] unless project.members notify_members = project.members.select do |notify_member| notify_user = notify_member.user if notify_user == User.current && notify_user.pref.no_self_notified From 6ae17d1ec308879d09233b999b98198716b7e31f Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Tue, 9 Aug 2016 14:42:07 +0200 Subject: [PATCH 93/94] project.members cannot be nil --- app/models/dmsf_mailer.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/app/models/dmsf_mailer.rb b/app/models/dmsf_mailer.rb index bfe8e2b3..70877fd8 100644 --- a/app/models/dmsf_mailer.rb +++ b/app/models/dmsf_mailer.rb @@ -88,7 +88,6 @@ class DmsfMailer < Mailer notify_files = files.select { |file| file.notify? } return [] if notify_files.empty? end - return [] unless project.members notify_members = project.members.select do |notify_member| notify_user = notify_member.user if notify_user == User.current && notify_user.pref.no_self_notified From 67f5a3fcb7b98f397a10e276698bd78020bc5d0a Mon Sep 17 00:00:00 2001 From: Karel Picman Date: Wed, 10 Aug 2016 10:22:55 +0200 Subject: [PATCH 94/94] Plugin settings "File default notifications" does not apply! #556 --- lib/redmine_dmsf/patches/project_patch.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/redmine_dmsf/patches/project_patch.rb b/lib/redmine_dmsf/patches/project_patch.rb index b0e35423..aadd9f29 100644 --- a/lib/redmine_dmsf/patches/project_patch.rb +++ b/lib/redmine_dmsf/patches/project_patch.rb @@ -46,11 +46,19 @@ module RedmineDmsf :class_name => 'DmsfLink', :foreign_key => 'project_id', :dependent => :destroy has_many :dmsf_links, -> { where dmsf_folder_id: nil }, :class_name => 'DmsfLink', :foreign_key => 'project_id', :dependent => :destroy + + before_save :set_default_dmsf_notification end end module InstanceMethods + def set_default_dmsf_notification + if self.new_record? + self.dmsf_notification = Setting.plugin_redmine_dmsf['dmsf_default_notifications'] == '1' + end + end + def dmsf_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
    <%= l(:label_dmsf_workflow_step) %> <%= l(:label_dmsf_workflow_approval_plural) %><%= l(:button_sort) %>
    - <%= reorder_links('workflow_step', {:action => 'edit', :id => @dmsf_workflow, :step => i}, :put) %> - + <%= reorder_handle(@dmsf_workflow, :url => url_for(:action => 'edit', :id => @dmsf_workflow, :step => i) ) %> <%= delete_link edit_dmsf_workflow_path(@dmsf_workflow, :step => i) %>
    <%= l(:label_dmsf_workflow_step) %> <%= l(:label_dmsf_workflow_approval_plural) %>
    <%= content_tag(:span, h(title), - :title => h(file.tooltip), + :title => h(file.last_revision.try(:tooltip)), :class => "icon icon-file #{DmsfHelper.filetype_css(file.name)}") %>
    <%= h(link ? link.path : file.display_name) %>