From 27435704f6cebd592527f66d2cd22172773ebfd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Pi=C4=8Dman?= Date: Wed, 10 Dec 2025 16:42:09 +0100 Subject: [PATCH] #9 Active Storage - Litmus --- app/models/dmsf_file_revision.rb | 10 +-- app/models/dmsf_folder.rb | 2 +- app/models/dmsf_link.rb | 2 +- app/models/dmsf_upload.rb | 2 +- app/validators/dmsf_file_name_validator.rb | 31 --------- app/validators/dmsf_folder_name_validator.rb | 44 ------------- app/validators/dmsf_link_name_validator.rb | 44 ------------- lib/redmine_dmsf/webdav/dmsf_resource.rb | 26 ++++---- test/unit/dmsf_file_revision_test.rb | 66 +------------------- test/unit/dmsf_folder_test.rb | 32 ---------- test/unit/dmsf_link_test.rb | 27 -------- 11 files changed, 18 insertions(+), 268 deletions(-) delete mode 100644 app/validators/dmsf_folder_name_validator.rb delete mode 100644 app/validators/dmsf_link_name_validator.rb diff --git a/app/models/dmsf_file_revision.rb b/app/models/dmsf_file_revision.rb index 34e04f4e..4805c7cc 100644 --- a/app/models/dmsf_file_revision.rb +++ b/app/models/dmsf_file_revision.rb @@ -90,7 +90,7 @@ class DmsfFileRevision < ApplicationRecord } ) - validates :title, presence: true, dmsf_file_name: true, length: { maximum: 255 } + validates :title, presence: true, length: { maximum: 255 }, dmsf_file_name: true validates :major_version, presence: true validates :name, presence: true, dmsf_file_name: true, length: { maximum: 255 }, dmsf_file_extension: true validates :description, length: { maximum: 1.kilobyte } @@ -133,14 +133,6 @@ class DmsfFileRevision < ApplicationRecord dmsf_file&.dmsf_folder end - def self.remove_extension(filename) - filename[0, (filename.length - File.extname(filename).length)] - end - - def self.filename_to_title(filename) - remove_extension(filename).gsub(/_+/, ' ') - end - def delete(commit: false, force: true) if dmsf_file.locked_for_user? errors.add :base, l(:error_file_is_locked) diff --git a/app/models/dmsf_folder.rb b/app/models/dmsf_folder.rb index 92ae68f3..acd490b4 100644 --- a/app/models/dmsf_folder.rb +++ b/app/models/dmsf_folder.rb @@ -91,7 +91,7 @@ class DmsfFolder < ApplicationRecord datetime: proc { |o| o.updated_at }, author: proc { |o| o.user } - validates :title, presence: true, length: { maximum: 255 }, dmsf_folder_name: true + validates :title, presence: true, length: { maximum: 255 }, dmsf_file_name: true validates :title, uniqueness: { scope: %i[dmsf_folder_id project_id deleted], conditions: -> { where(deleted: STATUS_ACTIVE) }, case_sensitive: true } validates :description, length: { maximum: 65_535 } diff --git a/app/models/dmsf_link.rb b/app/models/dmsf_link.rb index 50bfe2e9..67cc1466 100644 --- a/app/models/dmsf_link.rb +++ b/app/models/dmsf_link.rb @@ -26,7 +26,7 @@ class DmsfLink < ApplicationRecord belongs_to :deleted_by_user, class_name: 'User' belongs_to :user - validates :name, presence: true, length: { maximum: 255 }, dmsf_link_name: true + validates :name, presence: true, length: { maximum: 255 }, dmsf_file_name: true validates :name, uniqueness: { scope: %i[dmsf_folder_id project_id deleted], conditions: -> { where(deleted: STATUS_ACTIVE) }, case_sensitive: true } # There can be project_id = -1 when attaching links to an issue. The project_id is assigned later when saving the diff --git a/app/models/dmsf_upload.rb b/app/models/dmsf_upload.rb index c04d2e5a..6ae16585 100644 --- a/app/models/dmsf_upload.rb +++ b/app/models/dmsf_upload.rb @@ -86,7 +86,7 @@ class DmsfUpload @token = uploaded[:token] if file.nil? || file.last_revision.nil? - @title = DmsfFileRevision.filename_to_title(@name) + @title = File.basename(@name, '.*') @description = uploaded[:comment] if RedmineDmsf.empty_minor_version_by_default? @major_version = 1 diff --git a/app/validators/dmsf_file_name_validator.rb b/app/validators/dmsf_file_name_validator.rb index 552e32d5..91ec8dea 100644 --- a/app/validators/dmsf_file_name_validator.rb +++ b/app/validators/dmsf_file_name_validator.rb @@ -24,36 +24,5 @@ class DmsfFileNameValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) # Check invalid characters record.errors.add attribute, :error_contains_invalid_character unless ALL_INVALID_CHARACTERS.match?(value) - - # Check name uniqueness among files - project_id = record.dmsf_file.project_id - dmsf_folder_id = record.dmsf_file.dmsf_folder_id - id = record.dmsf_file_id - DmsfFile - .visible - .where(project_id: project_id, dmsf_folder_id: dmsf_folder_id) - .where.not(id: id) - .find_each do |file| - if file.name == value || file.title == value - record.errors.add attribute, :taken - break - end - end - - # Check name uniqueness among folders - DmsfFolder.visible.where(project_id: project_id, dmsf_folder_id: dmsf_folder_id).find_each do |folder| - if folder.title == value - record.errors.add attribute, :taken - break - end - end - - # Check name uniqueness among links - DmsfLink.visible.where(project_id: project_id, dmsf_folder_id: dmsf_folder_id).find_each do |link| - if link.name == value - record.errors.add attribute, :taken - break - end - end end end diff --git a/app/validators/dmsf_folder_name_validator.rb b/app/validators/dmsf_folder_name_validator.rb deleted file mode 100644 index 8b70035a..00000000 --- a/app/validators/dmsf_folder_name_validator.rb +++ /dev/null @@ -1,44 +0,0 @@ -# frozen_string_literal: true - -# Redmine plugin for Document Management System "Features" -# -# Vít Jonáš , Karel Pičman -# -# This file is part of Redmine DMSF plugin. -# -# Redmine DMSF plugin 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 3 of the License, or (at your option) any -# later version. -# -# Redmine DMSF plugin 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 Redmine DMSF plugin. If not, see -# . - -# File name validator -class DmsfFolderNameValidator < ActiveModel::EachValidator - ALL_INVALID_CHARACTERS = /\A[^#{DmsfFolder::INVALID_CHARACTERS}]*\z/ - - def validate_each(record, attribute, value) - # Check invalid characters - record.errors.add attribute, :error_contains_invalid_character unless ALL_INVALID_CHARACTERS.match?(value) - - # Check name uniqueness among files - DmsfFile.visible.where(project_id: record.project_id, dmsf_folder_id: record.dmsf_folder_id).find_each do |file| - if file.name == value || file.title == value - record.errors.add attribute, :taken - break - end - end - - # Check name uniqueness among links - DmsfLink.visible.where(project_id: record.project_id, dmsf_folder_id: record.dmsf_folder_id).find_each do |link| - if link.name == value - record.errors.add attribute, :taken - break - end - end - end -end diff --git a/app/validators/dmsf_link_name_validator.rb b/app/validators/dmsf_link_name_validator.rb deleted file mode 100644 index 25be2e0c..00000000 --- a/app/validators/dmsf_link_name_validator.rb +++ /dev/null @@ -1,44 +0,0 @@ -# frozen_string_literal: true - -# Redmine plugin for Document Management System "Features" -# -# Vít Jonáš , Karel Pičman -# -# This file is part of Redmine DMSF plugin. -# -# Redmine DMSF plugin 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 3 of the License, or (at your option) any -# later version. -# -# Redmine DMSF plugin 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 Redmine DMSF plugin. If not, see -# . - -# File name validator -class DmsfLinkNameValidator < ActiveModel::EachValidator - ALL_INVALID_CHARACTERS = /\A[^#{DmsfFolder::INVALID_CHARACTERS}]*\z/ - - def validate_each(record, attribute, value) - # Check invalid characters - record.errors.add attribute, :error_contains_invalid_character unless ALL_INVALID_CHARACTERS.match?(value) - - # Check name uniqueness among files - DmsfFile.visible.where(project_id: record.project_id, dmsf_folder_id: record.dmsf_folder_id).find_each do |file| - if file.name == value || file.title == value - record.errors.add attribute, :taken - break - end - end - - # Check name uniqueness among folders - DmsfFolder.visible.where(project_id: record.project_id, dmsf_folder_id: record.dmsf_folder_id).find_each do |folder| - if folder.title == value - record.errors.add attribute, :taken - break - end - end - end -end diff --git a/lib/redmine_dmsf/webdav/dmsf_resource.rb b/lib/redmine_dmsf/webdav/dmsf_resource.rb index c47b8bc5..6c228b33 100644 --- a/lib/redmine_dmsf/webdav/dmsf_resource.rb +++ b/lib/redmine_dmsf/webdav/dmsf_resource.rb @@ -224,8 +224,9 @@ module RedmineDmsf dest = ResourceProxy.new(dest_path, @request, @response, @options.merge(user: @user)) return PreconditionFailed if !dest.resource.is_a?(DmsfResource) || dest.resource.project.nil? - parent = dest.resource.parent + parent = dest_path.end_with?('/') && !collection? ? dest.resource : dest.resource.parent raise Forbidden unless dest.resource.project.module_enabled?(:dmsf) + if !parent.exist? || (!User.current.admin? && (!DmsfFolder.permissions?(folder, allow_system: false) || !DmsfFolder.permissions?(parent.folder, allow_system: false))) raise Forbidden @@ -241,13 +242,11 @@ module RedmineDmsf !User.current.allowed_to?(:folder_manipulation, dest.resource.project)) raise Forbidden end - return MethodNotAllowed unless folder # Moving sub-project not enabled + return MethodNotAllowed unless folder # Moving subprojects is not enabled raise Locked if folder.locked_for_user? # Change the title folder.title = dest.resource.basename - return PreconditionFailed unless folder.save - # Move to a new destination folder.move_to(dest.resource.project, parent.folder) ? Created : PreconditionFailed else @@ -294,7 +293,7 @@ module RedmineDmsf # Update Revision and names of file [We can link to old physical resource, as it's not changed] if file.last_revision file.last_revision.name = dest.resource.basename - file.last_revision.title = DmsfFileRevision.filename_to_title(dest.resource.basename) + file.last_revision.title = File.basename(dest.resource.basename, '.*') end # Save Changes if file.last_revision.save && file.save @@ -337,7 +336,7 @@ module RedmineDmsf # Manipulate folders on destination project :folder_manipulation # View folders on destination project :view_dmsf_folders # View files on the source project :view_dmsf_files - # View fodlers on the source project :view_dmsf_folders + # View folders on the source project :view_dmsf_folders raise Forbidden unless User.current.admin? || (User.current.allowed_to?(:folder_manipulation, dest.resource.project) && User.current.allowed_to?(:view_dmsf_folders, dest.resource.project) && @@ -367,6 +366,7 @@ module RedmineDmsf # Update Revision and names of file (We can link to old physical resource, as it's not changed) new_file.last_revision.name = dest.resource.basename + new_file.last_revision.title = File.basename(dest.resource.basename, '.*') # Save Changes unless new_file.last_revision.save && new_file.save new_file.delete commit: true @@ -582,7 +582,7 @@ module RedmineDmsf new_revision = DmsfFileRevision.new new_revision.minor_version = 1 new_revision.major_version = 0 - new_revision.title = DmsfFileRevision.filename_to_title(basename) + new_revision.title = File.basename(basename, '.*') end new_revision.dmsf_file = f @@ -764,11 +764,11 @@ module RedmineDmsf f = DmsfFile.new f.project_id = project.id f.dmsf_folder = parent.folder - if f.save(validate: false) # Skip validation due to invalid characters in the filename + if f.save r = DmsfFileRevision.new r.minor_version = 1 r.major_version = 0 - r.title = DmsfFileRevision.filename_to_title(basename) + r.title = File.basename(basename, '.*') r.dmsf_file = f r.user = User.current r.name = basename @@ -779,10 +779,10 @@ module RedmineDmsf r.custom_field_values << CustomValue.new({ custom_field: cf, value: cf.default_value }) end if r.save(validate: false) # Skip validation due to invalid characters in the filename - revision.file.attach( - io: File.new(upload.tempfile_path), - filename: file_upload.filename, - content_type: Redmine::MimeType.of(file_upload.filename), + r.file.attach( + io: File.new(DmsfHelper.temp_filename(basename), File::CREAT), + filename: basename, + content_type: 'application/octet-stream', identify: false ) return f diff --git a/test/unit/dmsf_file_revision_test.rb b/test/unit/dmsf_file_revision_test.rb index 61ba4c56..03f3957d 100644 --- a/test/unit/dmsf_file_revision_test.rb +++ b/test/unit/dmsf_file_revision_test.rb @@ -58,70 +58,6 @@ class DmsfFileRevisionTest < RedmineDmsf::Test::UnitTest assert_includes @revision1.errors.full_messages, 'Name is too long (maximum is 255 characters)' end - def test_name_uniqueness_validation - User.current = @admin - - # Duplicity among files names - @revision7.name = @revision1.name - assert @revision7.invalid? - assert_includes @revision7.errors.full_messages, 'Name has already been taken' - - # Duplicity among invisible files is all right - @revision7.name = @revision3.name - assert_not @revision7.invalid? - - # Duplicity among files titles - @revision7.name = @revision1.title - assert @revision7.invalid? - assert_includes @revision7.errors.full_messages, 'Name has already been taken' - - # Duplicity among folders - @revision7.name = @folder1.title - assert @revision7.invalid? - assert_includes @revision7.errors.full_messages, 'Name has already been taken' - - # Duplicity among links - @revision7.name = @folder_link1.name - assert @revision7.invalid? - assert_includes @revision7.errors.full_messages, 'Name has already been taken' - - # Name is all right - @revision7.name = 'xxx' - assert @revision7.valid? - end - - def test_title_uniqueness_validation - User.current = @admin - - # Duplicity among files names - @revision7.title = @revision1.name - assert @revision7.invalid? - assert_includes @revision7.errors.full_messages, 'Title has already been taken' - - # Duplicity among invisible files is all right - @revision7.title = @revision3.name - assert_not @revision7.invalid? - - # Duplicity among files titles - @revision7.title = @revision1.title - assert @revision7.invalid? - assert_includes @revision7.errors.full_messages, 'Title has already been taken' - - # Duplicity among folders - @revision7.title = @folder1.title - assert @revision7.invalid? - assert_includes @revision7.errors.full_messages, 'Title has already been taken' - - # Duplicity among links - @revision7.title = @folder_link1.name - assert @revision7.invalid? - assert_includes @revision7.errors.full_messages, 'Title has already been taken' - - # Name is all right - @revision7.title = 'xxx' - assert @revision7.valid? - end - def test_name_invalid_characters_validation @revision1.name << DmsfFolder::INVALID_CHARACTERS[0] assert @revision1.invalid? @@ -156,7 +92,7 @@ class DmsfFileRevisionTest < RedmineDmsf::Test::UnitTest r1.dmsf_file = @file1 # name test.txt r1.user = User.current r1.name = 'test.txt.png' - r1.title = DmsfFileRevision.filename_to_title(r1.name) + r1.title = File.basename(r1.name, '.*') r1.description = nil r1.comment = nil r1.size = 4 diff --git a/test/unit/dmsf_folder_test.rb b/test/unit/dmsf_folder_test.rb index 0b7a3203..b470a390 100644 --- a/test/unit/dmsf_folder_test.rb +++ b/test/unit/dmsf_folder_test.rb @@ -49,38 +49,6 @@ class DmsfFolderTest < RedmineDmsf::Test::UnitTest "Title #{l('activerecord.errors.messages.error_contains_invalid_character')}" end - def test_title_uniqueness_validation - User.current = @admin - - # Duplicity among files names - @folder1.title = @revision1.name - assert @folder1.invalid? - assert_includes @folder1.errors.full_messages, 'Title has already been taken' - - # Duplicity among invisible files is all right - @folder1.title = @revision3.name - assert_not @folder1.invalid? - - # Duplicity among files titles - @folder1.title = @revision1.title - assert @folder1.invalid? - assert_includes @folder1.errors.full_messages, 'Title has already been taken' - - # Duplicity among folders - @folder1.title = @folder6.title - assert @folder1.invalid? - assert_includes @folder1.errors.full_messages, 'Title has already been taken' - - # Duplicity among links - @folder1.title = @folder_link1.name - assert @folder1.invalid? - assert_includes @folder1.errors.full_messages, 'Title has already been taken' - - # Name is all right - @folder1.title = 'xxx' - assert @folder1.valid? - end - def test_visibility # The role has got permissions User.current = @jsmith diff --git a/test/unit/dmsf_link_test.rb b/test/unit/dmsf_link_test.rb index bb30d98b..38ae9c1e 100644 --- a/test/unit/dmsf_link_test.rb +++ b/test/unit/dmsf_link_test.rb @@ -84,33 +84,6 @@ class DmsfLinksTest < RedmineDmsf::Test::UnitTest "Name #{l('activerecord.errors.messages.error_contains_invalid_character')}" end - def test_name_uniqueness_validation - User.current = @admin - - # Duplicity among files names - @folder_link1.name = @revision1.name - assert @folder_link1.invalid? - assert_includes @folder_link1.errors.full_messages, 'Name has already been taken' - - # Duplicity among invisible files is all right - @folder_link1.name = @revision3.name - assert_not @folder_link1.invalid? - - # Duplicity among files titles - @folder_link1.name = @revision1.title - assert @folder_link1.invalid? - assert_includes @folder_link1.errors.full_messages, 'Name has already been taken' - - # Duplicity among folders - @folder_link1.name = @folder6.title - assert @folder_link1.invalid? - assert_includes @folder_link1.errors.full_messages, 'Name has already been taken' - - # Name is all right - @folder_link1.name = 'xxx' - assert @folder_link1.valid? - end - def test_validate_external_url_length @file_link2.target_type = 'DmsfUrl' @file_link2.external_url = "https://localhost/#{'a' * 256}"