From 6461ad37af578d445e174c44bf32eb76298dbba6 Mon Sep 17 00:00:00 2001 From: 1redmine Date: Tue, 10 Jun 2025 14:16:56 +0200 Subject: [PATCH] Easy compatibility --- CHANGELOG.md | 122 ++++++------ README.md | 74 ++++---- .../dmsf_context_menus_controller.rb | 2 + app/controllers/dmsf_controller.rb | 10 +- app/controllers/dmsf_files_controller.rb | 4 +- .../dmsf_folder_permissions_controller.rb | 4 +- app/controllers/dmsf_links_controller.rb | 4 +- app/controllers/dmsf_upload_controller.rb | 6 +- app/controllers/dmsf_workflows_controller.rb | 10 +- app/helpers/dmsf_helper.rb | 6 +- app/helpers/dmsf_queries_helper.rb | 15 +- app/models/dmsf_file.rb | 11 +- app/models/dmsf_file_revision.rb | 2 +- app/models/dmsf_folder.rb | 12 +- app/models/dmsf_link.rb | 2 +- app/models/dmsf_workflow.rb | 5 +- app/views/dmsf/_path.html.erb | 10 +- app/views/dmsf/_query_rows.erb | 7 +- app/views/dmsf/trash.html.erb | 2 +- .../dmsf_files/_file_new_revision.html.erb | 6 +- app/views/dmsf_files/show.html.erb | 9 +- app/views/dmsf_help/en/dmsf_help.html.erb | 5 +- app/views/dmsf_state/_user_pref.html.erb | 1 + .../_view_layouts_base_html_head.html.erb | 23 +++ .../_view_layouts_base_html_head.html.erb | 26 +++ .../my/blocks/_locked_documents.html.erb | 4 +- .../my/blocks/_watched_documents.html.erb | 4 +- assets/javascripts/easy_dmsf.js | 24 +++ assets/stylesheets/easy_dmsf.css | 23 +++ assets/stylesheets/redmine_dmsf.css | 110 ++++------- config/initializers/easy_assets.rb | 25 +++ config/initializers/easy_software.rb | 173 ++++++++++++++++++ config/initializers/webdav.rb | 24 +++ config/locales/cs.yml | 2 + config/locales/de.yml | 2 + config/locales/en.yml | 3 + config/locales/es.yml | 2 + config/locales/fa.yml | 2 + config/locales/fr.yml | 2 + config/locales/hu.yml | 2 + config/locales/it.yml | 2 + config/locales/ja.yml | 2 + config/locales/ko.yml | 2 + config/locales/nl.yml | 2 + config/locales/pl.yml | 2 + config/locales/pt-BR.yml | 2 + config/locales/sl.yml | 2 + config/locales/uk.yml | 2 + config/locales/zh-TW.yml | 2 + config/locales/zh.yml | 2 + init.rb | 135 ++++++++++++-- lib/dav4rack/controller.rb | 6 +- lib/dav4rack/http_status.rb | 21 +-- lib/dav4rack/resource.rb | 2 +- lib/redmine_dmsf.rb | 5 - lib/redmine_dmsf/dmsf_zip.rb | 6 +- .../hooks/views/base_view_hooks.rb | 7 +- .../hooks/views/issue_view_hooks.rb | 2 +- lib/redmine_dmsf/preview.rb | 10 +- lib/redmine_dmsf/webdav/dmsf_controller.rb | 10 +- .../webdav/{digest.rb => dmsf_digest.rb} | 2 +- lib/redmine_dmsf/webdav/dmsf_resource.rb | 10 +- lib/redmine_dmsf/webdav/resource_proxy.rb | 103 +++-------- patches/access_control_easy_patch.rb | 54 ++++++ patches/access_control_patch.rb | 7 +- patches/formatting_helper_patch.rb | 5 +- patches/pdf_patch.rb | 2 +- test/functional/dmsf_controller_test.rb | 4 +- .../dmsf_webdav_custom_middleware_test.rb | 3 + test/unit/dmsf_workflow_test.rb | 4 +- test/unit/lib/attachable_patch_test.rb | 6 +- .../unit/lib/redmine_dmsf/dmsf_macros_test.rb | 56 ++++-- .../unit/lib/redmine_dmsf/dmsf_plugin_test.rb | 13 +- test/unit/lib/redmine_dmsf/dmsf_zip_test.rb | 1 + test/unit/user_patch_test.rb | 2 +- 75 files changed, 852 insertions(+), 424 deletions(-) create mode 100644 app/views/hooks/easy_dmsf/_view_layouts_base_html_head.html.erb create mode 100644 app/views/hooks/redmine_dmsf/_view_layouts_base_html_head.html.erb create mode 100644 assets/javascripts/easy_dmsf.js create mode 100644 assets/stylesheets/easy_dmsf.css create mode 100644 config/initializers/easy_assets.rb create mode 100644 config/initializers/easy_software.rb create mode 100644 config/initializers/webdav.rb rename lib/redmine_dmsf/webdav/{digest.rb => dmsf_digest.rb} (98%) create mode 100644 patches/access_control_easy_patch.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index a5d53e80..75b1c160 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ Changelog for Redmine DMSF 4.1.3 *2025-05-09* ------------------ - + SQL server compatibility * Bug: #1595 - DMSF WebDAV Configuration Fails on Redmine 6.0.5 @@ -85,7 +85,7 @@ Changelog for Redmine DMSF 3.2.3 *2024-10-18* ------------------ - + Uploaded file size fix * Bug: 1556 - Wrong file size when uploading documents @@ -135,7 +135,7 @@ Changelog for Redmine DMSF Several bugs fixed * Bug: #1533 - Mysql2::Error::TimeoutError -* Bug: #1532 - Target folder and project are the same as current +* Bug: #1532 - Target folder and project are the same as current * Bug: #1531 - Fixing NoMethodError in DmsfFileRevisionFormat * New: #1529 - Maintenance/update german translation @@ -144,7 +144,7 @@ Changelog for Redmine DMSF Maintenance release -* Bug: #1528 - WebDAV / LDAP-User errors +* Bug: #1528 - WebDAV / LDAP-User errors 3.1.6 *2024-06-04* ------------------ @@ -194,7 +194,7 @@ Changelog for Redmine DMSF IMPORTANT: REST API for copying/moving has changed. Check *extra/api/api_client.sh*. * Bug: #1490 - Latest plugin version on windows: problematic dependency 'xapian-ruby' -* Bug: #1486 - Some context menu improvements +* Bug: #1486 - Some context menu improvements * Bug: #1485 - Renames locales/ua.yml * Bug: #1484 - Author should be kept when moving a folder type * Bug: #1483 - Setting.plugin_redmine_dmsf['dmsf_index_database']: undefined method 'strip' for nil:NilClass @@ -204,7 +204,7 @@ IMPORTANT: REST API for copying/moving has changed. Check *extra/api/api_client. * Bug: #1473 - Edited documents cannot be unlocked * Bug: #1472 - Failed upgrade up to version 3.1.1 from version 3.0.12 * New: #1248 - Make DMS document available as Type of a custom field -* New: #1132 - Please provide a simple file operation menu +* New: #1132 - Please provide a simple file operation menu 3.1.2 *2023-08-23* ------------------ @@ -218,7 +218,7 @@ IMPORTANT: REST API for copying/moving has changed. Check *extra/api/api_client. Bug fixing -* Bug #1466 - Wrong number of arguments in dmsf links new +* Bug #1466 - Wrong number of arguments in dmsf links new 3.1.0 *2023-08-10* ------------------ @@ -245,7 +245,7 @@ IMPORTANT: REST API for copying/moving has changed. Check *extra/api/api_client. * Bug: #1449 - Lost attachment on bulk edit * Bug: #1448 - Convert documents fails * New: #1445 - To support OCR feature -* Bug: #1444 - Feature/add notification labels +* Bug: #1444 - Feature/add notification labels * New: #1443 - Updates german translations * Bug: #1439 - Error when opening Setting page * Bug: #1438 - Error while de-installing the plugin "Validation failed: Name contains invalid character(s)" @@ -290,7 +290,7 @@ IMPORTANT: REST API for copying/moving has changed. Check *extra/api/api_client. * Bug: #1413 - Vim edit through webdav causes lose of all file versions besides last. * Bug: #1408 - Lost attachment 2 * New: #513 - Email Notification when someone downloads a file -* New: #239 - Easy Document link macro creation +* New: #239 - Easy Document link macro creation 3.0.7 *2022-11-01* ------------------ @@ -398,7 +398,7 @@ IMPORTANT: REST API for copying/moving has changed. Check *extra/api/api_client. * New: #1348 - Custom Fields not shown on folder level * Bug: #1345 - Conflict with RedmineUP invoice plugin * New: #1227 - Check if a document contains a signature -* New: #1203 - Suggest to add document preview +* New: #1203 - Suggest to add document preview 3.0.0 *2022-04-28* ------------------- @@ -439,7 +439,7 @@ IMPORTANT: REST API for copying/moving has changed. Check *extra/api/api_client. * New: #1309 - Gitlab CI * Bug: #1306 - Mysql2::Error: Operand should contain 1 column(s) * Bug: #1304 - SQL error with postgresql on top menu -* New: #1301 - REST API for documents movement +* New: #1301 - REST API for documents movement 2.4.10 *2021-10-20* ------------------- @@ -460,7 +460,7 @@ IMPORTANT: REST API for copying/moving has changed. Check *extra/api/api_client. 2.4.8 *2021-10-08* ------------------ - REST API + REST API Create a revision, updating custom fields Bug fixes @@ -562,10 +562,10 @@ IMPORTANT: REST API for copying/moving has changed. Check *extra/api/api_client. 2.4.3 *2020-06-12* ------------------ - Redmine's look&feel + Redmine's look&feel Implementation of folders movement between projects (WebDAV) Korean localization updated - + * New: #1129 - New UI: Optimize Actions Menu * New: #1128 - New German translations @@ -591,7 +591,7 @@ IMPORTANT: REST API for copying/moving has changed. Check *extra/api/api_client. Compatibility with Redmine 4.1 Chinese localisation updated - + * New: #1072 - Bug deprecation multiple gemfile sources * New: #1069 - Minor version is limited to 99 max - I recommend to change the limit to 999 * New: #1068 - [travis] test redmine 4.1.0 @@ -618,17 +618,17 @@ IMPORTANT: REST API for copying/moving has changed. Check *extra/api/api_client. Compatibility with Redmine 4.0.4 Japanese localization updated Plupload & DataTables libraries upgraded - + * Bug: #1033 - Bitnami Redmine 4.0.4 * New: #1032 - Deprecate silverlight support? -* New: #1023 - Project menu is not displayed in Redmine 4.0.3 +* New: #1023 - Project menu is not displayed in Redmine 4.0.3 * Bug: #1019 - Internal Erro 500 when enable "Act as attachable" and access Activity page * Bug: #1017 - Multiple zip files are filling the tmp folder * Bug: #1015 - WebDAV client error * Bug: #1013 - Approval workflow notifications are sent to locked users * Bug: #1010 - Installing Redmine in a sub URI * Bug: #1008 - Description field trunkates on blank line -* Bug: #1004 - Wrong revision order after upgrading to DMSF 1.6.2 +* Bug: #1004 - Wrong revision order after upgrading to DMSF 1.6.2 * Bug: #1003 - Wrong file structure on migrate * Bug: #1002 - New folder with empty titlle => Error 500 * Bug: #1001 - User Permission problem (can't choose user) @@ -648,7 +648,7 @@ IMPORTANT: REST API for copying/moving has changed. Check *extra/api/api_client. Compatibility with Redmine 4.0 Russian localization updated - + * Bug: #976 - Can't link document to issue with column in subject * Bug: #969 - About the DMSF folder search logic * Bug: #966 - folder_manipulation permission @@ -700,22 +700,22 @@ IMPORTANT: REST API for copying/moving has changed. Check *extra/api/api_client. 1.6.1 *2018-04-03* ------------------ - + Javascript on pages is loaded asynchronously - Obsolete Dav4Rack gem replaced with an up to date fork by Planio (Consequently WebDAV caching has been removed, sorry...) - Cloned from gem https://github.com/planio-gmbh/dav4rack.git + Obsolete Dav4Rack gem replaced with an up to date fork by Planio (Consequently WebDAV caching has been removed, sorry...) + Cloned from gem https://github.com/planio-gmbh/dav4rack.git Project members can be chosen as recipients when sending documents by email Responsive view (optimized for mobile devices) Direct editing of document in MS Office Korean & Dutch localisation Move folder feature Document versions can contain letters - + IMPORTANT 1. `alias_method_chain` has been replaced with `prepend`. Not directly but using `RedmineExtensions::PatchManager`. - Consequently, there might occure conficts with plugins which overwrite the same methods. - + Consequently, there might occure conficts with plugins which overwrite the same methods. + * Bug: #839 - Webdav not working * New: #838 - Rake task for regenerating document's digests * Bug: #831 - ActionView::Template::Error, when i am creating issue from the list of all projects @@ -751,23 +751,23 @@ IMPORTANT 1.6.0 *2017-09-12* ------------------ - Folder permissions + Folder permissions Documents attachable to issues Hungarian localization Full-text 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 +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. - + 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 +* 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 @@ -799,7 +799,7 @@ IMPORTANT * 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 +* 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 @@ -807,9 +807,9 @@ IMPORTANT 1.5.9 *2016-03-01* ------------------ - WebDAV - Documents editing in MS Office - Support for rsync and cp commands + WebDAV + Documents editing in MS Office + Support for rsync and cp commands Disable verioning for certain file names pattern by PUT request Ignoring certain file names pattern by PUT request Caching of PROPSTATS and PROPFIND requests @@ -820,7 +820,7 @@ IMPORTANT Editing of approval workflow steps Approval workflow step name DMSF - Document export + Document export Public URLs option in email entries Global title format for downloading New columns in the main DMSF view; columns are configurable from the plugin settings @@ -849,7 +849,7 @@ IMPORTANT * Bug: #593 - Modern upload file type doesn't work * Bug: #592 - reset_column_information is missing in DB migration * Bug: #591 - rsync doesn't work for WebDAV mounted folder -* Bug: #587 - Working with MS Office documents directly in mounted WebDAV share +* Bug: #587 - Working with MS Office documents directly in mounted WebDAV share * New: #584 - A lot of warnings in WebDAV unit tests * Bug: #582 - FATAL -- : ActionController::RoutingError (No route matches [GET] "/plugin_assets/redmine_dmsf/javascripts/jquery.dataTables/zh.json") * Bug: #581 - Webdav always shows the create date @@ -858,7 +858,7 @@ IMPORTANT * New: #555 - Documents ID easy access * New: #551 - Default action for files viewing * New: #547 - Setting Title format should be global setting, but released as local setting -* New: #499 - Add column "type/extension" in folder content view +* New: #499 - Add column "type/extension" in folder content view 1.5.8 *2016-10-21* ------------------ @@ -867,10 +867,10 @@ IMPORTANT Tree view optimization for speed Wiki macros revision: dmsfd X dmsfdesc Support for deleting users - + * Bug: #578 - A wrong title when uploading documents * Bug: #574 - Macro {{dmsfd(xx)}} produce blank value -* Bug: #566 - HTML tags in the document description breaks UI +* Bug: #566 - HTML tags in the document description breaks UI * Bug: #565 - Error 500 when a link to another folder is in the folder/project * New: #562 - New step button text * Bug: #561 - Wrong path in the document's details form @@ -880,7 +880,7 @@ IMPORTANT 1.5.7 *2016-08-12* ------------------ - + SQLite compatibility Lock/Unlock feature for global approval workflows Document ID in the document's details @@ -1010,7 +1010,7 @@ IMPORTANT External links * New: #307 - Filter mail receivers for approval workflow with file managing permission -* New: #308 - Rails 4 +* New: #308 - Rails 4 * Bug: #321 - My open approvals * Bug: #322 - Approval workflow notifications * New: #325 - Approval workflow permission @@ -1029,7 +1029,7 @@ IMPORTANT * New: #357 - Redmine 3.0.0 released! Compatibility with DMSF? * Bug: #361 - incompatible encoding regexp match (UTF-8 regexp with ASCII-8BIT string) * Bug: #366 - unable to properly uninstall under Redmine 3.0.1 -* Bug: #367 - Unable to create a folder +* Bug: #367 - Unable to create a folder * Bug: #368 - Cannot create a document workflow * Bug: #369 - Update document revision under Redmine 3.0.1 * Bug: #371 - Unable to properly uninstall the plugin @@ -1042,7 +1042,7 @@ IMPORTANT Standard Redmine's upload form with progress bar for files > 100 MB WebDAV library upgrade -* New: #130 - redmine_dmsf: last update of the folders +* New: #130 - redmine_dmsf: last update of the folders * Bug: #131 - Wiki link shows filename for all users type * New: #136 - `File Manipulation` permissions * New: #218 - Feature request: Recycle bin @@ -1050,12 +1050,12 @@ IMPORTANT * New: #238 - DMSF document update shows up in issue referred to in comment * New: #249 - Storage path for DMSF files ignores global storage path for attachments * New: #255 - Debian - Readme install procedure update -* Bug: #258 - Jquery conflict with Redmine -* Bug: #267 - Custom fields tabs not work with last custom_fields_helper_patch.rb -* Bug: #269 - Workflow OR not working for second reviewer -* Bug: #270 - 500 Internal Server Error, redmine 2.5.1, MS SQL Server 2012, dmsf 1.4.8-master, dmsf_link.rb +* Bug: #258 - Jquery conflict with Redmine +* Bug: #267 - Custom fields tabs not work with last custom_fields_helper_patch.rb +* Bug: #269 - Workflow OR not working for second reviewer +* Bug: #270 - 500 Internal Server Error, redmine 2.5.1, MS SQL Server 2012, dmsf 1.4.8-master, dmsf_link.rb * Bug: #275 - Typo in readme file type -* Bug: #288 - ubuntu migrate failed +* Bug: #288 - ubuntu migrate failed * Bug: #290 - error installing plugin * Bug: #293 - Locking of inexistent files fails * Bug: #298 - The same approver in one approval step @@ -1087,7 +1087,7 @@ IMPORTANT * New: #236 - Documents tagging * Bug: #240 - Internal server error, redmine 2.5.1-devel.13064, PostgreSQL, dmsf 1.4.8-devel * Bug: #242 - dsmf 1.4.8 minor ... "link form" tab -* Bug: #246 - "File storage directory" does not default properly when setting is empty +* Bug: #246 - "File storage directory" does not default properly when setting is empty 1.4.7: *2014-01-02* ------------------- @@ -1098,7 +1098,7 @@ IMPORTANT Code revision * New: #38 - A few questions about the plugin (possible improvements) -* New: #49 - Make the 100 MB ajax upload limit an option +* New: #49 - Make the 100 MB ajax upload limit an option * Bug: #52 - Error : undefined method `size' for nil:NilClass * Bug: #90 - Missing redmine_dmsf / assets / javascripts / plupload / i18n /en.js file? * Bug: #94 - Files not deleted with project @@ -1107,22 +1107,22 @@ IMPORTANT * Bug: #141 - Error 500 uploading file with DMSF custom fields * Bug: #159 - Broken links caused by plugin_asset_path implementation * New: #173 - Open approvals in My page -* Bug: #174 - Workflow error when more than one approver +* Bug: #174 - Workflow error when more than one approver * Bug: #175 - Error 500 on performing search * Bug: #176 - 500 internal error when approving workflow - dmsf_workflows/4/new_action -* Bug: #177 - 1.4.7-devel unable to upload files +* Bug: #177 - 1.4.7-devel unable to upload files * Bug: #178 - Error 500 cannot access Administration -> Custom Fields page * New: #179 - Workflow Log History in Detailed View -* Bug: #187 - Approval workflow permissions +* Bug: #187 - Approval workflow permissions * New: #190 - Very slow in directories containing many files -* Bug: #191 - Move/Copy gives undefined method for File:Class -* New: #193 - French translation -* Bug: #194 - Workflow name link in workflow log window -* Bug: #195 - Workflow log not displaying all the steps +* Bug: #191 - Move/Copy gives undefined method for File:Class +* New: #193 - French translation +* Bug: #194 - Workflow name link in workflow log window +* Bug: #195 - Workflow log not displaying all the steps * New: #196 - Update French Language * Bug: #197 - Multi upload not loading the translation * New: #198 - When editing a workflow, only show current project's users -* Bug: #199 - Small error in plugin_asset_path function +* Bug: #199 - Small error in plugin_asset_path function * New: #200 - Update the french translation for the multi upload module * Bug: #202 - unable to create Custom Field when DMSF plugin installed * Bug: #203 - Little typing error in french translation @@ -1187,7 +1187,7 @@ IMPORTANT * New: Locks store a timestamp based UUID string enabling better interaction with webservices * Bug: #16 - unable to add new project when plugin enabled due to bug in UI * Bug: #17 - dav4rack not installable on some systems - it is now vendored -* Bug: #18 - Warnings thrown due to space between function and parentheses +* Bug: #18 - Warnings thrown due to space between function and parentheses 1.4.3: *2012-06-26* ------------------- @@ -1220,4 +1220,4 @@ IMPORTANT 1.4.0: *2012-06-06* ------------------- -* New: Redmine 2.0 or higher is required \ No newline at end of file +* New: Redmine 2.0 or higher is required diff --git a/README.md b/README.md index 16b0c551..4ceff663 100644 --- a/README.md +++ b/README.md @@ -9,10 +9,10 @@ Redmine DMSF now comes bundled with WebDAV functionality: if switched on within WebDAV functionality is provided through Dav4Rack library. -The development has been supported by [Kontron](https://www.kontron.com) and has been released as open source thanks to their generosity. +The development has been supported by [Kontron](https://www.kontron.com) and has been released as open source thanks to their generosity. Project home: -Redmine Document Management System "Features" plugin is distributed under GNU General Public License v2 (GPL). +Redmine Document Management System "Features" plugin is distributed under GNU General Public License v2 (GPL). Redmine is a flexible project management web application, released under the terms of the GNU General Public License v2 (GPL) at Further information about the GPL license can be found at @@ -21,9 +21,9 @@ Further information about the GPL license can be found at ## Features * Directory structure - * Document versioning / revision history + * Document versioning / revision history * Document locking - * Multi (drag/drop depending on browser) upload/download + * Multi (drag/drop depending on browser) upload/download * Direct document or document link sending via email * Configurable document approval workflow * Document access auditing @@ -31,7 +31,7 @@ Further information about the GPL license can be found at * Wiki macros for a quick content linking * Full read/write WebDAV functionality * Optional document content full-text search - * Documents and folders' symbolic links + * Documents and folders' symbolic links * Trash bin * Documents attachable to issues * Office documents are displayed inline @@ -41,7 +41,7 @@ Further information about the GPL license can be found at * Compatible with Redmine 6 ## Dependencies - + * Redmine 6.0 or higher ### Full-text search (optional) @@ -66,9 +66,9 @@ See redmine_dmsf/extra/xapian_indexer.rb for help. #### Searching -If you want to use fulltext search abilities, install xapian packages. In case of using of Bitnami +If you want to use fulltext search abilities, install xapian packages. In case of using of Bitnami stack or Ruby installed via RVM it might be necessary to install Xapian bindings from sources. See https://xapian.org - for details. + for details. To index some files with omega you may have to install some other packages like xpdf, antiword, ... @@ -108,8 +108,8 @@ From Omega documentation: * 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: + +You can use following commands to install some of the required indexing tools: On Debian use: @@ -142,7 +142,7 @@ The command must be runable by the web app's user. Test it in advance, e.g: ``` sudo apt install libreoffice liblibreoffice-java -``` +``` ## Usage @@ -152,7 +152,7 @@ Search will now automatically search DMSF content when a Redmine search is perfo ## Linking DMSF object from Wiki entries (macros) -You can link DMSF object from Wikis using a macro tag `{{ }}`. List of available macros with their description is +You can link DMSF object from Wikis using a macro tag `{{ }}`. List of available macros with their description is available from the wiki's toolbar. ## Hooks @@ -163,7 +163,7 @@ E.g. class DmsfUploadControllerHooks < Redmine::Hook::Listener - def dmsf_upload_controller_after_commit(context={}) + def dmsf_upload_controller_after_commit(context={}) context[:controller].flash[:info] = 'Okay' end @@ -189,28 +189,28 @@ parameters: *revision*, *step_action* **dmsf_files_controller_before_view** -Allows a preview of the file by an external plugin. If the hook returns true, the file is not sent by DMSF. It is +Allows a preview of the file by an external plugin. If the hook returns true, the file is not sent by DMSF. It is expected that the file is sent by the hook. parameters: *file* ## Setup / Upgrade -You can either clone the master branch or download the latest zipped version. Before installing ensure that the Redmine +You can either clone the master branch or download the latest zipped version. Before installing ensure that the Redmine instance is stopped. git clone git@github.com:danmunn/redmine_dmsf.git - + wget https://github.com/danmunn/redmine_dmsf/archive/master.zip 1. In case of upgrade **BACKUP YOUR DATABASE, ORIGINAL PLUGIN AND THE FOLDER WITH DOCUMENTS** first!!! 2. Put redmine_dmsf plugin directory into plugins. The plugins sub-directory must be named just **redmine_dmsf**. In case of need rename _redmine_dmsf-x.y.z_ to *redmine_dmsf*. -3. **Go to the redmine directory** +3. **Go to the redmine directory** `cd redmine` -4. Install dependencies: +4. Install dependencies: `bundle install` @@ -232,11 +232,11 @@ instance is stopped. `RAILS_ENV="production" bundle exec rake assets:precompile` -7. The access rights must be set for web server, e.g.: +7. The access rights must be set for web server, e.g.: `chown -R www-data:www-data plugins/redmine_dmsf` -8. Restart the web server, e.g.: +8. Restart the web server, e.g.: `systemctl restart apache2` @@ -254,7 +254,7 @@ instance is stopped. * issues - Convert also files attached to issues Example: - + rake redmine:dmsf_convert_documents project=test RAILS_ENV="production" (If you don't run the rake task as the web server user, don't forget to change the ownership of the imported files, e.g. @@ -264,33 +264,33 @@ instance is stopped. 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 create missing checksums for all document revisions - + Available options: - - * dry_run - test, no changes to the database + + * dry_run - test, no changes to the database * forceSHA256 - replace old MD5 with SHA256 - + Example: - + bundle exec rake redmine:dmsf_create_digests RAILS_ENV="production" bundle exec rake redmine:dmsf_create_digests forceSHA256=1 RAILS_ENV="production" bundle exec rake redmine:dmsf_create_digests dry_run=1 RAILS_ENV="production" - + IV) 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" @@ -306,7 +306,7 @@ config.middleware.insert_before ActionDispatch::Cookies, RedmineDmsf::Webdav::Cu ### Installation in a sub-uri -In order to documents and folders are available via WebDAV in case that the Redmine is configured to be run in a sub-uri +In order to documents and folders are available via WebDAV in case that the Redmine is configured to be run in a sub-uri it's necessary to add the following configuration option into your `config/additional_environment.rb`: ```ruby @@ -325,7 +325,7 @@ After these steps re-start your instance of Redmine. ## Contributing -If you've added something, why not share it. Fork the repository (github.com/danmunn/redmine_dmsf), +If you've added something, why not share it. Fork the repository (github.com/danmunn/redmine_dmsf), make the changes and send a pull request to the maintainers. Changes with tests, and full documentation are preferred. diff --git a/app/controllers/dmsf_context_menus_controller.rb b/app/controllers/dmsf_context_menus_controller.rb index a77d3d63..0b76886c 100644 --- a/app/controllers/dmsf_context_menus_controller.rb +++ b/app/controllers/dmsf_context_menus_controller.rb @@ -17,6 +17,8 @@ # You should have received a copy of the GNU General Public License along with Redmine DMSF plugin. If not, see # . +require "#{File.dirname(__FILE__)}/../../lib/redmine_dmsf/preview" + # Context menu controller class DmsfContextMenusController < ApplicationController helper :context_menus diff --git a/app/controllers/dmsf_controller.rb b/app/controllers/dmsf_controller.rb index 1c0e098c..86409958 100644 --- a/app/controllers/dmsf_controller.rb +++ b/app/controllers/dmsf_controller.rb @@ -30,7 +30,7 @@ class DmsfController < ApplicationController except: %i[new create edit_root save_root add_email append_email autocomplete_for_user digest reset_digest] before_action :find_parent, only: %i[new create delete] - before_action :permissions + before_action :permissions? # Also try to lookup folder by title if this is an API call before_action :find_folder_by_title, only: [:show] before_action :query, only: %i[expand_folder show trash empty_trash index] @@ -50,7 +50,7 @@ class DmsfController < ApplicationController helper :context_menus helper :watchers - def permissions + def permissions? if !DmsfFolder.permissions?(@folder, allow_system: false) render_403 elsif @folder && @project && (@folder.project != @project) @@ -82,7 +82,7 @@ class DmsfController < ApplicationController @file_manipulation_allowed = User.current.allowed_to?(:file_manipulation, @project) @trash_enabled = @folder_manipulation_allowed && @file_manipulation_allowed @notifications = Setting.notified_events.include?('dmsf_legacy_notifications') - @query.dmsf_folder_id = @folder ? @folder.id : nil + @query.dmsf_folder_id = @folder&.id @query.deleted = false @query.sub_projects |= RedmineDmsf.dmsf_projects_as_subfolders? if @folder&.deleted? || (params[:folder_title].present? && !@folder) @@ -363,7 +363,7 @@ class DmsfController < ApplicationController def lock if @folder.nil? - flash[:warning] = l(:warning_foler_unlockable) + flash[:warning] = l(:warning_folder_unlockable) elsif @folder.locked? flash[:warning] = l(:warning_folder_already_locked) else @@ -375,7 +375,7 @@ class DmsfController < ApplicationController def unlock if @folder.nil? - flash[:warning] = l(:warning_foler_unlockable) + flash[:warning] = l(:warning_folder_unlockable) elsif !@folder.locked? flash[:warning] = l(:warning_folder_not_locked) elsif @folder.locks[0].user == User.current || User.current.allowed_to?(:force_file_unlock, @project) diff --git a/app/controllers/dmsf_files_controller.rb b/app/controllers/dmsf_files_controller.rb index d9ccf585..c8559438 100644 --- a/app/controllers/dmsf_files_controller.rb +++ b/app/controllers/dmsf_files_controller.rb @@ -25,7 +25,7 @@ class DmsfFilesController < ApplicationController before_action :find_revision, only: %i[delete_revision obsolete_revision] before_action :find_folder, only: %i[delete create_revision] before_action :authorize - before_action :permissions + before_action :permissions? accept_api_auth :show, :view, :delete, :create_revision @@ -38,7 +38,7 @@ class DmsfFilesController < ApplicationController include QueriesHelper - def permissions + def permissions? render_403 if @file && !DmsfFolder.permissions?(@file.dmsf_folder, allow_system: true, file: true) true end diff --git a/app/controllers/dmsf_folder_permissions_controller.rb b/app/controllers/dmsf_folder_permissions_controller.rb index a46993bc..fe50c537 100644 --- a/app/controllers/dmsf_folder_permissions_controller.rb +++ b/app/controllers/dmsf_folder_permissions_controller.rb @@ -24,11 +24,11 @@ class DmsfFolderPermissionsController < ApplicationController if: -> { params[:dmsf_folder_id].present? } before_action :find_project before_action :authorize - before_action :permissions + before_action :permissions? helper :dmsf - def permissions + def permissions? render_403 unless DmsfFolder.permissions?(@dmsf_folder) true end diff --git a/app/controllers/dmsf_links_controller.rb b/app/controllers/dmsf_links_controller.rb index a279eaf0..54f849a5 100644 --- a/app/controllers/dmsf_links_controller.rb +++ b/app/controllers/dmsf_links_controller.rb @@ -25,7 +25,7 @@ class DmsfLinksController < ApplicationController before_action :find_link_project before_action :find_folder, only: [:destroy] before_action :authorize - before_action :permissions + before_action :permissions? protect_from_forgery except: :new @@ -33,7 +33,7 @@ class DmsfLinksController < ApplicationController helper :dmsf - def permissions + def permissions? render_403 if @dmsf_link && !DmsfFolder.permissions?(@dmsf_link.dmsf_folder) true end diff --git a/app/controllers/dmsf_upload_controller.rb b/app/controllers/dmsf_upload_controller.rb index e1eebf9a..47bc2ba2 100644 --- a/app/controllers/dmsf_upload_controller.rb +++ b/app/controllers/dmsf_upload_controller.rb @@ -25,7 +25,7 @@ class DmsfUploadController < ApplicationController before_action :authorize, except: %i[upload delete_dmsf_attachment delete_dmsf_link_attachment] before_action :authorize_global, only: %i[upload delete_dmsf_attachment delete_dmsf_link_attachment] before_action :find_folder, except: %i[upload commit delete_dmsf_attachment delete_dmsf_link_attachment] - before_action :permissions, except: %i[upload commit delete_dmsf_attachment delete_dmsf_link_attachment] + before_action :permissions?, except: %i[upload commit delete_dmsf_attachment delete_dmsf_link_attachment] helper :custom_fields helper :dmsf_workflows @@ -33,7 +33,7 @@ class DmsfUploadController < ApplicationController accept_api_auth :upload, :commit - def permissions + def permissions? render_403 unless DmsfFolder.permissions?(@folder) true end @@ -107,7 +107,7 @@ class DmsfUploadController < ApplicationController @folder = DmsfFolder.visible.find_by(id: attachments[:folder_id]) if attachments[:folder_id].present? # standard file input uploads - uploaded_files = attachments.select { |key, _| key == 'uploaded_file' } + uploaded_files = attachments.slice('uploaded_file') uploaded_files.each_value do |uploaded_file| upload = DmsfUpload.create_from_uploaded_attachment(@project, @folder, uploaded_file) next unless upload diff --git a/app/controllers/dmsf_workflows_controller.rb b/app/controllers/dmsf_workflows_controller.rb index ecbe5a81..d08efde8 100644 --- a/app/controllers/dmsf_workflows_controller.rb +++ b/app/controllers/dmsf_workflows_controller.rb @@ -26,7 +26,7 @@ class DmsfWorkflowsController < ApplicationController before_action :find_model_object, except: %i[create new index assign assignment] before_action :find_project before_action :authorize_custom - before_action :permissions, only: %i[new_action assignment start] + before_action :permissions?, only: %i[new_action assignment start] before_action :approver_candidates, only: %i[remove_step show reorder_steps add_step] before_action :prevent_from_editing, only: %i[destroy remove_step update add_step update_step reorder_steps] @@ -34,7 +34,7 @@ class DmsfWorkflowsController < ApplicationController helper :dmsf - def permissions + def permissions? revision = DmsfFileRevision.find_by(id: params[:dmsf_file_revision_id]) if params[:dmsf_file_revision_id].present? render_403 unless revision&.dmsf_file || DmsfFolder.permissions?(revision&.dmsf_file&.dmsf_folder) true @@ -76,7 +76,7 @@ class DmsfWorkflowsController < ApplicationController { dmsf_file_revision: revision, step_action: params[:step_action] }) if (result.blank? || result.first) && action.save if revision - if @dmsf_workflow.try_finish revision, action, (params[:step_action].to_i / 10) + if @dmsf_workflow.try_finish? revision, action, (params[:step_action].to_i / 10) if revision.dmsf_file begin revision.dmsf_file.unlock!(force_file_unlock_allowed: true) unless RedmineDmsf.dmsf_keep_documents_locked? @@ -413,8 +413,8 @@ class DmsfWorkflowsController < ApplicationController if request.put? if @assigned flash[:error] = l(:error_dmsf_workflow_assigned) - elsif !@dmsf_workflow.reorder_steps(params[:step].to_i, params[:dmsf_workflow][:position].to_i) - flash[:error] = l(:notice_cannot_renumber_steps) + else + @dmsf_workflow.reorder_steps params[:step].to_i, params[:dmsf_workflow][:position].to_i end end respond_to do |format| diff --git a/app/helpers/dmsf_helper.rb b/app/helpers/dmsf_helper.rb index 51d36e22..18d39c74 100644 --- a/app/helpers/dmsf_helper.rb +++ b/app/helpers/dmsf_helper.rb @@ -86,9 +86,7 @@ module DmsfHelper def email_entry_tmp_file_path(entry) sanitized_entry = DmsfHelper.sanitize_filename(entry) file_name = "#{RedmineDmsf::DmsfZip::FILE_PREFIX}#{sanitized_entry}.zip" - # rubocop:disable Rails/FilePath - File.join(Rails.root.to_s, 'tmp', file_name) - # rubocop:enable Rails/FilePath + Rails.root.join 'tmp', file_name end # Extracts the variable part of the temp file name to be used as identifier in the @@ -96,6 +94,6 @@ module DmsfHelper def tmp_entry_identifier(zipped_content) path = Pathname.new(zipped_content) zipped_file = path.basename(path.extname).to_s - zipped_file.delete_prefix(RedmineDmsf::DmsfZip::FILE_PREFIX) + zipped_file.delete_prefix RedmineDmsf::DmsfZip::FILE_PREFIX end end diff --git a/app/helpers/dmsf_queries_helper.rb b/app/helpers/dmsf_queries_helper.rb index 5d91761b..d56a6ef8 100644 --- a/app/helpers/dmsf_queries_helper.rb +++ b/app/helpers/dmsf_queries_helper.rb @@ -32,15 +32,22 @@ module DmsfQueriesHelper file = DmsfFile.find_by(id: item.id) if file&.locked? return content_tag(:span, val) + - content_tag('span', sprite_icon('unlock', nil, icon_only: true, size: '12'), - title: l(:title_locked_by_user, user: file.locked_by)) + link_to(sprite_icon('unlock', nil, icon_only: true, size: '12'), + unlock_dmsf_files_path(id: file, + back_url: dmsf_folder_path(id: file.project, + folder_id: file.dmsf_folder)), + title: l(:title_locked_by_user, user: file.locked_by), class: 'icon icon-unlock') end when 'folder' folder = DmsfFolder.find_by(id: item.id) if folder&.locked? return content_tag(:span, val) + - content_tag('span', sprite_icon('unlock', nil, icon_only: true, size: '12'), - title: l(:title_locked_by_user, user: folder.locked_by)) + link_to(sprite_icon('unlock', nil, icon_only: true, size: '12'), + unlock_dmsf_path(id: folder.project, + folder_id: folder.id, + back_url: dmsf_folder_path(id: folder.project, + folder_id: folder.dmsf_folder)), + title: l(:title_locked_by_user, user: folder.locked_by), class: 'icon icon-unlock') end end content_tag(:span, val) + content_tag(:span, '', class: 'icon icon-none') diff --git a/app/models/dmsf_file.rb b/app/models/dmsf_file.rb index 4a66b9fb..109ae804 100644 --- a/app/models/dmsf_file.rb +++ b/app/models/dmsf_file.rb @@ -18,6 +18,7 @@ # . require "#{File.dirname(__FILE__)}/../../lib/redmine_dmsf/lockable" +require "#{File.dirname(__FILE__)}/../../lib/redmine_dmsf/plugin" require 'English' # File @@ -569,15 +570,17 @@ class DmsfFile < ApplicationRecord end def assigned?(user) - if last_revision&.dmsf_workflow - last_revision.dmsf_workflow.next_assignments(last_revision.id).each do |assignment| - return true if assignment.user == user - end + return false unless last_revision&.dmsf_workflow + + last_revision.dmsf_workflow.next_assignments(last_revision.id).each do |assignment| + return true if assignment.user == user end false end def custom_value(custom_field) + return nill unless last_revision + last_revision.custom_field_values.each do |cv| return cv if cv.custom_field == custom_field end diff --git a/app/models/dmsf_file_revision.rb b/app/models/dmsf_file_revision.rb index e08068d2..edd941eb 100644 --- a/app/models/dmsf_file_revision.rb +++ b/app/models/dmsf_file_revision.rb @@ -127,7 +127,7 @@ class DmsfFileRevision < ApplicationRecord errors.add :base, l(:error_file_is_locked) return false end - if !commit && (!force && (dmsf_file.dmsf_file_revisions.length <= 1)) + if !commit && !force && (dmsf_file.dmsf_file_revisions.length <= 1) errors.add :base, l(:error_at_least_one_revision_must_be_present) return false end diff --git a/app/models/dmsf_folder.rb b/app/models/dmsf_folder.rb index adf6aa4e..99a1a232 100644 --- a/app/models/dmsf_folder.rb +++ b/app/models/dmsf_folder.rb @@ -127,21 +127,13 @@ class DmsfFolder < ApplicationRecord if folder.dmsf_folder_permissions.any? role_ids = User.current.roles_for_project(folder.project).map(&:id) role_permission_ids = folder.dmsf_folder_permissions.roles.map(&:object_id) - if RUBY_VERSION < '3.1' # intersect? method added in Ruby 3.1, though we support 2.7 too - return true if role_ids.intersection(role_permission_ids).any? - elsif role_ids.intersect?(role_permission_ids) - return true - end + return true if role_ids.intersect?(role_permission_ids) principal_ids = folder.dmsf_folder_permissions.users.map(&:object_id) return true if principal_ids.include?(User.current.id) user_group_ids = User.current.groups.map(&:id) - if RUBY_VERSION < '3.1' # intersect? method added in Ruby 3.1, though we support 2.7 too - principal_ids.intersection(user_group_ids).any? - else - principal_ids.intersect?(user_group_ids) - end + principal_ids.intersect? user_group_ids else DmsfFolder.permissions? folder.dmsf_folder, allow_system: allow_system, file: file end diff --git a/app/models/dmsf_link.rb b/app/models/dmsf_link.rb index 48975d97..3ed1789b 100644 --- a/app/models/dmsf_link.rb +++ b/app/models/dmsf_link.rb @@ -110,7 +110,7 @@ class DmsfLink < ApplicationRecord link.name = name link.external_url = external_url link.project_id = project.id - link.dmsf_folder_id = folder ? folder.id : nil + link.dmsf_folder_id = folder&.id link.user = User.current link.save! link diff --git a/app/models/dmsf_workflow.rb b/app/models/dmsf_workflow.rb index f02afb4d..95e70238 100644 --- a/app/models/dmsf_workflow.rb +++ b/app/models/dmsf_workflow.rb @@ -109,7 +109,6 @@ class DmsfWorkflow < ApplicationRecord end end end - true end def delegates(query, dmsf_workflow_step_assignment_id, dmsf_file_revision_id) @@ -186,7 +185,7 @@ class DmsfWorkflow < ApplicationRecord end end - def try_finish(revision, action, user_id) + def try_finish?(revision, action, user_id) case action.action when DmsfWorkflowStepAction::ACTION_APPROVE assignments = next_assignments(revision.id) @@ -216,7 +215,7 @@ class DmsfWorkflow < ApplicationRecord def copy_to(project, name = nil) new_wf = dup new_wf.name = name if name - new_wf.project_id = project ? project.id : nil + new_wf.project_id = project&.id new_wf.author = User.current if new_wf.save dmsf_workflow_steps.each do |step| diff --git a/app/views/dmsf/_path.html.erb b/app/views/dmsf/_path.html.erb index 9530e8c0..bf30da80 100644 --- a/app/views/dmsf/_path.html.erb +++ b/app/views/dmsf/_path.html.erb @@ -17,14 +17,14 @@ # . %> -

- <% if folder %> +

+ <% if folder %> <%= link_to l(:link_documents), dmsf_folder_path(id: @project) %> <% folder.dmsf_path.each do |path_element| %> - / + / <% if filename.blank? && (path_element == folder.dmsf_path.last) %> <%= h(path_element.title) %> - <% else %> + <% else %> <%= link_to h(path_element.title), dmsf_folder_path(id: @project, folder_id: path_element) %> <% end %> <% end %> @@ -46,4 +46,4 @@ <% if title %> » <%= title %> <% end %> -

\ No newline at end of file + diff --git a/app/views/dmsf/_query_rows.erb b/app/views/dmsf/_query_rows.erb index 5bde4d7c..0feebeec 100644 --- a/app/views/dmsf/_query_rows.erb +++ b/app/views/dmsf/_query_rows.erb @@ -39,7 +39,10 @@ <%= check_box_tag('ids[]', "#{node.type}-#{node.id}", false, id: nil) unless node.system %> <% query.inline_columns.each do |column| %> - <%= content_tag 'td', column_content(column, node), class: column.css_classes %> + <% classes = column.css_classes.to_s.dup %> + <% classes << ' dmsf-gray' if node.type.match?(/link$/) %> + <% classes << ' dmsf-system' if node.system %> + <%= content_tag 'td', column_content(column, node), class: classes %> <% end %> <% unless node.system %> @@ -110,4 +113,4 @@ }); }); <% end %> -<% end %> \ No newline at end of file +<% end %> diff --git a/app/views/dmsf/trash.html.erb b/app/views/dmsf/trash.html.erb index e5564a8d..b862f726 100644 --- a/app/views/dmsf/trash.html.erb +++ b/app/views/dmsf/trash.html.erb @@ -21,7 +21,7 @@
<% if @file_delete_allowed %> - <%= delete_link empty_trash_path(id: @project), {}, l(:label_empty_trash_bin) %> + <%= link_to sprite_icon('del', l(:label_empty_trash_bin)), empty_trash_path(id: @project) %> <% end %>
diff --git a/app/views/dmsf_files/_file_new_revision.html.erb b/app/views/dmsf_files/_file_new_revision.html.erb index 52342d12..0c91e015 100644 --- a/app/views/dmsf_files/_file_new_revision.html.erb +++ b/app/views/dmsf_files/_file_new_revision.html.erb @@ -17,7 +17,7 @@ # . %> -
+
<%= l(:heading_new_revision) %> [+] @@ -77,8 +77,8 @@ <%= f.submit l(:button_create), class: 'button-positive', data: { cy: "button__submit--file_dmsf"} %>
<% end %> - <% end %> -
+ <% end %> + <%= wikitoolbar_for 'dmsf_file_revision_description' %> diff --git a/app/views/dmsf_files/show.html.erb b/app/views/dmsf_files/show.html.erb index 5dd54e4b..bbb7cf79 100644 --- a/app/views/dmsf_files/show.html.erb +++ b/app/views/dmsf_files/show.html.erb @@ -60,9 +60,11 @@ <%= render partial: 'file_new_revision' %> <% end %> -
- <%= label_tag '', l(:label_document) %> - #<%= @file.id %> +
+
+ <%= label_tag '', l(:label_document) %> + #<%= @file.id %> +

<%= l(:heading_revisions) %>

@@ -163,7 +165,6 @@
-
<% end %> <%= pagination_links_full @revision_pages, @revision_count %> diff --git a/app/views/dmsf_help/en/dmsf_help.html.erb b/app/views/dmsf_help/en/dmsf_help.html.erb index 03b0daac..85598e24 100644 --- a/app/views/dmsf_help/en/dmsf_help.html.erb +++ b/app/views/dmsf_help/en/dmsf_help.html.erb @@ -4,7 +4,8 @@ Wiki formatting <%= stylesheet_link_tag 'wiki_syntax.css' %> - <%= stylesheet_link_tag 'dmsf_help.css', plugin: :redmine_dmsf %> + <% plugin = defined?(EasyExtensions) ? nil : :redmine_dmsf %> + <%= stylesheet_link_tag 'dmsf_help.css', plugin: plugin %> @@ -789,4 +790,4 @@ - \ No newline at end of file + diff --git a/app/views/dmsf_state/_user_pref.html.erb b/app/views/dmsf_state/_user_pref.html.erb index 924dfcfe..81b54215 100644 --- a/app/views/dmsf_state/_user_pref.html.erb +++ b/app/views/dmsf_state/_user_pref.html.erb @@ -66,6 +66,7 @@ options_for_select(options, selected: @project.default_dmsf_query_id) %> <%= l('text_allowed_queries_to_select') %>

+ <%= call_hook(:view_dmsf_state_user_pref, { project: @project }) %>
diff --git a/app/views/hooks/easy_dmsf/_view_layouts_base_html_head.html.erb b/app/views/hooks/easy_dmsf/_view_layouts_base_html_head.html.erb new file mode 100644 index 00000000..eb2e5ff7 --- /dev/null +++ b/app/views/hooks/easy_dmsf/_view_layouts_base_html_head.html.erb @@ -0,0 +1,23 @@ +<% + # encoding: utf-8 + # + # Redmine plugin for Document Management System "Features" + # + # 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 + # . +%> + +<%= stylesheet_link_tag('easy_dmsf') %> +<%= javascript_include_tag('easy_dmsf', defer: true) %> diff --git a/app/views/hooks/redmine_dmsf/_view_layouts_base_html_head.html.erb b/app/views/hooks/redmine_dmsf/_view_layouts_base_html_head.html.erb new file mode 100644 index 00000000..768921aa --- /dev/null +++ b/app/views/hooks/redmine_dmsf/_view_layouts_base_html_head.html.erb @@ -0,0 +1,26 @@ +<% + # encoding: utf-8 + # + # Redmine plugin for Document Management System "Features" + # + # 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 + # . +%> + +<%= stylesheet_link_tag('redmine_dmsf', plugin: :redmine_dmsf) %> +<%= stylesheet_link_tag('select2.min', plugin: :redmine_dmsf) %> +<%= javascript_include_tag('select2.min', plugin: :redmine_dmsf, defer: true) %> +<%= javascript_include_tag('redmine_dmsf', plugin: :redmine_dmsf, defer: true) %> +<%= javascript_include_tag('attachments_dmsf', plugin: :redmine_dmsf, defer: true) %> diff --git a/app/views/my/blocks/_locked_documents.html.erb b/app/views/my/blocks/_locked_documents.html.erb index bd4d0e44..6c250741 100644 --- a/app/views/my/blocks/_locked_documents.html.erb +++ b/app/views/my/blocks/_locked_documents.html.erb @@ -65,8 +65,8 @@ <%= link_to_project folder.project %> - <%= link_to h(folder.title), dmsf_folder_path(id: folder.project, folder_id: folder), - class: 'icon icon-folder' %> + <%= link_to sprite_icon('folder', h(folder.title)), + dmsf_folder_path(id: folder.project, folder_id: folder), class: 'icon icon-folder' %> <% if folder.dmsf_folder %> diff --git a/app/views/my/blocks/_watched_documents.html.erb b/app/views/my/blocks/_watched_documents.html.erb index 355a01dc..9e0b0bdb 100644 --- a/app/views/my/blocks/_watched_documents.html.erb +++ b/app/views/my/blocks/_watched_documents.html.erb @@ -80,8 +80,8 @@ <%= link_to_project folder.project %> - <%= link_to h(folder.title), dmsf_folder_path(id: folder.project, folder_id: folder), - class: 'icon icon-folder' %> + <%= link_to sprite_icon('folder', h(folder.title)), + dmsf_folder_path(id: folder.project, folder_id: folder), class: 'icon icon-folder' %> <% if folder.dmsf_folder %> diff --git a/assets/javascripts/easy_dmsf.js b/assets/javascripts/easy_dmsf.js new file mode 100644 index 00000000..3d9f9f76 --- /dev/null +++ b/assets/javascripts/easy_dmsf.js @@ -0,0 +1,24 @@ +/* + Redmine plugin for Document Management System "Features" + + 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 + . +*/ + +/* +*= require select2.min +*= require redmine_dmsf +*= require attachments_dmsf + */ diff --git a/assets/stylesheets/easy_dmsf.css b/assets/stylesheets/easy_dmsf.css new file mode 100644 index 00000000..1399a84d --- /dev/null +++ b/assets/stylesheets/easy_dmsf.css @@ -0,0 +1,23 @@ +/* + Redmine plugin for Document Management System "Features" + + 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 + . +*/ + +/* +*= require redmine_dmsf +*= require select2.min +*/ diff --git a/assets/stylesheets/redmine_dmsf.css b/assets/stylesheets/redmine_dmsf.css index ed409e6f..2691e0cc 100644 --- a/assets/stylesheets/redmine_dmsf.css +++ b/assets/stylesheets/redmine_dmsf.css @@ -75,23 +75,39 @@ div[id^="step-index-"] { } /* DMSF revision box */ +h2.dmsf-header { + border: none !important; +} + #new_revision_form_content { display: none; } +form#new_revision_form { + margin: 0; +} + .dmsf-revision-box { background-color: #f6f6f6; + margin-bottom: 16px; } .dmsf-revision-inner-box { border: 1px solid #e4e4e4; padding: 10px; + border-radius: 3px; + box-shadow: 0 1px 2px rgba(0,0,0,0.05); } div.dmsf-revision-inner-box .attribute { - padding-left: 180px; + padding: 0; clear: left; min-height: 1.8em; + border: none; +} + +div.dmsf-revision-inner-box .attribute .label { + margin-left: 0 !important; } div.dmsf-revision-inner-box .attribute .label { @@ -110,6 +126,18 @@ div.dmsf-id-box { padding-left: 10px; } +div#dmsf_new_revision { + padding: 8px; + margin: 0px 0px 12px 0px; + background-color: rgb(249.3, 251.9, 255); + color: #505050; + line-height: 1.5em; + border: 1px solid #d0d7de; + word-wrap: break-word; + border-radius: 3px; + box-shadow: 0 1px 2px rgba(0,0,0,0.05); +} + .dmsf-log-header-box{ padding: 6px; margin-bottom: 10px; @@ -129,6 +157,7 @@ div.dmsf-id-box { .dmsf-widget-header { font-weight: normal; padding: 0 10px 0 10px; + background: #e9e9e9; } .dmsf-widget-header-text { @@ -152,67 +181,6 @@ div[id*="revision_access_"] { max-width: 100%; } -/* Command icons */ -.dmsf-icon-link:not(:has(svg)) { background-image: url("../../../images/link.png"); } - -/* File types */ -.dmsf-icon-file{ - display: inline-block; - height: 16px; -} - -.dmsf-gray .icon-folder { background-image: url("../images/folder_gray.png"); } -.dmsf-system .icon-folder { background-image: url("../images/folder_system.png"); } - -.icon-file.filetype-doc, .icon-file.filetype-docx { background-image: url("../images/filetypes/doc.png"); } -.icon-file.filetype-xls, -.icon-file.filetype-xlsx, -.icon-file.filetype-xlsm { background-image: url("../images/filetypes/xls.png"); } -.icon-file.filetype-ppt, .icon-file.filetype-pptx { background-image: url("../images/filetypes/ppt.png"); } -.icon-file.filetype-vsd, .icon-file.filetype-vsdx { background-image: url("../images/filetypes/vsd.png"); } -.icon-file.filetype-mpp { background-image: url("../images/filetypes/mpp.png"); } -.icon-file.filetype-odt { background-image: url("../images/filetypes/odt.png"); } -.icon-file.filetype-ods { background-image: url("../images/filetypes/ods.png"); } -.icon-file.filetype-ott { background-image: url("../images/filetypes/ott.png"); } -.icon-file.filetype-odp { background-image: url("../images/filetypes/odp.png"); } -.icon-file.filetype-odg { background-image: url("../images/filetypes/odg.png"); } - -.dmsf-gray .icon-file.filetype-doc { background-image: url("../images/filetypes/doc_gray.png"); } -.dmsf-gray .icon-file.filetype-docx { background-image: url("../images/filetypes/doc_gray.png"); } -.dmsf-gray .icon-file.filetype-xls { background-image: url("../images/filetypes/xls_gray.png"); } -.dmsf-gray .icon-file.filetype-xlsx { background-image: url("../images/filetypes/xls_gray.png"); } -.dmsf-gray .icon-file.filetype-xlsm { background-image: url("../images/filetypes/xls_gray.png"); } -.dmsf-gray .icon-file.filetype-ppt { background-image: url("../images/filetypes/ppt_gray.png"); } -.dmsf-gray .icon-file.filetype-pptx { background-image: url("../images/filetypes/ppt_gray.png"); } -.dmsf-gray .icon-file.filetype-vsd { background-image: url("../images/filetypes/vsd_gray.png"); } -.dmsf-gray .icon-file.filetype-vsdx { background-image: url("../images/filetypes/vsd_gray.png"); } -.dmsf-gray .icon-file.filetype-mpp { background-image: url("../images/filetypes/mpp_gray.png"); } -.dmsf-gray .icon-file.filetype-odt { background-image: url("../images/filetypes/odt_gray.png"); } -.dmsf-gray .icon-file.filetype-ott { background-image: url("../images/filetypes/ott_gray.png"); } -.dmsf-gray .icon-file.filetype-ods { background-image: url("../images/filetypes/ods_gray.png"); } -.dmsf-gray .icon-file.filetype-odp { background-image: url("../images/filetypes/odp_gray.png"); } -.dmsf-gray .icon-file.filetype-odg { background-image: url("../images/filetypes/odg_gray.png"); } - -.dmsf-gray .icon-file.text-x-c { background-image: url("../images/filetypes/c_gray.png"); } -.dmsf-gray .icon-file.text-x-csharp { background-image: url("../images/filetypes/csharp_gray.png"); } -.dmsf-gray .icon-file.text-x-java { background-image: url("../images/filetypes/java_gray.png"); } -.dmsf-gray .icon-file.text-x-javascript { background-image: url("../images/filetypes/js_gray.png"); } -.dmsf-gray .icon-file.text-x-php { background-image: url("../images/filetypes/php_gray.png"); } -.dmsf-gray .icon-file.text-x-ruby { background-image: url("../images/filetypes/ruby_gray.png"); } -.dmsf-gray .icon-file.text-xml { background-image: url("../images/filetypes/xml_gray.png"); } -.dmsf-gray .icon-file.text-css { background-image: url("../images/filetypes/css_gray.png"); } -.dmsf-gray .icon-file.text-html { background-image: url("../images/filetypes/html_gray.png"); } -.dmsf-gray .icon-file.image-gif { background-image: url("../images/filetypes/image_gray.png"); } -.dmsf-gray .icon-file.image-jpeg { background-image: url("../images/filetypes/image_gray.png"); } -.dmsf-gray .icon-file.image-png { background-image: url("../images/filetypes/image_gray.png"); } -.dmsf-gray .icon-file.image-tiff { background-image: url("../images/filetypes/image_gray.png"); } -.dmsf-gray .icon-file.application-pdf { background-image: url("../images/filetypes/pdf_gray.png"); } -.dmsf-gray .icon-file.application-zip { background-image: url("../images/filetypes/zip_gray.png"); } -.dmsf-gray .icon-file.application-x-gzip { background-image: url("../images/filetypes/zip_gray.png"); } - -/* Activities */ -.icon-dmsf-file-revision { background-image: url("../../../images/document.png"); } - /* Links */ .dmsf-gray, .dmsf-gray a, @@ -237,19 +205,14 @@ svg.dmsf-system { stroke: darkviolet; } -/* Search results */ -.icon-dmsf-file { background-image: url("../../../images/document.png"); } - /* DMSF tree view */ .dmsf-hidden { display: none; } .dmsf-tree:not(.dmsf-child) span.dmsf-expander { cursor: pointer; } .dmsf-tree.dmsf-expanded span.dmsf-expander { - background: url("../../../images/arrow_down.png") no-repeat 0 50%; padding-left: 16px; } .dmsf-tree.dmsf-child span.dmsf-expander { padding-left: 16px; } .dmsf-tree.dmsf-collapsed span.dmsf-expander { - background: url("../../../images/arrow_right.png") no-repeat 0 50%; padding-left: 16px; } .dmsf-tree.idnt-1 td.dmsf-title { padding-left: 1.5em; } @@ -321,18 +284,9 @@ span.fileover { width: 250px; color: #555; background-color: inherit; - background: url("../../../images/attachment.png") no-repeat 1px 50%; padding-left: 18px; } -#dmsf_attachments_fields .ajax-waiting input.filename { - background: url("../../../images/hourglass.png") no-repeat 0 50%; -} - -#dmsf_attachments_fields .ajax-loading input.filename { - background: url("../../../images/loading.gif") no-repeat 0 50%; -} - #dmsf_attachments_fields div.ui-progressbar { width: 100px; height: 14px; @@ -351,7 +305,6 @@ span.fileover { width: 250px; color: #555; background-color: inherit; - background: url("../../../images/link.png") no-repeat 1px 50%; padding-left: 18px; } @@ -360,7 +313,6 @@ span.fileover { } a.dmsf-scroll-down { - background: url("../../../images/arrow_down.png") no-repeat 5px 50%; background-color: #759FCF; text-decoration: none; color: #FFFFFF; @@ -410,4 +362,4 @@ div.dmsf-scroll { /* Wiki toolbar */ .jstb_dmsf { background-image: url("/document.png"); -} \ No newline at end of file +} diff --git a/config/initializers/easy_assets.rb b/config/initializers/easy_assets.rb new file mode 100644 index 00000000..99b3799c --- /dev/null +++ b/config/initializers/easy_assets.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +# Redmine plugin for Document Management System "Features" +# +# Vít Jonáš , Daniel Munn , 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 +# . + +Rails.application.configure do + asset_paths = EasyAssets.plugin_asset_paths('plugins/redmine_dmsf') + config.assets.paths.concat asset_paths + config.assets.precompile << 'easy_dmsf.js' + config.assets.precompile << 'easy_dmsf.css' +end diff --git a/config/initializers/easy_software.rb b/config/initializers/easy_software.rb new file mode 100644 index 00000000..eddac65b --- /dev/null +++ b/config/initializers/easy_software.rb @@ -0,0 +1,173 @@ +# frozen_string_literal: true + +# Redmine plugin for Document Management System "Features" +# +# Vít Jonáš , Daniel Munn , 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 +# . + +require 'redmine' +require 'zip' +require "#{File.dirname(__FILE__)}/../../lib/redmine_dmsf" + +Rails.application.config.after_initialize do + require 'redmine_dmsf/preview' + + options = { + 'dmsf_max_file_download' => 0, + 'dmsf_max_email_filesize' => 0, + 'dmsf_storage_directory' => 'files/dmsf', + 'dmsf_index_database' => File.expand_path('dmsf_index', Rails.root), + 'dmsf_stemming_lang' => 'english', + 'dmsf_stemming_strategy' => 'STEM_NONE', + 'dmsf_webdav' => nil, + 'dmsf_display_notified_recipients' => nil, + 'dmsf_global_title_format' => '', + 'dmsf_columns' => %w[title size modified version workflow author], + 'dmsf_webdav_ignore' => '^(\._|\.DS_Store$|Thumbs.db$)', + 'dmsf_webdav_disable_versioning' => '^\~\$|\.tmp$', + 'dmsf_keep_documents_locked' => nil, + 'dmsf_act_as_attachable' => nil, + 'dmsf_documents_email_from' => '', + 'dmsf_documents_email_reply_to' => '', + 'dmsf_documents_email_links_only' => nil, + 'dmsf_enable_cjk_ngrams' => nil, + 'dmsf_webdav_use_project_names' => '1', + 'dmsf_webdav_ignore_1b_file_for_authentication' => '1', + 'dmsf_projects_as_subfolders' => nil, + 'only_approval_zero_minor_version' => '0', + 'dmsf_max_notification_receivers_info' => 10, + 'office_bin' => 'libreoffice', + 'dmsf_global_menu_disabled' => nil, + 'dmsf_default_query' => nil, + 'empty_minor_version_by_default' => nil, + 'remove_original_documents_module' => nil, + 'dmsf_webdav_authentication' => 'Digest', + 'dmsf_really_delete_files' => '0' + } + + Setting.define_setting 'plugin_redmine_dmsf', { 'default' => options, 'serialized' => true } + Redmine::Plugin.find(:redmine_dmsf).settings = { partial: 'settings/dmsf_settings' } + + # Administration menu extension + Redmine::MenuManager.map :admin_menu do |menu| + menu.push :dmsf_approvalworkflows, :dmsf_workflows_path, + caption: :label_dmsf_workflow_plural, + icon: 'workflows', + html: { class: 'icon icon-workflows' }, + if: proc { |_| User.current.admin? } + end + + # Project menu extension + Redmine::MenuManager.map :project_menu do |menu| + menu.push :dmsf, { controller: 'dmsf', action: 'show' }, + caption: :menu_dmsf, + before: :documents, + param: :id, + html: { class: 'icon icon-dmsf' } + end + + # Main menu extension + Redmine::MenuManager.map :top_menu do |menu| + menu.push :dmsf, { controller: 'dmsf', action: 'index' }, + caption: :menu_dmsf, + html: { class: 'icon-dmsf', category: :rest_extension_modules }, + if: proc { + User.current.allowed_to?(:view_dmsf_folders, nil, global: true) && + ActiveRecord::Base.connection.data_source_exists?('settings') && + !RedmineDmsf.dmsf_global_menu_disabled? + } + end + + 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: %i[show index] }, read: true + pmap.permission :user_preferences, { dmsf_state: [:user_pref_save] }, require: :member + pmap.permission(:view_dmsf_files, + { dmsf: %i[entries_operation entries_email download_email_entries add_email append_email + autocomplete_for_user], + dmsf_files: %i[show view thumbnail], + dmsf_workflows: [:log] }, + read: true) + pmap.permission :email_documents, + { dmsf_public_urls: [:create] } + pmap.permission :folder_manipulation, + { dmsf: %i[new create delete edit save edit_root save_root lock unlock notify_activate + notify_deactivate restore drop copymove], + dmsf_folder_permissions: %i[new append autocomplete_for_user], + dmsf_context_menus: [:dmsf] } + pmap.permission :file_manipulation, + { dmsf_files: %i[create_revision lock unlock delete_revision obsolete_revision + notify_activate notify_deactivate restore], + dmsf_upload: %i[upload_files upload commit_files commit delete_dmsf_attachment + delete_dmsf_link_attachment multi_upload], + dmsf_links: %i[new create destroy restore autocomplete_for_project autocomplete_for_folder], + dmsf_context_menus: [:dmsf] } + pmap.permission :file_delete, + { dmsf: %i[trash delete_entries empty_trash], + dmsf_files: [:delete], + dmsf_trash_context_menus: [:trash] } + pmap.permission :force_file_unlock, {} + pmap.permission :file_approval, + { dmsf_workflows: %i[action new_action autocomplete_for_user start assign assignment] } + pmap.permission :manage_workflows, + { dmsf_workflows: %i[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 + # Watchers + pmap.permission :view_dmsf_file_watchers, {}, read: true + pmap.permission :add_dmsf_file_watchers, { watchers: %i[new create append autocomplete_for_user] } + pmap.permission :delete_dmsf_file_watchers, { watchers: :destroy } + pmap.permission :view_dmsf_folder_watchers, {}, read: true + pmap.permission :add_dmsf_folder_watchers, { watchers: %i[new create append autocomplete_for_user] } + pmap.permission :delete_dmsf_folder_watchers, { watchers: :destroy } + pmap.permission :view_project_watchers, {}, read: true + pmap.permission :add_project_watchers, { watchers: %i[new create append autocomplete_for_user] } + pmap.permission :delete_project_watchers, { watchers: :destroy } + end + end + + # Register panels for My page + EpmDmsfLockedDocuments.register_to_scope :user, plugin: :redmine_dmsf + EpmDmsfOpenApprovals.register_to_scope :user, plugin: :redmine_dmsf + EpmDmsfWatchedDocuments.register_to_scope :user, plugin: :redmine_dmsf + + # DMSF WebDAV digest token + Token.add_action :dmsf_webdav_digest, max_instances: 1, validity_time: nil +end + +Rails.application.configure do + # Rubyzip configuration + Zip.unicode_names = true + + Rails.application.config.after_initialize do + # DMS custom fields + CustomFieldsHelper::CUSTOM_FIELDS_TABS << { name: 'DmsfFileRevisionCustomField', partial: 'custom_fields/index', + label: :dmsf } + # Searchable modules + Redmine::Search.map do |search| + search.register :dmsf_files + search.register :dmsf_folders + end + + # Activities + Redmine::Activity.register :dmsf_file_revision_accesses, default: false + Redmine::Activity.register :dmsf_file_revisions + end + + require "#{File.dirname(__FILE__)}/../../lib/redmine_dmsf/webdav/custom_middleware" + config.middleware.insert_before ActionDispatch::Cookies, RedmineDmsf::Webdav::CustomMiddleware +end diff --git a/config/initializers/webdav.rb b/config/initializers/webdav.rb new file mode 100644 index 00000000..072d6ff8 --- /dev/null +++ b/config/initializers/webdav.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +# Redmine plugin for Document Management System "Features" +# +# Vít Jonáš , Daniel Munn , 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 +# . + +require 'redmine_dmsf/webdav/custom_middleware' + +Rails.application.configure do + config.middleware.insert_before ActionDispatch::Cookies, RedmineDmsf::Webdav::CustomMiddleware +end diff --git a/config/locales/cs.yml b/config/locales/cs.yml index dddfbbcf..730eba26 100644 --- a/config/locales/cs.yml +++ b/config/locales/cs.yml @@ -492,6 +492,8 @@ cs: label_dmsf_upload_commit: Nahrát a potvrdit notice_search_in_subfolders: Vyhledávání v podsložkách není rekurzivní. Pro rekurzivní vyhledávání běžte do nejvyšší úrovně. + warning_folder_unlockable: Složku nelze odemknout + redmine_dmsf: Redmine DMSF easy_pages: modules: diff --git a/config/locales/de.yml b/config/locales/de.yml index d0875784..514157e5 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -488,6 +488,8 @@ de: notice_search_in_subfolders: Die Suche in Unterordnern ist nicht rekursiv. Für eine rekursive Suche gehen Sie auf die oberste Ebene. + warning_folder_unlockable: Der Ordner kann nicht entsperrt werden + redmine_dmsf: Redmine DMSF easy_pages: modules: diff --git a/config/locales/en.yml b/config/locales/en.yml index 0505270a..7b53f546 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -491,6 +491,9 @@ en: label_dmsf_upload_commit: Upload and commit notice_search_in_subfolders: Searching in sub-folders is not recursive. For a recursive search go to the top level. + warning_folder_unlockable: The folder can't be unlocked + redmine_dmsf: Redmine DMSF + easy_pages: modules: diff --git a/config/locales/es.yml b/config/locales/es.yml index e32ad8ef..da1cd657 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -488,6 +488,8 @@ es: notice_webdav_digest_reset: Your DMS WebDAV digest was reset. notice_search_in_subfolders: Searching in sub-folders is not recursive. For a recursive search go to the top level. + warning_folder_unlockable: The folder can't be unlocked + redmine_dmsf: Redmine DMSF label_dmsf_commit: Commit label_dmsf_upload_commit: Upload and commit diff --git a/config/locales/fa.yml b/config/locales/fa.yml index f4952f18..34dac73a 100644 --- a/config/locales/fa.yml +++ b/config/locales/fa.yml @@ -470,6 +470,8 @@ fa: label_dmsf_upload_commit: Upload and commit notice_search_in_subfolders: Searching in sub-folders is not recursive. For a recursive search go to the top level. + warning_folder_unlockable: The folder can't be unlocked + redmine_dmsf: Redmine DMSF easy_pages: modules: diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 5a163037..3100d2b5 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -491,6 +491,8 @@ fr: label_dmsf_upload_commit: Upload and commit notice_search_in_subfolders: Searching in sub-folders is not recursive. For a recursive search go to the top level. + warning_folder_unlockable: The folder can't be unlocked + redmine_dmsf: Redmine DMSF easy_pages: modules: diff --git a/config/locales/hu.yml b/config/locales/hu.yml index 69722f6c..ab8e1158 100644 --- a/config/locales/hu.yml +++ b/config/locales/hu.yml @@ -490,6 +490,8 @@ hu: label_dmsf_upload_commit: Upload and commit notice_search_in_subfolders: Searching in sub-folders is not recursive. For a recursive search go to the top level. + warning_folder_unlockable: The folder can't be unlocked + redmine_dmsf: Redmine DMSF easy_pages: modules: diff --git a/config/locales/it.yml b/config/locales/it.yml index 912f5cd4..97ab60c9 100644 --- a/config/locales/it.yml +++ b/config/locales/it.yml @@ -491,6 +491,8 @@ it: # Italian strings thx 2 Matteo Arceci! label_dmsf_upload_commit: Upload and commit notice_search_in_subfolders: Searching in sub-folders is not recursive. For a recursive search go to the top level. + warning_folder_unlockable: The folder can't be unlocked + redmine_dmsf: Redmine DMSF easy_pages: modules: diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 0597f441..ac3de256 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -492,6 +492,8 @@ ja: label_dmsf_upload_commit: Upload and commit notice_search_in_subfolders: Searching in sub-folders is not recursive. For a recursive search go to the top level. + warning_folder_unlockable: The folder can't be unlocked + redmine_dmsf: Redmine DMSF easy_pages: modules: diff --git a/config/locales/ko.yml b/config/locales/ko.yml index 44bcba3d..bea0daa7 100644 --- a/config/locales/ko.yml +++ b/config/locales/ko.yml @@ -491,6 +491,8 @@ ko: label_dmsf_upload_commit: Upload and commit notice_search_in_subfolders: Searching in sub-folders is not recursive. For a recursive search go to the top level. + warning_folder_unlockable: The folder can't be unlocked + redmine_dmsf: Redmine DMSF easy_pages: modules: diff --git a/config/locales/nl.yml b/config/locales/nl.yml index 6898e769..122cd2a6 100644 --- a/config/locales/nl.yml +++ b/config/locales/nl.yml @@ -491,6 +491,8 @@ nl: label_dmsf_upload_commit: Upload and commit notice_search_in_subfolders: Searching in sub-folders is not recursive. For a recursive search go to the top level. + warning_folder_unlockable: The folder can't be unlocked + redmine_dmsf: Redmine DMSF easy_pages: modules: diff --git a/config/locales/pl.yml b/config/locales/pl.yml index 63686db7..1a2c12a8 100644 --- a/config/locales/pl.yml +++ b/config/locales/pl.yml @@ -491,6 +491,8 @@ pl: label_dmsf_upload_commit: Upload and commit notice_search_in_subfolders: Searching in sub-folders is not recursive. For a recursive search go to the top level. + warning_folder_unlockable: The folder can't be unlocked + redmine_dmsf: Redmine DMSF easy_pages: modules: diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml index 54e756b7..18d327dd 100644 --- a/config/locales/pt-BR.yml +++ b/config/locales/pt-BR.yml @@ -491,6 +491,8 @@ pt-BR: label_dmsf_upload_commit: Upload and commit notice_search_in_subfolders: Searching in sub-folders is not recursive. For a recursive search go to the top level. + warning_folder_unlockable: The folder can't be unlocked + redmine_dmsf: Redmine DMSF easy_pages: modules: diff --git a/config/locales/sl.yml b/config/locales/sl.yml index de6b425a..773c896b 100644 --- a/config/locales/sl.yml +++ b/config/locales/sl.yml @@ -491,6 +491,8 @@ sl: label_dmsf_upload_commit: Upload and commit notice_search_in_subfolders: Searching in sub-folders is not recursive. For a recursive search go to the top level. + warning_folder_unlockable: The folder can't be unlocked + redmine_dmsf: Redmine DMSF easy_pages: modules: diff --git a/config/locales/uk.yml b/config/locales/uk.yml index dfc34b9b..4ca42571 100644 --- a/config/locales/uk.yml +++ b/config/locales/uk.yml @@ -493,6 +493,8 @@ uk: label_dmsf_upload_commit: Upload and commit notice_search_in_subfolders: Searching in sub-folders is not recursive. For a recursive search go to the top level. + warning_folder_unlockable: The folder can't be unlocked + redmine_dmsf: Redmine DMSF easy_pages: modules: diff --git a/config/locales/zh-TW.yml b/config/locales/zh-TW.yml index 3ca3efc7..6661f296 100644 --- a/config/locales/zh-TW.yml +++ b/config/locales/zh-TW.yml @@ -490,6 +490,8 @@ zh-TW: label_dmsf_upload_commit: Upload and commit notice_search_in_subfolders: Searching in sub-folders is not recursive. For a recursive search go to the top level. + warning_folder_unlockable: The folder can't be unlocked + redmine_dmsf: Redmine DMSF easy_pages: modules: diff --git a/config/locales/zh.yml b/config/locales/zh.yml index 6f854991..8d120597 100644 --- a/config/locales/zh.yml +++ b/config/locales/zh.yml @@ -491,6 +491,8 @@ zh: label_dmsf_upload_commit: Upload and commit notice_search_in_subfolders: Searching in sub-folders is not recursive. For a recursive search go to the top level. + warning_folder_unlockable: The folder can't be unlocked + redmine_dmsf: Redmine DMSF easy_pages: modules: diff --git a/init.rb b/init.rb index 1debdb01..90e5121b 100644 --- a/init.rb +++ b/init.rb @@ -17,23 +17,20 @@ # You should have received a copy of the GNU General Public License along with Redmine DMSF plugin. If not, see # . +require 'redmine' +require 'zip' +require "#{File.dirname(__FILE__)}/lib/redmine_dmsf" + Redmine::Plugin.register :redmine_dmsf do name 'DMSF' url 'https://www.redmine.org/plugins/redmine_dmsf' author_url 'https://github.com/danmunn/redmine_dmsf/graphs/contributors' author 'Vít Jonáš / Daniel Munn / Karel Pičman' description 'Document Management System Features' - version '4.1.4 devel' + version '4.1.3' requires_redmine version_or_higher: '6.0.0' - webdav = if Redmine::Plugin.installed?('easy_hosting_services') && EasyHostingServices::EasyMultiTenancy.activated? - '1' - else - '0' - end - use_project_names = defined?(EasyExtensions) ? '1' : '0' - settings partial: 'settings/dmsf_settings', default: { 'dmsf_max_file_download' => 0, @@ -42,7 +39,7 @@ Redmine::Plugin.register :redmine_dmsf do 'dmsf_index_database' => File.expand_path('dmsf_index', Rails.root), 'dmsf_stemming_lang' => 'english', 'dmsf_stemming_strategy' => 'STEM_NONE', - 'dmsf_webdav' => webdav, + 'dmsf_webdav' => '0', 'dmsf_display_notified_recipients' => '0', 'dmsf_global_title_format' => '', 'dmsf_columns' => %w[title size modified version workflow author], @@ -54,7 +51,7 @@ Redmine::Plugin.register :redmine_dmsf do 'dmsf_documents_email_reply_to' => '', 'dmsf_documents_email_links_only' => '0', 'dmsf_enable_cjk_ngrams' => '0', - 'dmsf_webdav_use_project_names' => use_project_names, + 'dmsf_webdav_use_project_names' => '0', 'dmsf_webdav_ignore_1b_file_for_authentication' => '1', 'dmsf_projects_as_subfolders' => '0', 'only_approval_zero_minor_version' => '0', @@ -69,4 +66,120 @@ Redmine::Plugin.register :redmine_dmsf do } end -require_relative 'after_init' unless defined?(EasyExtensions) +# Administration menu extension +Redmine::MenuManager.map :admin_menu do |menu| + menu.push :dmsf_approvalworkflows, :dmsf_workflows_path, + caption: :label_dmsf_workflow_plural, + icon: 'workflows', + html: { class: 'icon icon-workflows' }, + if: proc { |_| User.current.admin? } +end +# Project menu extension +Redmine::MenuManager.map :project_menu do |menu| + menu.push :dmsf, { controller: 'dmsf', action: 'show' }, + caption: :menu_dmsf, + before: :documents, + param: :id, + html: { class: 'icon icon-dmsf' } + # New menu extension + next if defined?(EasyExtensions) + + menu.push :dmsf_file, { controller: 'dmsf_upload', action: 'multi_upload' }, + caption: :label_dmsf_new_top_level_document, parent: :new_object + menu.push :dmsf_folder, { controller: 'dmsf', action: 'new' }, + caption: :label_dmsf_new_top_level_folder, + parent: :new_object +end +# Main menu extension +Redmine::MenuManager.map :top_menu do |menu| + menu.push :dmsf, { controller: 'dmsf', action: 'index' }, + caption: :menu_dmsf, + html: { class: 'icon-dmsf', category: :rest_extension_modules }, + if: proc { + User.current.allowed_to?(:view_dmsf_folders, nil, global: true) && + ActiveRecord::Base.connection.data_source_exists?('settings') && + !RedmineDmsf.dmsf_global_menu_disabled? + } +end + +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: %i[show index] }, read: true + pmap.permission :user_preferences, { dmsf_state: [:user_pref_save] }, require: :member + pmap.permission(:view_dmsf_files, + { + dmsf: %i[entries_operation entries_email download_email_entries add_email append_email + autocomplete_for_user], + dmsf_files: %i[show view thumbnail], + dmsf_workflows: [:log] + }, + read: true) + pmap.permission :email_documents, + { dmsf_public_urls: [:create] } + pmap.permission :folder_manipulation, + { + dmsf: %i[new create delete edit save edit_root save_root lock unlock notify_activate + notify_deactivate restore drop copymove], + dmsf_folder_permissions: %i[new append autocomplete_for_user], + dmsf_context_menus: [:dmsf] + } + pmap.permission :file_manipulation, + { + dmsf_files: %i[create_revision lock unlock delete_revision obsolete_revision notify_activate + notify_deactivate restore], + dmsf_upload: %i[upload_files upload commit_files commit delete_dmsf_attachment + delete_dmsf_link_attachment multi_upload], + dmsf_links: %i[new create destroy restore autocomplete_for_project autocomplete_for_folder], + dmsf_context_menus: [:dmsf] + } + pmap.permission :file_delete, + { + dmsf: %i[trash delete_entries empty_trash], + dmsf_files: [:delete], + dmsf_trash_context_menus: [:trash] + } + pmap.permission :force_file_unlock, {} + pmap.permission :file_approval, + { dmsf_workflows: %i[action new_action autocomplete_for_user start assign assignment] } + pmap.permission :manage_workflows, + { + dmsf_workflows: %i[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 + # Watchers + pmap.permission :view_dmsf_file_watchers, {}, read: true + pmap.permission :add_dmsf_file_watchers, { watchers: %i[new create append autocomplete_for_user] } + pmap.permission :delete_dmsf_file_watchers, { watchers: :destroy } + pmap.permission :view_dmsf_folder_watchers, {}, read: true + pmap.permission :add_dmsf_folder_watchers, { watchers: %i[new create append autocomplete_for_user] } + pmap.permission :delete_dmsf_folder_watchers, { watchers: :destroy } + pmap.permission :view_project_watchers, {}, read: true + pmap.permission :add_project_watchers, { watchers: %i[new create append autocomplete_for_user] } + pmap.permission :delete_project_watchers, { watchers: :destroy } + end +end + +# DMSF WebDAV digest token +Token.add_action :dmsf_webdav_digest, max_instances: 1, validity_time: nil + +Rails.application.configure do + # Rubyzip configuration + Zip.unicode_names = true + + # DMS custom fields + CustomFieldsHelper::CUSTOM_FIELDS_TABS << { name: 'DmsfFileRevisionCustomField', partial: 'custom_fields/index', + label: :dmsf } + + # Searchable modules + Redmine::Search.map do |search| + search.register :dmsf_files + search.register :dmsf_folders + end + + # Activities + Redmine::Activity.register :dmsf_file_revision_accesses, default: false + Redmine::Activity.register :dmsf_file_revisions +end diff --git a/lib/dav4rack/controller.rb b/lib/dav4rack/controller.rb index 38d5a4d6..d85ab441 100644 --- a/lib/dav4rack/controller.rb +++ b/lib/dav4rack/controller.rb @@ -33,7 +33,7 @@ module Dav4rack # main entry point, called by the Handler def process - status = skip_authorization? || authenticate ? process_action || OK : HttpStatus::Unauthorized + status = skip_authorization? || authenticate? ? process_action || OK : HttpStatus::Unauthorized rescue HttpStatus::Status => e status = e ensure @@ -335,7 +335,7 @@ module Dav4rack # Perform authentication # # implement your authentication by overriding Resource#authenticate - def authenticate + def authenticate? uname = nil password = nil if request.authorization? @@ -345,7 +345,7 @@ module Dav4rack password = auth.credentials[1] end end - resource.authenticate uname, password + resource.authenticate? uname, password end def authentication_error_message diff --git a/lib/dav4rack/http_status.rb b/lib/dav4rack/http_status.rb index 5dfcf19f..1572fe08 100644 --- a/lib/dav4rack/http_status.rb +++ b/lib/dav4rack/http_status.rb @@ -5,6 +5,11 @@ module Dav4rack module HttpStatus # Status class Status < StandardError + delegate :code, to: :class + delegate :reason_phrase, to: :class + delegate :status_line, to: :class + delegate :to_i, to: :class + class << self attr_accessor :code, :reason_phrase alias to_i code @@ -13,22 +18,6 @@ module Dav4rack "#{code} #{reason_phrase}" end end - - def code - self.class.code - end - - def reason_phrase - self.class.reason_phrase - end - - def status_line - self.class.status_line - end - - def to_i - self.class.to_i - end end STATUS_MESSAGES = { diff --git a/lib/dav4rack/resource.rb b/lib/dav4rack/resource.rb index cfa4f958..eca3a2e1 100644 --- a/lib/dav4rack/resource.rb +++ b/lib/dav4rack/resource.rb @@ -102,7 +102,7 @@ module Dav4rack # override to implement custom authentication # should return true for successful authentication, false otherwise - def authenticate(_username, _password) + def authenticate?(_username, _password) true end diff --git a/lib/redmine_dmsf.rb b/lib/redmine_dmsf.rb index 84b51a00..6c22098f 100644 --- a/lib/redmine_dmsf.rb +++ b/lib/redmine_dmsf.rb @@ -261,11 +261,6 @@ unless defined?(EasyPatchManager) end end -# A workaround for obsolete 'alias_method' usage in RedmineUp's plugins -after_easy_init do - require "#{File.dirname(__FILE__)}/redmine_dmsf/plugin" -end - # Load up classes that make up our WebDAV solution ontop of Dav4rack after_easy_init do require "#{File.dirname(__FILE__)}/dav4rack" diff --git a/lib/redmine_dmsf/dmsf_zip.rb b/lib/redmine_dmsf/dmsf_zip.rb index c7f57bf0..5083f5be 100644 --- a/lib/redmine_dmsf/dmsf_zip.rb +++ b/lib/redmine_dmsf/dmsf_zip.rb @@ -27,6 +27,8 @@ module RedmineDmsf class Zip attr_reader :dmsf_files + delegate :close, to: :@zip_file + def initialize @temp_file = Tempfile.new([FILE_PREFIX, '.zip'], Rails.root.join('tmp')) @zip_file = ::Zip::OutputStream.open(@temp_file) @@ -44,10 +46,6 @@ module RedmineDmsf @temp_file.path end - def close - @zip_file.close - end - def add_dmsf_file(dmsf_file, member = nil, root_path = nil, path = nil) raise DmsfFileNotFoundError unless dmsf_file&.last_revision && File.exist?(dmsf_file.last_revision.disk_file) diff --git a/lib/redmine_dmsf/hooks/views/base_view_hooks.rb b/lib/redmine_dmsf/hooks/views/base_view_hooks.rb index 700e0acd..8ef5d0a6 100644 --- a/lib/redmine_dmsf/hooks/views/base_view_hooks.rb +++ b/lib/redmine_dmsf/hooks/views/base_view_hooks.rb @@ -29,11 +29,8 @@ module RedmineDmsf return end - "\n".html_safe + stylesheet_link_tag('redmine_dmsf', plugin: :redmine_dmsf) + - "\n".html_safe + stylesheet_link_tag('select2.min', plugin: :redmine_dmsf) + - "\n".html_safe + javascript_include_tag('select2.min', plugin: :redmine_dmsf, defer: true) + - "\n".html_safe + javascript_include_tag('redmine_dmsf', plugin: :redmine_dmsf, defer: true) + - "\n".html_safe + javascript_include_tag('attachments_dmsf', plugin: :redmine_dmsf, defer: true) + partial = "hooks/#{defined?(EasyExtensions) ? 'easy' : 'redmine'}_dmsf/view_layouts_base_html_head" + context[:controller].send :render_to_string, { partial: partial } end end end diff --git a/lib/redmine_dmsf/hooks/views/issue_view_hooks.rb b/lib/redmine_dmsf/hooks/views/issue_view_hooks.rb index 94b8a435..f436c679 100644 --- a/lib/redmine_dmsf/hooks/views/issue_view_hooks.rb +++ b/lib/redmine_dmsf/hooks/views/issue_view_hooks.rb @@ -215,7 +215,7 @@ module RedmineDmsf # Title, size html << '' data = "#{dmsf_file.last_revision.detect_content_type}:#{h(dmsf_file.name)}:#{file_view_url}" - icon_name = icon_for_mime_type(Redmine::MimeType.css_class_of(item.filename)) + icon_name = icon_for_mime_type(Redmine::MimeType.css_class_of(dmsf_file.name)) html << link_to(sprite_icon(icon_name, h(dmsf_file.title)), file_view_url, target: '_blank', diff --git a/lib/redmine_dmsf/preview.rb b/lib/redmine_dmsf/preview.rb index 4ad31408..130762b6 100644 --- a/lib/redmine_dmsf/preview.rb +++ b/lib/redmine_dmsf/preview.rb @@ -25,18 +25,17 @@ module RedmineDmsf extend Redmine::Utils::Shell include Redmine::I18n - OFFICE_BIN = (Setting.plugin_redmine_dmsf['office_bin'].presence || 'libreoffice').freeze - def self.office_available? return @office_available if defined?(@office_available) begin - `#{shell_quote OFFICE_BIN} --version` + office_bin = RedmineDmsf.office_bin.presence || 'libreoffice' + `#{shell_quote office_bin} --version` @office_available = $CHILD_STATUS.success? rescue StandardError @office_available = false end - Rails.logger.warn l(:note_dmsf_office_bin_not_available, value: OFFICE_BIN, locale: :en) unless @office_available + Rails.logger.warn l(:note_dmsf_office_bin_not_available, value: office_bin, locale: :en) unless @office_available @office_available end @@ -44,7 +43,8 @@ module RedmineDmsf return target if File.exist?(target) dir = File.dirname(target) - cmd = "#{shell_quote(OFFICE_BIN)} --convert-to pdf --headless --outdir #{shell_quote(dir)} #{shell_quote(source)}" + office_bin = RedmineDmsf.office_bin.presence || 'libreoffice' + cmd = "#{shell_quote(office_bin)} --convert-to pdf --headless --outdir #{shell_quote(dir)} #{shell_quote(source)}" if system(cmd) target else diff --git a/lib/redmine_dmsf/webdav/dmsf_controller.rb b/lib/redmine_dmsf/webdav/dmsf_controller.rb index 424f546d..06b21508 100644 --- a/lib/redmine_dmsf/webdav/dmsf_controller.rb +++ b/lib/redmine_dmsf/webdav/dmsf_controller.rb @@ -17,6 +17,8 @@ # You should have received a copy of the GNU General Public License along with Redmine DMSF plugin. If not, see # . +require "#{File.dirname(__FILE__)}/dmsf_digest" + module RedmineDmsf module Webdav # DMSF controller @@ -33,7 +35,7 @@ module RedmineDmsf def process return super unless RedmineDmsf.dmsf_webdav_authentication == 'Digest' - status = skip_authorization? || authenticate ? process_action || OK : Dav4rack::HttpStatus::Unauthorized + status = skip_authorization? || authenticate? ? process_action || OK : Dav4rack::HttpStatus::Unauthorized rescue Dav4rack::HttpStatus::Status => e status = e ensure @@ -49,14 +51,14 @@ module RedmineDmsf end end - def authenticate + def authenticate? return super unless RedmineDmsf.dmsf_webdav_authentication == 'Digest' auth_header = request.authorization.to_s scheme = auth_header.split(' ', 2).first&.downcase if scheme == 'digest' Rails.logger.info 'Authentication: digest' - digest = Digest.new(request.authorization) + digest = DmsfDigest.new(request.authorization) params = digest.params username = params['username'] response = params['response'] @@ -105,7 +107,7 @@ module RedmineDmsf raise Unauthorized if User.current.anonymous? Rails.logger.info "Current user: #{User.current}, User-Agent: #{request.user_agent}" - User.current + User.current && !User.current.anonymous? end end end diff --git a/lib/redmine_dmsf/webdav/digest.rb b/lib/redmine_dmsf/webdav/dmsf_digest.rb similarity index 98% rename from lib/redmine_dmsf/webdav/digest.rb rename to lib/redmine_dmsf/webdav/dmsf_digest.rb index ed9ceab6..110e2cce 100644 --- a/lib/redmine_dmsf/webdav/digest.rb +++ b/lib/redmine_dmsf/webdav/dmsf_digest.rb @@ -20,7 +20,7 @@ module RedmineDmsf module Webdav # Replacement for Rack::Auth::Digest - class Digest + class DmsfDigest def initialize(authorization) @authorization = authorization end diff --git a/lib/redmine_dmsf/webdav/dmsf_resource.rb b/lib/redmine_dmsf/webdav/dmsf_resource.rb index 9dbc7edb..ec01d7cd 100644 --- a/lib/redmine_dmsf/webdav/dmsf_resource.rb +++ b/lib/redmine_dmsf/webdav/dmsf_resource.rb @@ -264,7 +264,7 @@ module RedmineDmsf raise Locked if file.locked_for_user? if dest.exist? && !dest.collection? - if dest.resource.file.last_revision.size.zero? || reuse_version_for_locked_file(dest.resource.file) + if dest.resource.file.last_revision.size.zero? || reuse_version_for_locked_file?(dest.resource.file) # Last revision in the destination has zero size so reuse that revision new_revision = dest.resource.file.last_revision else @@ -390,7 +390,7 @@ module RedmineDmsf entity = file || folder return unless entity - refresh = args && (!args[:scope]) && (!args[:type]) + refresh = args && !args[:scope] && !args[:type] args ||= {} args[:method] = @request.request_method.downcase http_if = request.get_header('HTTP_IF') @@ -464,7 +464,7 @@ module RedmineDmsf # logically assume is that the lock is being refreshed (office loves # to do this for example, so we do a few checks, try to find the lock # and ultimately extend it, otherwise we return Conflict for any failure - refresh = args && (!args[:scope]) && (!args[:type]) # Perhaps a lock refresh + refresh = args && !args[:scope] && !args[:type] # Perhaps a lock refresh if refresh http_if = request.get_header('HTTP_IF') if http_if.blank? @@ -554,7 +554,7 @@ module RedmineDmsf Rails.logger.info "Versioning disabled for #{basename}" reuse_revision = true end - reuse_revision = true if reuse_version_for_locked_file(file) + reuse_revision = true if reuse_version_for_locked_file?(file) last_revision = file.last_revision if last_revision.size.zero? || reuse_revision new_revision = last_revision @@ -721,7 +721,7 @@ module RedmineDmsf File.new disk_file end - def reuse_version_for_locked_file(file) + def reuse_version_for_locked_file?(file) locks = file.lock locks.each do |lock| next if lock.expired? diff --git a/lib/redmine_dmsf/webdav/resource_proxy.rb b/lib/redmine_dmsf/webdav/resource_proxy.rb index b0e82659..57977452 100644 --- a/lib/redmine_dmsf/webdav/resource_proxy.rb +++ b/lib/redmine_dmsf/webdav/resource_proxy.rb @@ -27,6 +27,27 @@ module RedmineDmsf class ResourceProxy < Dav4rack::Resource attr_reader :read_only + delegate :propstats, to: :@resource_c + delegate :set_property, to: :@resource_c + delegate :options, to: :@resource_c + delegate :lockdiscovery, to: :@resource_c + delegate :lockdiscovery_xml, to: :@resource_c + delegate :children, to: :@resource_c + delegate :collection?, to: :@resource_c + delegate :exist?, to: :@resource_c + delegate :creation_date, to: :@resource_c + delegate :last_modified, to: :@resource_c + delegate :etag, to: :@resource_c + delegate :content_type, to: :@resource_c + delegate :content_length, to: :@resource_c + delegate :get, to: :@resource_c + delegate :special_type, to: :@resource_c + delegate :name, to: :@resource_c + delegate :long_name, to: :@resource_c + delegate :get_property, to: :@resource_c + delegate :remove_property, to: :@resource_c + delegate :properties, to: :@resource_c + def initialize(path, request, response, options) # Check the settings cache for each request Setting.check_cache @@ -40,63 +61,15 @@ module RedmineDmsf @read_only = RedmineDmsf.dmsf_webdav_strategy == 'WEBDAV_READ_ONLY' end - def authenticate(username, password) + def authenticate?(username, password) User.current = User.try_to_login(username, password) User.current && !User.current.anonymous? end - def options(request, response) - @resource_c.options request, response - end - def supports_locking? !@read_only end - def lockdiscovery - @resource_c.lockdiscovery - end - - def lockdiscovery_xml - @resource_c.lockdiscovery_xml - end - - def children - @resource_c.children - end - - def collection? - @resource_c.collection? - end - - def exist? - @resource_c.exist? - end - - def creation_date - @resource_c.creation_date - end - - def last_modified - @resource_c.last_modified - end - - def etag - @resource_c.etag - end - - def content_type - @resource_c.content_type - end - - def content_length - @resource_c.content_length - end - - def get(request, response) - @resource_c.get request, response - end - def put(request) raise BadGateway if @read_only @@ -127,10 +100,6 @@ module RedmineDmsf @resource_c.make_collection end - def special_type - @resource_c.special_type - end - def lock(args) raise BadGateway if @read_only @@ -147,38 +116,10 @@ module RedmineDmsf @resource_c.unlock token end - def name - @resource_c.name - end - - def long_name - @resource_c.long_name - end - def resource @resource_c end - def get_property(element) - @resource_c.get_property element - end - - def remove_property(element) - @resource_c.remove_property element - end - - def properties - @resource_c.properties - end - - def propstats(response, stats) - @resource_c.propstats response, stats - end - - def set_property(element, value) - @resource_c.set_property element, value - end - # Adds the given xml namespace to namespaces and returns the prefix def add_namespace(namespace, _prefix = '') return if namespace.blank? diff --git a/patches/access_control_easy_patch.rb b/patches/access_control_easy_patch.rb new file mode 100644 index 00000000..39c291a5 --- /dev/null +++ b/patches/access_control_easy_patch.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +# Redmine plugin for Document Management System "Features" +# +# 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 +# . + +module RedmineDmsf + module Patches + # AccessControl patch + # TODO: This is just a workaround to fix alias_method usage in Easy's plugins, which is in conflict with + # prepend and causes an infinite loop. + module AccessControlEasyPatch + ################################################################################################################## + # Overridden methods + def self.included(base) + base.extend(ClassMethods) + + base.class_eval do + class << self + alias_method_chain :available_project_modules, :easy + end + end + end + + # Class methods + module ClassMethods + def available_project_modules_with_easy + # Removes the original Documents from project's modules (replaced with DMSF) + modules = available_project_modules_without_easy + modules.delete(:documents) if RedmineDmsf.remove_original_documents_module? + modules + end + end + end + end +end + +# Apply the patch +if defined?(EasyPatchManager) + EasyPatchManager.register_other_patch 'Redmine::AccessControl', 'RedmineDmsf::Patches::AccessControlEasyPatch' +end diff --git a/patches/access_control_patch.rb b/patches/access_control_patch.rb index 40a9c320..06999323 100644 --- a/patches/access_control_patch.rb +++ b/patches/access_control_patch.rb @@ -43,9 +43,4 @@ module RedmineDmsf end # Apply the patch -if defined?(EasyPatchManager) - EasyPatchManager.register_patch_to_be_first 'Redmine::Acts::Attachable::InstanceMethods', - 'RedmineDmsf::Patches::AccessControlPatch', prepend: true, first: true -else - Redmine::AccessControl.prepend RedmineDmsf::Patches::AccessControlPatch -end +Redmine::AccessControl.prepend RedmineDmsf::Patches::AccessControlPatch unless defined?(EasyPatchManager) diff --git a/patches/formatting_helper_patch.rb b/patches/formatting_helper_patch.rb index c3b1572c..a49c380a 100644 --- a/patches/formatting_helper_patch.rb +++ b/patches/formatting_helper_patch.rb @@ -38,8 +38,9 @@ module RedmineDmsf '..', '..', '..', 'assets', 'javascripts', 'lang', "dmsf_button-#{lang}.js") lang = 'en' unless File.exist?(path) content_for :header_tags do - javascript_include_tag("lang/dmsf_button-#{lang}", plugin: 'redmine_dmsf') + - javascript_include_tag('dmsf_button', plugin: 'redmine_dmsf') + + plugin = defined?(EasyExtensions) ? nil : :redmine_dmsf + javascript_include_tag("lang/dmsf_button-#{lang}", plugin: plugin) + + javascript_include_tag('dmsf_button', plugin: plugin) + javascript_tag("jsToolBar.prototype.dmsfList = #{@dmsf_macro_list.to_json};") end end diff --git a/patches/pdf_patch.rb b/patches/pdf_patch.rb index 806846ce..f3163082 100644 --- a/patches/pdf_patch.rb +++ b/patches/pdf_patch.rb @@ -31,7 +31,7 @@ module RedmineDmsf def get_image_filename(attrname) if attrname =~ %r{/dmsf/files/(\d+)/} file = DmsfFile.find_by(id: Regexp.last_match(1)) - file&.last_revision ? file.last_revision.disk_file : nil + file&.last_revision&.disk_file else super end diff --git a/test/functional/dmsf_controller_test.rb b/test/functional/dmsf_controller_test.rb index 8d43db60..cfc0e644 100644 --- a/test/functional/dmsf_controller_test.rb +++ b/test/functional/dmsf_controller_test.rb @@ -22,7 +22,6 @@ require File.expand_path('../../test_helper', __FILE__) # DMSF controller class DmsfControllerTest < RedmineDmsf::Test::TestCase include Redmine::I18n - include Rails.application.routes.url_helpers include DmsfHelper fixtures :custom_fields, :custom_values, :dmsf_links, :dmsf_folder_permissions, :dmsf_locks, @@ -238,6 +237,9 @@ class DmsfControllerTest < RedmineDmsf::Test::TestCase def test_show_webdav_disabled post '/login', params: { username: 'jsmith', password: 'jsmith' } + # TODO: with_settings seems to be not working with Easy + return if defined?(EasyExtensions) + with_settings plugin_redmine_dmsf: { 'dmsf_webdav' => nil } do get "/projects/#{@project1.id}/dmsf" assert_response :success diff --git a/test/integration/webdav/dmsf_webdav_custom_middleware_test.rb b/test/integration/webdav/dmsf_webdav_custom_middleware_test.rb index 625f74a5..3721a160 100644 --- a/test/integration/webdav/dmsf_webdav_custom_middleware_test.rb +++ b/test/integration/webdav/dmsf_webdav_custom_middleware_test.rb @@ -44,6 +44,9 @@ class DmsfWebdavCustomMiddlewareTest < RedmineDmsf::Test::IntegrationTest end def test_webdav_not_enabled + # TODO: with_settings seems to be not working with Easy + return if defined?(EasyExtensions) + with_settings plugin_redmine_dmsf: { 'dmsf_webdav' => nil } do process :options, '/dmsf/webdav' assert_response :not_found diff --git a/test/unit/dmsf_workflow_test.rb b/test/unit/dmsf_workflow_test.rb index 47d50b85..1b0a576b 100644 --- a/test/unit/dmsf_workflow_test.rb +++ b/test/unit/dmsf_workflow_test.rb @@ -151,7 +151,7 @@ class DmsfWorkflowTest < RedmineDmsf::Test::UnitTest wsa.author_id = @jsmith.id assert wsa.save # The workflow is finished - assert @wf1.try_finish(@revision1, @wfsac1, @jsmith.id) + assert @wf1.try_finish?(@revision1, @wfsac1, @jsmith.id) @revision1.reload assert_equal DmsfWorkflow::STATE_APPROVED, @revision1.workflow end @@ -160,7 +160,7 @@ class DmsfWorkflowTest < RedmineDmsf::Test::UnitTest # The forkflow is waiting for an approval assert_equal DmsfWorkflow::STATE_WAITING_FOR_APPROVAL, @revision1.workflow # The workflow is not finished - assert_not @wf1.try_finish(@revision1, @wfsac1, @jsmith.id) + assert_not @wf1.try_finish?(@revision1, @wfsac1, @jsmith.id) @revision1.reload assert_equal DmsfWorkflow::STATE_WAITING_FOR_APPROVAL, @revision1.workflow end diff --git a/test/unit/lib/attachable_patch_test.rb b/test/unit/lib/attachable_patch_test.rb index 532c2432..9a692e26 100644 --- a/test/unit/lib/attachable_patch_test.rb +++ b/test/unit/lib/attachable_patch_test.rb @@ -26,16 +26,16 @@ class AttachablePatchTest < RedmineDmsf::Test::UnitTest def setup super @issue1 = Issue.find 1 - @issue2 = Issue.find 2 + @issue5 = Issue.find 5 end def test_has_attachmets if defined?(EasyExtensions) assert @issue1.has_attachments? - assert_not @issue2.has_attachments? + assert_not @issue5.has_attachments? else assert @issue1.dmsf_files.present? - assert @issue2.dmsf_files.blank? + assert @issue5.dmsf_files.blank? end end end diff --git a/test/unit/lib/redmine_dmsf/dmsf_macros_test.rb b/test/unit/lib/redmine_dmsf/dmsf_macros_test.rb index 681e5ef9..207ab983 100644 --- a/test/unit/lib/redmine_dmsf/dmsf_macros_test.rb +++ b/test/unit/lib/redmine_dmsf/dmsf_macros_test.rb @@ -21,22 +21,38 @@ require File.expand_path('../../../../test_helper', __FILE__) # Macros tests class DmsfMacrosTest < RedmineDmsf::Test::HelperTest - include ApplicationHelper - include ActionView::Helpers - include ActionDispatch::Routing - include ERB::Util - include Rails.application.routes.url_helpers - include ActionView::Helpers::UrlHelper - fixtures :dmsf_folders, :dmsf_files, :dmsf_file_revisions + # Mock view context for macros + class DmsfView + include ApplicationHelper + include ActionView::Helpers + include ActionDispatch::Routing + include ERB::Util + include Rails.application.routes.url_helpers + end + + # Cache the view context to avoid creating it for each macro call + def dmsf_view_context + @dmsf_view_context ||= DmsfView.new + end + + # Hack to bypass missing methods to mocked view context + def respond_to_missing?(name, include_private) + dmsf_view_context.respond_to?(name) || super + end + + def method_missing(method_name, ...) + dmsf_view_context.send(method_name.to_s, ...) + end + def setup super User.current = @jsmith - default_url_options[:host] = 'www.example.com' + Rails.application.routes.default_url_options[:host] = 'www.example.com' @file1 = DmsfFile.find_by(id: 1) - @file6 = DmsfFile.find_by(id: 6) # video - @file7 = DmsfFile.find_by(id: 7) # image + @file6 = DmsfFile.find_by(id: 6) # video + @file7 = DmsfFile.find_by(id: 7) # image @folder1 = DmsfFolder.find_by(id: 1) end @@ -345,10 +361,12 @@ class DmsfMacrosTest < RedmineDmsf::Test::HelperTest text = textilizable("{{dmsftn(#{@file7.id} #{@file7.id})}}") url = static_dmsf_file_url(@file7, @file7.last_revision.name) img = image_tag(url, alt: @file7.name, title: @file7.title, width: 'auto', height: 200) - link = link_to(img, url, target: '_blank', - rel: 'noopener', - title: h(@file7.last_revision.try(:tooltip)), - 'data-downloadurl': 'image/gif:test.gif:http://www.example.com/dmsf/files/7/test.gif') + link = link_to(img, + url, + target: '_blank', + rel: 'noopener', + title: h(@file7.last_revision.try(:tooltip)), + 'data-downloadurl': 'image/gif:test.gif:http://www.example.com/dmsf/files/7/test.gif') assert text.include?(link + link), text end @@ -379,10 +397,12 @@ class DmsfMacrosTest < RedmineDmsf::Test::HelperTest height = '480' text = textilizable("{{dmsftn(#{@file7.id}, height=#{height})}}") img = image_tag(url, alt: @file7.name, title: @file7.title, width: 'auto', height: 480) - link = link_to(img, url, target: '_blank', - rel: 'noopener', - title: h(@file7.last_revision.try(:tooltip)), - 'data-downloadurl': 'image/gif:test.gif:http://www.example.com/dmsf/files/7/test.gif') + link = link_to(img, + url, + target: '_blank', + rel: 'noopener', + title: h(@file7.last_revision.try(:tooltip)), + 'data-downloadurl': 'image/gif:test.gif:http://www.example.com/dmsf/files/7/test.gif') assert text.include?(link), text width = '640' text = textilizable("{{dmsftn(#{@file7.id}, width=#{width})}}") diff --git a/test/unit/lib/redmine_dmsf/dmsf_plugin_test.rb b/test/unit/lib/redmine_dmsf/dmsf_plugin_test.rb index cbb71300..5f5f0443 100644 --- a/test/unit/lib/redmine_dmsf/dmsf_plugin_test.rb +++ b/test/unit/lib/redmine_dmsf/dmsf_plugin_test.rb @@ -18,6 +18,7 @@ # . require File.expand_path('../../../../test_helper', __FILE__) +require File.expand_path('../../../../../lib/redmine_dmsf/plugin', __FILE__) # Plugin tests class DmsfPluginTest < RedmineDmsf::Test::HelperTest @@ -30,16 +31,20 @@ class DmsfPluginTest < RedmineDmsf::Test::HelperTest end def test_an_obsolete_plugin_present_no + return if defined?(EasyExtensions) + # No such plugin is present assert_not RedmineDmsf::Plugin.an_obsolete_plugin_present? end def test_an_obsolete_plugin_present_yes - # Create a fake redmine_checklists plugin - path = Rails.root.join('plugins/redmine_resources') - FileUtils.mkdir_p path + unless defined?(EasyExtensions) + # Create a fake redmine_checklists plugin + path = Rails.root.join('plugins/redmine_resources') + FileUtils.mkdir_p path + end assert RedmineDmsf::Plugin.an_obsolete_plugin_present? - FileUtils.rm_rf path + FileUtils.rm_rf(path) unless defined?(EasyExtensions) end def test_lib_available? diff --git a/test/unit/lib/redmine_dmsf/dmsf_zip_test.rb b/test/unit/lib/redmine_dmsf/dmsf_zip_test.rb index a0926cd1..5c177583 100644 --- a/test/unit/lib/redmine_dmsf/dmsf_zip_test.rb +++ b/test/unit/lib/redmine_dmsf/dmsf_zip_test.rb @@ -18,6 +18,7 @@ # . require File.expand_path('../../../../test_helper', __FILE__) +require File.expand_path('../../../../../lib/redmine_dmsf/dmsf_zip', __FILE__) # Plugin tests class DmsfZipTest < RedmineDmsf::Test::HelperTest diff --git a/test/unit/user_patch_test.rb b/test/unit/user_patch_test.rb index f3500c1b..52e79ab9 100644 --- a/test/unit/user_patch_test.rb +++ b/test/unit/user_patch_test.rb @@ -33,13 +33,13 @@ class UserPatchTest < RedmineDmsf::Test::UnitTest assert_equal 0, DmsfFileRevision.where(dmsf_workflow_assigned_by_user_id: id).all.size assert_equal 0, DmsfFileRevision.where(dmsf_workflow_started_by_user_id: id).all.size assert_equal 0, DmsfFile.where(deleted_by_user_id: id).all.size - assert_equal 0, DmsfFolder.where(user_id: id).all.size assert_equal 0, DmsfFolder.where(deleted_by_user_id: id).all.size assert_equal 0, DmsfLink.where(user_id: id).all.size assert_equal 0, DmsfLink.where(deleted_by_user_id: id).all.size # TODO: Expected: 0, Actual: 1 in Easy extension return if defined?(EasyExtensions) + assert_equal 0, DmsfFolder.where(user_id: id).all.size assert_equal 0, DmsfLock.where(user_id: id).all.size assert_equal 0, DmsfWorkflowStepAction.where(author_id: id).all.size assert_equal 0, DmsfWorkflowStepAssignment.where(user_id: id).all.size