diff --git a/.travis.yml b/.travis.yml index 15331bc2..5d9f0440 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,9 +3,9 @@ language: ruby sudo: false rvm: - - 2.0.0 + - 2.3 -install: "echo skip bundle install" +install: 'echo skip bundle install' script: - export WORKSPACE=`pwd`/workspace diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b6ce1f4..ddf9e871 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,62 @@ Changelog for Redmine DMSF ========================== +1.6.0 *2017-09-12* +------------------ + + Folder permissions + Documents attachable to issues + Hungarian localisation + Fulltext search in *.eml and *.msg + +IMPORTANT + +1. Files in the filesystem are re-organized by a new system based on dates. So, documents are not stored in folders named + by the project's identifier but by the data of uploading, e.g. 2017/09. It's the same system used by Redmine for + attachments. +2. DMS storage directory plugin option is related to the rails root directory. +3. The plugin is independent of the gem xapian-full-alaveteli which has been replaced with ruby-xapian package. Therefore + is recommended to uninstall xapian-full-alaveteli gem and install ruby-xapian package in order the full-text search + is working. + +* Bug: #758 - Error in template when retrieving details of a file in a subfolder +* New: #755 - Ability to retrieve the MD5 value of a Document type +* Bug: #749 - REST API - List of documents in folder fails when using folder_title +* Bug: #747 - Background icon repeating in admin panel (Redmine 3.4.2) +* Bug: #746 - Thumbnail macro: size paramter not respected +* Bug: #744 - Full stops within filename lead to false extensions +* New: #742 - WebDAV PROPSTATS and PROPFIND caching change +* Bug: #738 - Upload failure +* Bug: #734 - DMSF uploader seems to override built in uploader +* New: #733 - Make the storage path Rails.root related +* Bug: #732 - Buggy tree view +* Bug: #731 - Add users for new step in Worflow Dialogue +* Bug: #730 - Workflow "New Step" dialog not appearing +* Bug: #728 - Internal error 500 when uploading document via Edit issue +* New: #727 - Ability to disable document upload in issues +* Bug: #725 - Can't uninstall redmine dmsf in Bitnami +* New: #717 - Enhacement: Xapian parse eml and msg files in same way as word, excel... +* Bug: #714 - The full text search does not work +* New: #713 - Hungarian localisation +* New: #712 - Notifications ON/OFF are confusing +* Bug: #710 - Can't delete locked documents from the trash +* Bug: #701 - How tagging with multiple values works? +* Bug: #700 - 'Save as' from Excel does not work when using project names +* New: #699 - Speed up the main view +* New: #697 - Email notifications from WebDAV interface +* Bug: #694 - redmine:dmsf_convert_documents +* Bug: #693 - redmine:dmsf_convert_documents +* Bug: #692 - Error migrate plugin v1.5.9 +* New: #691 - The last approver in the CSV export +* Bug: #685 - Problem deleting plugin +* Bug: #683 - Approval reminder problem +* New: #667 - A better navigation in found results +* New: #651 - Incomplete copy of a file to another project +* Bug: #623 - Option "Navigate folders in a tree" seems not to be saved +* New: #543 - Feature Request: Document Location - Folder Structure +* New: #170 - Document and Folder Access Control. This issue may be duplicated as I saw it on google code some time ago. +* New: #48 - Linking Issues and DMSF Documents + 1.5.9 *2016-03-01* ------------------ diff --git a/Gemfile b/Gemfile index 4d3cf5fb..edeb2ba5 100644 --- a/Gemfile +++ b/Gemfile @@ -28,7 +28,4 @@ gem 'simple_enum' gem 'uuidtools' gem 'dav4rack' gem 'dalli' - -group :xapian do - gem 'xapian-full-alaveteli', :require => false -end \ No newline at end of file +gem 'redmine_extensions' unless Dir.exist?(File.expand_path('../../easyproject', __FILE__)) diff --git a/README.md b/README.md index 1f617b69..bbf579b2 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ Redmine DMSF Plugin =================== -The current version of Redmine DMSF is **1.5.9** [![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.6.0** [![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. @@ -37,7 +37,8 @@ Features * Documents and folders symbolic links * Document tagging * Trash bin - * Compatible with Redmine 3.3.x + * Documents attachable to issues + * Compatible with Redmine 3.4.x Dependencies ------------ @@ -46,51 +47,76 @@ Dependencies ### Full-text search (optional) -If you want to use fulltext search abilities: - - * Xapian () search engine - * Xapian Omega indexing tool - * Xapian ruby bindings - xapian or xapian-full gem +If you want to use fulltext search abilities, install xapian-omega and ruby-xapian packages. See https://xapian.org + for details. + +``` +sudo apt-get install xapian-omega ruby-xapian +``` To index some files with omega you may have to install some other packages like xpdf, antiword, ... From Omega documentation: - * PDF (.pdf) if pdftotext is available (comes with xpdf) - * PostScript (.ps, .eps, .ai) if ps2pdf (from ghostscript) and pdftotext (comes with xpdf) are available - * OpenOffice/StarOffice documents (.sxc, .stc, .sxd, .std, .sxi, .sti, .sxm, .sxw, .sxg, .stw) if unzip is available. - * OpenDocument format documents (.odt, .ods, .odp, .odg, .odc, .odf, .odb, .odi, .odm, .ott, .ots, .otp, .otg, .otc, .otf, .oti, .oth) if unzip is available - * MS Word documents (.doc, .dot) if antiword is available - * MS Excel documents (.xls, .xlb, .xlt) if xls2csv is available (comes with catdoc) - * MS Powerpoint documents (.ppt, .pps) if catppt is available (comes with catdoc) - * MS Office 2007 documents (.docx, .dotx, .xlsx, .xlst, .pptx, .potx, .ppsx) if unzip is available - * Wordperfect documents (.wpd) if wpd2text is available (comes with libwpd) - * MS Works documents (.wps, .wpt) if wps2text is available (comes with libwps) - * AbiWord documents (.abw) - * Compressed AbiWord documents (.zabw) if gzip is available - * Rich Text Format documents (.rtf) if unrtf is available - * Perl POD documentation (.pl, .pm, .pod) if pod2text is available - * TeX DVI files (.dvi) if catdvi is available - * DjVu files (.djv, .djvu) if djvutxt is available - * XPS files (.xps) if unzip is available + * HTML (.html, .htm, .shtml, .shtm, .xhtml, .xhtm) + * PHP (.php) - our HTML parser knows to ignore PHP code + * text files (.txt, .text) + * SVG (.svg) + * CSV (Comma-Separated Values) files (.csv) + * PDF (.pdf) if pdftotext is available (comes with poppler or xpdf) + * PostScript (.ps, .eps, .ai) if ps2pdf (from ghostscript) and pdftotext (comes with poppler or xpdf) are available + * OpenOffice/StarOffice documents (.sxc, .stc, .sxd, .std, .sxi, .sti, .sxm, .sxw, .sxg, .stw) if unzip is available + * OpenDocument format documents (.odt, .ods, .odp, .odg, .odc, .odf, .odb, .odi, .odm, .ott, .ots, .otp, .otg, .otc, .otf, .oti, .oth) if unzip is available + * MS Word documents (.dot) if antiword is available (.doc files are left to libmagic, as they may actually be RTF (AbiWord saves RTF when asked to save as .doc, and Microsoft Word quietly loads RTF files with a .doc extension), or plain-text). + * MS Excel documents (.xls, .xlb, .xlt, .xlr, .xla) if xls2csv is available (comes with catdoc) + * MS Powerpoint documents (.ppt, .pps) if catppt is available (comes with catdoc) + * MS Office 2007 documents (.docx, .docm, .dotx, .dotm, .xlsx, .xlsm, .xltx, .xltm, .pptx, .pptm, .potx, .potm, .ppsx, .ppsm) if unzip is available + * Wordperfect documents (.wpd) if wpd2text is available (comes with libwpd) + * MS Works documents (.wps, .wpt) if wps2text is available (comes with libwps) + * MS Outlook message (.msg) if perl with Email::Outlook::Message and HTML::Parser modules is available + * MS Publisher documents (.pub) if pub2xhtml is available (comes with libmspub) + * AbiWord documents (.abw) + * Compressed AbiWord documents (.zabw) + * Rich Text Format documents (.rtf) if unrtf is available + * Perl POD documentation (.pl, .pm, .pod) if pod2text is available + * reStructured text (.rst, .rest) if rst2html is available (comes with docutils) + * Markdown (.md, .markdown) if markdown is available + * TeX DVI files (.dvi) if catdvi is available + * DjVu files (.djv, .djvu) if djvutxt is available + * XPS files (.xps) if unzip is available + * Debian packages (.deb, .udeb) if dpkg-deb is available + * RPM packages (.rpm) if rpm is available + * Atom feeds (.atom) + * MAFF (.maff) if unzip is available + * MHTML (.mhtml, .mht) if perl with MIME::Tools is available + * MIME email messages (.eml) and USENET articles if perl with MIME::Tools and HTML::Parser is available + * vCard files (.vcf, .vcard) if perl with Text::vCard is available + +You can use following commands to install some of the required indexing tools: On Debian use: -```sudo apt-get install xapian-omega libxapian-dev xpdf poppler-utils \ +``` +sudo apt-get install xapian-omega libxapian-dev xpdf poppler-utils \ antiword unzip catdoc libwpd-tools libwps-tools gzip unrtf catdvi \ - djview djview3 uuid uuid-dev xz-utils``` + djview djview3 uuid uuid-dev xz-utils libemail-outlook-message-perl +``` On Ubuntu use: -```sudo apt-get install xapian-omega libxapian-dev xpdf poppler-utils antiword \ +``` +sudo apt-get install xapian-omega libxapian-dev xpdf poppler-utils antiword \ unzip catdoc libwpd-tools libwps-tools gzip unrtf catdvi djview djview3 \ - uuid uuid-dev xz-utils``` + uuid uuid-dev xz-utils libemail-outlook-message-perl +``` -On CentOS user: -```sudo yum install xapian-omega libxapian-dev xpdf poppler-utils antiword \ +On CentOS use: +``` +sudo yum install xapian-omega libxapian-dev xpdf poppler-utils antiword \ unzip catdoc libwpd-tools libwps-tools gzip unrtf catdvi djview djview3 \ - uuid uuid-dev xz``` + uuid uuid-dev xz libemail-outlook-message-perl +``` Usage ----- @@ -190,7 +216,7 @@ In the file /public/help//wiki_syntax.html, at the end o {{dmsf(83)}}Document #83 -There's a patch that helps you to modify all help files at once. In your Redmine folder: +There's a patch (tested with Redmine 3.4.2) that helps you to modify all help files at once. In your Redmine folder: `cd redmine` @@ -211,9 +237,9 @@ Before installing ensure that the Redmine instance is stopped. 6. Restart the web server. 7. You should configure the plugin via Redmine interface: Administration -> Plugins -> DMSF -> Configure. 8. Assign DMSF permissions to appropriate roles. -9. There are two rake tasks: +9. There are a few rake tasks: - a) To convert documents from the standard Redmine document module + I) To convert documents from the standard Redmine document module Available options: @@ -229,21 +255,53 @@ Before installing ensure that the Redmine instance is stopped. 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 + II) To alert all users who are expected to do an approval in the current approval steps Example: - rake redmine:dmsf_alert_approvals RAILS_ENV="production" + rake redmine:dmsf_alert_approvals RAILS_ENV="production" + + III) To clears the Webdav Cache + + Example: + + rake redmine:dmsf_clear_webdav_cache RAILS_ENV="production" + + IV) To create missing MD5 digest for all file revisions + + Available options: + + *dry_run - test, no changes to the 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" + + V) To maintain DMSF + + * Remove all files with no database record from the document directory + * Remove all links project_id = -1 (added links to an issue which hasn't been created) + + Available options: + + *dry_run - No physical deletion but to list of all unused files only + + Example: + + rake redmine:dmsf_maintenance RAILS_ENV="production" + rake redmine:dmsf_maintenance dry_run=1 RAILS_ENV="production" ### Fulltext search (optional) If you want to use full-text search features, you must setup file content indexing. -It is necessary to index DMSF files with omega before searching attempts to receive some output: +It is necessary to index DMSF files with omindex before searching attempts to receive some output: 1. Change the configuration part of redmine_dmsf/extra/xapian_indexer.rb file according to your environment. - 2. Run `ruby redmine_dmsf/extra/xapian_indexer.rb -f` + (The path to the index database set in xapian_indexer.rb must corresponds to the path set in the plugin's settings.) + 2. Run `ruby redmine_dmsf/extra/xapian_indexer.rb -vf` -This command must be run on regular basis (e.g. from cron) +This command should be run on regular basis (e.g. from cron) Example of cron job (once per hour at 8th minute): diff --git a/after_init.rb b/after_init.rb new file mode 100644 index 00000000..57a8f8a7 --- /dev/null +++ b/after_init.rb @@ -0,0 +1,83 @@ +require_dependency 'zip' +require_dependency File.dirname(__FILE__) + '/lib/redmine_dmsf.rb' + +def init + # Administration menu extension + Redmine::MenuManager.map :admin_menu do |menu| + menu.push :dmsf_approvalworkflows, :dmsf_workflows_path, :caption => :label_dmsf_workflow_plural, + :html => { :class => 'icon icon-approvalworkflows' }, :if => Proc.new { |_| User.current.admin? } + end + + Redmine::MenuManager.map :project_menu do |menu| + menu.push :dmsf, { :controller => 'dmsf', :action => 'show' }, :caption => :menu_dmsf, :before => :documents, + :param => :id + end + + # Permissions + Redmine::AccessControl.map do |map| + map.project_module :dmsf do |pmap| + pmap.permission :view_dmsf_file_revision_accesses, + :read => true + pmap.permission :view_dmsf_file_revisions, + :read => true + pmap.permission :view_dmsf_folders, + {:dmsf => [:show], + :dmsf_folders_copy => [:new, :copy_to, :move_to]}, + :read => true + pmap.permission :user_preferences, + {:dmsf_state => [:user_pref_save]} + pmap.permission :view_dmsf_files, + {:dmsf => [:entries_operation, :entries_email, :download_email_entries, :tag_changed], + :dmsf_files => [:show, :view, :thumbnail], + :dmsf_files_copy => [:new, :create, :move], + :dmsf_workflows => [:log]}, + :read => true + pmap.permission :email_documents, + {:dmsf_public_urls => [:create]} + pmap.permission :folder_manipulation, + {:dmsf => [:new, :create, :delete, :edit, :save, :edit_root, :save_root, :lock, :unlock, + :notify_activate, :notify_deactivate, :restore], + :dmsf_folder_permissions => [:new, :append, :autocomplete_for_user]} + pmap.permission :file_manipulation, + {:dmsf_files => [:create_revision, :lock, :unlock, :delete_revision, :notify_activate, + :notify_deactivate, :restore], + :dmsf_upload => [:upload_files, :upload_file, :upload, :commit_files, :commit, + :delete_dmsf_attachment, :delete_dmsf_link_attachment], + :dmsf_links => [:new, :create, :destroy, :restore, :autocomplete_for_project, + :autocomplete_for_folder] + } + pmap.permission :file_delete, + { :dmsf => [:trash, :delete_entries], + :dmsf_files => [:delete]} + pmap.permission :force_file_unlock, {} + pmap.permission :file_approval, + {:dmsf_workflows => [:action, :new_action, :autocomplete_for_user, :start, :assign, :assignment]} + pmap.permission :manage_workflows, + {:dmsf_workflows => [:index, :new, :create, :destroy, :show, :new_step, :add_step, :remove_step, + :reorder_steps, :update, :update_step, :delete_step, :edit]} + pmap.permission :display_system_folders, + :read => true + end + end +end + +unless Redmine::Plugin.installed?(:easy_extensions) + init +else + ActiveSupport.on_load(:easyproject, yield: true) do + init + end +end + +ActionDispatch::Reloader.to_prepare do + # Rubyzip configuration + Zip.unicode_names = true + + Redmine::Search.map do |search| + search.register :dmsf_files + search.register :dmsf_folders + end + + Redmine::Activity.register :dmsf_file_revision_accesses, :default => false + Redmine::Activity.register :dmsf_file_revisions +end diff --git a/app/controllers/dmsf_controller.rb b/app/controllers/dmsf_controller.rb index 79a25f20..759dd54d 100644 --- a/app/controllers/dmsf_controller.rb +++ b/app/controllers/dmsf_controller.rb @@ -1,4 +1,5 @@ # encoding: utf-8 +# encoding: utf-8 # # Redmine plugin for Document Management System "Features" # @@ -23,17 +24,24 @@ class DmsfController < ApplicationController unloadable - before_filter :find_project - before_filter :authorize, :except => [:expand_folder] - 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] + before_action :find_project + before_action :authorize, :except => [:expand_folder] + before_action :find_folder, :except => [:new, :create, :edit_root, :save_root] + before_action :find_parent, :only => [:new, :create] + before_action :tree_view, :only => [:delete, :show] + before_action :permissions accept_api_auth :show, :create, :save skip_before_action :verify_authenticity_token, if: -> { request.headers['HTTP_X_REDMINE_API_KEY'].present? } helper :all + helper :dmsf_folder_permissions + + def permissions + render_403 unless DmsfFolder.permissions?(@folder) + true + end def expand_folder @tree_view = true @@ -73,13 +81,15 @@ class DmsfController < ApplicationController @file_manipulation_allowed = User.current.allowed_to? :file_manipulation, @project @file_delete_allowed = User.current.allowed_to? :file_delete, @project @subfolders = DmsfFolder.deleted.where(:project_id => @project.id) - @files = DmsfFile.deleted.where(:container_id => @project.id, :container_type => 'Project') + @files = DmsfFile.deleted.where(:project_id => @project.id) @dir_links = DmsfLink.deleted.where(:project_id => @project.id, :target_type => DmsfFolder.model_name.to_s) @file_links = DmsfLink.deleted.where(:project_id => @project.id, :target_type => DmsfFile.model_name.to_s) @url_links = DmsfLink.deleted.where(:project_id => @project.id, :target_type => 'DmsfUrl') end def download_email_entries + # IE has got a tendency to cache files + expires_in(0.year, "must-revalidate" => true) send_file( params[:path], :filename => 'Documents.zip', @@ -150,6 +160,7 @@ class DmsfController < ApplicationController if params[:dmsf_folder] && params[:dmsf_folder][:custom_field_values].present? redirect_to dmsf_folder_path( :id => @project, + :folder_id => @folder, :custom_field_id => params[:dmsf_folder][:custom_field_values].first[0], :custom_value => params[:dmsf_folder][:custom_field_values].first[1]) else @@ -174,25 +185,18 @@ class DmsfController < ApplicationController render :action => 'edit' end + def edit + @parent = @folder.dmsf_folder + @pathfolder = copy_folder(@folder) + @force_file_unlock_allowed = User.current.allowed_to?(:force_file_unlock, @project) + @users = Principal.active.where(:id => @folder.dmsf_folder_permissions.users.map{ |p| p.object_id }) + end + 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 - - - + saved = @folder.update_from_params(params) respond_to do |format| format.js format.api { @@ -203,7 +207,7 @@ class DmsfController < ApplicationController format.html { if saved flash[:notice] = l(:notice_folder_created) - redirect_to dmsf_folder_path(:id => @project, :folder_id => @folder) + redirect_to dmsf_folder_path(:id => @project, :folder_id => @folder.dmsf_folder) else @pathfolder = @parent render :action => 'edit' @@ -213,30 +217,13 @@ class DmsfController < ApplicationController end - def edit - @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] redirect_to dmsf_folder_path(:id => @project, :folder_id => @folder) return end @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 - - saved = @folder.save + saved = @folder.update_from_params(params) respond_to do |format| format.api { unless saved @@ -246,7 +233,7 @@ class DmsfController < ApplicationController format.html { if saved flash[:notice] = l(:notice_folder_details_were_saved) - redirect_to dmsf_folder_path(:id => @project, :folder_id => @folder) + redirect_to dmsf_folder_path(:id => @project, :folder_id => @folder.dmsf_folder) else render :action => 'edit' end @@ -529,7 +516,7 @@ class DmsfController < ApplicationController recipients.each do |u| DmsfMailer.files_deleted(u, @project, deleted_files).deliver end - if Setting.plugin_redmine_dmsf[:dmsf_display_notified_recipients] == '1' + 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) ? ',...' : '.') @@ -563,7 +550,8 @@ class DmsfController < ApplicationController def find_folder_by_title # find by title has to be scoped to project - @folder = DmsfFolder.find_by(title: params[:folder_title], project_id: params[:id]) if params[:folder_title].present? + project = Project.find(params[:id]) + @folder = DmsfFolder.find_by(title: params[:folder_title], project_id: project.id) if params[:folder_title].present? rescue DmsfAccessError render_403 rescue ActiveRecord::RecordNotFound @@ -592,6 +580,7 @@ class DmsfController < ApplicationController private def get_display_params + @system_folder = @folder && @folder.system @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) @@ -600,93 +589,82 @@ class DmsfController < ApplicationController @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 - @extra_columns = [l(:field_project), l(:label_document_url), l(:label_last_revision_id)] + @extra_columns = [l(:label_last_approver), l(:field_project), l(:label_document_url), l(:label_last_revision_id)] if @tree_view @locked_for_user = false else - unless @folder - if tag - @subfolders = [] - DmsfFolder.where(:project_id => @project.id).visible.each do |f| - f.custom_field_values.each do |v| + if tag + @subfolders = [] + folder_id = @folder.id if @folder + DmsfFolder.where(:project_id => @project.id, :dmsf_folder_id => folder_id, :system => false).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, :dmsf_folder_id => folder_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]) - @subfolders << f + @files << f break end end end end - @files = [] - DmsfFile.where(:container_id => @project.id, :container_type => 'Project').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 + @dir_links = [] + DmsfLink.where(:project_id => @project.id, :target_type => DmsfFolder.model_name.to_s, :dmsf_folder_id => folder_id).where('target_id IS NOT NULL').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 - @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| + end + @file_links = [] + DmsfLink.where(:project_id => @project.id, :target_type => DmsfFile.model_name.to_s, :dmsf_folder_id => folder_id).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]) - @dir_links << l + @file_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 + end + @url_links = [] + else + if @folder + @subfolders = @folder.dmsf_folders.visible.to_a + @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? else - @subfolders = @project.dmsf_folders.visible + @subfolders = @project.dmsf_folders.visible.to_a @files = @project.dmsf_files.visible @dir_links = @project.folder_links.visible @file_links = @project.file_links.visible @url_links = @project.url_links.visible + @locked_for_user = false end - @locked_for_user = false - else - @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 + # Remove system folders you are not allowed to see because you are not allowed to see the issue + @subfolders = DmsfHelper.visible_folders(@subfolders, @project) end @ajax_upload_size = Setting.plugin_redmine_dmsf['dmsf_max_ajax_upload_filesize'].present? ? Setting.plugin_redmine_dmsf['dmsf_max_ajax_upload_filesize'] : 100 @@ -695,7 +673,7 @@ class DmsfController < ApplicationController @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(:container_id => @project.id, :container_type => 'Project').any? || + DmsfFile.deleted.where(:project_id => @project.id).any? || DmsfLink.deleted.where(:project_id => @project.id).any? end diff --git a/app/controllers/dmsf_files_controller.rb b/app/controllers/dmsf_files_controller.rb index 03f7ae2a..4a41975b 100644 --- a/app/controllers/dmsf_files_controller.rb +++ b/app/controllers/dmsf_files_controller.rb @@ -24,10 +24,11 @@ class DmsfFilesController < ApplicationController menu_item :dmsf - before_filter :find_file, :except => [:delete_revision] - before_filter :find_revision, :only => [:delete_revision] - before_filter :authorize - before_filter :tree_view, :only => [:delete] + before_action :find_file, :except => [:delete_revision] + before_action :find_revision, :only => [:delete_revision] + before_action :authorize + before_action :tree_view, :only => [:delete] + before_action :permissions accept_api_auth :show @@ -35,6 +36,13 @@ class DmsfFilesController < ApplicationController helper :dmsf_workflows helper :dmsf + def permissions + if @file + render_403 unless DmsfFolder.permissions?(@file.dmsf_folder) + end + true + end + def view begin if params[:download].blank? @@ -57,6 +65,8 @@ class DmsfFilesController < ApplicationController else title_format = Setting.plugin_redmine_dmsf['dmsf_global_title_format'] end + # IE has got a tendency to cache files + expires_in(0.year, "must-revalidate" => true) send_file(@revision.disk_file, :filename => filename_for_content_disposition(@revision.formatted_name(title_format)), :type => @revision.detect_content_type, @@ -121,10 +131,12 @@ class DmsfFilesController < ApplicationController end else upload = DmsfUpload.create_from_uploaded_attachment(@project, @folder, file_upload) - revision.size = upload.size - revision.disk_filename = revision.new_storage_filename - revision.mime_type = upload.mime_type - revision.digest = DmsfFileRevision.create_digest upload.disk_file + if upload + revision.size = upload.size + revision.disk_filename = revision.new_storage_filename + revision.mime_type = upload.mime_type + revision.digest = DmsfFileRevision.create_digest upload.tempfile_path + end end # Custom fields @@ -139,7 +151,7 @@ class DmsfFilesController < ApplicationController if revision.save revision.assign_workflow(params[:dmsf_workflow_id]) if upload - FileUtils.mv(upload.disk_file, revision.disk_file) + FileUtils.mv(upload.tempfile_path, revision.disk_file(false)) end if @file.locked? && !@file.locks.empty? begin @@ -158,7 +170,7 @@ class DmsfFilesController < ApplicationController recipients.each do |u| DmsfMailer.files_updated(u, @project, [@file]).deliver end - if Setting.plugin_redmine_dmsf[:dmsf_display_notified_recipients] == '1' + 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) ? ',...' : '.') @@ -184,14 +196,14 @@ class DmsfFilesController < ApplicationController commit = params[:commit] == 'yes' if @file.delete(commit) flash[:notice] = l(:notice_file_deleted) - if commit && (@file.container_type == 'Project') + if commit log_activity('deleted') begin 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' + 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) ? ',...' : '.') @@ -300,7 +312,7 @@ class DmsfFilesController < ApplicationController :disposition => 'inline' end else - render :nothing => true, :status => 404 + head 404 end end diff --git a/app/controllers/dmsf_files_copy_controller.rb b/app/controllers/dmsf_files_copy_controller.rb index 85d2c60a..b9741ca7 100644 --- a/app/controllers/dmsf_files_copy_controller.rb +++ b/app/controllers/dmsf_files_copy_controller.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-17 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,11 +22,19 @@ class DmsfFilesCopyController < ApplicationController menu_item :dmsf - before_filter :find_file - before_filter :authorize + before_action :find_file + before_action :authorize + before_action :permissions helper :all + def permissions + if @file + render_403 unless DmsfFolder.permissions?(@file.dmsf_folder) + end + true + end + def new @target_project = DmsfFile.allowed_target_projects_on_copy.detect {|p| p.id.to_s == params[:target_project_id]} if params[:target_project_id] @target_project ||= @project if User.current.allowed_to?(:file_manipulation, @project) @@ -108,12 +117,15 @@ class DmsfFilesCopyController < ApplicationController 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}" + if file && file.last_revision + 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 end def find_file - @file = DmsfFile.visible.find(params[:id]) - @project = @file.project + @file = DmsfFile.visible.find_by_id params[:id] + @project = @file.project if @file end end diff --git a/app/controllers/dmsf_folder_permissions_controller.rb b/app/controllers/dmsf_folder_permissions_controller.rb new file mode 100644 index 00000000..2b483598 --- /dev/null +++ b/app/controllers/dmsf_folder_permissions_controller.rb @@ -0,0 +1,68 @@ +# encoding: utf-8 +# +# Redmine plugin for Document Management System "Features" +# +# Copyright (C) 2011-17 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 DmsfFolderPermissionsController < ApplicationController + unloadable + + before_action :find_folder, :only => [:destroy] + before_action :find_project + before_action :authorize + before_action :permissions + + def permissions + render_403 unless DmsfFolder.permissions?(@dmsf_folder) + true + end + + def new + @principals = users_for_new_users + end + + def append + @principals = Principal.where(:id => params[:user_ids]).to_a + head 200 if @principals.blank? + end + + def autocomplete_for_user + @principals = users_for_new_users + respond_to do |format| + format.js + end + end + + private + + def users_for_new_users + Principal.active.visible.member_of(@project).like(params[:q]).order(:type, :lastname).to_a + end + + def find_project + if params[:project_id] + @project = Project.visible.find_by_param(params[:project_id]) + end + end + + def find_folder + if params[:dmsf_folder_id] + @dmsf_folder = DmsfFolder.visible.find_by_id(params[:dmsf_folder_id]) + end + end + +end diff --git a/app/controllers/dmsf_folders_copy_controller.rb b/app/controllers/dmsf_folders_copy_controller.rb index e3d685bd..cbe3e326 100644 --- a/app/controllers/dmsf_folders_copy_controller.rb +++ b/app/controllers/dmsf_folders_copy_controller.rb @@ -21,8 +21,14 @@ class DmsfFoldersCopyController < ApplicationController menu_item :dmsf - before_filter :find_folder - before_filter :authorize + before_action :find_folder + before_action :authorize + before_action :permissions + + def permissions + render_403 unless DmsfFolder.permissions?(@folder) + true + end def new @target_project = DmsfFolder.allowed_target_projects_on_copy.detect {|p| p.id.to_s == params[:target_project_id]} if params[:target_project_id] @@ -80,8 +86,8 @@ class DmsfFoldersCopyController < ApplicationController end def find_folder - @folder = DmsfFolder.visible.find(params[:id]) - @project = @folder.project + @folder = DmsfFolder.visible.find_by_id(params[:id]) + @project = @folder.project if @folder end end diff --git a/app/controllers/dmsf_links_controller.rb b/app/controllers/dmsf_links_controller.rb index 1a2427f7..a6162211 100644 --- a/app/controllers/dmsf_links_controller.rb +++ b/app/controllers/dmsf_links_controller.rb @@ -22,9 +22,18 @@ class DmsfLinksController < ApplicationController unloadable model_object DmsfLink - before_filter :find_model_object, :only => [:destroy, :restore] - before_filter :find_link_project - before_filter :authorize + before_action :find_model_object, :only => [:destroy, :restore] + before_action :find_link_project + before_action :authorize + before_action :permissions + protect_from_forgery except: :new + + def permissions + if @dmsf_link + render_403 unless DmsfFolder.permissions?(@dmsf_link.dmsf_folder) + end + true + end def initialize @dmsf_link = nil @@ -35,109 +44,78 @@ class DmsfLinksController < ApplicationController def new @dmsf_link = DmsfLink.new @dmsf_link.project_id = params[:project_id] - - if params[:dmsf_link].present? - # 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') - @dmsf_link.target_project_id = params[:dmsf_link][:target_project_id] - @target_folder_id = params[:dmsf_link][:target_folder_id].to_i if params[:reload].blank? && DmsfLinksHelper.is_a_number?(params[:dmsf_link][:target_folder_id]) - if @type == 'link_to' - if params[:dmsf_link][:dmsf_file_id].present? - file = DmsfFile.find_by_id params[:dmsf_link][:dmsf_file_id] - @dmsf_link.name = file.title if file - else - folder = DmsfFolder.find_by_id params[:dmsf_link][:dmsf_folder_id] - @dmsf_link.name = folder.title if folder - end + @dmsf_link.dmsf_folder_id = params[:dmsf_folder_id] + @dmsf_file_id = params[:dmsf_file_id] + @type = params[:type] + @dmsf_link.target_project_id = params[:project_id] + @dmsf_link.project_id = params[:project_id] + @target_folder_id = params[:dmsf_folder_id].to_i if params[:dmsf_folder_id].present? + if @type == 'link_to' + if @dmsf_file_id + file = DmsfFile.find_by_id @dmsf_file_id + @dmsf_link.name = file.title if file else - if params[:dmsf_link][:target_file_id].present? - @target_file_id = params[:dmsf_link][:target_file_id] - file = DmsfFile.find_by_id @target_file_id - - if file - folder = DmsfFolder.find_by_id params[:dmsf_link][:target_folder_id] - if (folder && (folder.project_id == @dmsf_link.target_project_id) && folder.dmsf_files.include?(file)) || folder.nil? - @dmsf_link.name = file.title - end - end - else - folder = DmsfFolder.find_by_id params[:dmsf_link][:target_folder_id] - - if folder - if folder.project_id == @dmsf_link.target_project_id - @dmsf_link.name = folder.title if folder - end - end - end - end - else - # Link from/to - @dmsf_link.dmsf_folder_id = params[:dmsf_folder_id] - @dmsf_file_id = params[:dmsf_file_id] - @type = params[:type] - @link_external = false - @dmsf_link.target_project_id = params[:project_id] - @dmsf_link.project_id = params[:project_id] - @target_folder_id = params[:dmsf_folder_id].to_i if params[:dmsf_folder_id].present? - if @type == 'link_to' - if @dmsf_file_id - file = DmsfFile.find_by_id @dmsf_file_id - @dmsf_link.name = file.title if file - else - folder = DmsfFolder.find_by_id @target_folder_id - @dmsf_link.name = folder.title if folder - end + folder = DmsfFolder.find_by_id @target_folder_id + @dmsf_link.name = folder.title if folder end end + @container = params[:container] + respond_to do |format| + format.html + format.js + end + end - render :layout => !request.xhr? + def autocomplete_for_project + respond_to do |format| + format.js + end + end + + def autocomplete_for_folder + respond_to do |format| + format.js + end end def create @dmsf_link = DmsfLink.new @dmsf_link.user = User.current - if params[:dmsf_link][:type] == 'link_from' # Link from - @dmsf_link.project_id = params[:dmsf_link][:project_id] - @dmsf_link.dmsf_folder_id = params[:dmsf_link][:dmsf_folder_id] + if params[:dmsf_link][:container].blank? + @dmsf_link.project_id = params[:dmsf_link][:project_id] + @dmsf_link.dmsf_folder_id = params[:dmsf_link][:dmsf_folder_id] + else + # A container link + @dmsf_link.project_id = -1 + @dmsf_link.dmsf_folder_id = nil + end @dmsf_link.target_project_id = params[:dmsf_link][:target_project_id] - @link_external = (params[:external_link] == 'true') - @dmsf_link.external_url = params[:dmsf_link][:external_url] - if (@link_external) + if (params[:external_link] == 'true') + @dmsf_link.external_url = params[:dmsf_link][:external_url] @dmsf_link.target_type = 'DmsfUrl' elsif params[:dmsf_link][:target_file_id].present? @dmsf_link.target_id = params[:dmsf_link][:target_file_id] @dmsf_link.target_type = DmsfFile.model_name.to_s else - @dmsf_link.target_id = DmsfLinksHelper.is_a_number?(params[:dmsf_link][:target_folder_id]) ? params[:dmsf_link][:target_folder_id].to_i : nil + @dmsf_link.target_id = DmsfLinksHelper.is_a_number?( + params[:dmsf_link][:target_folder_id]) ? params[:dmsf_link][:target_folder_id].to_i : nil @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) else - @dmsf_file_id = params[:dmsf_link][:dmsf_file_id] - @type = params[:dmsf_link][:type] - @target_folder_id = params[:dmsf_link][:target_folder_id].to_i if DmsfLinksHelper.is_a_number?(params[:dmsf_link][:target_folder_id]) - @target_file_id = @dmsf_link.target_id if @dmsf_link.target_type == DmsfFile.model_name.to_s - render :action => 'new' + flash[:error] = @dmsf_link.errors.full_messages.to_sentence end else # Link to @dmsf_link.project_id = params[:dmsf_link][:target_project_id] - @dmsf_link.dmsf_folder_id = DmsfLinksHelper.is_a_number?(params[:dmsf_link][:target_folder_id]) ? params[:dmsf_link][:target_folder_id].to_i : nil + @dmsf_link.dmsf_folder_id = DmsfLinksHelper.is_a_number?( + params[:dmsf_link][:target_folder_id]) ? params[:dmsf_link][:target_folder_id].to_i : nil @dmsf_link.target_project_id = params[:dmsf_link][:project_id] - @link_external = (params[:external_link] == 'true') - @dmsf_link.external_url = params[:dmsf_link][:external_url] - if (@link_external) - @dmsf_link.target_type = 'DmsfUrl' - elsif params[:dmsf_link][:dmsf_file_id].present? + if params[:dmsf_link][:dmsf_file_id].present? @dmsf_link.target_id = params[:dmsf_link][:dmsf_file_id] @dmsf_link.target_type = DmsfFile.model_name.to_s else @@ -145,24 +123,26 @@ 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) - if params[:dmsf_link][:dmsf_file_id].present? - redirect_to dmsf_file_path(@dmsf_link.target_file) - else - redirect_to edit_dmsf_path(:id => params[:dmsf_link][:project_id], :folder_id => params[:dmsf_link][:dmsf_folder_id]) - end else - @dmsf_file_id = params[:dmsf_link][:dmsf_file_id] - @type = params[:dmsf_link][:type] - @target_folder_id = @dmsf_link.dmsf_folder_id - @dmsf_link.target_project_id = @dmsf_link.project.id - @dmsf_link.project_id = params[:dmsf_link][:project_id] - @dmsf_link.dmsf_folder_id = params[:dmsf_link][:dmsf_folder_id] - render :action => 'new' + flash[:error] = @dmsf_link.errors.full_messages.to_sentence end end + respond_to do |format| + format.html { + if params[:dmsf_link][:type] == 'link_from' + redirect_to dmsf_folder_path(:id => @project.id, :folder_id => @dmsf_link.dmsf_folder_id) + else + if params[:dmsf_link][:dmsf_file_id].present? + redirect_to dmsf_file_path(@dmsf_link.target_file) + else + redirect_to edit_dmsf_path(:id => params[:dmsf_link][:project_id], :folder_id => params[:dmsf_link][:dmsf_folder_id]) + end + end + } + format.js + end end def destroy diff --git a/app/controllers/dmsf_public_urls_controller.rb b/app/controllers/dmsf_public_urls_controller.rb index dd16e5c2..eab00f5c 100644 --- a/app/controllers/dmsf_public_urls_controller.rb +++ b/app/controllers/dmsf_public_urls_controller.rb @@ -22,14 +22,16 @@ class DmsfPublicUrlsController < ApplicationController unloadable model_object DmsfPublicUrl - before_filter :authorize, :only => [:create] - skip_before_filter :check_if_login_required, :only => [:show] + before_action :authorize, :only => [:create] + skip_before_action :check_if_login_required, :only => [:show] def show dmsf_public_url = DmsfPublicUrl.where('token = ? AND expire_at >= ?', params[:token], DateTime.now).first if dmsf_public_url revision = dmsf_public_url.dmsf_file.last_revision begin + # IE has got a tendency to cache files + expires_in(0.year, "must-revalidate" => true) send_file(revision.disk_file, :filename => filename_for_content_disposition(revision.name), :type => revision.detect_content_type, diff --git a/app/controllers/dmsf_state_controller.rb b/app/controllers/dmsf_state_controller.rb index 18cb263c..cee02843 100644 --- a/app/controllers/dmsf_state_controller.rb +++ b/app/controllers/dmsf_state_controller.rb @@ -24,8 +24,8 @@ class DmsfStateController < ApplicationController menu_item :dmsf - before_filter :find_project - before_filter :authorize + before_action :find_project + before_action :authorize def user_pref_save member = @project.members.where(:user_id => User.current.id).first @@ -39,7 +39,10 @@ class DmsfStateController < ApplicationController end else flash[:warning] = l(:user_is_not_project_member) - end + end + if Setting.plugin_redmine_dmsf['dmsf_act_as_attachable'] + @project.update_attribute :dmsf_act_as_attachable, params[:act_as_attachable] + end redirect_to settings_project_path(@project, :tab => 'dmsf') end diff --git a/app/controllers/dmsf_upload_controller.rb b/app/controllers/dmsf_upload_controller.rb index deee07df..7be74b42 100644 --- a/app/controllers/dmsf_upload_controller.rb +++ b/app/controllers/dmsf_upload_controller.rb @@ -24,16 +24,22 @@ class DmsfUploadController < ApplicationController menu_item :dmsf - before_filter :find_project, :except => [:upload, :delete_dmsf_attachment] - before_filter :authorize, :except => [:upload, :delete_dmsf_attachment] - before_filter :authorize_global, :only => [:upload, :delete_dmsf_attachment] - before_filter :find_folder, :except => [:upload_file, :upload, :commit, :delete_dmsf_attachment] + before_action :find_project, :except => [:upload, :delete_dmsf_attachment, :delete_dmsf_link_attachment] + before_action :authorize, :except => [:upload, :delete_dmsf_attachment, :delete_dmsf_link_attachment] + before_action :authorize_global, :only => [:upload, :delete_dmsf_attachment, :delete_dmsf_link_attachment] + before_action :find_folder, :except => [:upload_file, :upload, :commit, :delete_dmsf_attachment, :delete_dmsf_link_attachment] + before_action :permissions, :except => [:upload_file, :upload, :commit, :delete_dmsf_attachment, :delete_dmsf_link_attachment] helper :all helper :dmsf_workflows accept_api_auth :upload, :commit + def permissions + render_403 unless DmsfFolder.permissions?(@folder) + true + end + def upload_files uploaded_files = params[:dmsf_attachments] @uploads = [] @@ -56,42 +62,23 @@ class DmsfUploadController < ApplicationController # async single file upload handling def upload_file - @tempfile = params[:file] - unless @tempfile.original_filename - render_404 - return - end - @disk_filename = DmsfHelper.temp_filename(@tempfile.original_filename) - target = "#{DmsfHelper.temp_dir}/#{@disk_filename}" begin - FileUtils.cp @tempfile.path, target - FileUtils.chmod 'u=wr,g=r', target - rescue Exception => e - Rails.logger.error e.message - end - if File.size(target) <= 0 - begin - File.delete target - rescue Exception => e - Rails.logger.error e.message + @tempfile = params[:file] + unless @tempfile.original_filename + render_404 + return 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) - } - } - else + @disk_filename = DmsfHelper.temp_filename(@tempfile.original_filename) render :layout => false + ensure + @tempfile.close false end end - # REST API document upload + # REST API and Redmine attachment form def upload unless request.content_type == 'application/octet-stream' - render :nothing => true, :status => 406 + head 406 return end @@ -127,7 +114,10 @@ class DmsfUploadController < ApplicationController uploaded_files = attachments.select { |key, value| key == 'uploaded_file'} uploaded_files.each_value do |uploaded_file| upload = DmsfUpload.create_from_uploaded_attachment(@project, @folder, uploaded_file) - uploaded_file[:disk_filename] = upload.disk_filename + if upload + uploaded_file[:disk_filename] = upload.disk_filename + uploaded_file[:tempfile_path] = upload.tempfile_path + end end commit_files_internal uploaded_files end @@ -140,6 +130,13 @@ class DmsfUploadController < ApplicationController render_404 end + def delete_dmsf_link_attachment + link = DmsfLink.find(params[:id]) + link.destroy + rescue ActiveRecord::RecordNotFound + render_404 + end + private def commit_files_internal(commited_files) diff --git a/app/controllers/dmsf_workflows_controller.rb b/app/controllers/dmsf_workflows_controller.rb index bb4e8431..db7aa460 100644 --- a/app/controllers/dmsf_workflows_controller.rb +++ b/app/controllers/dmsf_workflows_controller.rb @@ -22,12 +22,21 @@ class DmsfWorkflowsController < ApplicationController unloadable model_object DmsfWorkflow - before_filter :find_model_object, :except => [:create, :new, :index, :assign, :assignment] - before_filter :find_project - before_filter :authorize_custom + before_action :find_model_object, :except => [:create, :new, :index, :assign, :assignment] + before_action :find_project + before_action :authorize_custom + before_action :permissions, :only => [:new_action, :assignment, :start] layout :workflows_layout + def permissions + revision = DmsfFileRevision.find_by_id params[:dmsf_file_revision_id] if params[:dmsf_file_revision_id].present? + if revision + render_403 unless revision.dmsf_file || DmsfFolder.permissions?(revision.dmsf_file.dmsf_folder) + end + true + end + def initialize @dmsf_workflow = nil @project = nil @@ -72,7 +81,7 @@ class DmsfWorkflowsController < ApplicationController :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) ? ',...' : '.') @@ -95,7 +104,7 @@ class DmsfWorkflowsController < ApplicationController :text_email_to_see_history, action.note).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) ? ',...' : '.') @@ -116,7 +125,7 @@ class DmsfWorkflowsController < ApplicationController :text_email_finished_delegated, :text_email_to_proceed, action.note).deliver - if Setting.plugin_redmine_dmsf[:dmsf_display_notified_recipients] == '1' + if Setting.plugin_redmine_dmsf['dmsf_display_notified_recipients'] == '1' flash[:warning] = l(:warning_email_notifications, :to => delegate.name) end end @@ -147,7 +156,7 @@ class DmsfWorkflowsController < ApplicationController :text_email_finished_step_short, :text_email_to_see_status).deliver end - if Setting.plugin_redmine_dmsf[:dmsf_display_notified_recipients] == '1' + if Setting.plugin_redmine_dmsf['dmsf_display_notified_recipients'] == '1' recipients = assignments.collect{ |a| a.user } recipients << to if to recipients.uniq! @@ -178,31 +187,54 @@ class DmsfWorkflowsController < ApplicationController def assignment 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.assign_workflow(params[:dmsf_workflow_id]) - if request.post? - if revision.save - file = DmsfFile.find_by_id revision.dmsf_file_id - if file - begin - file.lock! - rescue DmsfLockError => e - logger.warn e.message + # DMS file + if params[:dmsf_file_revision_id].present? && params[:dmsf_link_id].blank? && params[:attachment_id].blank? + revision = DmsfFileRevision.find_by_id params[:dmsf_file_revision_id] + if revision + revision.set_workflow(params[:dmsf_workflow_id], params[:action]) + revision.assign_workflow(params[:dmsf_workflow_id]) + if request.post? + if revision.save + file = DmsfFile.find_by_id revision.dmsf_file_id + if file + begin + file.lock! + rescue DmsfLockError => e + Rails.logger.warn e.message + end + flash[:notice] = l(:notice_successful_update) end - flash[:notice] = l(:notice_successful_update) + else + flash[:error] = l(:error_workflow_assign) end - else - flash[:error] = l(:error_workflow_assign) end end + redirect_to :back + return + # DMS link (attached) + elsif params[:dmsf_link_id].present? + @dmsf_link_id = params[:dmsf_link_id] + @dmsf_workflow_id = params[:dmsf_workflow_id] + # Attachment (attached) + elsif params[:attachment_id].present? + @attachment_id = params[:attachment_id] + @dmsf_workflow_id = params[:dmsf_workflow_id] end + else + redirect_to :back + return + end + respond_to do |format| + format.html + format.js end - redirect_to :back end def log + respond_to do |format| + format.html + format.js + end end def edit @@ -366,26 +398,7 @@ class DmsfWorkflowsController < ApplicationController if revision revision.set_workflow(@dmsf_workflow.id, params[:action]) if revision.save - assignments = @dmsf_workflow.next_assignments revision.id - recipients = assignments.collect{ |a| a.user } - recipients.uniq! - recipients = recipients & DmsfMailer.get_notify_users(@project) - recipients.each do |user| - DmsfMailer.workflow_notification( - user, - @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' - unless recipients.blank? - 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 + @dmsf_workflow.notify_users(@project, revision, self) flash[:notice] = l(:notice_workflow_started) else flash[:error] = l(:notice_cannot_start_workflow) @@ -399,22 +412,21 @@ class DmsfWorkflowsController < ApplicationController if params[:dmsf_workflow].present? index = params[:step].to_i name = params[:dmsf_workflow][:name] - if name.present? - step = @dmsf_workflow.dmsf_workflow_steps[index] - step.name = name - unless step.save - flash[:error] = step.errors.full_messages.to_sentence - else - @dmsf_workflow.dmsf_workflow_steps.each do |s| - if s.step == step.step - s.name = step.name - s.save - end + step = @dmsf_workflow.dmsf_workflow_steps[index] + step.name = name + unless step.save + flash[:error] = step.errors.full_messages.to_sentence + else + @dmsf_workflow.dmsf_workflow_steps.each do |s| + if s.step == step.step + s.name = step.name + s.save end end end - else - # Operators + end + # Operators + if params[:operator_step].present? params[:operator_step].each do |id, operator| step = DmsfWorkflowStep.find_by_id id if step @@ -462,9 +474,11 @@ private elsif params[:project_id] @project = Project.find_by_id params[:project_id] else - @project = Project.find_by_identifier params[:id] + @project = Project.find params[:id] end end + rescue ActiveRecord::RecordNotFound + @project = nil end def workflows_layout diff --git a/app/helpers/dmsf_folder_permissions_helper.rb b/app/helpers/dmsf_folder_permissions_helper.rb new file mode 100644 index 00000000..8ada6ed6 --- /dev/null +++ b/app/helpers/dmsf_folder_permissions_helper.rb @@ -0,0 +1,18 @@ +module DmsfFolderPermissionsHelper + + def users_checkboxes(users) + s = '' + if users + users.each do |user| + content = check_box_tag('permissions[user_ids][]', user.id, true, :id => nil) + user.name + s << content_tag(:label, content, :id => "user_permission_ids_#{user.id}", :class => 'inline') + end + end + s.html_safe + end + + def render_principals_for_new_folder_permissions(users) + principals_check_box_tags 'user_ids[]', users + end + +end diff --git a/app/helpers/dmsf_helper.rb b/app/helpers/dmsf_helper.rb index 808aaf8e..99576739 100644 --- a/app/helpers/dmsf_helper.rb +++ b/app/helpers/dmsf_helper.rb @@ -59,10 +59,12 @@ module DmsfHelper extension = File.extname(filename) extension = extension[1, extension.length-1] if File.exist?("#{File.dirname(__FILE__)}/../../assets/images/filetypes/#{extension}.png") - "filetype-#{extension}"; + cls = "filetype-#{extension}"; else - Redmine::MimeType.css_class_of(filename) + cls = Redmine::MimeType.css_class_of(filename) end + cls << ' dmsf-icon-file' if cls + cls end def plugin_asset_path(plugin, asset_type, source) @@ -89,9 +91,31 @@ module DmsfHelper 'plupload/js/i18n/en.js' end + def self.visible_folders(folders, project) + allowed = Setting.plugin_redmine_dmsf['dmsf_act_as_attachable'] && + (project.dmsf_act_as_attachable == Project::ATTACHABLE_DMS_AND_ATTACHMENTS) + folders.reject{ |folder| + if folder.system + unless allowed + true + else + issue_id = folder.title.to_i + if issue_id > 0 + issue = Issue.find_by_id issue_id + issue && !issue.visible?(User.current) + else + false + end + end + else + false + end + } + end + def self.all_children_sorted(parent, pos, ident) # Folders && files && links - nodes = parent.dmsf_folders.visible + parent.dmsf_links.visible + parent.dmsf_files.visible + nodes = visible_folders(parent.dmsf_folders.visible.to_a, parent.is_a?(Project) ? parent : parent.project) + parent.dmsf_links.visible + parent.dmsf_files.visible # Alphabetical and type sort nodes.sort! do |x, y| if ((x.is_a?(DmsfFolder) || (x.is_a?(DmsfLink) && x.is_folder?)) && @@ -105,7 +129,9 @@ module DmsfHelper end end # Calculate position - step = 1.0 / (10 ** ident) + ident = 0 unless ident + pos = (10 ** 12) unless pos + step = (10 ** 12) / (10 ** (ident * 3)) tree = [] i = 0 nodes.each do |x| @@ -113,7 +139,7 @@ module DmsfHelper i += 1 tree << [x, pos + (step * i)] else - tree << [x, pos + step + i] + tree << [x, pos + (step * (i + 1))] end end tree diff --git a/app/helpers/dmsf_links_helper.rb b/app/helpers/dmsf_links_helper.rb index 758e5ce3..aa6d1a2d 100644 --- a/app/helpers/dmsf_links_helper.rb +++ b/app/helpers/dmsf_links_helper.rb @@ -37,6 +37,18 @@ module DmsfLinksHelper # An integer test def self.is_a_number?(s) s.to_s.match(/\A[+-]?\d+?(\.\d+)?\Z/) == nil ? false : true - end + end + + def files_for_select(project_id, folder_id) + files = [] + if folder_id && (folder_id != '0') + folder = DmsfFolder.find_by_id folder_id + files = folder.dmsf_files.visible.to_a if folder + elsif project_id + project = Project.find_by_id project_id + files = project.dmsf_files.visible.to_a if project + end + files + end end diff --git a/app/helpers/dmsf_upload_helper.rb b/app/helpers/dmsf_upload_helper.rb index 78d292ac..66cf99ca 100644 --- a/app/helpers/dmsf_upload_helper.rb +++ b/app/helpers/dmsf_upload_helper.rb @@ -21,20 +21,15 @@ module DmsfUploadHelper include Redmine::I18n - def self.commit_files_internal(commited_files, container, folder, controller) + def self.commit_files_internal(commited_files, project, folder, controller) failed_uploads = [] files = [] - if container.is_a?(Project) - project = container - else - project = container.project - end if commited_files && commited_files.is_a?(Hash) failed_uploads = [] commited_files.each_value do |commited_file| name = commited_file[:name] new_revision = DmsfFileRevision.new - file = DmsfFile.visible.find_file_by_name(container, folder, name) + 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 @@ -42,11 +37,10 @@ module DmsfUploadHelper unless file file = DmsfFile.new - file.container_type = container.class.name.demodulize - file.container_id = container.id + file.project_id = project.id file.name = name file.dmsf_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 @@ -66,8 +60,6 @@ module DmsfUploadHelper next end - commited_disk_filepath = "#{DmsfHelper.temp_dir}/#{commited_file[:disk_filename].gsub(/[\/\\]/,'')}" - new_revision.dmsf_file = file new_revision.user = User.current new_revision.name = name @@ -81,9 +73,9 @@ module DmsfUploadHelper else new_revision.increase_version(version) 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 + new_revision.mime_type = commited_file[:mime_type] + new_revision.size = commited_file[:size] + new_revision.digest = DmsfFileRevision.create_digest commited_file[:tempfile_path] if commited_file[:custom_field_values].present? commited_file[:custom_field_values].each_with_index do |v, i| @@ -104,7 +96,8 @@ module DmsfUploadHelper if new_revision.save new_revision.assign_workflow(commited_file[:dmsf_workflow_id]) begin - FileUtils.mv(commited_disk_filepath, new_revision.disk_file) + FileUtils.mv commited_file[:tempfile_path], new_revision.disk_file(false) + FileUtils.chmod 'u=wr,g=r', new_revision.disk_file(false) file.set_last_revision new_revision files.push(file) if file.container.is_a?(Issue) @@ -118,14 +111,36 @@ module DmsfUploadHelper else failed_uploads.push(commited_file) end + # Approval workflow + if commited_file[:workflow_id].present? + wf = DmsfWorkflow.find_by_id commited_file[:workflow_id] + if wf + # Assign the workflow + new_revision.set_workflow(wf.id, 'assign') + new_revision.assign_workflow(wf.id) + # Start the workflow + new_revision.set_workflow(wf.id, 'start') + if new_revision.save + wf.notify_users(project, new_revision, controller) + begin + file.lock! + rescue DmsfLockError => e + Rails.logger.warn e.message + end + else + Rails.logger.error l(:error_workflow_assign) + end + end + end end - if container.is_a?(Project) && ((folder && folder.notification?) || (!folder && project.dmsf_notification?)) + # Notifications + 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 - if Setting.plugin_redmine_dmsf[:dmsf_display_notified_recipients] == '1' + 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) ? ',...' : '.') diff --git a/app/helpers/dmsf_workflows_helper.rb b/app/helpers/dmsf_workflows_helper.rb index 33e2259d..1fd9dc08 100644 --- a/app/helpers/dmsf_workflows_helper.rb +++ b/app/helpers/dmsf_workflows_helper.rb @@ -23,7 +23,7 @@ module DmsfWorkflowsHelper def render_principals_for_new_dmsf_workflow_users(workflow, dmsf_workflow_step_assignment_id = nil, dmsf_file_revision_id = nil) 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'] + principal_pages = Redmine::Pagination::Paginator.new principal_count, 100, params['page'] principals = scope.offset(principal_pages.offset).limit(principal_pages.per_page).to_a # Delegation diff --git a/app/models/dmsf_file.rb b/app/models/dmsf_file.rb index 2db788f2..e2d6d0d3 100644 --- a/app/models/dmsf_file.rb +++ b/app/models/dmsf_file.rb @@ -23,7 +23,8 @@ begin require 'xapian' $xapian_bindings_available = true rescue LoadError - Rails.logger.info 'REDMAIN_XAPIAN ERROR: No Ruby bindings for Xapian installed !!. PLEASE install Xapian search engine interface for Ruby.' + Rails.logger.warn %{No Xapian search engine interface for Ruby installed => Full-text search won't be available. + Install a ruby-xapian package or an alternative Xapian binding (https://xapian.org).} $xapian_bindings_available = false end @@ -32,6 +33,7 @@ class DmsfFile < ActiveRecord::Base include RedmineDmsf::Lockable + belongs_to :project belongs_to :dmsf_folder belongs_to :deleted_by_user, :class_name => 'User', :foreign_key => 'deleted_by_user_id' @@ -43,8 +45,8 @@ class DmsfFile < ActiveRecord::Base :class_name => 'DmsfLink', :foreign_key => 'target_id', :dependent => :destroy has_many :dmsf_public_urls, :dependent => :destroy - STATUS_DELETED = 1 - STATUS_ACTIVE = 0 + STATUS_DELETED = 1.freeze + STATUS_ACTIVE = 0.freeze scope :visible, -> { where(:deleted => STATUS_ACTIVE) } scope :deleted, -> { where(:deleted => STATUS_DELETED) } @@ -55,10 +57,8 @@ class DmsfFile < ActiveRecord::Base validate :validates_name_uniqueness - attr_accessible :project, :project_id - def validates_name_uniqueness - existing_file = DmsfFile.visible.findn_file_by_name(self.container_id, self.container_type, self.dmsf_folder, self.name) + existing_file = DmsfFile.visible.findn_file_by_name(self.project_id, self.dmsf_folder, self.name) errors.add(:name, l('activerecord.errors.messages.taken')) unless (existing_file.nil? || existing_file.id == self.id) end @@ -69,7 +69,7 @@ class DmsfFile < ActiveRecord::Base Redmine::Search.cache_store.delete("DmsfFile-#{o.id}") else # Set desc to an empty string if o.description is nil - desc = o.description.nil? ? "" : o.description + desc = o.description.nil? ? '' : o.description desc += ' / ' if o.description.present? && o.last_revision.comment.present? desc += o.last_revision.comment if o.last_revision.comment.present? end @@ -84,12 +84,10 @@ class DmsfFile < ActiveRecord::Base :date_column => "#{table_name}.updated_at" before_create :default_values + def default_values - @notifications = Setting.plugin_redmine_dmsf['dmsf_default_notifications'] - if @notifications == '1' + if (Setting.plugin_redmine_dmsf['dmsf_default_notifications'] == '1') && (!self.dmsf_folder || !self.dmsf_folder.system) self.notification = true - else - self.notification = nil end end @@ -98,36 +96,24 @@ class DmsfFile < ActiveRecord::Base super end - @@storage_path = nil - def self.storage_path - 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? - path.strip if path - path - end - - # Lets introduce a write for storage path, that way we can also - # better interact from test-cases etc - def self.storage_path=(path) - begin - FileUtils.mkdir_p(path) unless File.exist?(path) - rescue Exception => e - Rails.logger.error e.message + if path.blank? + path = Pathname.new('files').join('dmsf').to_s + else + pn = Pathname.new(path) + return pn if pn.absolute? end - @@storage_path = path + Rails.root.join(path) end - def self.find_file_by_name(container, folder, name) - self.findn_file_by_name(container.id, container.class.name.demodulize, folder, name) + def self.find_file_by_name(project, folder, name) + self.findn_file_by_name(project.id, folder, name) end - def self.findn_file_by_name(container_id, container_type, folder, name) + def self.findn_file_by_name(project_id, folder, name) where( - :container_id => container_id, - :container_type => container_type, + :project_id => project_id, :dmsf_folder_id => folder ? folder.id : nil, :name => name).visible.first end @@ -148,9 +134,13 @@ class DmsfFile < ActiveRecord::Base end def delete(commit) - if locked_for_user? + if locked_for_user? && (!User.current.allowed_to?(:force_file_unlock, self.project)) Rails.logger.info l(:error_file_is_locked) - errors[:base] << l(:error_file_is_locked) + if self.lock.reverse[0].user + errors[:base] << l(:title_locked_by_user, :user => self.lock.reverse[0].user) + else + errors[:base] << l(:error_file_is_locked) + end return false end begin @@ -245,63 +235,43 @@ class DmsfFile < ActiveRecord::Base projects end - def move_to(container, folder) + def move_to(project, folder) if self.locked_for_user? errors[:base] << l(:error_file_is_locked) return false end - - project = container.is_a?(Project) ? container : container.project - # If the target project differs from the source project we must physically move the disk files - if self.project != project - 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 - end - end - # Must invalidate source parent folder cache before moving RedmineDmsf::Webdav::Cache.invalidate_item(propfind_cache_key) - - self.container_type = self.container_type - self.container_id = container.id + self.project_id = project.id self.dmsf_folder = folder new_revision = self.last_revision.clone new_revision.dmsf_file = self new_revision.comment = l(:comment_moved_from, :source => "#{self.project.identifier}:#{self.dmsf_path_str}") new_revision.custom_values = [] - self.last_revision.custom_values.each do |cv| new_revision.custom_values << CustomValue.new({:custom_field => cv.custom_field, :value => cv.value}) end - self.set_last_revision(new_revision) - self.save && new_revision.save end - def copy_to(container, folder = nil) - copy_to_filename(container, folder, self.name) + def copy_to(project, folder = nil) + copy_to_filename(project, folder, self.name) end - def copy_to_filename(container, folder=nil, filename) - project = container.is_a?(Project) ? container : container.project - # If the target project differs from the source project we must physically move the disk files - if (self.project != project) && self.last_revision - if File.exist? self.last_revision.disk_file(self.project) - FileUtils.cp self.last_revision.disk_file(self.project), self.last_revision.disk_file(project) - end - end + def copy_to_filename(project, folder, filename) file = DmsfFile.new file.dmsf_folder = folder - file.container_type = self.container_type - file.container_id = container.id + file.project_id = project.id file.name = filename file.notification = Setting.plugin_redmine_dmsf['dmsf_default_notifications'].present? if file.save && self.last_revision new_revision = self.last_revision.clone new_revision.dmsf_file = file + new_revision.disk_filename = new_revision.new_storage_filename + if File.exist? self.last_revision.disk_file + FileUtils.cp self.last_revision.disk_file, new_revision.disk_file(false) + end new_revision.comment = l(:comment_copied_from, :source => "#{project.identifier}: #{self.dmsf_path_str}") new_revision.custom_values = [] self.last_revision.custom_values.each do |cv| @@ -337,8 +307,7 @@ class DmsfFile < ActiveRecord::Base project_conditions << Project.allowed_to_condition(user, :view_dmsf_files) project_conditions << "#{Project.table_name}.id IN (#{project_ids.join(',')})" if project_ids.present? - scope = self.visible.joins(:dmsf_file_revisions).joins( - "JOIN #{Project.table_name} ON #{DmsfFile.table_name}.container_id = #{Project.table_name}.id AND #{DmsfFile.table_name}.container_type = 'Project'") + scope = self.visible.joins('JOIN dmsf_file_revisions ON dmsf_file_revisions.dmsf_file_id = dmsf_files.id').joins(:project) 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 ')) @@ -352,8 +321,8 @@ 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, initiated or it's corrupted." - Rails.logger.warn e.message + Rails.logger.error "REDMINE_XAPIAN ERROR: Xapian database is not properly set, initiated or it's corrupted." + Rails.logger.error e.message end if database @@ -401,7 +370,7 @@ class DmsfFile < ActiveRecord::Base if dmsf_file if user.allowed_to?(:view_dmsf_files, dmsf_file.project) && - (project_ids.blank? || (project_ids.include?(dmsf_file.project.id))) + (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]) @@ -423,7 +392,7 @@ class DmsfFile < ActiveRecord::Base end def display_name - member = Member.where(:user_id => User.current.id, :project_id => self.project.id).first + member = Member.where(:user_id => User.current.id, :project_id => self.project_id).first if member && !member.title_format.nil? && !member.title_format.empty? title_format = member.title_format else @@ -496,7 +465,13 @@ class DmsfFile < ActiveRecord::Base def custom_value(custom_field) self.last_revision.custom_field_values.each do |cv| - return cv.value if cv.custom_field == custom_field + if cv.custom_field == custom_field + if cv.value.is_a? Array + return cv.value.reject{ |x| x.empty? }.join(',') + else + return cv.value + end + end end nil end @@ -522,18 +497,16 @@ class DmsfFile < ActiveRecord::Base end def propfind_cache_key - if self.container_type == 'Project' - if dmsf_folder_id.nil? - # File is in project root - return "PROPFIND/#{self.container_id}" - else - return "PROPFIND/#{self.container_id}/#{self.dmsf_folder_id}" - end + unless dmsf_folder_id + # File is in project root + return "PROPFIND/#{self.project_id}" + else + return "PROPFIND/#{self.project_id}/#{self.dmsf_folder_id}" end end def extension - $1 if self.last_revision && self.last_revision.disk_filename =~ /\.(.+)$/ + File.extname(self.last_revision.disk_filename).strip.downcase[1..-1] if self.last_revision end include ActionView::Helpers::NumberHelper @@ -583,6 +556,14 @@ class DmsfFile < ActiveRecord::Base csv << '' end end + # Last approver + if columns.include?(l(:label_last_approver)) + if self.last_revision && self.last_revision.dmsf_workflow + csv << self.last_revision.workflow_tooltip + else + csv << '' + end + end # Url if columns.include?(l(:label_document_url)) default_url_options[:host] = Setting.host_name @@ -604,31 +585,6 @@ class DmsfFile < ActiveRecord::Base csv end - def project - unless @project - case self.container_type - when 'Project' - @project = Project.find_by_id(self.container_id) - when 'Issue' - issue = Issue.find_by_id(self.container_id) - @project = issue.project if issue - end - end - @project - end - - def container - unless @container - case self.container_type - when 'Project' - @container = Project.find_by_id(self.container_id) - when 'Issue' - @container = Issue.find_by_id(self.container_id) - end - end - @container - end - def thumbnail(options={}) if image? size = options[:size].to_i @@ -644,7 +600,7 @@ class DmsfFile < ActiveRecord::Base target = File.join(Attachment.thumbnails_storage_path, "#{self.id}_#{self.last_revision.digest}_#{size}.thumb") begin - Redmine::Thumbnail.generate(self.last_revision.disk_file, target, size) + Redmine::Thumbnail.generate(self.last_revision.disk_file.to_s, target, size) rescue => e Rails.logger.error "An error occured while generating thumbnail for #{self.last_revision.disk_file} to #{target}\nException was: #{e.message}" return nil @@ -652,4 +608,24 @@ class DmsfFile < ActiveRecord::Base end end + def get_locked_title + if self.locked_for_user? + if self.lock.reverse[0].user + return l(:title_locked_by_user, :user => self.lock.reverse[0].user) + else + return l(:notice_account_unknown_email) + end + end + l(:title_unlock_file) + end + + def container + unless @container + if self.dmsf_folder && self.dmsf_folder.system + @container = Issue.where(:id => self.dmsf_folder.title.to_i).first + end + end + @container + end + end diff --git a/app/models/dmsf_file_revision.rb b/app/models/dmsf_file_revision.rb index 1d6da7b6..2fb3922b 100644 --- a/app/models/dmsf_file_revision.rb +++ b/app/models/dmsf_file_revision.rb @@ -28,6 +28,7 @@ class DmsfFileRevision < ActiveRecord::Base belongs_to :source_revision, :class_name => 'DmsfFileRevision', :foreign_key => 'source_dmsf_file_revision_id' belongs_to :user belongs_to :deleted_by_user, :class_name => 'User', :foreign_key => 'deleted_by_user_id' + belongs_to :dmsf_workflow has_many :dmsf_file_revision_access, :dependent => :destroy has_many :dmsf_workflow_step_assignment, :dependent => :destroy @@ -48,10 +49,8 @@ class DmsfFileRevision < ActiveRecord::Base :timestamp => "#{DmsfFileRevision.table_name}.updated_at", :author_key => "#{DmsfFileRevision.table_name}.user_id", :permission => :view_dmsf_file_revisions, - :scope => select("#{DmsfFileRevision.table_name}.*"). - joins(:dmsf_file).joins( - "LEFT JOIN #{Project.table_name} ON #{DmsfFile.table_name}.container_id = #{Project.table_name}.id"). - where("#{DmsfFile.table_name}.container_type = ?", 'Project').visible + :scope => DmsfFileRevision.joins(:dmsf_file). + joins("JOIN #{Project.table_name} ON #{Project.table_name}.id = #{DmsfFile.table_name}.project_id").visible validates :title, :presence => true validates_format_of :name, :with => DmsfFolder::INVALID_CHARACTERS, @@ -72,6 +71,10 @@ class DmsfFileRevision < ActiveRecord::Base def self.filename_to_title(filename) remove_extension(filename).gsub(/_+/, ' '); end + + def self.easy_activity_custom_project_scope(scope, options, event_type) + scope.where(:dmsf_files => { :project_id => options[:project_ids] }) + end def delete(commit = false, force = true) if self.dmsf_file.locked_for_user? @@ -105,7 +108,7 @@ class DmsfFileRevision < ActiveRecord::Base d.save! end if Setting.plugin_redmine_dmsf['dmsf_really_delete_files'] - dependencies = DmsfFileRevision.where(:disk_filename => self.disk_filename).all.count + dependencies = DmsfFileRevision.where(:disk_filename => self.disk_filename).count File.delete(self.disk_file) if dependencies <= 1 && File.exist?(self.disk_file) end RedmineDmsf::Webdav::Cache.invalidate_item(propfind_cache_key) @@ -131,20 +134,37 @@ class DmsfFileRevision < ActiveRecord::Base "#{self.major_version}.#{self.minor_version}" end - def storage_base_path(project = nil) - project = self.dmsf_file.project unless project - path = DmsfFile.storage_path.dup - if self.dmsf_file && project - project_base = project.identifier.gsub(/[^\w\.\-]/,'_') - path << "/p_#{project_base}" - end + def storage_base_path + time = self.created_at || DateTime.now + path = time.strftime('%Y/%m') + DmsfFile.storage_path.join path end - def disk_file(project = nil) - project = self.dmsf_file.project unless project - path = storage_base_path(project) + def disk_file(search_if_not_exists = true) + path = self.storage_base_path FileUtils.mkdir_p(path) unless File.exist?(path) - "#{path}/#{self.disk_filename}" + filename = path.join(self.disk_filename) + if search_if_not_exists + unless File.exist?(filename) + # Let's search for the physical file in source revisions + revisions = self.dmsf_file.dmsf_file_revisions.where(['id < ?', self.id]).order(:id => :desc) + revisions.each do |rev| + filename = rev.disk_file + break if File.exist?(filename) + end + end + end + filename + end + + def new_storage_filename + 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?(storage_base_path.join("#{timestamp}_#{self.dmsf_file.id}_#{filename}")) + timestamp.succ! + end + "#{timestamp}_#{self.dmsf_file.id}_#{filename}" end def detect_content_type @@ -227,16 +247,6 @@ class DmsfFileRevision < ActiveRecord::Base end end - def new_storage_filename - 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(storage_base_path, "#{timestamp}_#{self.dmsf_file.id}_#{filename}")) - timestamp.succ! - end - "#{timestamp}_#{self.dmsf_file.id}_#{filename}" - end - def copy_file_content(open_file) File.open(self.disk_file, 'wb') do |f| while (buffer = open_file.read(8192)) @@ -311,4 +321,25 @@ class DmsfFileRevision < ActiveRecord::Base dmsf_file.propfind_cache_key end + def workflow_tooltip + tooltip = '' + if self.dmsf_workflow + case workflow + when DmsfWorkflow::STATE_WAITING_FOR_APPROVAL, DmsfWorkflow::STATE_ASSIGNED + assignments = self.dmsf_workflow.next_assignments(self.id) + if assignments + assignments.each_with_index do |assignment, index| + tooltip << ', ' if index > 0 + tooltip << assignment.user.name + end + end + when DmsfWorkflow::STATE_APPROVED, DmsfWorkflow::STATE_REJECTED + action = DmsfWorkflowStepAction.joins(:dmsf_workflow_step_assignment).where( + ['dmsf_workflow_step_assignments.dmsf_file_revision_id', self.id]).order('dmsf_workflow_step_actions.id').last + tooltip << action.author.name if action + end + end + tooltip + end + end diff --git a/app/models/dmsf_file_revision_access.rb b/app/models/dmsf_file_revision_access.rb index f83223af..a2189aad 100644 --- a/app/models/dmsf_file_revision_access.rb +++ b/app/models/dmsf_file_revision_access.rb @@ -40,9 +40,8 @@ class DmsfFileRevisionAccess < ActiveRecord::Base :timestamp => "#{DmsfFileRevisionAccess.table_name}.updated_at", :author_key => "#{DmsfFileRevisionAccess.table_name}.user_id", :permission => :view_dmsf_file_revision_accesses, - :scope => select("#{DmsfFileRevisionAccess.table_name}.*"). - joins(:dmsf_file_revision).joins( - "LEFT JOIN #{DmsfFile.table_name} ON #{DmsfFileRevision.table_name}.dmsf_file_id = #{DmsfFile.table_name}.id " + - "LEFT JOIN #{Project.table_name} ON #{DmsfFile.table_name}.container_id = #{Project.table_name}.id"). - where("#{DmsfFile.table_name}.deleted = ? AND #{DmsfFile.table_name}.container_type = ?", DmsfFile::STATUS_ACTIVE, 'Project') + :scope => DmsfFileRevisionAccess. + joins(:dmsf_file_revision).joins("JOIN #{DmsfFile.table_name} ON dmsf_files.id = dmsf_file_revisions.dmsf_file_id"). + joins("JOIN #{Project.table_name} on dmsf_files.project_id = projects.id"). + where(:dmsf_files => { :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 01147a3e..8a37299c 100644 --- a/app/models/dmsf_file_revision_custom_field.rb +++ b/app/models/dmsf_file_revision_custom_field.rb @@ -26,8 +26,16 @@ class DmsfFileRevisionCustomField < CustomField end def compare_values?(x, y) - if x.is_a?(Array) && y.is_a?(Array) && !y.empty? - x.include? y[0] + if x.is_a?(Array) && y.is_a?(Array) + y.reject!{ |a| a.empty? } + return true if y.empty? + x.reject!{ |a| a.empty? } + y.each do |b| + if x.include?(b) + return true + end + end + return false else x == y end diff --git a/app/models/dmsf_folder.rb b/app/models/dmsf_folder.rb index 8445e54b..60dab618 100644 --- a/app/models/dmsf_folder.rb +++ b/app/models/dmsf_folder.rb @@ -29,7 +29,7 @@ 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, -> { order(:title) }, :dependent => :destroy + has_many :dmsf_folders, -> { order :title }, :dependent => :destroy has_many :dmsf_files, :dependent => :destroy has_many :folder_links, -> { where(:target_type => 'DmsfFolder').order(:name) }, :class_name => 'DmsfLink', :foreign_key => 'dmsf_folder_id', :dependent => :destroy @@ -42,15 +42,24 @@ class DmsfFolder < ActiveRecord::Base :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 + has_many :dmsf_folder_permissions, :dependent => :destroy - INVALID_CHARACTERS = /\A[^\/\\\?":<>#%\*]*\z/.freeze + INVALID_CHARACTERS = /\A[^\[\]\/\\\?":<>#%\*]*\z/.freeze STATUS_DELETED = 1.freeze STATUS_ACTIVE = 0.freeze AVAILABLE_COLUMNS = %w(id title extension size modified version workflow author).freeze DEFAULT_COLUMNS = %w(title size modified version workflow author).freeze - scope :visible, -> { where(:deleted => STATUS_ACTIVE) } - scope :deleted, -> { where(:deleted => STATUS_DELETED) } + scope :visible, -> (system=true) { joins(:project).joins( + "LEFT JOIN #{DmsfFolderPermission.table_name} ON #{DmsfFolder.table_name}.id = #{DmsfFolderPermission.table_name}.dmsf_folder_id").where( + :deleted => STATUS_ACTIVE).where(DmsfFolder.visible_condition(system)).distinct + } + scope :deleted, -> { joins(:project).joins( + "LEFT JOIN #{DmsfFolderPermission.table_name} ON #{DmsfFolder.table_name}.id = #{DmsfFolderPermission.table_name}.dmsf_folder_id").where( + :deleted => STATUS_DELETED).where(DmsfFolder.visible_condition).distinct + } + scope :system, -> { where(:system => true) } + scope :notsystem, -> { where(:system => false) } acts_as_customizable @@ -72,14 +81,57 @@ class DmsfFolder < ActiveRecord::Base validates_format_of :title, :with => INVALID_CHARACTERS, :message => l(:error_contains_invalid_character) validate :check_cycle + validates_length_of :description, :maximum => 65535 before_create :default_values + + def self.visible_condition(system=true) + Project.allowed_to_condition(User.current, :view_dmsf_folders) do |role, user| + if user.id && user.logged? + permissions = "#{DmsfFolderPermission.table_name}" + folders = "#{DmsfFolder.table_name}" + group_ids = user.group_ids.join(',') + group_ids = -1 if group_ids.blank? + allowed = (system && role.allowed_to?(:display_system_folders)) ? 1 : 0 + %{ + (#{permissions}.object_id IS NULL) OR + (#{permissions}.object_id = #{role.id} AND #{permissions}.object_type = 'Role') OR + ((#{permissions}.object_id = #{user.id} OR #{permissions}.object_id IN (#{group_ids})) AND #{permissions}.object_type = 'User') AND + (#{folders}.system = #{DmsfFolder.connection.quoted_false} OR 1 = #{allowed}) + } + else + '0 = 1' + end + end + end + + def self.permissions?(folder, allow_system = true) + # Administrator? + return true if (User.current.admin? || folder.nil?) + # System folder? + if folder && folder.system + return false if (!allow_system || !User.current.allowed_to?(:display_system_folders, folder.project)) + return false unless self.issue && self.issue.visible?(User.current) + end + # Permissions? + if !folder.dmsf_folder || permissions?(folder.dmsf_folder, allow_system) + if folder.dmsf_folder_permissions.any? + role_ids = User.current.roles_for_project(folder.project).map{ |r| r.id } + role_permission_ids = folder.dmsf_folder_permissions.roles.map{ |p| p.object_id } + return true if (role_ids & role_permission_ids).any? + principal_ids = folder.dmsf_folder_permissions.users.map{ |p| p.object_id } + return true if principal_ids.include?(User.current.id) + user_group_ids = User.current.groups.map{ |g| g.id } + return true if (principal_ids & user_group_ids).any? + return false + end + true + end + end + def default_values - @notifications = Setting.plugin_redmine_dmsf['dmsf_default_notifications'] - if @notifications == '1' + if Setting.plugin_redmine_dmsf['dmsf_default_notifications'] == '1' && !self.system self.notification = true - else - self.notification = nil end end @@ -108,7 +160,7 @@ class DmsfFolder < ActiveRecord::Base if self.locked? errors[:base] << l(:error_folder_is_locked) return false - elsif !self.dmsf_folders.visible.empty? || !self.dmsf_files.visible.empty? + elsif !self.dmsf_folders.visible.empty? || !self.dmsf_files.visible.empty? || !self.dmsf_links.visible.empty? errors[:base] << l(:error_folder_is_not_empty) return false end @@ -169,8 +221,11 @@ class DmsfFolder < ActiveRecord::Base end def self.directory_tree(project, current_folder = nil) + unless project.is_a? Project + project = Project.find_by_id project + end tree = [[l(:link_documents), nil]] - project.dmsf_folders.visible.each do |folder| + project.dmsf_folders.notsystem.visible(false).each do |folder| unless folder == current_folder tree.push(["...#{folder.title}", folder.id]) directory_subtree(tree, folder, 2, current_folder) @@ -262,6 +317,10 @@ class DmsfFolder < ActiveRecord::Base l.copy_to project, new_folder end + self.dmsf_folder_permissions.each do |p| + p.copy_to new_folder + end + return new_folder end @@ -272,37 +331,32 @@ class DmsfFolder < ActiveRecord::Base def modified last_update = updated_at - dmsf_folders.each do |subfolder| - last_update = subfolder.updated_at if subfolder.updated_at > last_update - end - dmsf_files.each do |file| - last_update = file.updated_at if file.updated_at > last_update - end - folder_links.each do |folder_link| - last_update = folder_link.updated_at if folder_link.updated_at > last_update - end - file_links.each do |file_link| - last_update = file_link.updated_at if file_link.updated_at > last_update - end - url_links.each do |url_link| - last_update = url_link.updated_at if url_link.updated_at > last_update - end + time = DmsfFolder.where( + ['project_id = ? AND dmsf_folder_id = ? AND updated_at > ?', + self.project_id, self.id, last_update]).maximum(:updated_at) + last_update = time if time + time = DmsfFile.where( + ['project_id = ? AND dmsf_folder_id = ? AND updated_at > ?', + self.project_id, self.id, last_update]).maximum(:updated_at) + last_update = time if time + time = DmsfLink.where( + ['project_id = ? AND dmsf_folder_id = ? AND updated_at > ?', + self.project_id, self.id, last_update]).maximum(:updated_at) + last_update = time if time last_update end # Number of items in the folder def items - dmsf_folders.visible.count + - dmsf_files.visible.count + - folder_links.visible.count + - file_links.visible.count + - url_links.visible.count + dmsf_folders.visible.where(:project_id => self.project_id).count + + dmsf_files.visible.where(:project_id => self.project_id).count + + dmsf_links.visible.where(:project_id => self.project_id).count end def self.is_column_on?(column) - columns = Setting.plugin_redmine_dmsf['dmsf_columns'] - columns = DmsfFolder::DEFAULT_COLUMNS unless columns - columns.include? column + dmsf_columns = Setting.plugin_redmine_dmsf['dmsf_columns'] + dmsf_columns = DmsfFolder::DEFAULT_COLUMNS unless dmsf_columns + dmsf_columns.include? column end def custom_value(custom_field) @@ -313,61 +367,61 @@ class DmsfFolder < ActiveRecord::Base end def self.get_column_position(column) + dmsf_columns = Setting.plugin_redmine_dmsf['dmsf_columns'] + dmsf_columns = DmsfFolder::DEFAULT_COLUMNS unless dmsf_columns pos = 0 - columns = Setting.plugin_redmine_dmsf['dmsf_columns'] - columns = DmsfFolder::DEFAULT_COLUMNS unless columns # 0 - checkbox # 1 - id - if columns.include?('id') + if dmsf_columns.include?('id') pos += 1 return pos if column == 'id' else return nil if column == 'id' end # 2 - title - if columns.include?('title') + if dmsf_columns.include?('title') pos += 1 return pos if column == 'title' else return nil if column == 'title' end # 3 - extension - if columns.include?('extension') + if dmsf_columns.include?('extension') pos += 1 return pos if column == 'extension' else return nil if column == 'extension' end # 4 - size - if columns.include?('size') + if dmsf_columns.include?('size') pos += 1 return pos if column == 'size' else return nil if column == 'size' end # 5 - modified - if columns.include?('modified') + if dmsf_columns.include?('modified') pos += 1 return pos if column == 'modified' else return nil if column == 'modified' end # 6 - version - if columns.include?('version') + if dmsf_columns.include?('version') pos += 1 return pos if column == 'version' else return nil if column == 'version' end # 7 - workflow - if columns.include?('workflow') + if dmsf_columns.include?('workflow') pos += 1 return pos if column == 'workflow' else return nil if column == 'workflow' end # 8 - author - if columns.include?('author') + if dmsf_columns.include?('author') pos += 1 return pos if column == 'author' else @@ -399,22 +453,22 @@ class DmsfFolder < ActiveRecord::Base end def save(*args) - RedmineDmsf::Webdav::Cache.invalidate_item(propfind_cache_key) + RedmineDmsf::Webdav::Cache.invalidate_item(propfind_cache_key) super(*args) end def save!(*args) - RedmineDmsf::Webdav::Cache.invalidate_item(propfind_cache_key) + RedmineDmsf::Webdav::Cache.invalidate_item(propfind_cache_key) super(*args) end def destroy - RedmineDmsf::Webdav::Cache.invalidate_item(propfind_cache_key) + RedmineDmsf::Webdav::Cache.invalidate_item(propfind_cache_key) super end def destroy! - RedmineDmsf::Webdav::Cache.invalidate_item(propfind_cache_key) + RedmineDmsf::Webdav::Cache.invalidate_item(propfind_cache_key) super end @@ -450,6 +504,8 @@ class DmsfFolder < ActiveRecord::Base csv << '' if columns.include?('workflow') # Author csv << self.user.name if columns.include?('author') + # Last approver + csv << '' if columns.include?(l(:label_last_approver)) # Url if columns.include?(l(:label_document_url)) default_url_options[:host] = Setting.host_name @@ -465,10 +521,66 @@ class DmsfFolder < ActiveRecord::Base csv end + def get_locked_title + if self.locked_for_user? + if self.lock.reverse[0].user + return l(:title_locked_by_user, :user => self.lock.reverse[0].user) + else + return l(:notice_account_unknown_email) + end + end + l(:title_unlock_file) + end + + def issue + unless @issue + if self.system + issue_id = self.title.to_i + @issue = Issue.find_by_id(issue_id) if issue_id > 0 + end + end + @issue + end + + def update_from_params(params) + # Attributes + self.title = params[:dmsf_folder][:title].strip + self.description = params[:dmsf_folder][:description].strip + self.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| + self.custom_field_values[i].value = v[1] + end + end + # Permissions + self.dmsf_folder_permissions.delete_all + if params[:permissions] + if params[:permissions][:role_ids] + params[:permissions][:role_ids].each do |role_id| + permission = DmsfFolderPermission.new + permission.object_id = role_id + permission.object_type = Role.model_name.to_s + self.dmsf_folder_permissions << permission + end + end + if params[:permissions][:user_ids] + params[:permissions][:user_ids].each do |user_id| + permission = DmsfFolderPermission.new + permission.object_id = user_id + permission.object_type = User.model_name.to_s + self.dmsf_folder_permissions << permission + end + end + end + # Save + self.save + end + private def self.directory_subtree(tree, folder, level, current_folder) - folder.dmsf_folders.visible.each do |subfolder| + folder.dmsf_folders.visible(false).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_folder_permission.rb b/app/models/dmsf_folder_permission.rb new file mode 100644 index 00000000..0c3d86ab --- /dev/null +++ b/app/models/dmsf_folder_permission.rb @@ -0,0 +1,38 @@ +# encoding: utf-8 +# +# Redmine plugin for Document Management System "Features" +# +# Copyright (C) 2011-17 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 DmsfFolderPermission < ActiveRecord::Base + unloadable + + belongs_to :dmsf_folder + + scope :users, -> { where(:object_type => User.model_name.to_s) } + scope :roles, -> { where(:object_type => Role.model_name.to_s) } + + def copy_to(folder) + permission = DmsfFolderPermission.new + permission.dmsf_folder_id = folder.id + permission.object_id = self.object_id + permission.object_type = self.object_type + permission.save + permission + end + +end diff --git a/app/models/dmsf_link.rb b/app/models/dmsf_link.rb index 3c815f0b..cba58487 100644 --- a/app/models/dmsf_link.rb +++ b/app/models/dmsf_link.rb @@ -62,7 +62,12 @@ class DmsfLink < ActiveRecord::Base end def target_folder - DmsfFolder.find_by_id self.target_folder_id if self.target_folder_id + unless @target_folder + if self.target_folder_id + @target_folder = DmsfFolder.find_by_id self.target_folder_id + end + end + @target_folder end def target_file_id @@ -70,15 +75,28 @@ class DmsfLink < ActiveRecord::Base end def target_file - DmsfFile.find_by_id self.target_file_id if self.target_file_id + unless @target_file + if self.target_file_id + @target_file = DmsfFile.find_by_id self.target_file_id + end + end + @target_file end def target_project - Project.find_by_id self.target_project_id + unless @target_project + @target_project = Project.find_by_id self.target_project_id + end + @target_project end def folder - DmsfFolder.find_by_id self.dmsf_folder_id + unless @folder + if self.dmsf_folder_id + @folder = DmsfFolder.find_by_id self.dmsf_folder_id + end + end + @folder end def title @@ -110,20 +128,29 @@ class DmsfLink < ActiveRecord::Base end def copy_to(project, folder) - link = DmsfLink.new( - :target_project_id => self.target_project_id, - :target_id => self.target_id, - :target_type => self.target_type, - :name => self.name, - :external_url => self.external_url, - :project_id => project.id, - :dmsf_folder_id => folder ? folder.id : nil) + link = DmsfLink.new + link.target_project_id = self.target_project_id + link.target_id = self.target_id + link.target_type = self.target_type + link.name = self.name + link.external_url = self.external_url + link.project_id = project.id + link.dmsf_folder_id = folder ? folder.id : nil link.save link end + def container + if self.folder && self.folder.system + Issue.where(:id => self.folder.title.to_i).first + end + end + def delete(commit = false) if commit + if self.container.is_a?(Issue) + self.container.dmsf_file_removed(self.target_file) + end self.destroy else self.deleted = STATUS_DELETED @@ -171,6 +198,8 @@ class DmsfLink < ActiveRecord::Base csv << '' if columns.include?('workflow') # Author csv << self.user.name if columns.include?('author') + # Last approver + csv << '' if columns.include?(l(:label_last_approver)) # Url csv << self.external_url if columns.include?(l(:label_document_url)) # Revision diff --git a/app/models/dmsf_mailer.rb b/app/models/dmsf_mailer.rb index 0e5e9b22..00f00876 100644 --- a/app/models/dmsf_mailer.rb +++ b/app/models/dmsf_mailer.rb @@ -51,7 +51,6 @@ class DmsfMailer < Mailer end 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] == '1' @@ -61,6 +60,7 @@ class DmsfMailer < Mailer @files = email_params[:files] unless @links_only + zipped_content_data = open(email_params[:zipped_content], 'rb') { |io| io.read } attachments['Documents.zip'] = { :content_type => 'application/zip', :content => zipped_content_data } end mail :to => email_params[:to], :cc => email_params[:cc], @@ -87,6 +87,7 @@ class DmsfMailer < Mailer end def self.get_notify_users(project, files = []) + return [] unless project.active? if files.present? notify_files = files.select { |file| file.notify? } return [] if notify_files.empty? diff --git a/app/models/dmsf_upload.rb b/app/models/dmsf_upload.rb index c56408a8..2b5b5e6f 100644 --- a/app/models/dmsf_upload.rb +++ b/app/models/dmsf_upload.rb @@ -33,6 +33,7 @@ class DmsfUpload attr_accessor :locked attr_accessor :workflow attr_accessor :custom_values + attr_accessor :tempfile_path def disk_file "#{DmsfHelper.temp_dir}/#{self.disk_filename}" @@ -45,15 +46,14 @@ class DmsfUpload :disk_filename => DmsfHelper.temp_filename(a.filename), :content_type => a.content_type, :original_filename => a.filename, - :comment => uploaded_file[:description] + :comment => uploaded_file[:description], + :tempfile_path => a.diskfile } - FileUtils.mv(a.diskfile, "#{DmsfHelper.temp_dir}/#{uploaded[:disk_filename]}") - a.destroy - upload = DmsfUpload.new(project, folder, uploaded) + DmsfUpload.new(project, folder, uploaded) else Rails.logger.error "An attachment not found by its token: #{uploaded_file[:token]}" + nil end - upload end def initialize(project, folder, uploaded) @@ -67,7 +67,8 @@ class DmsfUpload @disk_filename = uploaded[:disk_filename] @mime_type = uploaded[:content_type] - @size = File.size(disk_file) + @size = File.size(uploaded[:tempfile_path]) + @tempfile_path = uploaded[:tempfile_path] if file.nil? || file.last_revision.nil? @title = DmsfFileRevision.filename_to_title(@name) @@ -76,8 +77,7 @@ class DmsfUpload @minor_version = 0 @workflow = nil file = DmsfFile.new - file.container_type = 'Project' - file.container_id = project.id + file.project_id = project.id revision = DmsfFileRevision.new revision.dmsf_file = file @custom_values = revision.custom_field_values diff --git a/app/models/dmsf_workflow.rb b/app/models/dmsf_workflow.rb index 015f6abf..c4358fd4 100644 --- a/app/models/dmsf_workflow.rb +++ b/app/models/dmsf_workflow.rb @@ -22,8 +22,8 @@ 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 :sorted, lambda { order(:name => :asc) } + scope :global, lambda { where(:project_id => nil) } scope :active, lambda { where(:status => STATUS_ACTIVE) } scope :status, lambda { |arg| where(arg.blank? ? nil : {:status => arg.to_i}) } @@ -106,8 +106,8 @@ class DmsfWorkflow < ActiveRecord::Base 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.container_id = m.project_id JOIN dmsf_file_revisions r ON r.dmsf_file_id = f.id WHERE r.id = ? AND container_type = ?)', - dmsf_workflow_step_assignment_id, dmsf_file_revision_id, 'Project'] + '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 @@ -167,19 +167,6 @@ class DmsfWorkflow < ActiveRecord::Base results end - def self.assignments_to_users_str(assignments) - str = '' - if assignments - assignments.each_with_index do |assignment, index| - if index > 0 - str << ', ' - end - str << assignment.user.name - end - end - str - end - def assign(dmsf_file_revision_id) dmsf_workflow_steps.each do |ws| ws.assign(dmsf_file_revision_id) @@ -230,4 +217,27 @@ class DmsfWorkflow < ActiveRecord::Base self.status == STATUS_ACTIVE end + def notify_users(project, revision, controller) + assignments = self.next_assignments revision.id + recipients = assignments.collect{ |a| a.user } + recipients.uniq! + recipients = recipients & DmsfMailer.get_notify_users(project) + recipients.each do |user| + DmsfMailer.workflow_notification( + user, + self, + revision, + :text_email_subject_started, + :text_email_started, + :text_email_to_proceed).deliver + end + 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) ? ',...' : '.') + controller.flash[:warning] = l(:warning_email_notifications, :to => to) + end + end + end + end diff --git a/app/models/dmsf_workflow_step_action.rb b/app/models/dmsf_workflow_step_action.rb index 0ab4171e..54de17a5 100644 --- a/app/models/dmsf_workflow_step_action.rb +++ b/app/models/dmsf_workflow_step_action.rb @@ -21,6 +21,7 @@ class DmsfWorkflowStepAction < ActiveRecord::Base belongs_to :dmsf_workflow_step_assignment + belongs_to :author, :class_name => 'User', :foreign_key => 'author_id' validates :dmsf_workflow_step_assignment_id, :presence => true validates :action, :presence => true diff --git a/app/views/dmsf/_custom_fields.html.erb b/app/views/dmsf/_custom_fields.html.erb index 7b709470..56a3f052 100644 --- a/app/views/dmsf/_custom_fields.html.erb +++ b/app/views/dmsf/_custom_fields.html.erb @@ -19,15 +19,18 @@ # # 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 object %>
<% object.show_custom_field_values.each do |custom_value| %> -

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

+
+ <%= content_tag :div, h(custom_value.custom_field.name), :class => 'label' %> +
+ <%= 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 123bd82a..534dfb15 100644 --- a/app/views/dmsf/_dir.html.erb +++ b/app/views/dmsf/_dir.html.erb @@ -20,13 +20,23 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. %> -<% locked_for_user = subfolder && subfolder.locked_for_user? %> -<% locked = subfolder && subfolder.locked? %> - -<%= check_box_tag(name, id, false, - :title => l(:title_check_for_zip_download_or_email), :id => "subfolder_#{id}") %> + + <% unless (subfolder && subfolder.system) %> + <%= check_box_tag(name, id, false, :title => l(:title_check_for_zip_download_or_email), :id => "subfolder_#{id}") %> + <% end %> + <% if DmsfFolder.is_column_on?('id') %> - <%= link_to(subfolder.id, edit_dmsf_path(:id => project, :folder_id => subfolder)) %> + + <% if subfolder %> + <% if subfolder.system %> + <%= subfolder.id %> + <% else %> + <%= link_to(subfolder.id, edit_dmsf_path(:id => project, :folder_id => subfolder)) %> + <% end %> + <% else %> + <%= link_to(link.target_project_id, project_path(link.target_project)) %> + <% end %> + <% end %> <% if DmsfFolder.is_column_on?('title') %> @@ -51,21 +61,7 @@ <% end %> <% if DmsfFolder.is_column_on?('modified') %> - <%= 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'), - 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'), - :title => l(:notice_account_unknown_email)) %> - <% end %> - <% elsif locked %> - <%= content_tag(:span, image_tag(link ? 'lockedbycurrent_gray.png' : 'lockedbycurrent.png', :plugin => 'redmine_dmsf'), - :title => l(:title_locked_by_you)) %> - <% end %> - + <%= format_time(subfolder.modified) if subfolder %> <% end %> <% if DmsfFolder.is_column_on?('version') %> @@ -85,60 +81,51 @@ <% end %> <% end %> - <% if @folder_manipulation_allowed %> - <% unless locked_for_user %> - <% if subfolder %> - <%= 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 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 %> + <% if @folder_manipulation_allowed && !(subfolder && subfolder.system) %> + <% if subfolder && subfolder.locked? %> + + <% if subfolder.unlockable? && (!subfolder.locked_for_user? || @force_file_unlock_allowed) %> + <%= link_to('', unlock_dmsf_path(:id => project, :folder_id => subfolder), + :title => subfolder.get_locked_title, + :class => 'icon-only icon-unlock') %> <% else %> - <%= link_to(image_tag('edit.png'), edit_root_dmsf_path(:id => @project), - :title => l(:link_edit, :title => l(:link_documents))) %> - - <% end %> - <% 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 %> - <%= 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)) %> - <% 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 %> - <%= 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 %> - - <% 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)) %> + <% if subfolder %> + <%= link_to('', edit_dmsf_path(:id => project, :folder_id => subfolder), + :title => l(:link_edit, :title => h(subfolder.title)), + :class => 'icon-only icon-edit') %> + <% else %> + <%= link_to('', edit_root_dmsf_path(:id => @project), + :title => l(:link_edit, :title => l(:link_documents)), + :class => 'icon-only icon-edit') %> <% end %> + <% if subfolder %> + <%= link_to('', lock_dmsf_path(:id => project, :folder_id => subfolder), + :title => l(:title_lock_file), + :class => 'icon-only icon-lock') %> + <% else %> + + <% end %> + <% if (subfolder && subfolder.notification) || (!subfolder && project.dmsf_notification) %> + <%= link_to('', notify_deactivate_dmsf_path(:id => project, :folder_id => subfolder), + :title => l(:title_notifications_active_deactivate), + :class => 'icon-only icon-email') %> + <% else %> + <%= link_to('', notify_activate_dmsf_path(:id => project, :folder_id => subfolder), + :title => l(:title_notifications_not_active_activate), + :class => 'icon-only icon-email-add') %> + <% end %> + <%= link_to('', link ? dmsf_link_path(link) : delete_dmsf_path(:id => project, :folder_id => subfolder), + :data => {:confirm => l(:text_are_you_sure)}, + :title => l(:title_delete), + :method => :delete, + :class => 'icon-only icon-del') %> <% end %> <% end %> -<%= position %> -0 -<%= subfolder.modified.to_i if subfolder %> -0 +<%= position %> +0 +<%= subfolder.modified.to_i if subfolder %> +0 diff --git a/app/views/dmsf/_dir_trash.html.erb b/app/views/dmsf/_dir_trash.html.erb index 3d16d0f7..bc581341 100644 --- a/app/views/dmsf/_dir_trash.html.erb +++ b/app/views/dmsf/_dir_trash.html.erb @@ -67,27 +67,25 @@ <% end %> <% if @folder_manipulation_allowed %> + <%= link_to('', link ? restore_dmsf_link_path(:id => link) : restore_dmsf_path(:id => project, :folder_id => subfolder), + :title => l(:title_restore), + :class => 'icon-only icon-cancel') %> <% 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'), + <%= link_to('', 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'), + :title => l(:title_delete), + :class => 'icon-only icon-delete') %> + <% else %> + <%= link_to('', delete_dmsf_path(:id => project, :folder_id => subfolder, :commit => 'yes'), :data => {:confirm => l(:text_are_you_sure)}, - :title => l(:title_delete)) %> + :title => l(:title_delete), + :method => :delete, + :class => 'icon-only icon-del') %> <% end %> <% end %> -0 -0 -<%= subfolder.modified.to_i if subfolder %> -0 +0 +0 +<%= subfolder.modified.to_i if subfolder %> +0 diff --git a/app/views/dmsf/_dmsf_rows.erb b/app/views/dmsf/_dmsf_rows.erb index bd8f58cf..146baa8d 100644 --- a/app/views/dmsf/_dmsf_rows.erb +++ b/app/views/dmsf/_dmsf_rows.erb @@ -1,9 +1,6 @@ <% parent = @folder ? @folder : @project %> -<% @idnt = 0 unless @idnt %> -<% @pos = 0.0 unless @pos %> <% DmsfHelper.all_children_sorted(parent, @pos, @idnt).each do |obj, position| %> <% classes = "dmsf_tree idnt-#{@idnt}" %> - <% classes += " dmsf-#{cycle('odd', 'even')}" %> <% 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 dmsf-not-loaded' %> <% id = "id='#{obj.id}span'".html_safe %> @@ -19,6 +16,8 @@ <% end %> <% if obj.is_a? DmsfFolder %> + <% classes << ' dmsf_system' if obj.system %> + <% classes << ' dmsf_system_closed' if obj.issue && obj.issue.closed? %> class="dir <%= classes %>"> <%= render(:partial => 'dir', :locals => { diff --git a/app/views/dmsf/_file.html.erb b/app/views/dmsf/_file.html.erb index 8690e904..7ca69544 100644 --- a/app/views/dmsf/_file.html.erb +++ b/app/views/dmsf/_file.html.erb @@ -20,7 +20,7 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. %> -<% wf = DmsfWorkflow.find_by_id(file.last_revision.dmsf_workflow_id) %> +<% wf = DmsfWorkflow.find_by_id(file.last_revision.dmsf_workflow_id) if file.last_revision.dmsf_workflow_id %> <%= check_box_tag(name, id, false, :title => l(:title_check_for_zip_download_or_email), :id => "file_#{id}") %> @@ -53,19 +53,6 @@ <% if DmsfFolder.is_column_on?('modified') %> <%= format_time(file.last_revision.updated_at) %> - <% 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), - :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'), - :title => l(:notice_account_unknown_email)) %> - <% end %> - <% elsif file.locked? %> - <%= content_tag(:span, image_tag(link ? 'lockedbycurrent_gray.png' : 'lockedbycurrent.png', :plugin => 'redmine_dmsf'), - :title => l(:title_locked_by_you)) %> - <% end %> <% end %> <% if DmsfFolder.is_column_on?('version') %> @@ -80,7 +67,7 @@ :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)), + :title => file.last_revision.workflow_tooltip, :remote => true) %> <% else %> <%= file.last_revision.workflow_str(false) %> @@ -100,112 +87,57 @@ <% end %> <% 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)) %> - <% elsif file.unlockable? %> - <%= link_to(image_tag('unlock.png', :plugin => 'redmine_dmsf'), - unlock_dmsf_files_path(:id => file), - :title => l(:title_unlock_file))%> - <% else %> - - <% end %> + <%= link_to('', dmsf_file_path(:id => file), + :title => l(:link_details, :title => h(file.last_revision.title)), + :class => 'icon-only icon-edit') %> + <% if !file.locked? %> + <%= link_to('', lock_dmsf_files_path(:id => file), + :title => l(:title_lock_file), + :class => 'icon-only icon-lock') %> + <% elsif file.unlockable? && (!file.locked_for_user? || @force_file_unlock_allowed) %> + <%= link_to('', unlock_dmsf_files_path(:id => file), + :title => file.get_locked_title, + :class => 'icon-only icon-unlock') %> + <% else %> + + <% end %> + <% unless file.locked? %> <% 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)) %> + <%= link_to('', notify_deactivate_dmsf_files_path(:id => file), + :title => l(:title_notifications_active_deactivate), + :class => 'icon-only icon-email') %> <% else %> - <%= link_to(image_tag('notifynot.png', :plugin => 'redmine_dmsf'), - notify_activate_dmsf_files_path(:id => file), - :title => l(:title_notifications_not_active_activate)) %> + <%= link_to('', notify_activate_dmsf_files_path(:id => file), + :title => l(:title_notifications_not_active_activate), + :class => 'icon-only icon-email-add') %> <% 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)) %> + <%= link_to('', dmsf_link_path(link), + :data => {:confirm => l(:text_are_you_sure)}, + :title => l(:title_delete), + :method => :delete, + :class => 'icon-only icon-del') %> <% else %> <% if @file_delete_allowed %> - <%= link_to(image_tag('delete.png'), - dmsf_file_path(:id => file), + <%= link_to('', dmsf_file_path(:id => file), :data => {:confirm => l(:text_are_you_sure)}, + :title => l(:title_delete), :method => :delete, - :title => l(:title_delete)) unless file.locked_for_user? %> + :class => 'icon-only icon-del') %> <% else %> - + <% 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 %> - <%= 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 %> - <%= 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, - :dmsf_file_revision_id => file.last_revision.id), - :title => l(:label_dmsf_wokflow_action_assign), - :remote => true) %> - <% end %> + + <% end %> <% end %> + <%= render(:partial => 'dmsf_workflows/approval_workflow_button', + :locals => {:file => file, :file_approval_allowed => @file_approval_allowed, + :workflows_available => @workflows_available, :project => project, :wf => wf, :dmsf_link_id => nil }) %> -<%= position %> -<%= file.last_revision.size %> -<%= file.last_revision.updated_at.to_i %> -<%= file.last_revision.iversion %> +<%= position %> +<%= file.last_revision.size %> +<%= file.last_revision.updated_at.to_i %> +<%= file.last_revision.iversion %> diff --git a/app/views/dmsf/_file_trash.html.erb b/app/views/dmsf/_file_trash.html.erb index 62bba0e5..55f54355 100644 --- a/app/views/dmsf/_file_trash.html.erb +++ b/app/views/dmsf/_file_trash.html.erb @@ -67,28 +67,19 @@ <% end %> <% 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 %> + <%= link_to('', link ? restore_dmsf_link_path(:id => link) : restore_dmsf_file_path(:id => file), + :title => l(:title_restore), + :class => 'icon-only icon-cancel') %> + <% end %> + <% if @file_delete_allowed %> + <%= link_to('', link ? dmsf_link_path(:id => link, :commit => 'yes') : dmsf_file_path(:id => file, :commit => 'yes'), + :data => {:confirm => l(:text_are_you_sure)}, + :method => :delete, + :title => l(:title_delete), + :class => 'icon-only icon-del') %> <% end %> -1 -<%= file.last_revision.size %> -<%= file.last_revision.updated_at.to_i %> -<%= file.last_revision.iversion %> +1 +<%= file.last_revision.size %> +<%= file.last_revision.updated_at.to_i %> +<%= file.last_revision.iversion %> diff --git a/app/views/dmsf/_list_view.erb b/app/views/dmsf/_list_view.erb index c40e9a0b..06b910a5 100644 --- a/app/views/dmsf/_list_view.erb +++ b/app/views/dmsf/_list_view.erb @@ -57,15 +57,15 @@ <% end %> <% end %> <%# controls %> - <%# position %> - <%# size %> - <%# updated %> - <%# version %> + <%# position %> + <%# size %> + <%# updated %> + <%# version %> <% @subfolders.each do |subfolder| %> - + <%= render(:partial => 'dir', :locals => { :project => @project, diff --git a/app/views/dmsf/_tree_view.erb b/app/views/dmsf/_tree_view.erb index 8c028c36..662082e7 100644 --- a/app/views/dmsf/_tree_view.erb +++ b/app/views/dmsf/_tree_view.erb @@ -57,10 +57,10 @@ <% end %> <% end %> <%# controls %> - <%# position %> - <%# size %> - <%# updated %> - <%# version %> + <%# position %> + <%# size %> + <%# updated %> + <%# version %> diff --git a/app/views/dmsf/_url.html.erb b/app/views/dmsf/_url.html.erb index 776b7e72..1bd0a450 100644 --- a/app/views/dmsf/_url.html.erb +++ b/app/views/dmsf/_url.html.erb @@ -32,7 +32,7 @@ <%= link_to(h(title), link.external_url, :target => '_blank', - :class => 'icon dmsf_icon-link') %> + :class => 'icon icon-link') %>
<%= link.external_url %>
@@ -67,17 +67,18 @@ - <% if @file_manipulation_allowed %> - <%= link_to(image_tag('delete.png'), + <% if @file_delete_allowed %> + <%= link_to('', dmsf_link_path(link), :data => {:confirm => l(:text_are_you_sure)}, :method => :delete, - :title => l(:title_delete)) %> + :title => l(:title_delete), + :class => "icon icon-del") %> <% else %> <% end %> -<%= position %> - -link.updated_at.to_i - +<%= position %> + +link.updated_at.to_i + diff --git a/app/views/dmsf/_url_trash.html.erb b/app/views/dmsf/_url_trash.html.erb index de4623f1..9a59a11e 100644 --- a/app/views/dmsf/_url_trash.html.erb +++ b/app/views/dmsf/_url_trash.html.erb @@ -30,7 +30,7 @@ <%= link_to(h(title), link.external_url, :target => '_blank', - :class => 'icon dmsf_icon-link') %> + :class => 'icon icon-link') %>
<%= link.external_url %>
<% end %> @@ -59,18 +59,15 @@ <% end %> <% end %> - <% if @file_manipulation_allowed %> - <%= 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)) %> + <%= link_to_if(@file_manipulation_allowed, '', restore_dmsf_link_path(:id => link), + :title => l(:title_restore), :class => "icon icon-cancel") %> + <% if @file_delete_allowed %> + <%= link_to_if(@file_delete_allowed, '', dmsf_link_path(:id => link, :commit => 'yes'), + :data => {:confirm => l(:text_are_you_sure)}, :method => :delete, + :title => l(:title_delete), :class => "icon icon-delete" ) %> <% end %> -1 - -link.updated_at.to_i - +1 + +link.updated_at.to_i + diff --git a/app/views/dmsf/dmsf_rows.js.erb b/app/views/dmsf/dmsf_rows.js.erb index 8c1b6316..21c538c7 100644 --- a/app/views/dmsf/dmsf_rows.js.erb +++ b/app/views/dmsf/dmsf_rows.js.erb @@ -13,6 +13,7 @@ $("#browser").dataTable().fnDestroy(); // Add rows $('#<%= params[:row_id] %>').after('<%= escape_javascript(render(:partial => 'dmsf/dmsf_rows')) %>'); +hideOnLoad(); // Reinitialize the dataTable <% title = DmsfFolder.get_column_position('title') %> diff --git a/app/views/dmsf/edit.html.erb b/app/views/dmsf/edit.html.erb index 57cc6265..eafeab1b 100644 --- a/app/views/dmsf/edit.html.erb +++ b/app/views/dmsf/edit.html.erb @@ -25,42 +25,38 @@ <% html_title(l(:dmsf)) %>
- <% 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 dmsf_icon-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 dmsf_icon-unlock')%> - <% end %> - <% 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 dmsf_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 dmsf_icon-notification-off') %> - <% 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 dmsf_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') %> - <% elsif @force_file_unlock_allowed %> - <%= link_to_if(@folder.unlockable?, l(:button_unlock), + <% if !@folder.new_record? && User.current.allowed_to?(:folder_manipulation, @project) && !@folder.system %> + <% unless @folder.locked? %> + <%= link_to(l(:button_lock), lock_dmsf_path(:id => @project, :folder_id => @folder), + :title => l(:title_lock_file), :class => 'icon icon-lock') %> + <% else %> + <%= link_to_if(@folder.unlockable? && (!@folder.locked_for_user? || @force_file_unlock_allowed), l(:button_unlock), unlock_dmsf_path(:id => @project, :folder_id => @folder), - :title => l(:title_unlock_file), :class => 'icon dmsf_icon-unlock')%> - <% end %> + :title => l(:title_unlock_file), :class => 'icon icon-unlock')%> + <% end %> + <% unless @folder.locked? %> + <% 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-email') %> + <% 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-email-add') %> + <% end %> + <% 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') %> + <%= link_to(l(:button_copy), copy_folder_path(:id => @folder), + :title => l(:title_copy), :class => 'icon icon-copy') %> + <% unless @folder.locked? %> + <%= 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', :method => :delete) %> + <% end %> <% end %>
@@ -85,7 +81,28 @@

<%= f.text_area :description, :rows => 8, :class => 'wiki-edit' %>

- <% values = @folder ? @folder.custom_field_values : @parent ? @parent.custom_field_values : DmsfFolder.new(:project => @project).custom_field_values %> +

+ <%= label_tag '', l(:label_permissions) %> + <% User.current.managed_roles(@project).each do |role| %> + <% checked = @folder.dmsf_folder_permissions.roles.exists?(:object_id => role.id) %> + + <% end %> + +
+ <% checkboxes = users_checkboxes(@users) %> + <%= checkboxes %> +
+ <% if checkboxes.present? %> +
+ <% end %> + + <%= link_to l(:label_user_search_add), + new_dmsf_folder_permissions_path(:project_id => @project, :dmsf_folder_id => @folder), + :remote => true, + :method => 'get' %> + +

+ <% values = @folder ? @folder.custom_field_values : @parent ? @parent.custom_field_values : DmsfFolder.new.custom_field_values %> <% values.each do |value| %>

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

<% end %> @@ -97,4 +114,4 @@ \ 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 7a89db5a..a4535e50 100644 --- a/app/views/dmsf/edit_root.html.erb +++ b/app/views/dmsf/edit_root.html.erb @@ -30,12 +30,12 @@ <%= link_to(l(:label_notifications_off), notify_deactivate_dmsf_path(:id => @project), :title => l(:title_notifications_active_deactivate), - :class => 'icon dmsf_icon-notification-on') %> + :class => 'icon icon-email') %> <% else %> <%= link_to(l(:label_notifications_on), notify_activate_dmsf_path(:id => @project), :title => l(:title_notifications_active_deactivate), - :class => 'icon dmsf_icon-notification-off') %> + :class => 'icon icon-email-add') %> <% end %> <% end %> @@ -54,4 +54,4 @@ <%= f.submit l(:submit_save) %> <% end %> -<%= wikitoolbar_for 'project_dmsf_description' %> \ No newline at end of file +<%= wikitoolbar_for 'project_dmsf_description' %> diff --git a/app/views/dmsf/show.html.erb b/app/views/dmsf/show.html.erb index 318c5dd7..e7c5096c 100644 --- a/app/views/dmsf/show.html.erb +++ b/app/views/dmsf/show.html.erb @@ -1,78 +1,79 @@ <% -# encoding: utf-8 -# -# Redmine plugin for Document Management System "Features" -# -# Copyright (C) 2011 Vít Jonáš -# Copyright (C) 2012 Daniel Munn -# Copyright (C) 2011-17 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. + # encoding: utf-8 + # + # Redmine plugin for Document Management System "Features" + # + # Copyright (C) 2011 Vít Jonáš + # Copyright (C) 2012 Daniel Munn + # Copyright (C) 2011-17 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. %> <% html_title l(:dmsf) %>
- <% if @folder_manipulation_allowed %> - <% if @folder.nil? %> - <%= 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 %> - <%= link_to(l(:button_edit), - edit_dmsf_path(:id => @project, :folder_id => @folder), - :title => l(:link_edit, :title => h(@folder.title)), - :class => 'icon icon-edit') %> - <% end %> - <% if @folder && (!@locked_for_user || User.current.allowed_to?(:force_file_unlock, @project)) %> - <% 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 dmsf_icon-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 dmsf_icon-lock') %> + <% if @folder_manipulation_allowed && !@system_folder %> + <% if @folder.nil? %> + <%= 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 %> + <%= link_to(l(:button_edit), + edit_dmsf_path(:id => @project, :folder_id => @folder), + :title => l(:link_edit, :title => h(@folder.title)), + :class => 'icon icon-edit') %> <% end %> - <% end %> - <% 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), - :class => 'icon dmsf_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 dmsf_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 dmsf_icon-link') %> - <% end %> - <%= link_to(l(:link_create_folder), - new_dmsf_path(:id => @project, :parent_id => @folder), - :title => l(:link_create_folder), - :class => 'icon icon-add') unless @locked_for_user %> + <% if @folder && (!@locked_for_user || User.current.allowed_to?(:force_file_unlock, @project)) %> + <% 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-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-lock') %> + <% end %> + <% end %> + <% 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), + :class => 'icon icon-email') %> + <% 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-email-add') %> + <% 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') %> + <% end %> + <%= link_to(l(:link_create_folder), + new_dmsf_path(:id => @project, :parent_id => @folder), + :title => l(:link_create_folder), + :class => 'icon icon-add') unless @locked_for_user %> <% end %> <%= link_to_if(@trash_enabled, l(:link_trash_bin), trash_dmsf_path(@project), - :title => l(:link_trash_bin), :class => 'icon icon-del') if @trash_visible %> + :title => l(:link_trash_bin), :class => 'icon icon-del') if @trash_visible %>
<%= render(:partial => 'path', - :locals => {:folder => @folder, :filename => nil, :title => nil}) %> + :locals => {:folder => @folder, :filename => nil, :title => nil}) %>
@@ -82,134 +83,145 @@ <%= error_messages_for('dmsf_workflow') %> -<%= form_tag({:action => :entries_operation, :id => @project, :folder_id => @folder}, :method => :post, - :class => 'dmsf_entries', :id => 'entries_form') do %> - <%= hidden_field_tag('action') %> -
- <%= submit_tag(l(:button_download), :title => l(:title_download_checked), :name => 'download_entries') if @file_view_allowed %> - <%= submit_tag(l(:field_mail), :title => l(:title_send_checked_by_email), :name => 'email_entries') if (@file_view_allowed && User.current.allowed_to?(:email_documents, @project)) %> - <% if @file_delete_allowed%> - <%= submit_tag(l(:button_delete), :title => l(:title_delete_checked), :name => 'delete_entries') if @file_delete_allowed %> - <% end %> -
- <% values = @folder ? @folder.custom_field_values : @parent ? @parent.custom_field_values : DmsfFolder.new(:project => @project).custom_field_values %> - <% unless values.empty? %> -
- <%= custom_field_tag_with_label( - :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])) %> +<%= form_tag(entries_operations_dmsf_path(:id => @project, :folder_id => @folder), :method => :post, + :class => 'dmsf_entries', :id => 'entries_form') do %> + <%= hidden_field_tag('action') %> +
+ <%= submit_tag(l(:button_download), :title => l(:title_download_checked), :name => 'download_entries') if @file_view_allowed %> + <%= submit_tag(l(:field_mail), :title => l(:title_send_checked_by_email), :name => 'email_entries') if (@file_view_allowed && User.current.allowed_to?(:email_documents, @project)) %> + <% if @file_delete_allowed%> + <%= submit_tag(l(:button_delete), :title => l(:title_delete_checked), :name => 'delete_entries') if @file_delete_allowed %> + <% end %>
- <% end %> -
- <% if @tree_view %> - <%= render(:partial => 'tree_view') %> - <% else %> - <%= render(:partial => 'list_view') %> + <% unless @system_folder %> + <% values = @folder ? @folder.custom_field_values : DmsfFolder.new.custom_field_values %> + <% unless values.empty? %> +
+ <% custom_value = values.first %> + <% custom_value.custom_field.is_required = false %> + <% custom_value.value = params[:custom_value].present? ? params[:custom_value] : '' %> + <%= h(custom_value.custom_field.name) %>: + <%= custom_value.custom_field.format.edit_tag(self, + custom_field_tag_id(:dmsf_folder, custom_value.custom_field), + custom_field_tag_name(:dmsf_folder, custom_value.custom_field), + custom_value, + :class => "#{custom_value.custom_field.field_format}_cf", + :style => 'width: auto') %> +
+ <% end %> <% end %> -
+
+ <% if @tree_view %> + <%= render(:partial => 'tree_view') %> + <% else %> + <%= render(:partial => 'list_view') %> + <% end %> +
<% end %> <% 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' %> - <%= javascript_include_tag 'jquery.dataTables/jquery.dataTables.min.js', :plugin => 'redmine_dmsf' %> + <%= javascript_include_tag 'bowser.min.js', :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' %> - <% title = DmsfFolder.get_column_position('title') %> - <% position = DmsfFolder.get_column_position('position') %> - <% commands = DmsfFolder.get_column_position('commands') %> - <% position = DmsfFolder.get_column_position('position') %> - <% version = DmsfFolder.get_column_position('version') %> - <% size_calculated = DmsfFolder.get_column_position('size_calculated') %> - <% modified_calculated = DmsfFolder.get_column_position('modified_calculated') %> - <% version_calculated = DmsfFolder.get_column_position('version_calculated') %> - <% size = DmsfFolder.get_column_position('size') %> - <% modified = DmsfFolder.get_column_position('modified') %> + <% title = DmsfFolder.get_column_position('title') %> + <% position = DmsfFolder.get_column_position('position') %> + <% commands = DmsfFolder.get_column_position('commands') %> + <% position = DmsfFolder.get_column_position('position') %> + <% version = DmsfFolder.get_column_position('version') %> + <% size_calculated = DmsfFolder.get_column_position('size_calculated') %> + <% modified_calculated = DmsfFolder.get_column_position('modified_calculated') %> + <% version_calculated = DmsfFolder.get_column_position('version_calculated') %> + <% size = DmsfFolder.get_column_position('size') %> + <% modified = DmsfFolder.get_column_position('modified') %> - + $('#entries_form').submit(function () { + $(this).removeAttr('data-submitted'); + }); + }); + <% end %> -<% if (@file_manipulation_allowed && !@locked_for_user) %> - <%= render(:partial => 'dmsf_upload/multi_upload') %> +<% if (@file_manipulation_allowed && !@locked_for_user && !@system_folder) %> + <%= render(:partial => 'dmsf_upload/multi_upload', :local => { :lbl => true }) %> <% end %> -<% other_formats_links do |f| %> - <%= f.link_to 'CSV', :url => params, :onclick => "showModal('csv-export-options', '350px'); return false;" %> +<% unless @system_folder %> + <% other_formats_links do |f| %> + <%= f.link_to 'CSV', :url => params, :onclick => "showModal('csv-export-options', '350px'); return false;" %> + <% end %> <% end %>