Compare commits

..

228 Commits

Author SHA1 Message Date
01e79b6bb9 Merge branch 'feature/pdf-type' into release/v8.0.0 2023-12-22 17:19:00 +03:00
95060d2e4b nodejs: isForm in editor config 2023-12-22 17:15:15 +03:00
79da267a9c nodejs: add method isExtendedPDFFile 2023-12-22 17:15:14 +03:00
3cf0e897df nodejs: fill pdf 2023-12-22 17:14:22 +03:00
1109f35376 nodejs: pdf as new documentType 2023-12-22 17:10:36 +03:00
6546a51f17 nodejs: change submitForm status 2023-12-22 17:02:14 +03:00
685a62bb9c Merge pull request #497 from ONLYOFFICE/feature/version-on-front
Feature/version on front
2023-12-05 13:37:43 +03:00
2603acae16 ruby: Layout/ExtraSpacing correct 2023-12-05 13:34:30 +03:00
11489ac8ba ruby: fix sr-Latn-CS 2023-12-05 13:26:42 +03:00
e65f6f27be Merge remote-tracking branch 'remotes/origin/develop' into feature/version-on-front
# Conflicts:
#	CHANGELOG.md
2023-12-05 13:21:41 +03:00
a0a1b036e3 version to changelog 2023-12-05 13:19:57 +03:00
f846197396 csharp: add version Index.aspx 2023-12-04 16:29:37 +07:00
97f95fb850 csharp: add version Default.aspx 2023-12-04 16:17:37 +07:00
7d8a1a8b6d sr-Latn-CS skin language 2023-12-01 18:07:41 +03:00
23389056e8 ruby: add version application.html.erb 2023-12-01 10:52:44 +03:00
457e38c35a python: add version index.html 2023-12-01 10:52:14 +03:00
11075304a5 php: add version index.tpl 2023-12-01 10:51:59 +03:00
7af14e75e8 nodejs: add version index.ejs 2023-12-01 10:51:31 +03:00
c8ccb3b502 java: add version index.jsp 2023-12-01 10:50:57 +03:00
449ad0759b java-spring: add version index.html 2023-12-01 10:50:26 +03:00
32e4cb6d64 Merge pull request #498 from ONLYOFFICE/fix/hash-code-overflow (Fix Bug 65288)
node: fixed overflow when hashing сyrillic characters
2023-12-01 10:40:13 +03:00
83b1dc07e1 node: fixed overflow when hashing сyrillic characters 2023-11-30 18:19:38 +03:00
b61e05d203 Merge pull request #490 from ONLYOFFICE/feature/ruby-rubocop
Feature/ruby rubocop
2023-11-30 17:39:34 +03:00
9cf058078c ruby: fix lint problems after merging develop 2023-11-30 14:05:57 +03:00
d72834687e Merge branch 'develop' into feature/ruby-rubocop 2023-11-30 13:31:47 +03:00
af1dc6d9b5 ruby: add rubocop-rails 2023-11-30 09:28:05 +03:00
e5560b25d7 ruby: Rails/Present correct 2023-11-30 09:04:24 +03:00
d894663a4a ruby: Rails/NegateInclude correct 2023-11-30 09:02:38 +03:00
b262a34540 ruby: Rails/ToFormattedS correct 2023-11-30 09:02:38 +03:00
3d0ae07de4 ruby: Rails/TimeZone correct 2023-11-30 09:02:38 +03:00
efb4616ca6 ruby: Rails/HttpStatus correct 2023-11-30 09:02:38 +03:00
037c5682f8 ruby: Rails/Blank correct 2023-11-30 09:02:38 +03:00
7c1939e8c9 ruby: updated rubocop configuration file 2023-11-30 09:02:38 +03:00
6312833dd3 Merge pull request #491 from ONLYOFFICE/flake-make
Flake8 configuration file
2023-11-29 10:22:06 +03:00
ce58acbca9 Merge pull request #493 from ONLYOFFICE/fix/open-history-without-changes
Fix/open history without changes
2023-11-29 10:21:28 +03:00
d40f932863 python: deleted unnecessary lines from workflows 2023-11-29 10:11:43 +07:00
d6d1981296 getting history in changelog 2023-11-28 10:11:09 +03:00
b9fc1304d9 Merge pull request #469 from ONLYOFFICE/feature/restore-history
Feature/restore history
2023-11-28 10:09:11 +03:00
dac1cdcdc1 format 2023-11-28 10:08:01 +03:00
248beed471 Merge remote-tracking branch 'remotes/origin/develop' into feature/restore-history
# Conflicts:
#	CHANGELOG.md
2023-11-28 10:05:46 +03:00
9d5feaaf99 python: flake8 and mypy separated 2023-11-28 13:49:12 +07:00
d397db61cd python: lint workflow python version fix 2023-11-27 17:36:21 +07:00
969240ad53 python: intalling dependencies by make in lint workflow 2023-11-27 17:30:09 +07:00
8fd9113c47 python: mypy in lint workflow 2023-11-27 15:36:56 +07:00
068687ba91 Merge pull request #495 from ONLYOFFICE/feature/auto-convert
Convert after uploading only tagged formats
2023-11-27 11:35:51 +03:00
495d97bd3a - auto-convert in changelog 2023-11-27 11:35:33 +03:00
160c0bef3c ruby: fix opening history without changes 2023-11-27 10:58:03 +03:00
0b5b0d9c27 python: fix opening history without changes 2023-11-27 10:58:03 +03:00
b903bfc0cb java: fix opening history without changes 2023-11-27 10:57:59 +03:00
580f8f3e89 csharp: convert after uploading only tagged formats 2023-11-27 10:45:29 +03:00
b19abbcb36 csharp-mvc: convert after uploading only tagged formats 2023-11-27 10:44:17 +03:00
4de6f4d828 python: removing irrelevant changes 2023-11-27 14:36:18 +07:00
ad4b2d136c java-spring: fix opening history without changes 2023-11-24 13:41:43 +03:00
6578d414ea php: fix opening history without changes 2023-11-24 13:28:44 +03:00
7ca8a2122f Merge branch 'develop' into feature/restore-history 2023-11-22 11:29:31 +07:00
9389607aaf python: flake8 configuration file 2023-11-21 15:39:31 +07:00
3d63728344 Merge branch 'develop' into feature/restore-history 2023-11-21 14:21:58 +07:00
40d1c0c03b python: getting storage path to file uri from config manager 2023-11-21 14:20:35 +07:00
0c2a271357 ruby: added rubocop configuration file 2023-11-20 13:11:01 +03:00
e94d81e228 ruby: Layout/FirstMethodParameterLineBreak correct 2023-11-20 13:04:00 +03:00
4c9c79ae8e ruby: Layout/EmptyLinesAroundClassBody correct 2023-11-20 13:02:20 +03:00
d51dab82c5 ruby: Layout/EndAlignment correct 2023-11-20 13:01:55 +03:00
1e66124a8a ruby: ElseAlignment correct 2023-11-20 13:01:15 +03:00
5fd2878c3d ruby: Layout/FirstHashElementIndentation correct 2023-11-20 13:00:04 +03:00
63ec4db0b3 ruby: Lint/AmbiguousOperatorPrecedence correct 2023-11-20 12:58:23 +03:00
0d3ed49adf ruby: Layout/MultilineOperationIndentation correct 2023-11-20 12:57:33 +03:00
7197a216f8 ruby: Layout/ParameterAlignment correct 2023-11-20 12:56:29 +03:00
716d4dc694 ruby: Layout/FirstHashElementIndentation correct 2023-11-20 12:55:58 +03:00
d744bc2e23 ruby: Layout/ClosingParenthesisIndentation correct 2023-11-20 12:55:19 +03:00
a69f7f343a ruby: Layout/HashAlignment correct 2023-11-20 12:54:56 +03:00
10b40423ad ruby: Layout/CommentIndentation correct 2023-11-20 12:54:30 +03:00
4ea23d1677 ruby: Style/Lambda correct 2023-11-20 12:54:00 +03:00
e0ed22d1f5 ruby: Layout/FirstArgumentIndentation correct 2023-11-20 12:52:31 +03:00
5148fd2601 ruby: Layout/EmptyLines correct 2023-11-20 12:52:02 +03:00
97420e18cf ruby: Style/AccessModifierDeclarations correct 2023-11-20 12:51:34 +03:00
10e1848beb ruby: Style/AccessorGrouping correct 2023-11-20 12:50:59 +03:00
d4b2bf95bc ruby: Layout/LineContinuationSpacing correct 2023-11-20 12:50:33 +03:00
d613a02068 ruby: Layout/FirstHashElementLineBreak correct 2023-11-20 12:50:13 +03:00
4ff7b6e543 ruby: Layout/IndentationWidth correct 2023-11-20 12:49:36 +03:00
066de3359d ruby: Layout/ExtraSpacing correct 2023-11-20 12:49:06 +03:00
4577861f8b ruby: Layout/ClosingParenthesisIndentation correct 2023-11-20 12:48:42 +03:00
94df21fe21 ruby: Style/ClassMethodsDefinitions correct 2023-11-20 12:48:18 +03:00
af38a4ba61 ruby: Layout/LineEndStringConcatenationIndentation correct 2023-11-20 12:47:24 +03:00
7e9356df5e ruby: Layout/MultilineMethodCallBraceLayout correct 2023-11-20 12:46:53 +03:00
5f68d09e2f ruby: Layout/FirstHashElementIndentation correct 2023-11-20 12:46:25 +03:00
18aa9487b3 ruby: Layout/FirstMethodArgumentLineBreak correct 2023-11-20 12:45:27 +03:00
f00f212662 ruby: Lint/SymbolConversion correct 2023-11-20 12:44:59 +03:00
743176bb5c ruby: Layout/TrailingWhitespace correct 2023-11-20 12:44:29 +03:00
58eec51312 ruby: Layout/IndentationConsistency correct 2023-11-20 12:44:03 +03:00
1f5c23a2d1 ruby: Layout/CommentIndentation correct 2023-11-20 12:43:31 +03:00
992dbe18af ruby: Layout/ArgumentAlignment correct 2023-11-20 12:41:52 +03:00
ae738e1be2 ruby: Style/RedundantSelfAssignmentBranch correct 2023-11-20 12:41:28 +03:00
9796abf846 ruby: Layout/FirstArgumentIndentation correct 2023-11-20 12:40:48 +03:00
9471891a89 ruby: Lint/NonAtomicFileOperation correct 2023-11-20 12:40:05 +03:00
9b64957189 ruby: Layout/MultilineMethodParameterLineBreaks correct 2023-11-20 12:39:19 +03:00
6f5a854cd2 ruby: Style/WordArray correct 2023-11-20 12:38:50 +03:00
f441ab1650 ruby: Style/RedundantParentheses correct 2023-11-20 12:38:22 +03:00
fd5a2b7b0e ruby: Style/NegatedIfElseCondition correct 2023-11-20 12:37:42 +03:00
f2504ab740 ruby: Style/FileWrite correct 2023-11-20 12:36:45 +03:00
7710c6d6e8 ruby: Style/QuotedSymbols correct 2023-11-20 12:36:03 +03:00
5b99e3d17a ruby: Style/HashSyntax correct 2023-11-20 12:35:14 +03:00
955b35f382 ruby: Style/MethodCallWithArgsParentheses correct 2023-11-20 12:33:36 +03:00
8580ea8a57 ruby: Style/StringHashKeys correct 2023-11-20 12:32:54 +03:00
cba2eac65d ruby: Lint/NumberConversion correct 2023-11-20 12:32:09 +03:00
f5ccc21f69 ruby: Style/FetchEnvVar correct 2023-11-20 12:31:23 +03:00
03a585685e ruby: Layout/FirstMethodArgumentLineBreak correct 2023-11-20 12:29:36 +03:00
40c520e8fd ruby: Layout/ArgumentAlignment correct 2023-11-20 12:29:02 +03:00
940b4bfc80 ruby: Layout/TrailingWhitespace correct 2023-11-20 12:28:12 +03:00
7fe7753c88 ruby: Layout/MultilineMethodArgumentLineBreaks correct 2023-11-20 12:27:25 +03:00
8fa40ab439 ruby: Style/SymbolArray correct 2023-11-20 12:10:24 +03:00
8f4de5ce0d ruby: Naming/PredicateName correct 2023-11-20 11:53:13 +03:00
94d22003b5 ruby: Naming/MethodName correct 2023-11-20 11:50:10 +03:00
f8d62ab8db ruby: Naming/AccessorMethodName correct 2023-11-20 00:37:46 +03:00
f955096fc0 ruby: Naming/VariableNumber correct 2023-11-20 00:37:46 +03:00
9b0d3e1a20 ruby: Naming/VariableName correct 2023-11-20 00:37:39 +03:00
dafc66d5d8 ruby: Style/Documentation correct 2023-11-19 02:38:42 +03:00
902ad1341c ruby: Naming/RescuedExceptionsVariableName correct 2023-11-16 15:16:47 +03:00
1cd457c5c6 ruby: Lint/DuplicateMethods correct 2023-11-16 13:35:06 +03:00
4536f83d10 ruby: Style/OptionalBooleanParameter correct 2023-11-16 13:33:09 +03:00
acdb60dd3b ruby: Style/ClassVars correct 2023-11-16 12:21:22 +03:00
c5315de84f ruby: Lint/Void correct 2023-11-16 10:16:22 +03:00
c639ba4287 ruby: Style/NegatedIf correct 2023-11-16 10:15:11 +03:00
ea15f12551 ruby: Style/TrailingUnderscoreVariable correct 2023-11-16 09:34:57 +03:00
c0135c52c5 ruby: Layout/TrailingWhitespace correct 2023-11-16 09:34:57 +03:00
f87fc045d3 ruby: Lint/UselessAssignment correct 2023-11-16 09:34:57 +03:00
684fa89c13 ruby: Style/EmptyLineAfterMagicComment correct 2023-11-16 09:34:57 +03:00
f4d838710c ruby: Style/MultilineIfModifier correct 2023-11-16 09:34:57 +03:00
0728a71f01 ruby: Style/EmptyElse correct 2023-11-16 09:34:57 +03:00
9cbb946c8f ruby: Style/MultilineTernaryOperator correct 2023-11-16 09:34:57 +03:00
24ff9ce804 ruby: Style/NegatedIf correct 2023-11-16 09:34:57 +03:00
498268a7a3 ruby: Style/ParenthesesAroundCondition correct 2023-11-16 09:34:57 +03:00
2477fb21c5 ruby: Style/RedundantAssignment correct 2023-11-16 09:34:57 +03:00
a809af8eca ruby: Style/IfUnlessModifier correct 2023-11-16 09:34:57 +03:00
5f053de539 ruby: Style/RedundantParentheses correct 2023-11-16 09:34:57 +03:00
f47f9bf897 ruby: Style/FrozenStringLiteralComment correct 2023-11-16 09:34:57 +03:00
7d09d87891 ruby: Layout/LineLength correct 2023-11-16 09:34:57 +03:00
83843342d4 ruby: Style/HashSyntax correct 2023-11-16 09:34:57 +03:00
a407b2b9c7 ruby: Style/For correct 2023-11-16 09:34:57 +03:00
b29b0c3fa5 ruby: Style/AndOr correct 2023-11-16 09:34:57 +03:00
821df5c700 ruby: Style/RescueStandardError correct 2023-11-16 09:34:57 +03:00
56e96eeac1 ruby: Style/Redundant correct 2023-11-16 09:34:57 +03:00
6525c29107 ruby: Style/NumericPredicate correct 2023-11-16 09:34:57 +03:00
3197702d7b ruby: Style/CommentedKeyword correct 2023-11-16 09:34:57 +03:00
286fb47fb8 ruby: Lint/RedundantStringCoercion correct 2023-11-16 09:34:57 +03:00
44445de6e7 ruby: Layout/CommentIndentation correct 2023-11-16 09:34:57 +03:00
986eeefb4d ruby: Layout/EmptyLinesAroundMethodBody correct 2023-11-16 09:34:57 +03:00
12aba3d66d ruby: Layout/EmptyLines correct 2023-11-16 09:34:57 +03:00
4c92c1c750 ruby: Layout/IndentationWidth correct 2023-11-16 09:34:57 +03:00
4e685d9eab ruby: Layout/ArrayAlignment correct 2023-11-16 09:34:57 +03:00
bb4e2b3b46 ruby: Layout/ExtraSpacing correct 2023-11-16 09:34:57 +03:00
d1b972fb18 ruby: Layout/HashAlignment correct 2023-11-16 09:34:57 +03:00
a17819efb2 ruby: Layout/RescueEnsureAlignment correct 2023-11-16 09:34:57 +03:00
79b98bed53 ruby: Layout/FirstHashElementIndentation correct 2023-11-16 09:34:57 +03:00
27267e2d5b ruby: Layout/ElseAlignment correct 2023-11-16 09:34:57 +03:00
de5fc508f2 ruby: Layout/CaseIndentation correct 2023-11-16 09:34:57 +03:00
dccc970fa1 ruby: Layout/TrailingWhitespace correct 2023-11-16 09:34:57 +03:00
5259cc19fa ruby: Layout/IndentationConsistency correct 2023-11-16 09:34:57 +03:00
8527ac602f ruby: Layout/EmptyLineAfterGuardClause correct 2023-11-16 09:34:57 +03:00
6ff7b4fa8f ruby: Layout/EmptyLines correct 2023-11-16 09:34:57 +03:00
9747867935 ruby: Layout/CommentIndentation correct 2023-11-16 09:34:57 +03:00
42c2c93e81 ruby: Layout/EndAlignment correct 2023-11-16 09:34:57 +03:00
c0d8e0e339 ruby: Layout/IndentationWidth correct 2023-11-16 09:34:49 +03:00
d06bb98a61 ruby: Style/DefWithParentheses correct 2023-11-14 16:18:58 +03:00
dd3b7ab077 ruby: Style/WordArray correct 2023-11-14 16:18:25 +03:00
7d6933803f ruby: Style/TrailingCommaInArrayLiteral correct 2023-11-14 16:17:39 +03:00
26fc316cee ruby: Style/OrAssignment correct 2023-11-14 16:15:09 +03:00
bd922d202e ruby: Style/ParenthesesAroundCondition correct 2023-11-14 16:13:10 +03:00
c350dd022f ruby: Style/TrailingCommaInHashLiteral correct 2023-11-14 16:12:30 +03:00
f3cfdc353e ruby: Style/MultilineTernaryOperator correct 2023-11-14 16:11:54 +03:00
9b010f8315 ruby: Style/MethodCallWithoutArgsParentheses correct 2023-11-14 16:11:23 +03:00
5171624adc ruby: Style/IfInsideElse correct 2023-11-14 16:10:50 +03:00
3247a75318 ruby: Style/ConditionalAssignment correct 2023-11-14 16:09:49 +03:00
81a43656b2 ruby: Style/SelfAssignment correct 2023-11-14 16:08:52 +03:00
3df552b4b2 ruby: Style/BlockDelimiters correct 2023-11-14 16:08:14 +03:00
e9b4fce50b ruby: Style/GuardClause correct 2023-11-14 16:06:55 +03:00
1989c5f44d ruby: Style/AccessorGrouping correct 2023-11-14 16:05:53 +03:00
23193739f7 ruby: Style/NonNilCheck correct 2023-11-14 16:04:41 +03:00
21d75418c2 ruby: Style/MultipleComparison correct 2023-11-14 16:03:32 +03:00
b630764daf ruby: Style/NegatedIf correct 2023-11-14 16:02:59 +03:00
f9375a7414 ruby: Style/SoleNestedConditional correct 2023-11-14 15:59:48 +03:00
52d72371d5 ruby: Style/Semicolon correct 2023-11-14 15:58:46 +03:00
519f6d6f32 ruby: Style/NilComparison correct 2023-11-14 15:54:50 +03:00
eb6861059e ruby: Style/StringConcatenation correct 2023-11-14 15:00:52 +03:00
f5801a7f8c ruby: Style/IfUnlessModifier correct 2023-11-14 14:53:05 +03:00
15b905613c ruby: Style/EmptyMethod correct 2023-11-14 14:47:16 +03:00
5255f0e462 ruby: Style/SymbolArray correct 2023-11-14 14:46:08 +03:00
5e29215a11 ruby: Style/StringLiterals correct 2023-11-14 14:38:10 +03:00
de32be3945 ruby: Lint/RedundantStringCoercion correct 2023-11-14 14:30:45 +03:00
63c04c85b0 ruby: Lint/ParenthesesAsGroupedExpression correct 2023-11-14 14:01:39 +03:00
2d3fc72283 ruby: Layout/FirstArrayElementIndentation correct 2023-11-14 13:53:42 +03:00
b6e71e42c1 ruby: Layout/ParameterAlignment correct 2023-11-14 13:53:08 +03:00
dcf78b13f2 ruby: Layout/ArgumentAlignment correct 2023-11-14 13:52:39 +03:00
d8b0bd55cf ruby: Layout/FirstHashElementIndentation correct 2023-11-14 13:52:07 +03:00
8ddbb6ba18 ruby: Layout/EmptyLinesAroundBeginBody correct 2023-11-14 13:51:36 +03:00
831e59b390 ruby: Layout/EmptyLineBetweenDefs correct 2023-11-14 13:50:53 +03:00
95de0fc2c7 ruby: Layout/CaseIndentation correct 2023-11-14 13:50:09 +03:00
b48efac604 ruby: Layout/SpaceAroundOperators correct 2023-11-14 13:48:40 +03:00
c0eb1c8a85 ruby: Layout/SpaceAroundEqualsInParameterDefault correct 2023-11-14 13:48:06 +03:00
118779aac1 ruby: Layout/SpaceInsideBlockBraces correct 2023-11-14 13:47:02 +03:00
276f5c1e20 ruby: Layout/SpaceAfterMethodName correct 2023-11-14 13:42:27 +03:00
64f66d4fa3 ruby: Layout/EmptyLinesAroundClassBody correct 2023-11-14 13:41:38 +03:00
ac3e0faba8 ruby: Layout/MultilineOperationIndentation correct 2023-11-14 13:40:44 +03:00
fde6a09f75 ruby: Layout/SpaceAfterComma correct 2023-11-14 13:39:32 +03:00
f12fb576d8 ruby: Layout/SpaceInsideHashLiteralBraces correct 2023-11-14 13:34:49 +03:00
b5585cf3db ruby: Layout/LeadingCommentSpace correct 2023-11-14 13:33:31 +03:00
f1a69218ae ruby: Layout/IndentationConsistency correct 2023-11-14 13:32:08 +03:00
dbce9946e0 ruby: Layout/TrailingWhitespace correct 2023-11-14 13:29:46 +03:00
3b66092a45 ruby: Layout/IndentationWidth correct 2023-11-14 13:25:32 +03:00
1b91592a9a ruby: Layout/ExtraSpacing correct 2023-11-14 13:10:31 +03:00
e9e6586d6d ruby: Layout/EmptyLinesAroundMethodBody correct 2023-11-14 12:43:19 +03:00
266e249b12 ruby: Layout/TrailingEmptyLines correct 2023-11-14 12:37:38 +03:00
ce9d62ad7d ruby: Layout/EmptyLineAfterGuardClause correct 2023-11-14 12:31:56 +03:00
febce17e86 Merge branch 'develop' into feature/restore-history 2023-11-13 16:16:10 +07:00
63b2202f82 python: storage folder in configuration manager 2023-11-13 16:15:05 +07:00
7c57b0f575 python: code refactoring 2023-11-08 13:08:07 +07:00
0494d24e29 Merge branch 'develop' into feature/restore-history 2023-11-07 10:21:57 +07:00
9d3d5243b2 python: lint fix 2023-11-01 10:59:41 +07:00
440b51c7ff Merge branch 'develop' into feature/restore-history 2023-11-01 10:51:44 +07:00
0d3cda32fa php: code refactoring 2023-10-26 11:33:38 +07:00
e5d4be0d61 python: code refactoring 2023-10-26 11:27:01 +07:00
5f2c5cea58 ruby: code refactoring 2023-10-26 11:15:26 +07:00
71215e342f Merge remote-tracking branch 'remotes/origin/develop' into feature/restore-history
# Conflicts:
#	CHANGELOG.md
2023-10-23 13:45:19 +03:00
4b822dafe9 Merge branch 'develop' into feature/restore-history 2023-10-20 18:54:16 +07:00
39820cec0a php: getting history via api 2023-10-19 14:19:15 +07:00
e9893c7c51 python: correct url/uri fix 2023-10-19 12:42:48 +07:00
fe352ebad1 ruby: getting history via api 2023-10-19 12:15:35 +07:00
18cf21efe4 python: getting history via api 2023-10-18 11:21:58 +07:00
825aafda25 nodejs: date format fix 2023-10-13 17:31:47 +07:00
71 changed files with 2186 additions and 1600 deletions

View File

@ -21,15 +21,17 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.10'
python-version: '3.11'
- name: Install Dependencies
run: |
python -m pip install --upgrade pip
pip install flake8
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
make dev
- name: Lint Flake8
run: |
flake8 --count --select=E9,F63,F7,F82 --show-source --statistics
flake8 --count --max-complexity=15 --max-line-length=120 --per-file-ignores="__init__.py:F4" --statistics
make lint
# TODO: Configure mypy
# - name: Types mypy
# run: |
# make types

2
.gitmodules vendored
View File

@ -5,7 +5,7 @@
[submodule "web/documentserver-example/nodejs/public/assets/document-formats"]
path = web/documentserver-example/nodejs/public/assets/document-formats
url = https://github.com/ONLYOFFICE/document-formats
branch = master
branch = feature/v8.0
[submodule "web/documentserver-example/csharp-mvc/assets/document-templates"]
path = web/documentserver-example/csharp-mvc/assets/document-templates
url = https://github.com/ONLYOFFICE/document-templates

View File

@ -1,10 +1,11 @@
# Change Log
- link in referenceData
- version number to page meta
- sr-Latn-CS skin languages
- getting history via api
- using a repo with a list of formats
- ruby: convert after uploading only tagged formats
- python: convert after uploading only tagged formats
- php: convert after uploading only tagged formats
- convert after uploading only tagged formats
- link in referenceData
- setUsers for region protection
- onRequestOpen method
- user avatar
@ -13,8 +14,6 @@
- onRequestSelectSpreadsheet method
- key in referenceData
- restore from history
- java: getting history by a separate request
- java-spring: getting history by a separate request
## 1.7.0
- nodejs: onRequestSelectDocument method

View File

@ -31,6 +31,12 @@ namespace OnlineEditorsExampleMVC.Helpers
{
public class DocManagerHelper
{
//get server version
public static string GetVersion()
{
return WebConfigurationManager.AppSettings["version"];
}
// get max file size
public static long MaxFileSize
{

View File

@ -126,9 +126,7 @@ namespace OnlineEditorsExampleMVC.Models
public static List<Format> Convertible()
{
return All()
.Where(format => (format.Type == FileType.Cell && format.Convert.Contains("xlsx"))
|| (format.Type == FileType.Slide && format.Convert.Contains("pptx"))
|| (format.Type == FileType.Word && format.Convert.Contains("docx")))
.Where(format => format.Actions.Contains("auto-convert"))
.ToList();
}

View File

@ -13,6 +13,7 @@
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width" />
<meta name="server-version" content=<%= DocManagerHelper.GetVersion() %> />
<!--
*
* (c) Copyright Ascensio System SIA 2023

View File

@ -15,7 +15,7 @@
<add key="files.docservice.verify-peer-off" value="true"/>
<add key="files.docservice.languages" value="en:English|hy:Armenian|az:Azerbaijani|eu:Basque|be:Belarusian|bg:Bulgarian|ca:Catalan|zh:Chinese (Simplified)|zh-TW:Chinese (Traditional)|cs:Czech|da:Danish|nl:Dutch|fi:Finnish|fr:French|gl:Galego|de:German|el:Greek|hu:Hungarian|id:Indonesian|it:Italian|ja:Japanese|ko:Korean|lo:Lao|lv:Latvian|ms:Malay (Malaysia)|no:Norwegian|pl:Polish|pt:Portuguese (Brazil)|pt-PT:Portuguese (Portugal)|ro:Romanian|ru:Russian|si:Sinhala (Sri Lanka)|sk:Slovak|sl:Slovenian|es:Spanish|sv:Swedish|tr:Turkish|uk:Ukrainian|vi:Vietnamese|aa-AA: Test Language"/>
<add key="files.docservice.languages" value="en:English|hy:Armenian|az:Azerbaijani|eu:Basque|be:Belarusian|bg:Bulgarian|ca:Catalan|zh:Chinese (Simplified)|zh-TW:Chinese (Traditional)|cs:Czech|da:Danish|nl:Dutch|fi:Finnish|fr:French|gl:Galego|de:German|el:Greek|hu:Hungarian|id:Indonesian|it:Italian|ja:Japanese|ko:Korean|lo:Lao|lv:Latvian|ms:Malay (Malaysia)|no:Norwegian|pl:Polish|pt:Portuguese (Brazil)|pt-PT:Portuguese (Portugal)|ro:Romanian|ru:Russian|sr-Latn-CS:Serbian|si:Sinhala (Sri Lanka)|sk:Slovak|sl:Slovenian|es:Spanish|sv:Swedish|tr:Turkish|uk:Ukrainian|vi:Vietnamese|aa-AA: Test Language"/>
<add key="files.docservice.url.site" value="http://documentserver/"/>

View File

@ -13,6 +13,7 @@
<head runat="server">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width" />
<meta name="server-version" content=<%= GetVersion() %> />
<title>ONLYOFFICE</title>
<!--
*

View File

@ -33,6 +33,12 @@ namespace OnlineEditorsExample
public partial class _Default : Page
{
//get server version
public static string GetVersion()
{
return WebConfigurationManager.AppSettings["version"];
}
// get the virtual path
public static string VirtualPath
{

View File

@ -104,9 +104,7 @@ namespace OnlineEditorsExample
public static List<Format> Convertible()
{
return All()
.Where(format => (format.Type == "cell" && format.Convert.Contains("xlsx"))
|| (format.Type == "slide" && format.Convert.Contains("pptx"))
|| (format.Type == "word" && format.Convert.Contains("docx")))
.Where(format => format.Actions.Contains("auto-convert"))
.ToList();
}

View File

@ -14,7 +14,7 @@
<add key="files.docservice.token.useforrequest" value="true" />
<add key="files.docservice.languages" value="en:English|hy:Armenian|az:Azerbaijani|eu:Basque|be:Belarusian|bg:Bulgarian|ca:Catalan|zh:Chinese (Simplified)|zh-TW:Chinese (Traditional)|cs:Czech|da:Danish|nl:Dutch|fi:Finnish|fr:French|gl:Galego|de:German|el:Greek|hu:Hungarian|id:Indonesian|it:Italian|ja:Japanese|ko:Korean|lo:Lao|lv:Latvian|ms:Malay (Malaysia)|no:Norwegian|pl:Polish|pt:Portuguese (Brazil)|pt-PT:Portuguese (Portugal)|ro:Romanian|ru:Russian|si:Sinhala (Sri Lanka)|sk:Slovak|sl:Slovenian|es:Spanish|sv:Swedish|tr:Turkish|uk:Ukrainian|vi:Vietnamese|aa-AA: Test Language"/>
<add key="files.docservice.languages" value="en:English|hy:Armenian|az:Azerbaijani|eu:Basque|be:Belarusian|bg:Bulgarian|ca:Catalan|zh:Chinese (Simplified)|zh-TW:Chinese (Traditional)|cs:Czech|da:Danish|nl:Dutch|fi:Finnish|fr:French|gl:Galego|de:German|el:Greek|hu:Hungarian|id:Indonesian|it:Italian|ja:Japanese|ko:Korean|lo:Lao|lv:Latvian|ms:Malay (Malaysia)|no:Norwegian|pl:Polish|pt:Portuguese (Brazil)|pt-PT:Portuguese (Portugal)|ro:Romanian|ru:Russian|sr-Latn-CS:Serbian|si:Sinhala (Sri Lanka)|sk:Slovak|sl:Slovenian|es:Spanish|sv:Swedish|tr:Turkish|uk:Ukrainian|vi:Vietnamese|aa-AA: Test Language"/>
<add key="files.docservice.url.site" value="http://documentserver/"/>

View File

@ -76,6 +76,9 @@ public class IndexController {
@Value("${files.docservice.languages}")
private String langs;
@Value("${server.version}")
private String serverVersion;
@GetMapping("${url.index}")
public String index(@RequestParam(value = "directUrl", required = false) final Boolean directUrl,
final Model model) {
@ -124,6 +127,7 @@ public class IndexController {
model.addAttribute("users", users);
model.addAttribute("languages", languages);
model.addAttribute("directUrl", directUrl);
model.addAttribute("serverVersion", serverVersion);
return "index.html";
}

View File

@ -40,6 +40,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.nio.file.Paths;
// todo: Rebuild completely
@Component
@ -280,8 +281,9 @@ public class DefaultHistoryManager implements HistoryManager {
dataObj.put("version", i);
if (i > 1) { //check if the version number is greater than 1
Integer verdiff = i - 1;
// get the history data from the previous file version
Map<String, Object> prev = (Map<String, Object>) histData.get(Integer.toString(i - 1));
Map<String, Object> prev = (Map<String, Object>) histData.get(Integer.toString(verdiff));
Map<String, Object> prevInfo = new HashMap<String, Object>();
prevInfo.put("fileType", prev.get("fileType"));
prevInfo.put("key", prev.get("key")); // write key and URL information about previous file version
@ -292,10 +294,12 @@ public class DefaultHistoryManager implements HistoryManager {
// write information about previous file version to the data object
dataObj.put("previous", prevInfo);
// write the path to the diff.zip archive with differences in this file version
Integer verdiff = i - 1;
dataObj.put("changesUrl", documentManager
.getHistoryFileUrl(fileName, verdiff, "diff.zip", true));
if (diffExists(histDir, verdiff)) {
// write the path to the diff.zip archive with differences in this file version
dataObj.put("changesUrl", documentManager
.getHistoryFileUrl(fileName, verdiff, "diff.zip", true));
}
}
if (jwtManager.tokenEnabled()) {
@ -331,4 +335,11 @@ public class DefaultHistoryManager implements HistoryManager {
}
return output;
}
// diff.zip existence check
private Boolean diffExists(final String histDir, final Integer verdiff) {
String filePath = Paths.get(histDir, String.valueOf(verdiff), "diff.zip").toString();
File file = new File(filePath);
return file.exists();
}
}

View File

@ -25,7 +25,7 @@ files.docservice.token-use-for-request=true
files.docservice.verify-peer-off=true
files.docservice.languages=en:English|hy:Armenian|az:Azerbaijani|eu:Basque|be:Belarusian|bg:Bulgarian|ca:Catalan|zh:Chinese (Simplified)|zh-TW:Chinese (Traditional)|cs:Czech|da:Danish|nl:Dutch|fi:Finnish|fr:French|gl:Galego|de:German|el:Greek|hu:Hungarian|id:Indonesian|it:Italian|ja:Japanese|ko:Korean|lo:Lao|lv:Latvian|ms:Malay (Malaysia)|no:Norwegian|pl:Polish|pt:Portuguese (Brazil)|pt-PT:Portuguese (Portugal)|ro:Romanian|ru:Russian|si:Sinhala (Sri Lanka)|sk:Slovak|sl:Slovenian|es:Spanish|sv:Swedish|tr:Turkish|uk:Ukrainian|vi:Vietnamese|aa-AA:Test Language
files.docservice.languages=en:English|hy:Armenian|az:Azerbaijani|eu:Basque|be:Belarusian|bg:Bulgarian|ca:Catalan|zh:Chinese (Simplified)|zh-TW:Chinese (Traditional)|cs:Czech|da:Danish|nl:Dutch|fi:Finnish|fr:French|gl:Galego|de:German|el:Greek|hu:Hungarian|id:Indonesian|it:Italian|ja:Japanese|ko:Korean|lo:Lao|lv:Latvian|ms:Malay (Malaysia)|no:Norwegian|pl:Polish|pt:Portuguese (Brazil)|pt-PT:Portuguese (Portugal)|ro:Romanian|ru:Russian|sr-Latn-CS:Serbian|si:Sinhala (Sri Lanka)|sk:Slovak|sl:Slovenian|es:Spanish|sv:Swedish|tr:Turkish|uk:Ukrainian|vi:Vietnamese|aa-AA:Test Language
spring.datasource.url=jdbc:h2:mem:usersdb
spring.datasource.driverClassName=org.h2.Driver

View File

@ -3,6 +3,7 @@
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width" />
<meta name="server-version" th:content="${serverVersion}"/>
<!--
*
* (c) Copyright Ascensio System SIA 2023

View File

@ -995,9 +995,10 @@ public class IndexServlet extends HttpServlet {
dataObj.put("version", i);
if (i > 1) { //check if the version number is greater than 1
Integer verdiff = i - 1;
// get the history data from the previous file version
Map<String, Object> prev = (Map<String, Object>) histData.get(Integer.toString(i - 1));
Map<String, Object> prev = (Map<String, Object>) histData.get(Integer.toString(verdiff));
Map<String, Object> prevInfo = new HashMap<String, Object>();
prevInfo.put("fileType", prev.get("fileType"));
@ -1010,12 +1011,16 @@ public class IndexServlet extends HttpServlet {
// write information about previous file version to the data object
dataObj.put("previous", prevInfo);
// write the path to the diff.zip archive with differences in this file version
Integer verdiff = i - 1;
String changesUrl = DocumentManager
.getDownloadHistoryUrl(fileName, verdiff,
"diff.zip", true);
dataObj.put("changesUrl", changesUrl);
String diffPath = Paths.get(histDir, String.valueOf(verdiff), "diff.zip").toString();
File diffFile = new File(diffPath);
if (diffFile.exists()) {
// write the path to the diff.zip archive with differences in this file version
String changesUrl = DocumentManager
.getDownloadHistoryUrl(fileName, verdiff,
"diff.zip", true);
dataObj.put("changesUrl", changesUrl);
}
}
if (DocumentManager.tokenEnabled()) {

View File

@ -13,7 +13,7 @@ files.docservice.url.api=web-apps/apps/api/documents/api.js
files.docservice.url.preloader=web-apps/apps/api/documents/cache-scripts.html
files.docservice.url.example=
files.docservice.languages=en:English|hy:Armenian|az:Azerbaijani|eu:Basque|be:Belarusian|bg:Bulgarian|ca:Catalan|zh:Chinese (Simplified)|zh-TW:Chinese (Traditional)|cs:Czech|da:Danish|nl:Dutch|fi:Finnish|fr:French|gl:Galego|de:German|el:Greek|hu:Hungarian|id:Indonesian|it:Italian|ja:Japanese|ko:Korean|lo:Lao|lv:Latvian|ms:Malay (Malaysia)|no:Norwegian|pl:Polish|pt:Portuguese (Brazil)|pt-PT:Portuguese (Portugal)|ro:Romanian|ru:Russian|si:Sinhala (Sri Lanka)|sk:Slovak|sl:Slovenian|es:Spanish|sv:Swedish|tr:Turkish|uk:Ukrainian|vi:Vietnamese|aa-AA:Test Language
files.docservice.languages=en:English|hy:Armenian|az:Azerbaijani|eu:Basque|be:Belarusian|bg:Bulgarian|ca:Catalan|zh:Chinese (Simplified)|zh-TW:Chinese (Traditional)|cs:Czech|da:Danish|nl:Dutch|fi:Finnish|fr:French|gl:Galego|de:German|el:Greek|hu:Hungarian|id:Indonesian|it:Italian|ja:Japanese|ko:Korean|lo:Lao|lv:Latvian|ms:Malay (Malaysia)|no:Norwegian|pl:Polish|pt:Portuguese (Brazil)|pt-PT:Portuguese (Portugal)|ro:Romanian|ru:Russian|sr-Latn-CS:Serbian|si:Sinhala (Sri Lanka)|sk:Slovak|sl:Slovenian|es:Spanish|sv:Swedish|tr:Turkish|uk:Ukrainian|vi:Vietnamese|aa-AA:Test Language
files.docservice.secret=
files.docservice.header=Authorization

View File

@ -16,6 +16,7 @@
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width" />
<meta name="server-version" content="<%= ConfigManager.getProperty("version") %>" />
<!--
*
* (c) Copyright Ascensio System SIA 2023

View File

@ -55,7 +55,7 @@ String.prototype.hashCode = function hashCode() {
const len = this.length;
let ret = 0;
for (let i = 0; i < len; i++) {
ret = Math.trunc(31 * ret + this.charCodeAt(i));
ret = Math.imul(ret, 31) + this.charCodeAt(i);
}
return ret;
};
@ -100,6 +100,7 @@ app.get('/', (req, res) => { // define a handler for default page
params: req.DocManager.getCustomParams(),
users,
languages: configServer.get('languages'),
serverVersion: config.get('version'),
});
} catch (ex) {
console.log(ex); // display error message in the console
@ -993,19 +994,27 @@ app.get('/editor', (req, res) => { // define a handler for editing document
if (!canEdit && mode === 'edit') {
mode = 'view';
}
const submitForm = mode === 'fillForms' && userid === 'uid-1';
let submitForm = false;
const ext = fileUtility.getFileExtension(fileName, true);
let isForm = 'null';
if (mode === 'fillForms') {
submitForm = userid === 'uid-1';
isForm = req.DocManager.isExtendedPDFFile(fileName);
}
// file config data
const argss = {
apiUrl: siteUrl + configServer.get('apiUrl'),
file: {
name: fileName,
ext: fileUtility.getFileExtension(fileName, true),
ext: ext,
uri: url,
directUrl: !userDirectUrl ? null : directUrl,
uriUser: directUrl,
created: new Date().toDateString(),
favorite: user.favorite != null ? user.favorite : 'null',
isForm: isForm,
},
editor: {
type,

View File

@ -26,6 +26,7 @@
"storagePath": "/files",
"maxFileSize": 1073741824,
"maxNameLength": 50,
"gFormatOformPdfMetaTag": "ONLYOFFICEFORM",
"mobileRegEx": "android|avantgo|playbook|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od|ad)|iris|kindle|lge |maemo|midp|mmp|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\\/|plucker|pocket|psp|symbian|treo|up\\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino",
"token": {
"enable": false,
@ -69,6 +70,7 @@
"pt-PT": "Portuguese (Portugal)",
"ro": "Romanian",
"ru": "Russian",
"sr-Latn-CS": "Serbian",
"si": "Sinhala (Sri Lanka)",
"sk": "Slovak",
"sl": "Slovenian",

View File

@ -381,6 +381,10 @@ DocManager.prototype.getInternalExtension = function getInternalExtension(fileTy
return '.pptx';
}
if (fileType === fileUtility.fileType.pdf) { // .pptx for pdf type
return '.pdf';
}
return '.docx'; // the default value is .docx
};
@ -606,6 +610,58 @@ DocManager.prototype.getFilesInfo = function getFilesInfo(fileId) {
} return responseArray;
};
DocManager.prototype.isExtendedPDFFile = function isExtendedPDFFile(fileName) {
let filePath = this.forcesavePath(fileName, null, false);
if (filePath === '') {
filePath = this.storagePath(fileName);
}
const bufferSize = 110;
const buffer = Buffer.alloc(bufferSize);
const fd = fileSystem.openSync(filePath, 'r');
fileSystem.readSync(fd, buffer, 0, bufferSize);
const pBuffer = buffer.toString('latin1');
const indexFirst = pBuffer.indexOf('%\xCD\xCA\xD2\xA9\x0D');
if (indexFirst === -1) {
return false;
}
let pFirst = pBuffer.substring(indexFirst + 6);
if (!pFirst.startsWith('1 0 obj\x0A<<\x0A')) {
return false;
}
pFirst = pFirst.substring(11);
const indexStream = pFirst.indexOf('stream\x0D\x0A');
const indexMeta = pFirst.indexOf(configServer.get('gFormatOformPdfMetaTag'));
if (indexStream === -1 || indexMeta === -1 || indexStream < indexMeta) {
return false;
}
let pMeta = pFirst.substring(indexMeta);
pMeta = pMeta.substring(configServer.get('gFormatOformPdfMetaTag').length + 3);
let indexMetaLast = pMeta.indexOf(' ');
if (indexMetaLast === -1) {
return false;
}
pMeta = pMeta.substring(indexMetaLast + 1);
indexMetaLast = pMeta.indexOf(' ');
if (indexMetaLast === -1) {
return false;
}
return true;
};
DocManager.prototype.getInstanceId = function getInstanceId() {
return this.getServerUrl();
};

View File

@ -64,6 +64,7 @@ fileUtility.fileType = {
word: 'word',
cell: 'cell',
slide: 'slide',
pdf: 'pdf',
};
fileUtility.getSuppotredExtensions = function getSuppotredExtensions() {

View File

@ -0,0 +1 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M2 1h8l4 4v10H2V1z" fill="#fff"/><path fill-rule="evenodd" clip-rule="evenodd" d="M14 5l-4-4H2v14h12V5zm-4-5l5 5v11H1V0h9z" fill="#BFBFBF"/><path fill="#9E1919" d="M3 10h10v4H3z"/><path d="M7 7V2H3v5h4zM8 3V2h2v1H8zM8 5V4h5v1H8zM13 6H8v1h5V6zM13 8H3v1h10V8z" fill="#BFBFBF"/><path opacity=".3" d="M9 1h1v3h4l1 1H9V1z" fill="#333"/></svg>

After

Width:  |  Height:  |  Size: 441 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

View File

@ -470,6 +470,11 @@ footer table tr td:first-child {
background-image: url("../images/icon_pptx.svg");
}
.stored-edit.pdf,
.uploadFileName.pdf {
background-image: url("../images/icon_pdf.svg");
}
.stored-edit span {
font-size: 12px;
line-height: 12px;

View File

@ -6,6 +6,7 @@
"uploaded": "<%- file.created %>",
"favorite": <%- file.favorite %>
},
"isForm": <%- file.isForm %>,
"key": "<%- editor.key %>",
"permissions": {
"chat": <%- editor.chat %>,

View File

@ -5,6 +5,7 @@
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width" />
<meta name="server-version" content="<%= serverVersion %>">
<!--
*
* (c) Copyright Ascensio System SIA 2023

View File

@ -103,6 +103,11 @@ function routers()
echo json_encode($response);
return;
}
if (str_starts_with($path, '/objhistory')) {
$response = historyObj();
echo json_encode($response);
return;
}
if (str_starts_with($path, '/reference')) {
$response = reference();
$response['status'] = 'success';

View File

@ -380,6 +380,18 @@ function historyDownload()
}
}
function historyObj()
{
$input = file_get_contents('php://input');
$body = json_decode($input, true);
$fileName = $body['fileName'];
$filetype = mb_strtolower(pathinfo($fileName, PATHINFO_EXTENSION));
$docKey = getDocEditorKey($fileName);
$fileuri = fileUri($fileName, true);
$historyObject = getHistory($fileName, $filetype, $docKey, $fileuri, false);
return $historyObject;
}
/**
* Download a file
*

View File

@ -24,6 +24,11 @@ class ConfigurationManager
{
public string $version = '1.7.0';
public function getVersion(): string
{
return $this -> version;
}
public function exampleURL(): ?URL
{
$url = getenv('EXAMPLE_URL');
@ -189,6 +194,7 @@ class ConfigurationManager
'pt-PT' => 'Portuguese (Portugal)',
'ro' => 'Romanian',
'ru' => 'Russian',
'sr-Latn-CS' => 'Serbian',
'si' => 'Sinhala (Sri Lanka)',
'sk' => 'Slovak',
'sl' => 'Slovenian',

View File

@ -951,8 +951,11 @@ function getHistory($filename, $filetype, $docKey, $fileuri, $isEnableDirectUrl)
"url" => $prev["url"],
];
// write the path to the diff.zip archive with differences in this file version
$dataObj["changesUrl"] = getHistoryDownloadUrl($filename, $i - 1, "diff.zip");
$diffPath = implode(DIRECTORY_SEPARATOR, [$histDir, ($i - 1), "diff.zip"]);
if (file_exists($diffPath)) {
// write the path to the diff.zip archive with differences in this file version
$dataObj["changesUrl"] = getHistoryDownloadUrl($filename, $i - 1, "diff.zip");
}
}
$jwtManager = new JwtManager();

View File

@ -244,23 +244,9 @@ final class DocEditorView extends View
// encode the dataSpreadsheet object into the token
$dataSpreadsheet["token"] = $jwtManager->jwtEncode($dataSpreadsheet);
}
$out = getHistory($filename, $filetype, $docKey, $fileuri, $isEnableDirectUrl);
$history = $out[0];
$historyData = $out[1];
$historyLayout = "";
if ($user->id != "uid-0") {
if ($history != null && $historyData != null) {
$historyLayout .= " config.events['onRequestHistory'] = function () {
// show the document version history
docEditor.refreshHistory(".json_encode($history).");};";
$historyLayout .= " config.events['onRequestHistoryData'] = function (event) {
var ver = event.data;
var histData = ".json_encode($historyData).";".
"docEditor.setHistoryData(histData[ver - 1]);};
config.events['onRequestHistoryClose'] = function () {
document.location.reload();
};";
}
$historyLayout .= "// add mentions for not anonymous users
config.events['onRequestUsers'] = function (event) {
if (event && event.data){

View File

@ -29,6 +29,7 @@ final class IndexView extends View
{
parent::__construct($tempName);
$formatManager = new FormatManager();
$configManager = new ConfigurationManager();
$storedList = new IndexStoredListView($request);
$portalInfo = $this->getPortalInfoStyleDisplay();
@ -46,6 +47,7 @@ final class IndexView extends View
"fillFormsExtList" => implode(",", $formatManager->fillableExtensions()),
"converExtList" => implode(",", $formatManager->convertibleExtensions()),
"editedExtList" => implode(",", $formatManager->editableExtensions()),
"serverVersion" => $configManager -> getVersion(),
];
}

View File

@ -45,6 +45,7 @@
var docEditor;
var config;
let history;
var innerAlert = function (message, inEditor) {
if (console && console.log)
@ -218,6 +219,41 @@
}
};
function onRequestHistory() {
const query = new URLSearchParams(window.location.search)
const data = {
fileName: query.get('fileID')
}
const req = new XMLHttpRequest()
req.open("POST", 'objhistory')
req.setRequestHeader('Content-Type', 'application/json')
req.send(JSON.stringify(data))
req.onload = function () {
if (req.status != 200) {
response = JSON.parse(req.response)
innerAlert(response.error)
return
}
history = JSON.parse(req.response)
docEditor.refreshHistory(
{
currentVersion: history[0].currentVersion,
history: history[0].history
}
)
}
}
function onRequestHistoryData(event) {
var ver = event.data;
var histData = history[1]
docEditor.setHistoryData(histData[ver - 1])
}
function onRequestHistoryClose() {
document.location.reload()
}
function onRequestRestore(event) {
const query = new URLSearchParams(window.location.search)
const config = {config}
@ -235,7 +271,7 @@
innerAlert(response.error)
return
}
document.location.reload();
onRequestHistory()
}
}
@ -259,6 +295,9 @@
'onRequestSelectSpreadsheet': onRequestSelectSpreadsheet,
'onRequestReferenceData': onRequestReferenceData,
'onRequestRestore': onRequestRestore,
'onRequestHistoryData': onRequestHistoryData,
'onRequestHistory': onRequestHistory,
'onRequestHistoryClose': onRequestHistoryClose
"onRequestOpen": onRequestOpen,
};

View File

@ -3,6 +3,7 @@
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width" />
<meta name="server-version" content="{serverVersion}">
<title>ONLYOFFICE Document Editors</title>
<link rel="icon" href="assets/images/favicon.ico" type="image/x-icon" />

View File

@ -0,0 +1,4 @@
[flake8]
max-complexity = 15
max-line-length = 120
per-file-ignores = __init__.py:F4

View File

@ -46,8 +46,11 @@ compose-prod: # Up containers in a production environment.
up --detach
.PHONY: lint
lint: # Lint the source code for style and check for types.
@flake8
lint: # Lint the source code for style.
@flake8 --count --show-source --statistics
.PHONY: types
types: # Check the source code for types.
@mypy .
.PHONY: test

View File

@ -71,6 +71,7 @@ def routers():
path('assets', actions.assets),
path('download', actions.download),
path('downloadhistory', actions.downloadhistory),
path('historyobj', actions.history_obj),
path('edit', actions.edit),
path('files', actions.files),
path('reference', actions.reference),

View File

@ -24,6 +24,9 @@ from src.common import string
class ConfigurationManager:
version = '1.7.0'
def getVersion(self) -> str:
return self.version
def example_url(self) -> Optional[ParseResult]:
url = environ.get('EXAMPLE_URL')
if not url:
@ -151,6 +154,7 @@ class ConfigurationManager:
'pt-PT': 'Portuguese (Portugal)',
'ro': 'Romanian',
'ru': 'Russian',
'sr-Latn-CS': 'Serbian',
'si': 'Sinhala (Sri Lanka)',
'sk': 'Slovak',
'sl': 'Slovenian',

View File

@ -214,8 +214,11 @@ def getHistoryObject(storagePath, filename, docKey, docUrl, isEnableDirectUrl, r
'url': prev['url']
}
dataObj['previous'] = prevInfo # write information about previous file version to the data object
# write the path to the diff.zip archive with differences in this file version
dataObj['changesUrl'] = getPublicHistUri(filename, i - 1, "diff.zip", req)
diffPath = os.path.sep.join([histDir, str(i - 1), "diff.zip"])
if (os.path.exists(diffPath)):
# write the path to the diff.zip archive with differences in this file version
dataObj['changesUrl'] = getPublicHistUri(filename, i - 1, "diff.zip", req)
if jwtManager.isEnabled():
dataObj['token'] = jwtManager.encode(dataObj)

View File

@ -187,7 +187,6 @@ def edit(request):
ext = fileUtils.getFileExt(filename)
fileUri = docManager.getFileUri(filename, True, request)
directUrl = docManager.getDownloadUrl(filename, request, False)
docKey = docManager.generateFileKey(filename, request)
fileType = fileUtils.getFileType(filename)
@ -373,15 +372,8 @@ def edit(request):
dataDocument['token'] = jwtManager.encode(dataDocument) # encode the dataDocument object into a token
dataSpreadsheet['token'] = jwtManager.encode(dataSpreadsheet) # encode the dataSpreadsheet object into a token
# get the document history
hist = historyManager.getHistoryObject(storagePath, filename, docKey, fileUri, isEnableDirectUrl, request)
context = { # the data that will be passed to the template
'cfg': json.dumps(edConfig), # the document config in json format
# the information about the current version
'history': json.dumps(hist['history']) if 'history' in hist else None,
# the information about the previous document versions if they exist
'historyData': json.dumps(hist['historyData']) if 'historyData' in hist else None,
'fileType': fileType, # the file type of the document (text, spreadsheet or presentation)
'apiUrl': config_manager.document_server_api_url().geturl(), # the absolute URL to the api
# the image which will be inserted into the document
@ -528,6 +520,27 @@ def downloadhistory(request):
return HttpResponse(json.dumps(response), content_type='application/json', status=404)
def history_obj(request):
body = json.loads(request.body)
response = {}
file_name = None
try:
file_name = body['fileName']
except Exception:
pass
if file_name is None:
response.setdefault('error', 'File not found')
return HttpResponse(json.dumps(response), content_type='application/json', status=404)
storage_path = docManager.getStoragePath(file_name, request)
doc_key = docManager.generateFileKey(file_name, request)
file_url = docManager.getDownloadUrl(file_name, request)
response = historyManager.getHistoryObject(storage_path, file_name, doc_key, file_url, False, request)
return HttpResponse(json.dumps(response), content_type='application/json')
# referenceData
def reference(request):
response = {}

View File

@ -45,7 +45,8 @@ def default(request): # default parameters that will be passed to the template
'convExt': json.dumps(format_manager.convertible_extensions()), # file extensions that can be converted
'files': docManager.getStoredFiles(request), # information about stored files
'fillExt': json.dumps(format_manager.fillable_extensions()),
'directUrl': str(getDirectUrlParam(request)).lower
'directUrl': str(getDirectUrlParam(request)).lower,
'serverVersion': config_manager.getVersion()
}
# execute the "index.html" template with context data and return http response in json format
return render(request, 'index.html', context)

View File

@ -42,6 +42,7 @@
var docEditor;
var config;
var hist;
var innerAlert = function (message, inEditor) {
if (console && console.log)
@ -224,10 +225,39 @@
innerAlert(response.error)
return
}
document.location.reload();
onRequestHistory()
}
}
function onRequestHistory(){
const query = new URLSearchParams(window.location.search)
data = {
fileName: query.get('filename')
}
const req = new XMLHttpRequest()
req.open("POST", '/historyobj')
req.send(JSON.stringify(data))
req.onload = function () {
if (req.status != 200) {
response = JSON.parse(req.response)
innerAlert(response.error)
return
}
hist = JSON.parse(req.response)
docEditor.refreshHistory(hist.history)
}
}
function onRequestHistoryData(event) {
var ver = event.data;
var histData = hist.historyData;
docEditor.setHistoryData(histData[ver - 1]); // send the link to the document for viewing the version history
}
function onRequestHistoryClose(){
document.location.reload();
}
var connectEditor = function () {
config = {{ cfg | safe }}
@ -243,32 +273,16 @@
'onRequestInsertImage': onRequestInsertImage,
'onRequestSelectDocument': onRequestSelectDocument,
"onRequestSelectSpreadsheet": onRequestSelectSpreadsheet,
'onRequestRestore': onRequestRestore
'onRequestRestore': onRequestRestore,
'onRequestHistory': onRequestHistory,
'onRequestHistoryData': onRequestHistoryData,
'onRequestHistoryClose': onRequestHistoryClose
};
if (config.editorConfig.user.id) {
{% if history and historyData %}
// the user is trying to show the document version history
config.events['onRequestHistory'] = function () {
docEditor.refreshHistory({{ history | safe }}); // show the document version history
};
// the user is trying to click the specific document version in the document version history
config.events['onRequestHistoryData'] = function (event) {
var ver = event.data;
var histData = {{ historyData | safe }};
docEditor.setHistoryData(histData[ver - 1]); // send the link to the document for viewing the version history
};
// the user is trying to go back to the document from viewing the document version history
config.events['onRequestHistoryClose'] = function () {
document.location.reload();
};
{% endif %}
// add mentions for not anonymous users
config.events['onRequestUsers'] = function (event) {
if (event && event.data){

View File

@ -25,6 +25,7 @@
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width" />
<meta name="server-version" content="{{serverVersion}}">
<title>ONLYOFFICE Document Editors</title>
<link href="{% static "images/favicon.ico" %}" rel="shortcut icon" type="image/x-icon" />
<link rel="stylesheet" type="text/css" href="https://fonts.googleapis.com/css?family=Open+Sans:900,800,700,600,500,400,300&subset=latin,cyrillic-ext,cyrillic,latin-ext" />

View File

@ -0,0 +1,163 @@
require: rubocop-rails
AllCops:
NewCops: enable
# Bundler
Bundler/GemVersion:
Enabled: true
EnforcedStyle: required
# Layout
Layout/EmptyComment:
Enabled: true
AllowBorderComment: false
AllowMarginComment: true
Layout/EmptyLineAfterGuardClause:
Enabled: false
Layout/EndOfLine:
Enabled: true
EnforcedStyle: lf
Layout/FirstArrayElementLineBreak:
Enabled: true
AllowMultilineFinalElement: false
Layout/FirstHashElementLineBreak:
Enabled: true
AllowMultilineFinalElement: false
Layout/FirstMethodArgumentLineBreak:
Enabled: true
AllowMultilineFinalElement: false
Layout/FirstMethodParameterLineBreak:
Enabled: true
AllowMultilineFinalElement: false
Layout/LineLength:
Enabled: true
Max: 120
Layout/MultilineArrayLineBreaks:
Enabled: true
AllowMultilineFinalElement: false
Layout/MultilineAssignmentLayout:
Enabled: true
EnforcedStyle: new_line
SupportedTypes: []
Layout/MultilineHashKeyLineBreaks:
Enabled: true
AllowMultilineFinalElement: false
Layout/MultilineMethodArgumentLineBreaks:
Enabled: true
AllowMultilineFinalElement: false
Layout/MultilineMethodParameterLineBreaks:
Enabled: true
AllowMultilineFinalElement: false
Layout/SingleLineBlockChain:
Enabled: true
# Lint
Lint/NumberConversion:
Enabled: true
Lint/DuplicateBranch:
Enabled: false
# Metrics
Metrics:
Enabled: false
# Naming
Naming/InclusiveLanguage:
Enabled: true
# Style
Style/AccessModifierDeclarations:
Enabled: true
EnforcedStyle: inline
Style/AccessorGrouping:
Enabled: true
EnforcedStyle: separated
Style/ArrayCoercion:
Enabled: true
Style/ClassAndModuleChildren:
Enabled: true
EnforcedStyle: compact
Style/ClassMethodsDefinitions:
Enabled: true
Style/CollectionMethods:
Enabled: true
Style/DateTime:
Enabled: true
Style/EndlessMethod:
Enabled: true
EnforcedStyle: disallow
Style/FrozenStringLiteralComment:
Enabled: true
EnforcedStyle: always
Exclude:
- "Gemfile"
Style/IfUnlessModifier:
Enabled: false
Style/Lambda:
Enabled: true
EnforcedStyle: lambda
Style/MethodCallWithArgsParentheses:
Enabled: true
AllowedMethods: []
AllowedPatterns: []
Style/MethodCalledOnDoEndBlock:
Enabled: true
Style/MissingElse:
Enabled: true
EnforcedStyle: case
Style/MultipleComparison:
Enabled: false
Style/NumberedParameters:
Enabled: true
EnforcedStyle: disallow
Style/ParenthesesAroundCondition:
Enabled: true
AllowInMultilineConditions: true
Style/StringHashKeys:
Enabled: true
Style/SymbolArray:
Enabled: true
EnforcedStyle: brackets
Style/WordArray:
Enabled: true
EnforcedStyle: brackets

View File

@ -1,25 +1,28 @@
source "https://rubygems.org"
# frozen_string_literal: true
gem "byebug", "~> 11.1", :groups => [:development, :test]
gem "coffee-rails", "~> 5.0"
gem "dalli", "~> 3.2", :group => :development
gem "jbuilder", "~> 2.11"
gem "jquery-rails", "~> 4.5"
gem "jwt", "~> 2.7"
gem "mimemagic", github: "mimemagicrb/mimemagic", ref: "01f92d86d15d85cfd0f20dabd025dcbd36a8a60f"
gem "rack-cors", "~> 2.0"
gem "rails", "~> 7.0.8"
gem "rubocop", "~> 1.52", :group => :development
gem "sass-rails", "~> 6.0"
gem "sdoc", "~> 2.6", :group => :doc
gem "sorbet-runtime", "~> 0.5.10871"
gem "test-unit", "~> 3.6", :groups => [:development, :test]
gem "turbolinks", "~> 5.2"
gem "tzinfo-data", "~> 1.2023"
gem "uglifier", "~> 4.2"
gem "uuid", "~> 2.3"
gem "web-console", "~> 4.2", :groups => [:development, :test]
gem "webrick", "~> 1.8"
source 'https://rubygems.org'
gem 'byebug', '~> 11.1', groups: [:development, :test]
gem 'coffee-rails', '~> 5.0'
gem 'dalli', '~> 3.2', group: :development
gem 'jbuilder', '~> 2.11'
gem 'jquery-rails', '~> 4.5'
gem 'jwt', '~> 2.7'
gem 'mimemagic', github: 'mimemagicrb/mimemagic', ref: '01f92d86d15d85cfd0f20dabd025dcbd36a8a60f'
gem 'rack-cors', '~> 2.0'
gem 'rails', '~> 7.0.8'
gem 'rubocop', '~> 1.52', group: :development
gem 'rubocop-rails', '~> 2.20', group: :development
gem 'sass-rails', '~> 6.0'
gem 'sdoc', '~> 2.6', group: :doc
gem 'sorbet-runtime', '~> 0.5.10871'
gem 'test-unit', '~> 3.6', groups: [:development, :test]
gem 'turbolinks', '~> 5.2'
gem 'tzinfo-data', '~> 1.2023'
gem 'uglifier', '~> 4.2'
gem 'uuid', '~> 2.3'
gem 'web-console', '~> 4.2', groups: [:development, :test]
gem 'webrick', '~> 1.8'
# Unfortunately, Sorbet only supports Darwin and Linux-based systems.
# Additionally, it doesn't support Linux on ARM64, which may be used in a Docker
@ -27,7 +30,7 @@ gem "webrick", "~> 1.8"
#
# https://github.com/sorbet/sorbet/issues/4011
# https://github.com/sorbet/sorbet/issues/4119
install_if -> { RUBY_PLATFORM =~ /darwin/ || RUBY_PLATFORM =~ /x86_64/ } do
gem "sorbet", "~> 0.5.10871", :group => :development
gem "tapioca", "~> 0.11.6", :group => :development
install_if lambda { RUBY_PLATFORM =~ /darwin/ || RUBY_PLATFORM =~ /x86_64/ } do
gem 'sorbet', '~> 0.5.10871', group: :development
gem 'tapioca', '~> 0.11.6', group: :development
end

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'rake/testtask'
require_relative 'config/application'

View File

@ -22,6 +22,7 @@ require 'pathname'
require 'sorbet-runtime'
require 'uri'
# ConfigurationManager manages configuration settings for the application.
class ConfigurationManager
extend T::Sig
@ -35,8 +36,9 @@ class ConfigurationManager
sig { returns(T.nilable(URI::Generic)) }
def example_uri
url = ENV['EXAMPLE_URL']
url = ENV.fetch('EXAMPLE_URL', nil)
return nil if url.nil?
URI(url)
end
@ -48,8 +50,9 @@ class ConfigurationManager
sig { returns(URI::Generic) }
def document_server_private_uri
url = ENV['DOCUMENT_SERVER_PRIVATE_URL']
url = ENV.fetch('DOCUMENT_SERVER_PRIVATE_URL', nil)
return URI(url) if url
document_server_public_uri
end
@ -97,15 +100,17 @@ class ConfigurationManager
sig { returns(T::Boolean) }
def jwt_use_for_request
env = ENV['JWT_USE_FOR_REQUEST']
env = ENV.fetch('JWT_USE_FOR_REQUEST', nil)
return ActiveModel::Type::Boolean.new.cast(env) if env
true
end
sig { returns(T::Boolean) }
def ssl_verify_peer_mode_enabled
env = ENV['SSL_VERIFY_PEER_MODE_ENABLED']
env = ENV.fetch('SSL_VERIFY_PEER_MODE_ENABLED', nil)
return ActiveModel::Type::Boolean.new.cast(env) if env
false
end
@ -114,6 +119,7 @@ class ConfigurationManager
storage_path = ENV['STORAGE_PATH'] || 'storage'
storage_directory = Pathname(storage_path)
return storage_directory if storage_directory.absolute?
current_directory = Pathname(File.expand_path(__dir__))
directory = current_directory.join('..', '..', storage_directory)
directory.cleanpath
@ -121,8 +127,9 @@ class ConfigurationManager
sig { returns(Numeric) }
def maximum_file_size
env = ENV['MAXIMUM_FILE_SIZE']
return env.to_i if env
env = ENV.fetch('MAXIMUM_FILE_SIZE', nil)
return Integer(env, 10) if env
5 * 1024 * 1024
end
@ -134,46 +141,47 @@ class ConfigurationManager
sig { returns(T::Hash[String, String]) }
def languages
{
'en' => 'English',
'hy' => 'Armenian',
'az' => 'Azerbaijani',
'eu' => 'Basque',
'be' => 'Belarusian',
'bg' => 'Bulgarian',
'ca' => 'Catalan',
'zh' => 'Chinese (Simplified)',
'zh-TW' => 'Chinese (Traditional)',
'cs' => 'Czech',
'da' => 'Danish',
'nl' => 'Dutch',
'fi' => 'Finnish',
'fr' => 'French',
'gl' => 'Galego',
'de' => 'German',
'el' => 'Greek',
'hu' => 'Hungarian',
'id' => 'Indonesian',
'it' => 'Italian',
'ja' => 'Japanese',
'ko' => 'Korean',
'lo' => 'Lao',
'lv' => 'Latvian',
'ms' => 'Malay (Malaysia)',
'no' => 'Norwegian',
'pl' => 'Polish',
'pt' => 'Portuguese (Brazil)',
'pt-PT' => 'Portuguese (Portugal)',
'ro' => 'Romanian',
'ru' => 'Russian',
'si' => 'Sinhala (Sri Lanka)',
'sk' => 'Slovak',
'sl' => 'Slovenian',
'es' => 'Spanish',
'sv' => 'Swedish',
'tr' => 'Turkish',
'uk' => 'Ukrainian',
'vi' => 'Vietnamese',
'aa-AA' => 'Test Language'
en: 'English',
hy: 'Armenian',
az: 'Azerbaijani',
eu: 'Basque',
be: 'Belarusian',
bg: 'Bulgarian',
ca: 'Catalan',
zh: 'Chinese (Simplified)',
'zh-TW': 'Chinese (Traditional)',
cs: 'Czech',
da: 'Danish',
nl: 'Dutch',
fi: 'Finnish',
fr: 'French',
gl: 'Galego',
de: 'German',
el: 'Greek',
hu: 'Hungarian',
id: 'Indonesian',
it: 'Italian',
ja: 'Japanese',
ko: 'Korean',
lo: 'Lao',
lv: 'Latvian',
ms: 'Malay (Malaysia)',
no: 'Norwegian',
pl: 'Polish',
pt: 'Portuguese (Brazil)',
'pt-PT': 'Portuguese (Portugal)',
ro: 'Romanian',
ru: 'Russian',
'sr-Latn-CS': 'Serbian',
si: 'Sinhala (Sri Lanka)',
sk: 'Slovak',
sl: 'Slovenian',
es: 'Spanish',
sv: 'Swedish',
tr: 'Turkish',
uk: 'Ukrainian',
vi: 'Vietnamese',
'aa-AA': 'Test Language'
}
end
end

View File

@ -20,6 +20,7 @@
require 'test/unit'
require_relative 'configuration'
# Enviroment module provides a mechanism for capturing and restoring the environment.
module Enviroment
def initialize(name)
@env = ENV.to_hash
@ -27,10 +28,11 @@ module Enviroment
end
def setup
ENV.replace @env
ENV.replace(@env)
end
end
# For testing the ConfigurationManager class.
class ConfigurationManagerTests < Test::Unit::TestCase
def test_corresponds_the_latest_version
config_manager = ConfigurationManager.new
@ -38,6 +40,7 @@ class ConfigurationManagerTests < Test::Unit::TestCase
end
end
# For testing the example_uri method of ConfigurationManager.
class ConfigurationManagerExampleURITests < Test::Unit::TestCase
include Enviroment
@ -55,6 +58,7 @@ class ConfigurationManagerExampleURITests < Test::Unit::TestCase
end
end
# For testing the document_server_public_uri method of ConfigurationManager.
class ConfigurationManagerDocumentServerPublicURITests < Test::Unit::TestCase
include Enviroment
@ -72,6 +76,7 @@ class ConfigurationManagerDocumentServerPublicURITests < Test::Unit::TestCase
end
end
# For testing the document_server_private_uri method of ConfigurationManager.
class ConfigurationManagerDocumentServerPrivateURITests < Test::Unit::TestCase
include Enviroment
@ -89,6 +94,7 @@ class ConfigurationManagerDocumentServerPrivateURITests < Test::Unit::TestCase
end
end
# For testing the document_server_api_uri method of ConfigurationManager.
class ConfigurationManagerDocumentServerAPIURITests < Test::Unit::TestCase
include Enviroment
@ -112,6 +118,7 @@ class ConfigurationManagerDocumentServerAPIURITests < Test::Unit::TestCase
end
end
# For testing the document_server_preloader_uri method of ConfigurationManager.
class ConfigurationManagerDocumentServerPreloaderURITests < Test::Unit::TestCase
include Enviroment
@ -135,6 +142,7 @@ class ConfigurationManagerDocumentServerPreloaderURITests < Test::Unit::TestCase
end
end
# For testing the document_server_command_uri method of ConfigurationManager.
class ConfigurationManagerDocumentServerCommandURITests < Test::Unit::TestCase
include Enviroment
@ -158,6 +166,7 @@ class ConfigurationManagerDocumentServerCommandURITests < Test::Unit::TestCase
end
end
# For testing the document_server_converter_uri method of ConfigurationManager.
class ConfigurationManagerDocumentServerConverterURITests < Test::Unit::TestCase
include Enviroment
@ -181,6 +190,7 @@ class ConfigurationManagerDocumentServerConverterURITests < Test::Unit::TestCase
end
end
# For testing the jwt_secret method of ConfigurationManager.
class ConfigurationManagerJWTSecretTests < Test::Unit::TestCase
include Enviroment
@ -198,6 +208,7 @@ class ConfigurationManagerJWTSecretTests < Test::Unit::TestCase
end
end
# For testing the jwt_header method of ConfigurationManager.
class ConfigurationManagerJWTHeaderTests < Test::Unit::TestCase
include Enviroment
@ -215,6 +226,7 @@ class ConfigurationManagerJWTHeaderTests < Test::Unit::TestCase
end
end
# For testing the jwt_use_for_request method of ConfigurationManager.
class ConfigurationManagerJWTUseForRequest < Test::Unit::TestCase
include Enviroment
@ -232,6 +244,7 @@ class ConfigurationManagerJWTUseForRequest < Test::Unit::TestCase
end
end
# For testing the ssl_verify_peer_mode_enabled method of ConfigurationManager.
class ConfigurationManagerSSLTests < Test::Unit::TestCase
include Enviroment
@ -249,6 +262,7 @@ class ConfigurationManagerSSLTests < Test::Unit::TestCase
end
end
# For testing the storage_path method of ConfigurationManager.
class ConfigurationManagerStoragePathTests < Test::Unit::TestCase
include Enviroment
@ -275,6 +289,7 @@ class ConfigurationManagerStoragePathTests < Test::Unit::TestCase
end
end
# For testing the maximum_file_size method of ConfigurationManager.
class ConfigurationManagerMaximumFileSizeTests < Test::Unit::TestCase
include Enviroment
@ -292,6 +307,7 @@ class ConfigurationManagerMaximumFileSizeTests < Test::Unit::TestCase
end
end
# For testing the convertation_timeout method of ConfigurationManager.
class ConfigurationManagerConversionTimeoutTests < Test::Unit::TestCase
def test_assigns_a_default_value
config_manager = ConfigurationManager.new

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
#
# (c) Copyright Ascensio System SIA 2023
#
@ -15,4 +17,4 @@
#
class ApplicationController < ActionController::Base
end
end

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
#
# (c) Copyright Ascensio System SIA 2023
#
@ -19,6 +21,7 @@ require 'net/http'
require 'mimemagic'
require_relative '../configuration/configuration'
# Handling requests controller
class HomeController < ApplicationController
@config_manager = ConfigurationManager.new
@ -26,30 +29,33 @@ class HomeController < ApplicationController
attr_reader :config_manager
end
def index
end
def index; end
def editor
DocumentHelper.init(request.remote_ip, request.base_url)
user = Users.get_user(params[:userId])
@file = FileModel.new(:file_name => File.basename(params[:fileName]), :mode => params[:editorsMode], :type => params[:editorsType], :user_ip => request.remote_ip, :lang => cookies[:ulang], :user => user, :action_data => params[:actionLink], :direct_url => params[:directUrl])
@file = FileModel.new(
file_name: File.basename(params[:fileName]),
mode: params[:editorsMode],
type: params[:editorsType],
user_ip: request.remote_ip,
lang: cookies[:ulang],
user:,
action_data: params[:actionLink],
direct_url: params[:directUrl]
)
end
# creating a sample document
def sample
DocumentHelper.init(request.remote_ip, request.base_url)
user = Users.get_user(params[:userId])
file_name = DocumentHelper.create_demo(params[:fileExt], params[:sample], user)
redirect_to :controller => 'home', :action => 'editor', :fileName => file_name, :userId => user.id
redirect_to(controller: 'home', action: 'editor', fileName: file_name, userId: user.id)
end
# uploading a file
def upload
DocumentHelper.init(request.remote_ip, request.base_url)
begin
@ -58,187 +64,196 @@ class HomeController < ApplicationController
cur_size = http_posted_file.size
# check if the file size exceeds the maximum file size
if DocumentHelper.file_size_max < cur_size || cur_size <= 0
raise 'File size is incorrect'
end
raise('File size is incorrect') if DocumentHelper.file_size_max < cur_size || cur_size <= 0
cur_ext = File.extname(file_name).downcase
# check if the file extension is supported by the editor
unless DocumentHelper.file_exts.include? cur_ext
raise 'File type is not supported'
end
raise('File type is not supported') unless DocumentHelper.file_exts.include?(cur_ext)
# get the correct file name if such a name already exists
file_name = DocumentHelper.get_correct_name(file_name, nil)
document_type = FileUtility.get_file_type(file_name)
# write the uploaded file to the storage directory
File.open(DocumentHelper.storage_path(file_name, nil), 'wb') do |file|
file.write(http_posted_file.read)
end
File.binwrite(DocumentHelper.storage_path(file_name, nil), http_posted_file.read)
# create file meta information
user = Users.get_user(params[:userId])
DocumentHelper.create_meta(file_name, user.id, user.name, nil)
render plain: '{ "filename": "' + file_name + '", "documentType": "' + document_type + '"}' # write a new file name to the response
rescue => ex
render plain: '{ "error": "' + ex.message + '"}' # write an error message to the response
# write a new file name to the response
render(plain: "{ \"filename\": \"#{file_name}\", \"documentType\": \"#{document_type}\"}")
rescue StandardError => e
render(plain: "{ \"error\": \"#{e.message}\"}") # write an error message to the response
end
end
# converting a file
def convert
file_data = request.body.read
return '' if file_data.blank?
begin
file_data = request.body.read
if file_data == nil || file_data.empty?
return ""
body = JSON.parse(file_data)
file_name = File.basename(body['filename'])
lang = cookies[:ulang] || 'en'
file_pass = body['filePass'] || nil
file_uri = DocumentHelper.get_download_url(file_name)
extension = File.extname(file_name).downcase
internal_extension = 'ooxml'
if DocumentHelper.convert_exts.include?(extension) # check if the file with such an extension can be converted
key = ServiceConverter.generate_revision_id(file_uri) # generate document key
percent, new_file_uri, new_file_type = ServiceConverter.get_converted_data(
file_uri,
extension.delete('.'),
internal_extension.delete('.'),
key,
true,
file_pass,
lang
) # get the url and file type of the converted file and the conversion percentage
# if the conversion isn't completed, write file name and step values to the response
if percent != 100
render(plain: "{ \"step\" : \"#{percent}\", \"filename\" : \"#{file_name}\"}")
return
end
body = JSON.parse(file_data)
file_name = File.basename(body["filename"])
lang = cookies[:ulang] ? cookies[:ulang] : "en"
file_pass = body["filePass"] ? body["filePass"] : nil
file_uri = DocumentHelper.get_download_url(file_name)
extension = File.extname(file_name).downcase
internal_extension = 'ooxml'
# get the correct file name if such a name already exists
correct_name = DocumentHelper.get_correct_name("#{File.basename(file_name, extension)}.#{new_file_type}", nil)
if DocumentHelper.convert_exts.include? (extension) # check if the file with such an extension can be converted
key = ServiceConverter.generate_revision_id(file_uri) # generate document key
percent, new_file_uri, new_file_type = ServiceConverter.get_converted_data(file_uri, extension.delete('.'), internal_extension.delete('.'), key, true, file_pass, lang) # get the url and file type of the converted file and the conversion percentage
uri = URI.parse(new_file_uri) # create the request url
http = Net::HTTP.new(uri.host, uri.port) # create a connection to the http server
# if the conversion isn't completed, write file name and step values to the response
if percent != 100
render plain: '{ "step" : "' + percent.to_s + '", "filename" : "' + file_name + '"}'
return
end
DocumentHelper.verify_ssl(new_file_uri, http)
# get the correct file name if such a name already exists
correct_name = DocumentHelper.get_correct_name(File.basename(file_name, extension) + "." + new_file_type, nil)
req = Net::HTTP::Get.new(uri.request_uri) # create the get requets
res = http.request(req)
data = res.body
uri = URI.parse(new_file_uri) # create the request url
http = Net::HTTP.new(uri.host, uri.port) # create a connection to the http server
raise('stream is null') if data.nil?
DocumentHelper.verify_ssl(new_file_uri, http)
# write a file with a new extension, but with the content from the origin file
File.binwrite(DocumentHelper.storage_path(correct_name, nil), data)
req = Net::HTTP::Get.new(uri.request_uri) # create the get requets
res = http.request(req)
data = res.body
old_storage_path = DocumentHelper.storage_path(file_name, nil)
FileUtils.rm_f(old_storage_path)
if data == nil
raise 'stream is null'
end
file_name = correct_name
user = Users.get_user(params[:userId])
# write a file with a new extension, but with the content from the origin file
File.open(DocumentHelper.storage_path(correct_name, nil), 'wb') do |file|
file.write(data)
end
old_storage_path = DocumentHelper.storage_path(file_name, nil)
if File.exist?(old_storage_path)
File.delete(old_storage_path)
end
file_name = correct_name
user = Users.get_user(params[:userId])
DocumentHelper.create_meta(file_name, user.id, user.name, nil) # create meta data of the new file
end
render plain: '{ "filename" : "' + file_name + '"}'
rescue => ex
render plain: '{ "error": "' + ex.message + '"}'
DocumentHelper.create_meta(file_name, user.id, user.name, nil) # create meta data of the new file
end
render(plain: "{ \"filename\" : \"#{file_name}\"}")
rescue StandardError => e
render(plain: "{ \"error\": \"#{e.message}\"}")
end
def historyobj
data = request.body.read
if data.blank?
return ''
end
file_data = JSON.parse(data)
file = FileModel.new(
file_name: File.basename(file_data['file_name']),
mode: file_data['mode'],
type: file_data['type'],
user_ip: file_data['user_ip'],
lang: file_data['lang'],
user: file_data['user'],
action_data: file_data['action_data'],
direct_url: file_data['direct_url']
)
history = file.get_history
render(json: history)
rescue StandardError
render(json: '{ "error": "File not found"}')
end
# downloading a history file from public
def downloadhistory
begin
file_name = File.basename(params[:fileName])
user_address = params[:userAddress]
version = params[:ver]
file = params[:file]
isEmbedded = params[:dmode]
file_name = File.basename(params[:fileName])
user_address = params[:userAddress]
version = params[:ver]
file = params[:file]
params[:dmode]
if JwtHelper.is_enabled && JwtHelper.use_for_request
jwtHeader = HomeController.config_manager.jwt_header;
if request.headers[jwtHeader]
hdr = request.headers[jwtHeader]
hdr.slice!(0, "Bearer ".length)
token = JwtHelper.decode(hdr)
if !token || token.eql?("")
render plain: "JWT validation failed", :status => 403
return
end
else
render plain: "JWT validation failed", :status => 403
if JwtHelper.enabled? && JwtHelper.use_for_request
jwt_header = HomeController.config_manager.jwt_header
if request.headers[jwt_header]
hdr = request.headers[jwt_header]
hdr.slice!(0, 'Bearer '.length)
token = JwtHelper.decode(hdr)
if !token || token.eql?('')
render(plain: 'JWT validation failed', status: :forbidden)
return
end
else
render(plain: 'JWT validation failed', status: :forbidden)
return
end
hist_path = DocumentHelper.storage_path(file_name, user_address) + "-hist" # or to the original document
file_path = File.join(hist_path, version, file)
# add headers to the response to specify the page parameters
response.headers['Content-Length'] = File.size(file_path).to_s
response.headers['Content-Type'] = MimeMagic.by_path(file_path).eql?(nil) ? nil : MimeMagic.by_path(file_path).type
response.headers['Content-Disposition'] = "attachment;filename*=UTF-8\'\'" + ERB::Util.url_encode(file)
send_file file_path, :x_sendfile => true
rescue => ex
render plain: '{ "error": "File not found"}'
end
hist_path = "#{DocumentHelper.storage_path(file_name, user_address)}-hist" # or to the original document
file_path = File.join(hist_path, version, file)
# add headers to the response to specify the page parameters
response.headers['Content-Length'] = File.size(file_path).to_s
response.headers['Content-Type'] =
MimeMagic.by_path(file_path).eql?(nil) ? nil : MimeMagic.by_path(file_path).type
response.headers['Content-Disposition'] = "attachment;filename*=UTF-8''#{ERB::Util.url_encode(file)}"
send_file(file_path, x_sendfile: true)
rescue StandardError
render(plain: '{ "error": "File not found"}')
end
# tracking file changes
def track
file_data = TrackHelper.read_body(request) # read the request body
if file_data == nil || file_data.empty?
render plain: '{"error":1}' # an error occurs if the file is empty
file_data = TrackHelper.read_body(request) # read the request body
if file_data.blank?
render(plain: '{"error":1}') # an error occurs if the file is empty
return
end
status = file_data['status'].to_i
status = file_data['status']
user_address = params[:userAddress]
file_name = File.basename(params[:fileName])
if status == 1 # editing
if file_data['actions'][0]['type'] == 0 # finished edit
user = file_data['actions'][0]['userid'] # get the user id
if !file_data['users'].index(user)
json_data = TrackHelper.command_request("forcesave", file_data['key']) # call the forcesave command
end
if status == 1 && (file_data['actions'][0]['type']).zero? # finished edit
user = file_data['actions'][0]['userid'] # get the user id
unless file_data['users'].index(user)
TrackHelper.command_request('forcesave', file_data['key']) # call the forcesave command
end
end
if status == 2 || status == 3 # MustSave, Corrupted
saved = TrackHelper.process_save(file_data, file_name, user_address) # save file
render plain: '{"error":' + saved.to_s + '}'
if [2, 3].include?(status) # MustSave, Corrupted
saved = TrackHelper.process_save(file_data, file_name, user_address) # save file
render(plain: "{\"error\":#{saved}}")
return
end
if status == 6 || status == 7 # MustForceave, CorruptedForcesave
saved = TrackHelper.process_force_save(file_data, file_name, user_address) # force save file
render plain: '{"error":' + saved.to_s + '}'
if [6, 7].include?(status) # MustForceave, CorruptedForcesave
saved = TrackHelper.process_force_save(file_data, file_name, user_address) # force save file
render(plain: "{\"error\":#{saved}}")
return
end
render plain: '{"error":0}'
return
render(plain: '{"error":0}')
nil
end
# removing a file
def remove
file_name = File.basename(params[:filename]) # get the file name
if !file_name # if it doesn't exist
render plain: '{"success":false}' # report that the operation is unsuccessful
file_name = File.basename(params[:filename]) # get the file name
unless file_name # if it doesn't exist
render(plain: '{"success":false}') # report that the operation is unsuccessful
return
end
@ -246,36 +261,34 @@ class HomeController < ApplicationController
storage_path = DocumentHelper.storage_path(file_name, nil)
hist_dir = DocumentHelper.history_dir(storage_path)
if File.exist?(storage_path) # if the file exists
File.delete(storage_path) # delete it from the storage path
end
# if the file exists
FileUtils.rm_f(storage_path) # delete it from the storage path
if Dir.exist?(hist_dir) # if the history directory of this file exists
FileUtils.remove_entry_secure(hist_dir) # delete it
end
# if the history directory of this file exists
FileUtils.rm_rf(hist_dir) # delete it
render plain: '{"success":true}' # report that the operation is successful
return
render(plain: '{"success":true}') # report that the operation is successful
nil
end
# getting files information
def files
file_id = params[:fileId]
filesInfo = DocumentHelper.get_files_info(file_id) # get the information about the file specified by a file id
render json: filesInfo
files_info = DocumentHelper.get_files_info(file_id) # get the information about the file specified by a file id
render(json: files_info)
end
# downloading a csv file
def csv
file_name = "csv.csv"
csvPath = Rails.root.join('assets', 'document-templates', 'sample', file_name)
file_name = 'csv.csv'
csv_path = Rails.root.join('assets', 'document-templates', 'sample', file_name)
# add headers to the response to specify the page parameters
response.headers['Content-Length'] = File.size(csvPath).to_s
response.headers['Content-Type'] = MimeMagic.by_path(csvPath).type
response.headers['Content-Disposition'] = "attachment;filename*=UTF-8\'\'" + ERB::Util.url_encode(file_name)
response.headers['Content-Length'] = File.size(csv_path).to_s
response.headers['Content-Type'] = MimeMagic.by_path(csv_path).type
response.headers['Content-Disposition'] = "attachment;filename*=UTF-8''#{ERB::Util.url_encode(file_name)}"
send_file csvPath, :x_sendfile => true
send_file(csv_path, x_sendfile: true)
end
# downloading an assets file
@ -285,180 +298,173 @@ class HomeController < ApplicationController
response.headers['Content-Length'] = File.size(asset_path).to_s
response.headers['Content-Type'] = MimeMagic.by_path(asset_path).type
response.headers['Content-Disposition'] = "attachment;filename*=UTF-8\'\'" + ERB::Util.url_encode(file_name)
response.headers['Content-Disposition'] = "attachment;filename*=UTF-8''#{ERB::Util.url_encode(file_name)}"
send_file asset_path, :x_sendfile => true
send_file(asset_path, x_sendfile: true)
end
# downloading a file
def download
begin
file_name = File.basename(params[:fileName])
user_address = params[:userAddress]
isEmbedded = params[:dmode]
file_name = File.basename(params[:fileName])
user_address = params[:userAddress]
is_embedded = params[:dmode]
if JwtHelper.is_enabled && isEmbedded == nil && user_address != nil && JwtHelper.use_for_request
jwtHeader = HomeController.config_manager.jwt_header;
if request.headers[jwtHeader]
hdr = request.headers[jwtHeader]
hdr.slice!(0, "Bearer ".length)
token = JwtHelper.decode(hdr)
end
if !token || token.eql?("")
render plain: "JWT validation failed", :status => 403
return
end
if JwtHelper.enabled? && is_embedded.nil? && !user_address.nil? && JwtHelper.use_for_request
jwt_header = HomeController.config_manager.jwt_header
if request.headers[jwt_header]
hdr = request.headers[jwt_header]
hdr.slice!(0, 'Bearer '.length)
token = JwtHelper.decode(hdr)
end
file_path = DocumentHelper.forcesave_path(file_name, user_address, false) # get the path to the force saved document version
if file_path.eql?("")
file_path = DocumentHelper.storage_path(file_name, user_address) # or to the original document
if !token || token.eql?('')
render(plain: 'JWT validation failed', status: :forbidden)
return
end
# add headers to the response to specify the page parameters
response.headers['Content-Length'] = File.size(file_path).to_s
response.headers['Content-Type'] = MimeMagic.by_path(file_path).eql?(nil) ? nil : MimeMagic.by_path(file_path).type
response.headers['Content-Disposition'] = "attachment;filename*=UTF-8\'\'" + ERB::Util.url_encode(file_name)
send_file file_path, :x_sendfile => true
rescue => ex
render plain: '{ "error": "File not found"}'
end
# get the path to the force saved document version
file_path = DocumentHelper.forcesave_path(file_name, user_address, false)
if file_path.eql?('')
file_path = DocumentHelper.storage_path(file_name, user_address) # or to the original document
end
# add headers to the response to specify the page parameters
response.headers['Content-Length'] = File.size(file_path).to_s
response.headers['Content-Type'] =
MimeMagic.by_path(file_path).eql?(nil) ? nil : MimeMagic.by_path(file_path).type
response.headers['Content-Disposition'] = "attachment;filename*=UTF-8''#{ERB::Util.url_encode(file_name)}"
send_file(file_path, x_sendfile: true)
rescue StandardError
render(plain: '{ "error": "File not found"}')
end
# Save Copy as...
def saveas
begin
body = JSON.parse(request.body.read)
file_url = body["url"]
title = body["title"]
file_name = DocumentHelper.get_correct_name(title, nil)
extension = File.extname(file_name).downcase
all_exts = DocumentHelper.convert_exts + DocumentHelper.edited_exts + DocumentHelper.viewed_exts + DocumentHelper.fill_forms_exts
body = JSON.parse(request.body.read)
file_url = body['url']
title = body['title']
file_name = DocumentHelper.get_correct_name(title, nil)
extension = File.extname(file_name).downcase
all_exts = DocumentHelper.convert_exts +
DocumentHelper.edited_exts +
DocumentHelper.viewed_exts +
DocumentHelper.fill_forms_exts
unless all_exts.include?(extension)
render plain: '{"error": "File type is not supported"}'
return
end
uri = URI.parse(file_url) # create the request url
http = Net::HTTP.new(uri.host, uri.port) # create a connection to the http server
DocumentHelper.verify_ssl(file_url, http)
req = Net::HTTP::Get.new(uri.request_uri) # create the get requets
res = http.request(req)
data = res.body
if data.size <= 0 || data.size > HomeController.config_manager.maximum_file_size
render plain: '{"error": "File size is incorrect"}'
return
end
File.open(DocumentHelper.storage_path(file_name, nil), 'wb') do |file|
file.write(data)
end
user = Users.get_user(params[:userId])
DocumentHelper.create_meta(file_name, user.id, user.name, nil) # create meta data of the new file
render plain: '{"file" : "' + file_name + '"}'
return
rescue => ex
render plain: '{"error":1, "message": "' + ex.message + '"}'
unless all_exts.include?(extension)
render(plain: '{"error": "File type is not supported"}')
return
end
uri = URI.parse(file_url) # create the request url
http = Net::HTTP.new(uri.host, uri.port) # create a connection to the http server
DocumentHelper.verify_ssl(file_url, http)
req = Net::HTTP::Get.new(uri.request_uri) # create the get requets
res = http.request(req)
data = res.body
if data.size <= 0 || data.size > HomeController.config_manager.maximum_file_size
render(plain: '{"error": "File size is incorrect"}')
return
end
File.binwrite(DocumentHelper.storage_path(file_name, nil), data)
user = Users.get_user(params[:userId])
DocumentHelper.create_meta(file_name, user.id, user.name, nil) # create meta data of the new file
render(plain: "{\"file\" : \"#{file_name}\"}")
nil
rescue StandardError => e
render(plain: "{\"error\":1, \"message\": \"#{e.message}\"}")
nil
end
# Rename...
def rename
body = JSON.parse(request.body.read)
dockey = body["dockey"]
newfilename = body["newfilename"]
# Rename...
def rename
body = JSON.parse(request.body.read)
dockey = body['dockey']
newfilename = body['newfilename']
orig_ext = '.' + body["ext"]
cur_ext = File.extname(newfilename).downcase
if orig_ext != cur_ext
newfilename += orig_ext
orig_ext = ".#{body['ext']}"
cur_ext = File.extname(newfilename).downcase
newfilename += orig_ext if orig_ext != cur_ext
meta = {
title: newfilename
}
json_data = TrackHelper.command_request('meta', dockey, meta)
render(plain: "{ \"result\" : \"#{JSON.dump(json_data)}\"}")
end
# ReferenceData
def reference
body = JSON.parse(request.body.read)
file_name = ''
if body.key?('referenceData')
reference_data = body['referenceData']
instance_id = reference_data['instanceId']
if instance_id == DocumentHelper.get_server_url(false)
file_key = JSON.parse(reference_data['fileKey'])
user_address = file_key['userAddress']
file_name = file_key['fileName'] if user_address == DocumentHelper.cur_user_host_address(nil)
end
meta = {
:title => newfilename
}
json_data = TrackHelper.command_request("meta", dockey, meta)
render plain: '{ "result" : "' + JSON.dump(json_data) + '"}'
end
#ReferenceData
def reference
body = JSON.parse(request.body.read)
fileName = ""
if body.key?("referenceData")
referenceData = body["referenceData"]
instanceId = referenceData["instanceId"]
if instanceId == DocumentHelper.get_server_url(false)
fileKey = JSON.parse(referenceData["fileKey"])
userAddress = fileKey["userAddress"]
if userAddress == DocumentHelper.cur_user_host_address(nil)
fileName = fileKey["fileName"]
end
end
end
link = body["link"]
if fileName.empty? and body.key?("link")
if !link.include?(DocumentHelper.get_server_url(false))
data = {
url: link,
directUrl: link
}
render plain: data.to_json
return
end
url_obj = URI(link)
query_params = CGI.parse(url_obj.query)
fileName = query_params['fileName'].first
if !File.exist?(DocumentHelper.storage_path(fileName, nil))
render plain: '{ "error": "File is not exist"}'
return
end
end
if fileName.empty? and body.key?("path")
path = File.basename(body["path"])
if File.exist?(DocumentHelper.storage_path(path, nil))
fileName = path
end
end
if fileName.empty?
render plain: '{ "error": "File not found"}'
link = body['link']
if file_name.empty? && body.key?('link')
unless link.include?(DocumentHelper.get_server_url(false))
data = {
url: link,
directUrl: link
}
render(plain: data.to_json)
return
end
data = {
:fileType => File.extname(fileName).downcase.delete("."),
:key => ServiceConverter.generate_revision_id("#{DocumentHelper.cur_user_host_address(nil) + '/' + fileName}.#{File.mtime(DocumentHelper.storage_path(fileName, nil)).to_s}"),
:url => DocumentHelper.get_download_url(fileName),
:directUrl => body["directUrl"] ? DocumentHelper.get_download_url(fileName, false) : nil,
:referenceData => {
:instanceId => DocumentHelper.get_server_url(false),
:fileKey => {:fileName => fileName,:userAddress => DocumentHelper.cur_user_host_address(nil)}.to_json
},
:path => fileName,
:link => DocumentHelper.get_server_url(false) + '/editor?fileName=' + fileName
}
if JwtHelper.is_enabled
data["token"] = JwtHelper.encode(data)
url_obj = URI(link)
query_params = CGI.parse(url_obj.query)
file_name = query_params['fileName'].first
unless File.exist?(DocumentHelper.storage_path(file_name, nil))
render(plain: '{ "error": "File is not exist"}')
return
end
render plain: data.to_json
end
if file_name.empty? && body.key?('path')
path = File.basename(body['path'])
file_name = path if File.exist?(DocumentHelper.storage_path(path, nil))
end
if file_name.empty?
render(plain: '{ "error": "File not found"}')
return
end
data = {
fileType: File.extname(file_name).downcase.delete('.'),
key: ServiceConverter.generate_revision_id(
"#{DocumentHelper.cur_user_host_address(nil)}/#{file_name}" \
".#{File.mtime(DocumentHelper.storage_path(file_name, nil))}"
),
url: DocumentHelper.get_download_url(file_name),
directUrl: body['directUrl'] ? DocumentHelper.get_download_url(file_name, is_serverUrl: false) : nil,
referenceData: {
instanceId: DocumentHelper.get_server_url(false),
fileKey: { fileName: file_name, userAddress: DocumentHelper.cur_user_host_address(nil) }.to_json
},
path: file_name,
link: "#{DocumentHelper.get_server_url(false)}/editor?fileName=#{file_name}"
}
data['token'] = JwtHelper.encode(data) if JwtHelper.enabled?
render(plain: data.to_json)
end
def restore
body = JSON.parse(request.body.read)
@ -472,7 +478,7 @@ class HomeController < ApplicationController
DocumentHelper.init(request.remote_ip, request.base_url)
file_model = FileModel.new(
{
'file_name': source_basename
file_name: source_basename
}
)
@ -497,13 +503,13 @@ class HomeController < ApplicationController
bumped_changes_file = bumped_version_directory.join('changes.json')
bumped_changes = {
'serverVersion': nil,
'changes': [
serverVersion: nil,
changes: [
{
'created': Time.now.to_formatted_s(:db),
'user': {
'id': user.id,
'name': user.name
created: Time.zone.now.to_fs(:db),
user: {
id: user.id,
name: user.name
}
}
]
@ -515,15 +521,19 @@ class HomeController < ApplicationController
FileUtils.cp(source_file, bumped_file)
FileUtils.cp(recovery_file, source_file)
render json: {
error: nil,
success: true
}
rescue => error
render(
json: {
error: nil,
success: true
}
)
rescue StandardError => e
response.status = :internal_server_error
render json: {
error: error.message,
success: false
}
render(
json: {
error: e.message,
success: false
}
)
end
end

View File

@ -20,6 +20,7 @@
require 'pathname'
require 'sorbet-runtime'
# Struct representing a document format with properties.
class Format < T::Struct
extend T::Sig
@ -40,6 +41,7 @@ class Format < T::Struct
end
end
# FormatManager is responsible for managing document formats and providing various lists of supported extensions.
class FormatManager
extend T::Sig
@ -76,7 +78,7 @@ class FormatManager
def editable
all.filter do |format|
format.actions.include?('edit') ||
format.actions.include?('lossy-edit')
format.actions.include?('lossy-edit')
end
end
@ -136,6 +138,7 @@ class FormatManager
sig { returns(T::Array[Format]) }
def all
return @all if defined?(@all)
content = file.read
hash = JSON.parse(content)
@all ||= hash.map do |item|
@ -143,15 +146,13 @@ class FormatManager
end
end
private
sig { returns(Pathname) }
def file
private def file
directory.join('onlyoffice-docs-formats.json')
end
sig { returns(Pathname) }
def directory
private def directory
current_directory = Pathname(T.must(__dir__))
directory = current_directory.join('..', '..', 'assets', 'document-formats')
directory.cleanpath

View File

@ -21,6 +21,7 @@ require 'json'
require 'test/unit'
require_relative 'format'
# Test case for the Format class.
class FormatTests < Test::Unit::TestCase
def test_generates_extension
content =
@ -39,6 +40,7 @@ class FormatTests < Test::Unit::TestCase
end
end
# Test case for the FormatManager class, checks availability "all" formats.
class FormatManagerAllTests < Test::Unit::TestCase
def test_loads
format_manager = FormatManager.new
@ -46,6 +48,7 @@ class FormatManagerAllTests < Test::Unit::TestCase
end
end
# Test case for the FormatManager class, checks availability "documents" formats.
class FormatManagerDocumentsTests < Test::Unit::TestCase
def test_loads
format_manager = FormatManager.new
@ -53,6 +56,7 @@ class FormatManagerDocumentsTests < Test::Unit::TestCase
end
end
# Test case for the FormatManager class, checks availability "presentations" formats.
class FormatManagerPresentationsTests < Test::Unit::TestCase
def test_loads
format_manager = FormatManager.new
@ -60,6 +64,7 @@ class FormatManagerPresentationsTests < Test::Unit::TestCase
end
end
# Test case for the FormatManager class, checks availability "spreadsheets" formats.
class FormatManagerSpreadsheetsTests < Test::Unit::TestCase
def test_loads
format_manager = FormatManager.new
@ -67,6 +72,7 @@ class FormatManagerSpreadsheetsTests < Test::Unit::TestCase
end
end
# Test case for the FormatManager class, checks availability "all convertible" formats.
class FormatManagerConvertibleTests < Test::Unit::TestCase
def test_loads
format_manager = FormatManager.new
@ -74,6 +80,7 @@ class FormatManagerConvertibleTests < Test::Unit::TestCase
end
end
# Test case for the FormatManager class, checks availability "all editable" formats.
class FormatManagerEditableTests < Test::Unit::TestCase
def test_loads
format_manager = FormatManager.new
@ -81,6 +88,7 @@ class FormatManagerEditableTests < Test::Unit::TestCase
end
end
# Test case for the FormatManager class, checks availability "all viewable" formats.
class FormatManagerViewableTests < Test::Unit::TestCase
def test_loads
format_manager = FormatManager.new
@ -88,6 +96,7 @@ class FormatManagerViewableTests < Test::Unit::TestCase
end
end
# Test case for the FormatManager class, checks availability "all filable" formats.
class FormatManagerFilableTests < Test::Unit::TestCase
def test_loads
format_manager = FormatManager.new

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
#
# (c) Copyright Ascensio System SIA 2023
#
@ -14,5 +16,6 @@
# limitations under the License.
#
# Helper for application
module ApplicationHelper
end

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
#
# (c) Copyright Ascensio System SIA 2023
#
@ -14,5 +16,6 @@
# limitations under the License.
#
# Helper for home
module HomeHelper
end

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
#
# (c) Copyright Ascensio System SIA 2023
#
@ -17,6 +19,7 @@
require_relative '../configuration/configuration'
require_relative '../format/format'
# Class that provides various utility methods related to documents.
class DocumentHelper
@config_manager = ConfigurationManager.new
@format_manager = FormatManager.new
@ -26,318 +29,296 @@ class DocumentHelper
attr_reader :format_manager
end
@@runtime_cache = {}
@@remote_ip = nil
@@base_url = nil
@runtime_cache = {}
@remote_ip = nil
@base_url = nil
class << self
def self.init(ip, url)
@remote_ip = ip
@base_url = url
end
# define max file size
def self.file_size_max
DocumentHelper.config_manager.maximum_file_size
end
# all the supported file extensions
def self.file_exts
DocumentHelper.format_manager.all_extensions
end
def self.fill_forms_exts
DocumentHelper.format_manager.fillable_extensions
end
# file extensions that can be viewed
def self.viewed_exts
DocumentHelper.format_manager.viewable_extensions
end
# file extensions that can be edited
def self.edited_exts
DocumentHelper.format_manager.editable_extensions
end
# file extensions that can be converted
def self.convert_exts
DocumentHelper.format_manager.convertible_extensions
end
# get current user host address
def self.cur_user_host_address(user_address)
(user_address.nil? ? @remote_ip : user_address).gsub(/[^0-9\-.a-zA-Z_=]/, '_')
end
# get the storage path of the given file
def self.storage_path(file_name, user_address)
directory = DocumentHelper.config_manager.storage_path.join(cur_user_host_address(user_address))
# create a new directory if it doesn't exist
FileUtils.mkdir_p(directory) unless File.directory?(directory)
# put the given file to this directory
File.join(directory, File.basename(file_name))
end
# get the path to the forcesaved file version
def self.forcesave_path(file_name, user_address, create)
directory = DocumentHelper.config_manager.storage_path.join(cur_user_host_address(user_address))
# the directory with host address doesn't exist
return '' unless File.directory?(directory)
# get the path to the history of the given file
directory = File.join(directory, "#{File.basename(file_name)}-hist")
unless File.directory?(directory)
return '' unless create
FileUtils.mkdir_p(directory) # create history directory if it doesn't exist
# the history directory doesn't exist and we are not supposed to create it
def init (ip, url)
@@remote_ip = ip
@@base_url = url
end
# define max file size
def file_size_max
DocumentHelper.config_manager.maximum_file_size
end
directory = File.join(directory, File.basename(file_name)) # get the path to the given file
return '' if !File.file?(directory) && !create
# all the supported file extensions
def file_exts
DocumentHelper.format_manager.all_extensions
end
directory.to_s
end
def fill_forms_exts
DocumentHelper.format_manager.fillable_extensions
end
# get the path to the file history
def self.history_dir(storage_path)
directory = "#{storage_path}-hist"
# file extensions that can be viewed
def viewed_exts
DocumentHelper.format_manager.viewable_extensions
end
# create history directory if it doesn't exist
FileUtils.mkdir_p(directory) unless File.directory?(directory)
# file extensions that can be edited
def edited_exts
DocumentHelper.format_manager.editable_extensions
end
directory
end
# file extensions that can be converted
def convert_exts
DocumentHelper.format_manager.convertible_extensions
end
# get the path to the specified file version
def self.version_dir(hist_dir, ver)
File.join(hist_dir, ver.to_s)
end
# get current user host address
def cur_user_host_address(user_address)
(user_address == nil ? @@remote_ip : user_address).gsub(/[^0-9\-.a-zA-Z_=]/, '_');
end
# get the last file version
def self.get_file_version(hist_dir)
return 1 unless Dir.exist?(hist_dir)
# get the storage path of the given file
def storage_path(file_name, user_address)
directory = DocumentHelper.config_manager.storage_path.join(cur_user_host_address(user_address))
ver = 1
Dir.foreach(hist_dir) do |e| # run through all the file versions
next if e.eql?('.')
next if e.eql?('..')
# create a new directory if it doesn't exist
unless File.directory?(directory)
FileUtils.mkdir_p(directory)
end
# put the given file to this directory
File.join(directory, File.basename(file_name))
end
# get the path to the forcesaved file version
def forcesave_path(file_name, user_address, create)
directory = DocumentHelper.config_manager.storage_path.join(cur_user_host_address(user_address))
# the directory with host address doesn't exist
unless File.directory?(directory)
return ""
end
directory = File.join(directory,"#{File.basename(file_name)}-hist") # get the path to the history of the given file
unless File.directory?(directory)
if create
FileUtils.mkdir_p(directory) # create history directory if it doesn't exist
else
return "" # the history directory doesn't exist and we are not supposed to create it
end
end
directory = File.join(directory, File.basename(file_name)) # get the path to the given file
unless File.file?(directory)
if !create
return ""
end
end
return directory.to_s
end
# get the path to the file history
def history_dir(storage_path)
directory = "#{storage_path}-hist"
# create history directory if it doesn't exist
unless File.directory?(directory)
FileUtils.mkdir_p(directory)
end
return directory
end
# get the path to the specified file version
def version_dir(hist_dir, ver)
return File.join(hist_dir, ver.to_s)
end
# get the last file version
def get_file_version(hist_dir)
if !Dir.exist?(hist_dir)
return 1
end
ver = 1
Dir.foreach(hist_dir) {|e| # run through all the file versions
next if e.eql?(".")
next if e.eql?("..")
if File.directory?(File.join(hist_dir, e))
ver += 1 # and count them
end
}
return ver
end
# get the correct file name if such a name already exists
def get_correct_name(file_name, user_address)
maxName = 50
ext = File.extname(file_name) # get file extension
# get file name without extension
base_name = File.basename(file_name, ext)[0...maxName] + (file_name.length > maxName ? '[...]' : '')
name = base_name + ext.downcase # get full file name
index = 1
while File.exist?(storage_path(name, user_address)) # if the file with such a name already exists in this directory
name = base_name + ' (' + index.to_s + ')' + ext.downcase # add an index after its base name
index = index + 1
end
name
end
# get all the stored files from the folder
def get_stored_files(user_address)
directory = DocumentHelper.config_manager.storage_path.join(cur_user_host_address(user_address))
arr = [];
if Dir.exist?(directory)
Dir.foreach(directory) {|e| # run through all the elements from the folder
next if e.eql?(".")
next if e.eql?("..")
next if File.directory?(File.join(directory, e)) # if the element is a directory, skip it
arr.push(e) # push the file to the array
}
end
return arr
end
# create file meta information
def create_meta(file_name, uid, uname, user_address)
hist_dir = history_dir(storage_path(file_name, user_address)) # get the path to the file history
# write user name, user uid and the creation time to the json object
json = {
:created => Time.now.to_formatted_s(:db),
:uid => uid,
:uname => uname
}
# write file meta information to the createdInfo.json file
File.open(File.join(hist_dir, "createdInfo.json"), 'wb') do |file|
file.write(json.to_json)
if File.directory?(File.join(hist_dir, e))
ver += 1 # and count them
end
end
# create demo document
def create_demo(file_ext, sample, user)
demo_name = (sample == 'true' ? 'sample.' : 'new.') + file_ext
file_name = get_correct_name(demo_name, nil) # get the correct file name if such a name already exists
ver
end
src = Rails.root.join('assets', 'document-templates', sample == 'true' ? 'sample' : 'new', demo_name) # save sample document of a necessary extension to the storage directory
dest = storage_path file_name, nil
# get the correct file name if such a name already exists
def self.get_correct_name(file_name, user_address)
max_name = 50
ext = File.extname(file_name) # get file extension
# get file name without extension
base_name = File.basename(file_name, ext)[0...max_name] + (file_name.length > max_name ? '[...]' : '')
name = base_name + ext.downcase # get full file name
index = 1
FileUtils.cp src, dest
# save file meta data to the file
create_meta(file_name, user.id, user.name, nil)
file_name
# if the file with such a name already exists in this directory
while File.exist?(storage_path(name, user_address))
name = "#{base_name} (#{index})#{ext.downcase}" # add an index after its base name
index += 1
end
# get file url
def get_file_uri(file_name, for_document_server)
uri = get_server_url(for_document_server) + '/' + DocumentHelper.config_manager.storage_path.to_s + '/' + cur_user_host_address(nil) + '/' + ERB::Util.url_encode(file_name)
name
end
return uri
end
# get all the stored files from the folder
def self.get_stored_files(user_address)
directory = DocumentHelper.config_manager.storage_path.join(cur_user_host_address(user_address))
# get history path url
def get_historypath_uri(file_name,version,file,is_serverUrl=true)
# for redirection to my link
user_host = is_serverUrl ? '&userAddress=' + cur_user_host_address(nil) : ""
uri = get_server_url(is_serverUrl) + '/downloadhistory/?fileName=' + ERB::Util.url_encode(file_name) + '&ver='+ version.to_s + '&file='+ ERB::Util.url_encode(file) + user_host
return uri
end
arr = []
# get server url
def get_server_url(for_document_server)
if for_document_server && DocumentHelper.config_manager.example_uri
return DocumentHelper.config_manager.example_uri.to_s
else
return @@base_url
if Dir.exist?(directory)
Dir.foreach(directory) do |e| # run through all the elements from the folder
next if e.eql?('.')
next if e.eql?('..')
next if File.directory?(File.join(directory, e)) # if the element is a directory, skip it
arr.push(e) # push the file to the array
end
end
# get callback url
def get_callback(file_name)
arr
end
get_server_url(true) + '/track?fileName=' + ERB::Util.url_encode(file_name) + '&userAddress=' + cur_user_host_address(nil)
# create file meta information
def self.create_meta(file_name, uid, uname, user_address)
hist_dir = history_dir(storage_path(file_name, user_address)) # get the path to the file history
end
# write user name, user uid and the creation time to the json object
json = {
created: Time.zone.now.to_fs(:db),
uid:,
uname:
}
# get url to the created file
def get_create_url(document_type)
# write file meta information to the createdInfo.json file
File.binwrite(File.join(hist_dir, 'createdInfo.json'), json.to_json)
end
get_server_url(false) + '/sample?fileExt=' + get_internal_extension(document_type).delete('.')
# create demo document
def self.create_demo(file_ext, sample, user)
demo_name = (sample == 'true' ? 'sample.' : 'new.') + file_ext
file_name = get_correct_name(demo_name, nil) # get the correct file name if such a name already exists
end
# save sample document of a necessary extension to the storage directory
src = Rails.root.join('assets', 'document-templates', sample == 'true' ? 'sample' : 'new', demo_name)
dest = storage_path(file_name, nil)
# get url to download a file
def get_download_url(file_name, is_serverUrl=true)
FileUtils.cp(src, dest)
user_host = is_serverUrl ? '&userAddress=' + cur_user_host_address(nil) : ""
get_server_url(is_serverUrl) + '/download?fileName=' + ERB::Util.url_encode(file_name) + user_host
# save file meta data to the file
end
create_meta(file_name, user.id, user.name, nil)
# get internal file extension by its type
def get_internal_extension(file_type)
file_name
end
case file_type
when 'word' # .docx for word type
ext = '.docx'
when 'cell' # .xlsx for cell type
ext = '.xlsx'
when 'slide' # .pptx for slide type
ext = '.pptx'
else
ext = '.docx' # the default value is .docx
end
# get file url
def self.get_file_uri(file_name, for_document_server)
"#{get_server_url(for_document_server)}/" \
"#{DocumentHelper.config_manager.storage_path}/" \
"#{cur_user_host_address(nil)}/" \
"#{ERB::Util.url_encode(file_name)}"
end
ext
end
# get history path url
def self.get_historypath_uri(file_name, version, file, is_server_url: true)
# for redirection to my link
user_host = is_server_url ? "&userAddress=#{cur_user_host_address(nil)}" : ''
"#{get_server_url(is_server_url)}/downloadhistory/?" \
"fileName=#{ERB::Util.url_encode(file_name)}&ver=#{version}" \
"&file=#{ERB::Util.url_encode(file)}#{user_host}"
end
# get image url for templates
def get_template_image_url(file_type)
path = get_server_url(true) + "/assets/"
case file_type
when 'word' # for word type
full_path = path + 'file_docx.svg'
when 'cell' # .xlsx for cell type
full_path = path + 'file_xlsx.svg'
when 'slide' # .pptx for slide type
full_path = path + 'file_pptx.svg'
else
full_path = path + 'file_docx.svg' # the default value is .docx
end
# get server url
def self.get_server_url(for_document_server)
return DocumentHelper.config_manager.example_uri.to_s if
for_document_server &&
DocumentHelper.config_manager.example_uri
full_path
end
@base_url
end
# get files information
def get_files_info(file_id)
result = [];
# get callback url
def self.get_callback(file_name)
"#{get_server_url(true)}/track?" \
"fileName=#{ERB::Util.url_encode(file_name)}&" \
"userAddress=#{cur_user_host_address(nil)}"
end
for fileName in get_stored_files(nil) # run through all the stored files from the folder
directory = storage_path(fileName, nil)
uri = cur_user_host_address(nil) + '/' + fileName
# get url to the created file
def self.get_create_url(document_type)
"#{get_server_url(false)}/sample?fileExt=#{get_internal_extension(document_type).delete('.')}"
end
# write file parameters to the info object
info = {
"version" => get_file_version(history_dir(directory)),
"id" => ServiceConverter.generate_revision_id("#{uri}.#{File.mtime(directory).to_s}"),
"contentLength" => "#{(File.size(directory)/ 1024.0).round(2)} KB",
"pureContentLength" => File.size(directory),
"title" => fileName,
"updated" => File.mtime(directory)
}
# get url to download a file
def self.get_download_url(file_name, is_server_url: true)
user_host = is_server_url ? "&userAddress=#{cur_user_host_address(nil)}" : ''
"#{get_server_url(is_server_url)}/download?fileName=#{ERB::Util.url_encode(file_name)}#{user_host}"
end
if file_id == nil # if file id is undefined
result.push(info) # push info object to the response array
else # if file id is defined
if file_id.eql?(info["id"]) # and it is equal to the document key value
result.push(info) # response object will be equal to the info object
return result
end
end
end
if file_id != nil
return "\"File not found\""
else
return result
end
end
# enable ignore certificate
def verify_ssl(file_uri, http)
if file_uri.start_with?('https') && DocumentHelper.config_manager.ssl_verify_peer_mode_enabled
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE # set the flags for the server certificate verification at the beginning of SSL session
end
# get internal file extension by its type
def self.get_internal_extension(file_type)
case file_type
when 'word' # .docx for word type
'.docx'
when 'cell' # .xlsx for cell type
'.xlsx'
when 'slide' # .pptx for slide type
'.pptx'
else
'.docx' # the default value is .docx
end
end
end
# get image url for templates
def self.get_template_image_url(file_type)
path = "#{get_server_url(true)}/assets/"
case file_type
when 'word' # for word type
"#{path}file_docx.svg"
when 'cell' # .xlsx for cell type
"#{path}file_xlsx.svg"
when 'slide' # .pptx for slide type
"#{path}file_pptx.svg"
else
"#{path}file_docx.svg" # the default value is .docx
end
end
# get files information
def self.get_files_info(file_id)
result = []
get_stored_files(nil).each do |file_name| # run through all the stored files from the folder
directory = storage_path(file_name, nil)
uri = "#{cur_user_host_address(nil)}/#{file_name}"
# write file parameters to the info object
info = {
version: get_file_version(history_dir(directory)),
id: ServiceConverter.generate_revision_id("#{uri}.#{File.mtime(directory)}"),
contentLength: "#{(File.size(directory) / 1024.0).round(2)} KB",
pureContentLength: File.size(directory),
title: file_name,
updated: File.mtime(directory)
}
if file_id.nil? # if file id is undefined
result.push(info) # push info object to the response array
elsif file_id.eql?(info['id']) # if file id is defined
result.push(info) # response object will be equal to the info object
return result # and it is equal to the document key value
end
end
return '"File not found"' unless file_id.nil?
result
end
# enable ignore certificate
def self.verify_ssl(file_uri, http)
return unless file_uri.start_with?('https') && DocumentHelper.config_manager.ssl_verify_peer_mode_enabled
http.use_ssl = true
# set the flags for the server certificate verification at the beginning of SSL session
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
end
end

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
#
# (c) Copyright Ascensio System SIA 2023
#
@ -16,16 +18,23 @@
require_relative '../configuration/configuration'
# Class for handling file-related operations and information.
class FileModel
attr_accessor :file_name, :mode, :type, :user_ip, :lang, :user, :action_data, :direct_url
attr_accessor :file_name
attr_accessor :mode
attr_accessor :type
attr_accessor :user_ip
attr_accessor :lang
attr_accessor :user
attr_accessor :action_data
attr_accessor :direct_url
attr_reader :config_manager
# set file parameters
def initialize(attributes = {})
@file_name = attributes[:file_name]
@mode = attributes[:mode]
@type = attributes[:type]
@type = attributes[:type] || 'desktop' # the default platform type is desktop
@user_ip = attributes[:user_ip]
@lang = attributes[:lang]
@user = attributes[:user]
@ -34,10 +43,6 @@ class FileModel
@config_manager = ConfigurationManager.new
end
def type
@type ? @type : "desktop" # the default platform type is desktop
end
# get file extension from its name
def file_ext
File.extname(@file_name).downcase
@ -50,7 +55,14 @@ class FileModel
# get file uri for document server
def file_uri_user
@config_manager.storage_path.absolute? ? download_url + "&dmode=emb" : DocumentHelper.get_file_uri(@file_name, false)
if @config_manager.storage_path.absolute?
"#{download_url}&dmode=emb"
else
DocumentHelper.get_file_uri(
@file_name,
false
)
end
end
# get document type from its name (word, cell or slide)
@ -60,9 +72,9 @@ class FileModel
# generate the document key value
def key
uri = DocumentHelper.cur_user_host_address(nil) + '/' + @file_name # get current user host address
stat = File.mtime(DocumentHelper.storage_path(@file_name, nil)) # get the modification time of the given file
return ServiceConverter.generate_revision_id("#{uri}.#{stat.to_s}")
uri = "#{DocumentHelper.cur_user_host_address(nil)}/#{@file_name}" # get current user host address
stat = File.mtime(DocumentHelper.storage_path(@file_name, nil)) # get the modification time of the given file
ServiceConverter.generate_revision_id("#{uri}.#{stat}")
end
# get callback url
@ -76,8 +88,8 @@ class FileModel
end
# get url to download a file
def download_url(is_serverUrl=true)
DocumentHelper.get_download_url(@file_name, is_serverUrl)
def download_url(is_server_url: true)
DocumentHelper.get_download_url(@file_name, is_server_url:)
end
# get current user host address
@ -86,304 +98,377 @@ class FileModel
end
# get config parameters
def get_config
editorsmode = @mode ? @mode : "edit" # mode: view/edit/review/comment/fillForms/embedded
canEdit = DocumentHelper.edited_exts.include?(file_ext) # check if the document can be edited
if (!canEdit && editorsmode.eql?("edit") || editorsmode.eql?("fillForms")) && DocumentHelper.fill_forms_exts.include?(file_ext)
editorsmode = "fillForms"
canEdit = true
def config
editors_mode = @mode || 'edit' # mode: view/edit/review/comment/fillForms/embedded
can_edit = DocumentHelper.edited_exts.include?(file_ext) # check if the document can be edited
if ((!can_edit && editors_mode.eql?('edit')) || editors_mode.eql?('fillForms')) &&
DocumentHelper.fill_forms_exts.include?(file_ext)
editors_mode = 'fillForms'
can_edit = true
end
submitForm = editorsmode.eql?("fillForms") && @user.id.eql?("uid-1") # the Submit form button state
mode = canEdit && !editorsmode.eql?("view") ? "edit" : "view"
templatesImageUrl = DocumentHelper.get_template_image_url(document_type) # templates image url in the "From Template" section
submit_form = editors_mode.eql?('fillForms') && @user.id.eql?('uid-1') # the Submit form button state
mode = can_edit && !editors_mode.eql?('view') ? 'edit' : 'view'
# templates image url in the "From Template" section
templates_image_url = DocumentHelper.get_template_image_url(document_type)
templates = [
{
:image => "",
:title => "Blank",
:url => create_url
image: '',
title: 'Blank',
url: create_url
},
{
:image => templatesImageUrl,
:title => "With sample content",
:url => create_url + "&sample=true"
image: templates_image_url,
title: 'With sample content',
url: "#{create_url}&sample=true"
}
]
config = {
:type => type(),
:documentType => document_type,
:document => {
:title => @file_name,
:url => download_url,
:directUrl => is_enable_direct_url ? download_url(false) : "",
:fileType => file_ext.delete("."),
:key => key,
:info => {
:owner => "Me",
:uploaded => Time.now.to_s,
:favorite => @user.favorite
type:,
documentType: document_type,
document: {
title: @file_name,
url: download_url,
directUrl: enable_direct_url? ? download_url(is_server_url: false) : '',
fileType: file_ext.delete('.'),
key:,
info: {
owner: 'Me',
uploaded: Time.zone.now.to_s,
favorite: @user.favorite
},
:permissions => { # the permission for the document to be edited and downloaded or not
:comment => !editorsmode.eql?("view") && !editorsmode.eql?("fillForms") && !editorsmode.eql?("embedded") && !editorsmode.eql?("blockcontent"),
:copy => !@user.deniedPermissions.include?("copy"),
:download => !@user.deniedPermissions.include?("download"),
:edit => canEdit && (editorsmode.eql?("edit") || editorsmode.eql?("view") || editorsmode.eql?("filter") || editorsmode.eql?("blockcontent")),
:print => !@user.deniedPermissions.include?("print"),
:fillForms => !editorsmode.eql?("view") && !editorsmode.eql?("comment") && !editorsmode.eql?("embedded") && !editorsmode.eql?("blockcontent"),
:modifyFilter => !editorsmode.eql?("filter"),
:modifyContentControl => !editorsmode.eql?("blockcontent"),
:review => canEdit && (editorsmode.eql?("edit") || editorsmode.eql?("review")),
:chat => !@user.id.eql?("uid-0"),
:reviewGroups => @user.reviewGroups,
:commentGroups => @user.commentGroups,
:userInfoGroups => @user.userInfoGroups,
:protect => !@user.deniedPermissions.include?("protect")
permissions: { # the permission for the document to be edited and downloaded or not
comment: ['view', 'fillForms', 'embedded', 'blockcontent'].exclude?(editors_mode),
copy: @user.denied_permissions.exclude?('copy'),
download: @user.denied_permissions.exclude?('download'),
edit: can_edit && ['edit', 'view', 'filter', 'blockcontent'].include?(editors_mode),
print: @user.denied_permissions.exclude?('print'),
fillForms: ['view', 'comment', 'embedded', 'blockcontent'].exclude?(editors_mode),
modifyFilter: !editors_mode.eql?('filter'),
modifyContentControl: !editors_mode.eql?('blockcontent'),
review: can_edit && (editors_mode.eql?('edit') || editors_mode.eql?('review')),
chat: !@user.id.eql?('uid-0'),
reviewGroups: @user.review_groups,
commentGroups: @user.comment_groups,
userInfoGroups: @user.user_info_groups,
protect: @user.denied_permissions.exclude?('protect')
},
:referenceData => {
:instanceId => DocumentHelper.get_server_url(false),
:fileKey => !@user.id.eql?("uid-0") ? {:fileName => @file_name,:userAddress => DocumentHelper.cur_user_host_address(nil)}.to_json : nil
referenceData: {
instanceId: DocumentHelper.get_server_url(false),
fileKey: unless @user.id.eql?('uid-0')
{
fileName: @file_name,
userAddress: DocumentHelper.cur_user_host_address(nil)
}.to_json
end
}
},
:editorConfig => {
:actionLink => @action_data ? JSON.parse(@action_data) : nil,
:mode => mode,
:lang => @lang ? @lang : "en",
:callbackUrl => callback_url, # absolute URL to the document storage service
:coEditing => editorsmode.eql?("view") && @user.id.eql?("uid-0") ? {
:mode => "strict",
:change => false
} : nil,
:createUrl => !@user.id.eql?("uid-0") ? create_url : nil,
:templates => @user.templates ? templates : nil,
:user => { # the user currently viewing or editing the document
:id => !@user.id.eql?("uid-0") ? @user.id : nil,
:name => @user.name,
:group => @user.group,
:image => @user.avatar ? "#{DocumentHelper.get_server_url(true)}/assets/#{@user.id}.png" : nil
editorConfig: {
actionLink: @action_data ? JSON.parse(@action_data) : nil,
mode:,
lang: @lang || 'en',
callbackUrl: callback_url, # absolute URL to the document storage service
coEditing: if editors_mode.eql?('view') && @user.id.eql?('uid-0')
{
mode: 'strict',
change: false
}
end,
createUrl: @user.id.eql?('uid-0') ? nil : create_url,
templates: @user.templates ? templates : nil,
user: { # the user currently viewing or editing the document
id: @user.id.eql?('uid-0') ? nil : @user.id,
name: @user.name,
group: @user.group,
image: @user.avatar ? "#{DocumentHelper.get_server_url(true)}/assets/#{@user.id}.png" : nil
},
:embedded => { # the parameters for the embedded document type
:saveUrl => download_url(false), # the absolute URL that will allow the document to be saved onto the user personal computer
:embedUrl => download_url(false), # the absolute URL to the document serving as a source file for the document embedded into the web page
:shareUrl => download_url(false), # the absolute URL that will allow other users to share this document
:toolbarDocked => "top" # the place for the embedded viewer toolbar (top or bottom)
embedded: { # the parameters for the embedded document type
# the absolute URL that will allow the document to be saved onto the user personal computer
saveUrl: download_url(is_server_url: false),
# the absolute URL to the document serving as a source file for the document embedded into the web page
embedUrl: download_url(is_server_url: false),
# the absolute URL that will allow other users to share this document
shareUrl: download_url(is_server_url: false),
toolbarDocked: 'top' # the place for the embedded viewer toolbar (top or bottom)
},
:customization => { # the parameters for the editor interface
:about => true, # the About section display
:comments => true,
:feedback => true, # the Feedback & Support menu button display
:forcesave => false, # adding the request for the forced file saving to the callback handler
:submitForm => submitForm, # the Submit form button state
:goback => {
:url => DocumentHelper.get_server_url(false)
},
customization: { # the parameters for the editor interface
about: true, # the About section display
comments: true,
feedback: true, # the Feedback & Support menu button display
forcesave: false, # adding the request for the forced file saving to the callback handler
submitForm: submit_form, # the Submit form button state
goback: {
url: DocumentHelper.get_server_url(false)
}
}
}
}
if JwtHelper.is_enabled # check if a secret key to generate token exists or not
config["token"] = JwtHelper.encode(config) # encode a payload object into a token and write it to the config
if JwtHelper.enabled? # check if a secret key to generate token exists or not
config['token'] = JwtHelper.encode(config) # encode a payload object into a token and write it to the config
end
return config
config
end
# get document history
def get_history
def history
file_name = @file_name
file_ext = File.extname(file_name).downcase
doc_key = key()
doc_uri = file_uri()
doc_key = key
file_uri
hist_dir = DocumentHelper.history_dir(DocumentHelper.storage_path(@file_name, nil)) # get the path to the file history
cur_ver = DocumentHelper.get_file_version(hist_dir) # get the file version
# get the path to the file history
hist_dir = DocumentHelper.history_dir(DocumentHelper.storage_path(@file_name, nil))
cur_ver = DocumentHelper.get_file_version(hist_dir) # get the file version
if (cur_ver > 0) # if file was modified
if cur_ver.positive? # if file was modified
hist = []
histData = {}
hist_data = {}
for i in 1..cur_ver # run through all the file versions
(1..cur_ver).each do |i| # run through all the file versions
obj = {}
dataObj = {}
ver_dir = DocumentHelper.version_dir(hist_dir, i) # get the path to the given file version
data_obj = {}
ver_dir = DocumentHelper.version_dir(hist_dir, i) # get the path to the given file version
# get document key
cur_key = doc_key
if (i != cur_ver)
File.open(File.join(ver_dir, "key.txt"), 'r') do |file|
cur_key = file.read()
if i != cur_ver
File.open(File.join(ver_dir, 'key.txt'), 'r') do |file|
cur_key = file.read
end
end
obj["key"] = cur_key
obj["version"] = i
obj['key'] = cur_key
obj['version'] = i
if (i == 1) # check if the version number is equal to 1
if File.file?(File.join(hist_dir, "createdInfo.json")) # check if the createdInfo.json file with meta data exists
File.open(File.join(hist_dir, "createdInfo.json"), 'r') do |file| # open it
cr_info = JSON.parse(file.read()) # parse the file content
# check if the createdInfo.json file with meta data exists
if (i == 1) && File.file?(File.join(hist_dir, 'createdInfo.json'))
File.open(File.join(hist_dir, 'createdInfo.json'), 'r') do |file| # open it
cr_info = JSON.parse(file.read) # parse the file content
# write information about changes to the object
obj["created"] = cr_info["created"]
obj["user"] = {
:id => cr_info["uid"],
:name => cr_info["uname"]
}
end
# write information about changes to the object
obj['created'] = cr_info['created']
obj['user'] = {
id: cr_info['uid'],
name: cr_info['uname']
}
end
end
# get the history data from the previous file version and write key and url information about it
dataObj["fileType"] = file_ext[1..file_ext.length]
dataObj["key"] = cur_key
dataObj["url"] = i == cur_ver ? doc_uri : DocumentHelper.get_historypath_uri(file_name, i, "prev#{file_ext}")
if is_enable_direct_url == true
dataObj["directUrl"] = i == cur_ver ? download_url(false) : DocumentHelper.get_historypath_uri(file_name, i, "prev#{file_ext}", false)
data_obj['fileType'] = file_ext[1..file_ext.length]
data_obj['key'] = cur_key
data_obj['url'] =
if i == cur_ver
DocumentHelper.get_download_url(
file_name,
true
)
else
DocumentHelper.get_historypath_uri(
file_name,
i,
"prev#{file_ext}"
)
end
if enable_direct_url? == true
data_obj['directUrl'] =
if i == cur_ver
download_url(is_server_url: false)
else
DocumentHelper.get_historypath_uri(
file_name,
i,
"prev#{file_ext}",
false
)
end
end
dataObj["version"] = i
data_obj['version'] = i
if (i > 1) # check if the version number is greater than 1
if i > 1 # check if the version number is greater than 1
changes = nil
change = nil
File.open(File.join(DocumentHelper.version_dir(hist_dir, i - 1), "changes.json"), 'r') do |file| # get the path to the changes.json file
changes = JSON.parse(file.read()) # and parse its content
# get the path to the changes.json file
File.open(File.join(DocumentHelper.version_dir(hist_dir, i - 1), 'changes.json'), 'r') do |file|
changes = JSON.parse(file.read) # and parse its content
end
change = changes["changes"][0]
change = changes['changes'][0]
# write information about changes to the object
obj["changes"] = change ? changes["changes"] : nil
obj["serverVersion"] = changes["serverVersion"]
obj["created"] = change ? change["created"] : nil
obj["user"] = change ? change["user"] : nil
obj['changes'] = change ? changes['changes'] : nil
obj['serverVersion'] = changes['serverVersion']
obj['created'] = change ? change['created'] : nil
obj['user'] = change ? change['user'] : nil
prev = histData[(i - 2).to_s] # get the history data from the previous file version
dataObj["previous"] = is_enable_direct_url == true ? { # write key and url information about previous file version with optional direct url
:fileType => prev["fileType"],
:key => prev["key"],
:url => prev["url"],
:directUrl => prev["directUrl"]
} : {
:fileType => prev["fileType"],
:key => prev["key"],
:url => prev["url"]
}
prev = hist_data[(i - 2).to_s] # get the history data from the previous file version
# write key and url information about previous file version with optional direct url
data(
obj['previous'] = if enable_direct_url? == true
{ # write key and url information about previous file version with optional directUrl
fileType: prev['fileType'],
key: prev['key'],
url: prev['url'],
directUrl: prev['directUrl']
}
else
{
fileType: prev['fileType'],
key: prev['key'],
url: prev['url']
}
end
)
# write the path to the diff.zip archive with differences in this file version
dataObj["changesUrl"] = DocumentHelper.get_historypath_uri(file_name, i - 1, "diff.zip")
diff_path = [hist_dir, (i - 1).to_s, 'diff.zip'].join(File::SEPARATOR)
if File.exist?(diff_path)
# write the path to the diff.zip archive with differences in this file version
data_obj['changesUrl'] = DocumentHelper.get_historypath_uri(file_name, i - 1, 'diff.zip')
end
end
if JwtHelper.is_enabled # check if a secret key to generate token exists or not
dataObj["token"] = JwtHelper.encode(dataObj) # encode a payload object into a token and write it to the data object
if JwtHelper.enabled? # check if a secret key to generate token exists or not
# encode a payload object into a token and write it to the data object
data_obj['token'] = JwtHelper.encode(data_obj)
end
hist.push(obj) # add object dictionary to the hist list
histData[(i - 1).to_s] = dataObj # write data object information to the history data
hist.push(obj) # add object dictionary to the hist list
hist_data[(i - 1).to_s] = data_obj # write data object information to the history data
end
return {
:hist => { # write history information about the current file version to the hist
:currentVersion => cur_ver,
:history => hist
hist: { # write history information about the current file version to the hist
currentVersion: cur_ver,
history: hist
},
:histData => histData
histData: hist_data
}
end
return nil
nil
end
# get image information
def get_insert_image
insert_image = is_enable_direct_url == true ? {
:fileType => "png", # image file type
:url => DocumentHelper.get_server_url(true) + "/assets/logo.png", # server url to the image
:directUrl => DocumentHelper.get_server_url(false) + "/assets/logo.png" # direct url to the image
} : {
:fileType => "png", # image file type
:url => DocumentHelper.get_server_url(true) + "/assets/logo.png" # server url to the image
}
def insert_image
# image file type
# server url to the image
# direct url to the image
insert_image = if enable_direct_url? == true
{
fileType: 'png', # image file type
url: "#{DocumentHelper.get_server_url(true)}/assets/logo.png", # server url to the image
directUrl: "#{DocumentHelper.get_server_url(false)}/assets/logo.png" # direct url to the image
}
else
{
fileType: 'png', # image file type
url: "#{DocumentHelper.get_server_url(true)}/assets/logo.png" # server url to the image
}
end
if JwtHelper.is_enabled # check if a secret key to generate token exists or not
insert_image["token"] = JwtHelper.encode(insert_image) # encode a payload object into a token and write it to the insert_image object
if JwtHelper.enabled? # check if a secret key to generate token exists or not
# encode a payload object into a token and write it to the insert_image object
insert_image['token'] = JwtHelper.encode(insert_image)
end
return insert_image.to_json.tr("{", "").tr("}","")
insert_image.to_json.tr('{', '').tr('}', '')
end
# get compared file information
def dataDocument
compare_file = is_enable_direct_url == true ? {
:fileType => "docx", # file type
:url => DocumentHelper.get_server_url(true) + "/asset?fileName=sample.docx", # server url to the compared file
:directUrl => DocumentHelper.get_server_url(false) + "/asset?fileName=sample.docx" # direct url to the compared file
} : {
:fileType => "docx", # file type
:url => DocumentHelper.get_server_url(true) + "/asset?fileName=sample.docx" # server url to the compared file
}
def data_document
# file type
# server url to the compared file
# direct url to the compared file
compare_file = if enable_direct_url? == true
{
fileType: 'docx', # file type
# server url to the compared file
url: "#{DocumentHelper.get_server_url(true)}/asset?fileName=sample.docx",
# direct url to the compared file
directUrl: "#{DocumentHelper.get_server_url(false)}/asset?fileName=sample.docx"
}
else
{
fileType: 'docx', # file type
# server url to the compared file
url: "#{DocumentHelper.get_server_url(true)}/asset?fileName=sample.docx"
}
end
if JwtHelper.is_enabled # check if a secret key to generate token exists or not
compare_file["token"] = JwtHelper.encode(compare_file) # encode a payload object into a token and write it to the compare_file object
if JwtHelper.enabled? # check if a secret key to generate token exists or not
# encode a payload object into a token and write it to the compare_file object
compare_file['token'] = JwtHelper.encode(compare_file)
end
return compare_file
compare_file
end
# get mail merge recipients information
def dataSpreadsheet
dataSpreadsheet = is_enable_direct_url == true ? {
:fileType => "csv", # file type
:url => DocumentHelper.get_server_url(true) + "/csv", # server url to the mail merge recipients file
:directUrl => DocumentHelper.get_server_url(false) + "/csv" # direct url to the mail merge recipients file
} : {
:fileType => "csv", # file type
:url => DocumentHelper.get_server_url(true) + "/csv" # server url to the mail merge recipients file
}
def data_spreadsheet
# file type
# server url to the mail merge recipients file
# direct url to the mail merge recipients file
data_spreadsheet = if enable_direct_url? == true
{
fileType: 'csv', # file type
# server url to the mail merge recipients file
url: "#{DocumentHelper.get_server_url(true)}/csv",
# direct url to the mail merge recipients file
directUrl: "#{DocumentHelper.get_server_url(false)}/csv"
}
else
{
fileType: 'csv', # file type
# server url to the mail merge recipients file
url: "#{DocumentHelper.get_server_url(true)}/csv"
}
end
if JwtHelper.is_enabled # check if a secret key to generate token exists or not
dataSpreadsheet["token"] = JwtHelper.encode(dataSpreadsheet) # encode a payload object into a token and write it to the dataSpreadsheet object
if JwtHelper.enabled? # check if a secret key to generate token exists or not
# encode a payload object into a token and write it to the data_spreadsheet object
data_spreadsheet['token'] = JwtHelper.encode(data_spreadsheet)
end
return dataSpreadsheet
data_spreadsheet
end
# get users data for mentions
def get_users_mentions
return !@user.id.eql?("uid-0") ? Users.get_users_for_mentions(@user.id) : nil
def users_mentions
@user.id.eql?('uid-0') ? nil : Users.get_users_for_mentions(@user.id)
end
def get_users_info
def users_info
users_info = []
if !@user.id.eql?("uid-0")
Users.get_all_users().each do |user_info|
u = {
id: user_info.id,
name: user_info.name,
email: user_info.email,
group: user_info.group,
reviewGroups: user_info.reviewGroups,
commentGroups: user_info.commentGroups,
userInfoGroups: user_info.userInfoGroups,
favorite: user_info.favorite,
deniedPermissions: user_info.deniedPermissions,
descriptions: user_info.descriptions,
templates: user_info.templates,
avatar: user_info.avatar
}
u["image"] = user_info.avatar ? "#{DocumentHelper.get_server_url(true)}/assets/#{user_info.id}.png" : nil
users_info.push(u)
end
return users_info
return if @user.id.eql?('uid-0')
Users.all_users.each do |user_info|
u = {
id: user_info.id,
name: user_info.name,
email: user_info.email,
group: user_info.group,
reviewGroups: user_info.review_groups,
commentGroups: user_info.comment_groups,
userInfoGroups: user_info.user_info_groups,
favorite: user_info.favorite,
deniedPermissions: user_info.denied_permissions,
descriptions: user_info.descriptions,
templates: user_info.templates,
avatar: user_info.avatar
}
u['image'] = user_info.avatar ? "#{DocumentHelper.get_server_url(true)}/assets/#{user_info.id}.png" : nil
users_info.push(u)
end
users_info
end
# get users data for protect
def get_users_protect
return !@user.id.eql?("uid-0") ? Users.get_users_for_protect(@user.id) : nil
def users_protect
@user.id.eql?('uid-0') ? nil : Users.get_users_for_protect(@user.id)
end
# get direct url existence flag
def is_enable_direct_url
return @direct_url != nil && @direct_url == "true"
def enable_direct_url?
!@direct_url.nil? && @direct_url == 'true'
end
end
end

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
#
# (c) Copyright Ascensio System SIA 2023
#
@ -16,20 +18,21 @@
require_relative '../format/format'
# Determination file type based on extensions, utilizing `@format_manager` for format management.
class FileUtility
@format_manager = FormatManager.new
class << self
attr_reader :format_manager
end
def get_file_type(file_name)
ext = File.extname(file_name).downcase
def self.get_file_type(file_name)
ext = File.extname(file_name).downcase
return 'word' if FileUtility.format_manager.document_extensinons.include?(ext)
return 'cell' if FileUtility.format_manager.spreadsheet_extensinons.include?(ext)
return 'slide' if FileUtility.format_manager.presentation_extensinons.include?(ext)
return 'word' if FileUtility.format_manager.document_extensinons.include?(ext)
return 'cell' if FileUtility.format_manager.spreadsheet_extensinons.include?(ext)
return 'slide' if FileUtility.format_manager.presentation_extensinons.include?(ext)
'word'
end
'word'
end
end

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
#
# (c) Copyright Ascensio System SIA 2023
#
@ -17,37 +19,35 @@
require 'jwt'
require_relative '../configuration/configuration'
# Helper class for JSON Web Token (JWT) operations, including encoding and decoding.
class JwtHelper
@jwt_secret = ConfigurationManager.new.jwt_secret
@token_use_for_request = ConfigurationManager.new.jwt_use_for_request
@jwt_secret = ConfigurationManager.new.jwt_secret
@token_use_for_request = ConfigurationManager.new.jwt_use_for_request
# check if a secret key to generate token exists or not
def self.enabled?
@jwt_secret.present?
end
class << self
# check if a secret key to generate token exists or not
def is_enabled
return @jwt_secret && !@jwt_secret.empty? ? true : false
end
# check if a secret key used for request
def self.use_for_request
@token_use_for_request
end
# check if a secret key used for request
def use_for_request
return @token_use_for_request
end
# encode a payload object into a token using a secret key
def self.encode(payload)
JWT.encode(payload, @jwt_secret, 'HS256') # define the hashing algorithm and get token
end
# encode a payload object into a token using a secret key
def encode(payload)
return JWT.encode payload, @jwt_secret, 'HS256' # define the hashing algorithm and get token
end
# decode a token into a payload object using a secret key
def decode(token)
begin
decoded = JWT.decode token, @jwt_secret, true, { algorithm: 'HS256' }
rescue
return ""
end
# decoded = Array [ {"data"=>"test"}, # payload
# {"alg"=>"HS256"} # header ]
return decoded[0].to_json # get json payload
end
# decode a token into a payload object using a secret key
def self.decode(token)
begin
decoded = JWT.decode(token, @jwt_secret, true, { algorithm: 'HS256' })
rescue StandardError
return ''
end
end
# decoded = Array [ {"data"=>"test"}, # payload
# {"alg"=>"HS256"} # header ]
decoded[0].to_json # get json payload
end
end

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
#
# (c) Copyright Ascensio System SIA 2023
#
@ -16,6 +18,7 @@
require_relative '../configuration/configuration'
# Class responsible for converting documents using a document conversion service.
class ServiceConverter
@config_manager = ConfigurationManager.new
@ -23,165 +26,151 @@ class ServiceConverter
attr_reader :config_manager
end
@@convert_timeout = ServiceConverter.config_manager.convertation_timeout
@@document_converter_url = ServiceConverter.config_manager.document_server_converter_uri.to_s
@convert_timeout = ServiceConverter.config_manager.convertation_timeout
@document_converter_url = ServiceConverter.config_manager.document_server_converter_uri.to_s
class << self
# get the url of the converted file
def self.get_converted_data(document_uri, from_ext, to_ext, document_revision_id, is_async, file_pass, lang = nil)
from_ext = File.extname(document_uri).downcase if from_ext.nil? # get the current document extension
# get the url of the converted file
def get_converted_data(document_uri, from_ext, to_ext, document_revision_id, is_async, file_pass, lang = nil)
# get the current document name or uuid
title = File.basename(URI.parse(document_uri).path)
title = UUID.generate.to_s if title.nil?
from_ext = from_ext == nil ? File.extname(document_uri).downcase : from_ext # get the current document extension
# get the document key
document_revision_id = document_uri if document_revision_id.empty?
document_revision_id = generate_revision_id(document_revision_id)
# get the current document name or uuid
title = File.basename(URI.parse(document_uri).path)
title = title == nil ? UUID.generate.to_s : title
payload = { # write all the conversion parameters to the payload
async: is_async ? true : false,
url: document_uri,
outputtype: to_ext.delete('.'),
filetype: from_ext.delete('.'),
title:,
key: document_revision_id,
password: file_pass,
region: lang
}
# get the document key
document_revision_id = document_revision_id.empty? ? document_uri : document_revision_id
document_revision_id = generate_revision_id(document_revision_id)
data = nil
begin
uri = URI.parse(@document_converter_url) # create the request url
http = Net::HTTP.new(uri.host, uri.port) # create a connection to the http server
payload = { # write all the conversion parameters to the payload
:async => is_async ? true : false,
:url => document_uri,
:outputtype => to_ext.delete('.'),
:filetype => from_ext.delete('.'),
:title => title,
:key => document_revision_id,
:password => file_pass,
:region => lang
}
DocumentHelper.verify_ssl(@document_converter_url, http)
data = nil
begin
http.read_timeout = @convert_timeout
http.open_timeout = 5
req = Net::HTTP::Post.new(uri.request_uri) # create the post request
req.add_field('Accept', 'application/json') # set headers
req.add_field('Content-Type', 'application/json')
uri = URI.parse(@@document_converter_url) # create the request url
http = Net::HTTP.new(uri.host, uri.port) # create a connection to the http server
DocumentHelper.verify_ssl(@@document_converter_url, http)
http.read_timeout = @@convert_timeout
http.open_timeout = 5
req = Net::HTTP::Post.new(uri.request_uri) # create the post request
req.add_field("Accept", "application/json") # set headers
req.add_field("Content-Type", "application/json")
if JwtHelper.is_enabled && JwtHelper.use_for_request # if the signature is enabled
payload["token"] = JwtHelper.encode(payload) # get token and save it to the payload
jwtHeader = ServiceConverter.config_manager.jwt_header; # get signature authorization header
req.add_field(jwtHeader, "Bearer #{JwtHelper.encode({ :payload => payload })}") # set it to the request with the Bearer prefix
end
req.body = payload.to_json
res = http.request(req) # get the response
status_code = res.code.to_i
if status_code != 200 # checking status code
raise "Conversion service returned status: #{status_code}"
end
data = res.body # and take its body
rescue Timeout::Error
# try again
rescue => ex
raise ex.message
if JwtHelper.enabled? && JwtHelper.use_for_request # if the signature is enabled
payload['token'] = JwtHelper.encode(payload) # get token and save it to the payload
jwt_header = ServiceConverter.config_manager.jwt_header; # get signature authorization header
# set it to the request with the Bearer prefix
req.add_field(jwt_header, "Bearer #{JwtHelper.encode({ payload: })}")
end
json_data = JSON.parse(data) # parse response body
return get_response_data(json_data) # get response url
end
# generate the document key value
def generate_revision_id(expected_key)
require 'zlib'
if expected_key.length > 20 # check if the expected key length is greater than 20
expected_key = (Zlib.crc32 expected_key).to_s # calculate 32-bit crc value from the expected key and turn it into the string
end
key = expected_key.gsub(/[^0-9a-zA-Z.=]/, '_')
key[(key.length - [key.length, 20].min)..key.length] # the resulting key is of the length 20 or less
end
# create an error message for the error code
def process_convert_service_responce_error(error_code)
error_message = 'unknown error'
# add an error message to the error message template depending on the error code
case error_code
when -8
error_message = 'Error occurred in the ConvertService.ashx: Error document VKey'
when -7
error_message = 'Error occurred in the ConvertService.ashx: Error document request'
when -6
error_message = 'Error occurred in the ConvertService.ashx: Error database'
when -5
error_message = 'Error occurred in the ConvertService.ashx: Incorrect password'
when -4
error_message = 'Error occurred in the ConvertService.ashx: Error download error'
when -3
error_message = 'Error occurred in the ConvertService.ashx: Error convertation error'
when -2
error_message = 'Error occurred in the ConvertService.ashx: Error convertation timeout'
when -1
error_message = 'Error occurred in the ConvertService.ashx: Error convertation unknown'
when 0
# public const int c_nErrorNo = 0
else
error_message = 'ErrorCode = ' + error_code.to_s # default value for the error message
end
raise error_message
end
# get the response url
def get_response_data(json_data)
file_result = json_data
error_element = file_result['error']
if error_element != nil # if an error occurs
process_convert_service_responce_error(error_element.to_i) # get an error message
end
is_end_convert = file_result['endConvert'] # check if the conversion is completed
result_percent = 0 # the conversion percentage
response_uri = ''
response_file_type = ''
if is_end_convert # if the conversion is completed
file_url_element = file_result['fileUrl']
file_type_element = file_result['fileType']
if file_url_element == nil # and the file url doesn't exist
raise 'Invalid answer format' # get ann error message
end
response_uri = file_url_element # otherwise, get the file url
response_file_type = file_type_element # get the file type
result_percent = 100
else # if the conversion isn't completed
percent_element = file_result['percent'] # get the percentage value
if percent_element != nil
result_percent = percent_element.to_i
end
result_percent = result_percent >= 100 ? 99 : result_percent
end
return result_percent, response_uri, response_file_type
req.body = payload.to_json
res = http.request(req) # get the response
status_code = Integer(res.code, 10)
raise("Conversion service returned status: #{status_code}") if status_code != 200 # checking status code
data = res.body # and take its body
rescue Timeout::Error
# try again
rescue StandardError => e
raise(e.message)
end
json_data = JSON.parse(data) # parse response body
get_response_data(json_data) # get response url
end
end
# generate the document key value
def self.generate_revision_id(expected_key)
require('zlib')
if expected_key.length > 20 # check if the expected key length is greater than 20
# calculate 32-bit crc value from the expected key and turn it into the string
expected_key = Zlib.crc32(expected_key).to_s
end
key = expected_key.gsub(/[^0-9a-zA-Z.=]/, '_')
key[(key.length - [key.length, 20].min)..key.length] # the resulting key is of the length 20 or less
end
# create an error message for the error code
def self.process_convert_service_responce_error(error_code)
error_message = 'unknown error'
# add an error message to the error message template depending on the error code
case error_code
when -8
error_message = 'Error occurred in the ConvertService.ashx: Error document VKey'
when -7
error_message = 'Error occurred in the ConvertService.ashx: Error document request'
when -6
error_message = 'Error occurred in the ConvertService.ashx: Error database'
when -5
error_message = 'Error occurred in the ConvertService.ashx: Incorrect password'
when -4
error_message = 'Error occurred in the ConvertService.ashx: Error download error'
when -3
error_message = 'Error occurred in the ConvertService.ashx: Error convertation error'
when -2
error_message = 'Error occurred in the ConvertService.ashx: Error convertation timeout'
when -1
error_message = 'Error occurred in the ConvertService.ashx: Error convertation unknown'
when 0
# public const int c_nErrorNo = 0
else
error_message = "ErrorCode = #{error_code}" # default value for the error message
end
raise(error_message)
end
# get the response url
def self.get_response_data(json_data)
file_result = json_data
error_element = file_result['error']
unless error_element.nil? # if an error occurs
process_convert_service_responce_error(Integer(error_element, 10)) # get an error message
end
is_end_convert = file_result['endConvert'] # check if the conversion is completed
result_percent = 0 # the conversion percentage
response_uri = ''
response_file_type = ''
if is_end_convert # if the conversion is completed
file_url_element = file_result['fileUrl']
file_type_element = file_result['fileType']
if file_url_element.nil? # and the file url doesn't exist
raise('Invalid answer format') # get ann error message
end
response_uri = file_url_element # otherwise, get the file url
response_file_type = file_type_element # get the file type
result_percent = 100
else # if the conversion isn't completed
percent_element = file_result['percent'] # get the percentage value
result_percent = Integer(percent_element, 10) unless percent_element.nil?
result_percent = 99 if result_percent >= 100
end
[result_percent, response_uri, response_file_type]
end
end

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
#
# (c) Copyright Ascensio System SIA 2023
#
@ -19,6 +21,7 @@ require 'uri'
require_relative '../configuration/configuration'
require_relative '../proxy/proxy'
# Helper class for managing document tracking functionalities, such as saving and processing documents.
class TrackHelper
@config_manager = ConfigurationManager.new
@proxy_manager = ProxyManager.new(config_manager: @config_manager)
@ -28,307 +31,318 @@ class TrackHelper
attr_reader :proxy_manager
end
@@document_command_url = TrackHelper.config_manager.document_server_command_uri.to_s
@document_command_url = TrackHelper.config_manager.document_server_command_uri.to_s
class << self
# read the request body
def self.read_body(request)
body = request.body.read
# read the request body
def read_body(request)
body = request.body.read
return '' if body.blank?
if body == nil || body.empty?
return ""
end
file_data = JSON.parse(body) # parse file data
file_data = JSON.parse(body) # parse file data
# check if a secret key to generate token exists or not
if JwtHelper.enabled? && JwtHelper.use_for_request
in_header = false
token = nil
jwt_header = TrackHelper.config_manager.jwt_header; # get the authorization header from the config
if file_data['token'] # if the token is in the body
token = JwtHelper.decode(file_data['token']) # decode a token into a payload object using a secret key
elsif request.headers[jwt_header] # if the token is in the header
hdr = request.headers[jwt_header]
hdr.slice!(0, 'Bearer '.length) # get token from it (after Bearer prefix)
token = JwtHelper.decode(hdr) # decode a token into a payload object using a secret key
in_header = true
else
raise('Expected JWT') # token missing error message
end
# check if a secret key to generate token exists or not
if JwtHelper.is_enabled && JwtHelper.use_for_request
inHeader = false
token = nil
jwtHeader = TrackHelper.config_manager.jwt_header; # get the authorization header from the config
if file_data["token"] # if the token is in the body
token = JwtHelper.decode(file_data["token"]) # decode a token into a payload object using a secret key
elsif request.headers[jwtHeader] # if the token is in the header
hdr = request.headers[jwtHeader]
hdr.slice!(0, "Bearer ".length) # get token from it (after Bearer prefix)
token = JwtHelper.decode(hdr) # decode a token into a payload object using a secret key
inHeader = true
else
raise "Expected JWT" # token missing error message
end
raise('Invalid JWT signature') if !token || token.eql?('')
if !token || token.eql?("")
raise "Invalid JWT signature"
end
file_data = JSON.parse(token)
file_data = JSON.parse(token)
if inHeader
file_data = file_data["payload"]
end
end
return file_data
end
def resolve_process_save_body(body)
copied = body.dup
url = copied['url']
if url
uri = URI(url)
resolved_uri = TrackHelper.proxy_manager.resolve_uri(uri)
copied['url'] = resolved_uri.to_s
end
changesurl = copied['changesurl']
if changesurl
uri = URI(changesurl)
resolved_uri = TrackHelper.proxy_manager.resolve_uri(uri)
copied['changesurl'] = resolved_uri.to_s
end
home = copied['home']
if home
copied['home'] = resolve_process_save_body(home)
end
copied
end
# file saving process
def process_save(raw_file_data, file_name, user_address)
file_data = resolve_process_save_body(raw_file_data)
download_uri = file_data['url']
if download_uri.eql?(nil)
saved = 1
return saved
end
new_file_name = file_name
download_ext = "."+file_data['filetype'] # get the extension of the downloaded file
cur_ext = File.extname(file_name).downcase # get current file extension
# convert downloaded file to the file with the current extension if these extensions aren't equal
unless cur_ext.eql?(download_ext)
key = ServiceConverter.generate_revision_id(download_uri) # get the document key
begin
percent, new_file_uri, new_file_type = ServiceConverter.get_converted_data(download_uri, download_ext.delete('.'), cur_ext.delete('.'), key, false, nil) # get the url of the converted file
if new_file_uri == nil || new_file_uri.empty?
new_file_name = DocumentHelper.get_correct_name(File.basename(file_name, cur_ext) + download_ext, user_address) # get the correct file name if it already exists
else
download_uri = new_file_uri
end
rescue StandardError => msg
new_file_name = DocumentHelper.get_correct_name(File.basename(file_name, cur_ext) + download_ext, user_address)
end
end
saved = 1
data = download_file(download_uri) # download document file
if data.eql?(nil)
return saved
end
begin
storage_path = DocumentHelper.storage_path(new_file_name, user_address) # get the storage directory of the new file
hist_dir = DocumentHelper.history_dir(storage_path) # get the history directory of the new file
ver_dir = DocumentHelper.version_dir(hist_dir, DocumentHelper.get_file_version(hist_dir)) # get the path to the specified file version
FileUtils.mkdir_p(ver_dir) # create the version directory if doesn't exist
FileUtils.move(DocumentHelper.storage_path(file_name, user_address), File.join(ver_dir, "prev#{cur_ext}")) # move the file from the storage directory to the previous file version directory
save_file(data, storage_path) # save the downloaded file to the storage directory
change_data = download_file(file_data["changesurl"]) # download file with document versions differences
save_file(change_data, File.join(ver_dir, "diff.zip")) # save file with document versions differences
hist_data = file_data["changeshistory"]
unless hist_data # if there are no changes in the history
hist_data = file_data["history"].to_json # write the original history information to the history data
end
if hist_data
File.open(File.join(ver_dir, "changes.json"), 'wb') do |file| # open the file with document changes
file.write(hist_data) # and write history data to this file
end
end
# write the key value to the key.txt file
File.open(File.join(ver_dir, "key.txt"), 'wb') do |file|
file.write(file_data["key"])
end
forcesave_path = DocumentHelper.forcesave_path(new_file_name, user_address, false) # get the path to the forcesaved file
unless forcesave_path.eql?("") # if this path is empty
File.delete(forcesave_path) # remove it
end
saved = 0
rescue StandardError => msg
saved = 1
end
saved
end
# file force saving process
def process_force_save(file_data, file_name, user_address)
download_uri = file_data['url']
if download_uri.eql?(nil)
saved = 1
return saved
end
download_ext = "."+file_data['filetype'] # get the extension of the downloaded file
cur_ext = File.extname(file_name).downcase # get current file extension
new_file_name = false
# convert downloaded file to the file with the current extension if these extensions aren't equal
unless cur_ext.eql?(download_ext)
key = ServiceConverter.generate_revision_id(download_uri) # get the document key
begin
percent, new_file_uri, new_file_type = ServiceConverter.get_converted_data(download_uri, download_ext.delete('.'), cur_ext.delete('.'), key, false, nil) # get the url of the converted file
if new_file_uri == nil || new_file_uri.empty?
new_file_name = true
else
download_uri = new_file_uri
end
rescue StandardError => msg
new_file_name = true
end
end
saved = 1
data = download_file(download_uri) # download document file
if data.eql?(nil)
return saved
end
begin
is_submit_form = file_data["forcesavetype"].to_i == 3 # check if the forcesave type is equal to 3 (the form was submitted)
if is_submit_form
if new_file_name
file_name = DocumentHelper.get_correct_name(File.basename(file_name, cur_ext) + "-form" + download_ext, user_address) # get the correct file name if it already exists
else
file_name = DocumentHelper.get_correct_name(File.basename(file_name, cur_ext) + "-form" + cur_ext, user_address)
end
forcesave_path = DocumentHelper.storage_path(file_name, user_address) # get the path to the new file
else
if new_file_name
file_name = DocumentHelper.get_correct_name(File.basename(file_name, cur_ext) + download_ext, user_address)
end
forcesave_path = DocumentHelper.forcesave_path(file_name, user_address, false)
if forcesave_path.eql?("")
forcesave_path = DocumentHelper.forcesave_path(file_name, user_address, true) # if the path to the new file doesn't exist, create it
end
end
save_file(data, forcesave_path) # save the downloaded file to the storage directory
if is_submit_form
uid = file_data['actions'][0]['userid']
DocumentHelper.create_meta(file_name, uid, "Filling Form", user_address) # create file meta information with the Filling form tag instead of user name
forms_data_url = file_data["formsdataurl"].to_s
if forms_data_url && !forms_data_url.eql?("")
forms_name = DocumentHelper.get_correct_name(File.basename(file_name, cur_ext) + ".txt", user_address)
forms_path = DocumentHelper.storage_path(forms_name, user_address)
forms = download_file(forms_data_url)
if forms.eql?(nil)
return saved
end
save_file(forms, forms_path)
else
raise 'Document editing service did not return formsDataUrl'
end
end
saved = 0
rescue StandardError => msg
saved = 1
end
saved
end
# send the command request
def command_request(method, key, meta = nil)
# create a payload object with the method and key
payload = {
:c => method,
:key => key
}
if (meta != nil)
payload.merge!({:meta => meta})
end
data = nil
begin
uri = URI.parse(@@document_command_url) # parse the document command url
http = Net::HTTP.new(uri.host, uri.port) # create a connection to the http server
DocumentHelper.verify_ssl(@@document_command_url, http)
req = Net::HTTP::Post.new(uri.request_uri) # create the post request
req.add_field("Content-Type", "application/json") # set headers
if JwtHelper.is_enabled && JwtHelper.use_for_request # if the signature is enabled
payload["token"] = JwtHelper.encode(payload) # get token and save it to the payload
jwtHeader = TrackHelper.config_manager.jwt_header; # get signature authorization header
req.add_field(jwtHeader, "Bearer #{JwtHelper.encode({ :payload => payload })}") # set it to the request with the Bearer prefix
end
req.body = payload.to_json # convert the payload object into the json format
res = http.request(req) # get the response
data = res.body # and take its body
rescue => ex
raise ex.message
end
json_data = JSON.parse(data) # convert the response body into the json format
return json_data
end
# save file from the url
def download_file(uristr)
uri = URI.parse(uristr) # parse the url string
http = Net::HTTP.new(uri.host, uri.port) # create a connection to the http server
http.open_timeout = 5
DocumentHelper.verify_ssl(uristr, http)
req = Net::HTTP::Get.new(uri)
res = http.request(req) # get the response
status_code = res.code
if status_code != '200' # checking status code
raise "Document editing service returned status: #{status_code}"
end
data = res.body # and take its body
if data == nil
raise 'stream is null'
end
data
end
def save_file(data, path)
File.open(path, 'wb') do |file| # open the file from the path specified
file.write(data) # and write the response data to it
end
end
file_data = file_data['payload'] if in_header
end
end
file_data
end
def self.resolve_process_save_body(body)
copied = body.dup
url = copied['url']
if url
uri = URI(url)
resolved_uri = TrackHelper.proxy_manager.resolve_uri(uri)
copied['url'] = resolved_uri.to_s
end
changesurl = copied['changesurl']
if changesurl
uri = URI(changesurl)
resolved_uri = TrackHelper.proxy_manager.resolve_uri(uri)
copied['changesurl'] = resolved_uri.to_s
end
home = copied['home']
copied['home'] = resolve_process_save_body(home) if home
copied
end
# file saving process
def self.process_save(raw_file_data, file_name, user_address)
file_data = resolve_process_save_body(raw_file_data)
download_uri = file_data['url']
if download_uri.eql?(nil)
saved = 1
return saved
end
new_file_name = file_name
download_ext = ".#{file_data['filetype']}" # get the extension of the downloaded file
cur_ext = File.extname(file_name).downcase # get current file extension
# convert downloaded file to the file with the current extension if these extensions aren't equal
unless cur_ext.eql?(download_ext)
key = ServiceConverter.generate_revision_id(download_uri) # get the document key
begin
_, new_file_uri, = ServiceConverter.get_converted_data(
download_uri,
download_ext.delete('.'),
cur_ext.delete('.'),
key,
false,
nil
) # get the url of the converted file
if new_file_uri.blank?
new_file_name = DocumentHelper.get_correct_name(
File.basename(file_name, cur_ext) + download_ext,
user_address
) # get the correct file name if it already exists
else
download_uri = new_file_uri
end
rescue StandardError
new_file_name = DocumentHelper.get_correct_name(
File.basename(file_name, cur_ext) + download_ext,
user_address
)
end
end
saved = 1
data = download_file(download_uri) # download document file
return saved if data.eql?(nil)
begin
# get the storage directory of the new file
storage_path = DocumentHelper.storage_path(new_file_name, user_address)
hist_dir = DocumentHelper.history_dir(storage_path) # get the history directory of the new file
# get the path to the specified file version
ver_dir = DocumentHelper.version_dir(hist_dir, DocumentHelper.get_file_version(hist_dir))
FileUtils.mkdir_p(ver_dir) # create the version directory if doesn't exist
# move the file from the storage directory to the previous file version directory
FileUtils.move(DocumentHelper.storage_path(file_name, user_address), File.join(ver_dir, "prev#{cur_ext}"))
save_file(data, storage_path) # save the downloaded file to the storage directory
change_data = download_file(file_data['changesurl']) # download file with document versions differences
save_file(change_data, File.join(ver_dir, 'diff.zip')) # save file with document versions differences
hist_data = file_data['changeshistory']
hist_data ||= file_data['history'].to_json
if hist_data
File.binwrite(File.join(ver_dir, 'changes.json'), hist_data)
end
# write the key value to the key.txt file
File.binwrite(File.join(ver_dir, 'key.txt'), file_data['key'])
# get the path to the forcesaved file
forcesave_path = DocumentHelper.forcesave_path(new_file_name, user_address, false)
unless forcesave_path.eql?('') # if this path is empty
File.delete(forcesave_path) # remove it
end
saved = 0
rescue StandardError
saved = 1
end
saved
end
# file force saving process
def self.process_force_save(file_data, file_name, user_address)
download_uri = file_data['url']
if download_uri.eql?(nil)
saved = 1
return saved
end
download_ext = ".#{file_data['filetype']}" # get the extension of the downloaded file
cur_ext = File.extname(file_name).downcase # get current file extension
new_file_name = false
# convert downloaded file to the file with the current extension if these extensions aren't equal
unless cur_ext.eql?(download_ext)
key = ServiceConverter.generate_revision_id(download_uri) # get the document key
begin
_, new_file_uri, = ServiceConverter.get_converted_data(
download_uri,
download_ext.delete('.'),
cur_ext.delete('.'),
key,
false,
nil
) # get the url of the converted file
if new_file_uri.blank?
new_file_name = true
else
download_uri = new_file_uri
end
rescue StandardError
new_file_name = true
end
end
saved = 1
data = download_file(download_uri) # download document file
return saved if data.eql?(nil)
begin
# check if the forcesave type is equal to 3 (the form was submitted)
is_submit_form = Integer(file_data['forcesavetype'], 10) == 3
if is_submit_form
file_name = if new_file_name
DocumentHelper.get_correct_name(
"#{File.basename(file_name, cur_ext)}-form#{download_ext}",
user_address
) # get the correct file name if it already exists
else
DocumentHelper.get_correct_name(
"#{File.basename(file_name, cur_ext)}-form#{cur_ext}",
user_address
)
end
forcesave_path = DocumentHelper.storage_path(file_name, user_address) # get the path to the new file
else
if new_file_name
file_name = DocumentHelper.get_correct_name(
File.basename(file_name, cur_ext) + download_ext,
user_address
)
end
forcesave_path = DocumentHelper.forcesave_path(file_name, user_address, false)
if forcesave_path.eql?('')
# if the path to the new file doesn't exist, create it
forcesave_path = DocumentHelper.forcesave_path(file_name, user_address, true)
end
end
save_file(data, forcesave_path) # save the downloaded file to the storage directory
if is_submit_form
uid = file_data['actions'][0]['userid']
# create file meta information with the Filling form tag instead of user name
DocumentHelper.create_meta(file_name, uid, 'Filling Form', user_address)
forms_data_url = file_data['formsdataurl'].to_s
unless forms_data_url && !forms_data_url.eql?('')
raise('Document editing service did not return formsDataUrl')
end
forms_name = DocumentHelper.get_correct_name(
"#{File.basename(file_name, cur_ext)}.txt",
user_address
)
forms_path = DocumentHelper.storage_path(forms_name, user_address)
forms = download_file(forms_data_url)
if forms.eql?(nil)
return saved
end
save_file(forms, forms_path)
end
saved = 0
rescue StandardError
saved = 1
end
saved
end
# send the command request
def self.command_request(method, key, meta = nil)
# create a payload object with the method and key
payload = {
c: method,
key:
}
payload.merge!({ meta: }) unless meta.nil?
data = nil
begin
uri = URI.parse(@document_command_url) # parse the document command url
http = Net::HTTP.new(uri.host, uri.port) # create a connection to the http server
DocumentHelper.verify_ssl(@document_command_url, http)
req = Net::HTTP::Post.new(uri.request_uri) # create the post request
req.add_field('Content-Type', 'application/json') # set headers
if JwtHelper.enabled? && JwtHelper.use_for_request # if the signature is enabled
payload['token'] = JwtHelper.encode(payload) # get token and save it to the payload
jwt_header = TrackHelper.config_manager.jwt_header; # get signature authorization header
# set it to the request with the Bearer prefix
req.add_field(jwt_header, "Bearer #{JwtHelper.encode({ payload: })}")
end
req.body = payload.to_json # convert the payload object into the json format
res = http.request(req) # get the response
data = res.body # and take its body
rescue StandardError => e
raise(e.message)
end
JSON.parse(data) # convert the response body into the json format
end
# save file from the url
def self.download_file(uristr)
uri = URI.parse(uristr) # parse the url string
http = Net::HTTP.new(uri.host, uri.port) # create a connection to the http server
http.open_timeout = 5
DocumentHelper.verify_ssl(uristr, http)
req = Net::HTTP::Get.new(uri)
res = http.request(req) # get the response
status_code = res.code
raise("Document editing service returned status: #{status_code}") if status_code != '200' # checking status code
data = res.body # and take its body
raise('stream is null') if data.nil?
data
end
def self.save_file(data, path)
File.binwrite(path, data)
end
end

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
#
# (c) Copyright Ascensio System SIA 2023
#
@ -14,139 +16,201 @@
# limitations under the License.
#
# Represents a user with various attributes
class User
attr_accessor :id, :name, :email, :group, :reviewGroups, :commentGroups, :userInfoGroups, :favorite,
:deniedPermissions, :descriptions, :templates, :avatar
attr_accessor :id
attr_accessor :name
attr_accessor :email
attr_accessor :group
attr_accessor :review_groups
attr_accessor :comment_groups
attr_accessor :user_info_groups
attr_accessor :favorite
attr_accessor :denied_permissions
attr_accessor :descriptions
attr_accessor :templates
attr_accessor :avatar
def initialize (id, name, email, group, reviewGroups, commentGroups, userInfoGroups, favorite,
deniedPermissions, descriptions, templates, avatar)
@id = id
@name = name
@email = email
@group = group
@reviewGroups = reviewGroups
@commentGroups = commentGroups
@favorite = favorite
@deniedPermissions = deniedPermissions
@descriptions = descriptions
@templates = templates
@userInfoGroups = userInfoGroups
@avatar = avatar
end
def initialize(
id,
name,
email,
group,
review_groups,
comment_groups,
user_info_groups,
favorite,
denied_permissions,
descriptions,
templates,
avatar
)
@id = id
@name = name
@email = email
@group = group
@review_groups = review_groups
@comment_groups = comment_groups
@favorite = favorite
@denied_permissions = denied_permissions
@descriptions = descriptions
@templates = templates
@user_info_groups = user_info_groups
@avatar = avatar
end
end
# Manages user-related data and operations.
class Users
@@descr_user_1 = [
"File author by default",
"Doesnt belong to any group",
"Can review all the changes",
"Can perform all actions with comments",
"The file favorite state is undefined",
"Can create files from templates using data from the editor",
"Can see the information about all users",
"Has an avatar",
"Can submit forms",
];
@descr_user_first = [
'File author by default',
'Doesnt belong to any group',
'Can review all the changes',
'Can perform all actions with comments',
'The file favorite state is undefined',
'Can create files from templates using data from the editor',
'Can see the information about all users',
'Has an avatar',
'Can submit forms'
]
@@descr_user_2 = [
"Belongs to Group2",
"Can review only his own changes or changes made by users with no group",
"Can view comments, edit his own comments and comments left by users with no group. Can remove his own comments only",
"This file is marked as favorite",
"Can create new files from the editor",
"Can see the information about users from Group2 and users who dont belong to any group",
"Has an avatar",
"Can't submit forms",
];
@descr_user_second = [
'Belongs to Group2',
'Can review only his own changes or changes made by users with no group',
'Can view comments, edit his own comments, and comments left by users with no group. ' \
'Can remove his own comments only',
'This file is marked as favorite',
'Can create new files from the editor',
'Can see the information about users from Group2 and users who dont belong to any group',
'Has an avatar',
'Cant submit forms'
]
@@descr_user_3 = [
"Belongs to Group3",
"Can review changes made by Group2 users",
"Can view comments left by Group2 and Group3 users. Can edit comments left by the Group2 users",
"This file isnt marked as favorite",
"Cant copy data from the file to clipboard",
"Cant download the file",
"Cant print the file",
"Can create new files from the editor",
"Can see the information about Group2 users",
"Can't submit forms",
];
@descr_user_third = [
'Belongs to Group3',
'Can review changes made by Group2 users',
'Can view comments left by Group2 and Group3 users. Can edit comments left by the Group2 users',
'This file isnt marked as favorite',
'Cant copy data from the file to clipboard',
'Cant download the file',
'Cant print the file',
'Can create new files from the editor',
'Can see the information about Group2 users',
'Cant submit forms'
]
@@descr_user_0 = [
"The name is requested when the editor is opened",
"Doesnt belong to any group",
"Can review all the changes",
"Can perform all actions with comments",
"The file favorite state is undefined",
"Can't mention others in comments",
"Can't create new files from the editor",
"Cant see anyones information",
"Can't rename files from the editor",
"Can't view chat",
"Can't protect file",
"View file without collaboration",
"Can't submit forms"
];
@descr_user_null = [
'The name is requested when the editor is opened',
'Doesnt belong to any group',
'Can review all the changes',
'Can perform all actions with comments',
'The file favorite state is undefined',
"Can't mention others in comments",
"Can't create new files from the editor",
'Cant see anyones information',
"Can't rename files from the editor",
"Can't view chat",
"Can't protect file",
'View file without collaboration',
'Cant submit forms'
]
@@users = [
User.new("uid-1", "John Smith", "smith@example.com",
"", nil, {}, nil,
nil, [], @@descr_user_1, true, true),
User.new("uid-2", "Mark Pottato", "pottato@example.com",
"group-2", ["group-2", ""], {
:view => "",
:edit => ["group-2", ""],
:remove => ["group-2"]
},
["group-2", ""],
true, [], @@descr_user_2, false, true),
User.new("uid-3", "Hamish Mitchell", nil,
"group-3", ["group-2"], {
:view => ["group-3", "group-2"],
:edit => ["group-2"],
:remove => []
},
["group-2"],
false, ["copy", "download", "print"], @@descr_user_3, false, false),
User.new("uid-0", nil, nil,
"", nil, {}, [],
nil, ["protect"], @@descr_user_0, false, false)
]
@users = [
User.new(
'uid-1',
'John Smith',
'smith@example.com',
'',
nil,
{},
nil,
nil,
[],
@descr_user_first,
true,
true
),
User.new(
'uid-2',
'Mark Pottato',
'pottato@example.com',
'group-2',
['group-2', ''],
{
view: '',
edit: ['group-2', ''],
remove: ['group-2']
},
['group-2', ''],
true,
[],
@descr_user_second,
false,
true
),
User.new(
'uid-3',
'Hamish Mitchell',
nil,
'group-3',
['group-2'],
{
view: ['group-3', 'group-2'],
edit: ['group-2'],
remove: []
},
['group-2'],
false,
['copy', 'download', 'print'],
@descr_user_third,
false,
false
),
User.new(
'uid-0',
nil,
nil,
'',
nil,
{},
[],
nil,
['protect'],
@descr_user_null,
false,
false
)
]
class << self
def get_all_users() # get a list of all the users
@@users
end
def get_user(id) # get a user by id specified
for user in @@users do
if user.id.eql?(id)
return user
end
end
return @@users[0]
end
def get_users_for_mentions(id) # get a list of users with their names and emails for mentions
usersData = []
for user in @@users do
if (!user.id.eql?(id) && user.name != nil && user.email != nil)
usersData.push({:name => user.name, :email => user.email})
end
end
return usersData
end
def get_users_for_protect(id) # get a list of users with their id, names and emails for protect
users_data = []
for user in @@users do
if (!user.id.eql?(id) && user.name != nil)
users_data.push({id: user.id, name: user.name, email: user.email})
end
end
return users_data
end
def self.all_users
@users
end
# get a user by id specified
def self.get_user(id)
@users.each do |user|
return user if user.id.eql?(id)
end
end
@users[0]
end
# get a list of users with their names and emails for mentions
def self.get_users_for_mentions(id)
users_data = []
@users.each do |user|
if !user.id.eql?(id) && !user.name.nil? && !user.email.nil?
users_data.push({ name: user.name, email: user.email })
end
end
users_data
end
# get a list of users with their id, names and emails for protect
def self.get_users_for_protect(id)
users_data = []
@users.each do |user|
users_data.push({ id: user.id, name: user.name, email: user.email }) if !user.id.eql?(id) && !user.name.nil?
end
users_data
end
end

View File

@ -21,6 +21,7 @@ require 'sorbet-runtime'
require 'uri'
require_relative '../configuration/configuration'
# Class manages URI resolution, redirecting public URLs to private ones based on the configuration.
class ProxyManager
extend T::Sig
@ -32,21 +33,20 @@ class ProxyManager
sig { params(uri: URI::Generic).returns(URI::Generic) }
def resolve_uri(uri)
return uri unless refer_public_url(uri)
redirect_public_url(uri)
end
private
sig { params(uri: URI::Generic).returns(T::Boolean) }
def refer_public_url(uri)
private def refer_public_url(uri)
public_uri = @config_manager.document_server_public_uri
uri.scheme == public_uri.scheme &&
uri.host == public_uri.host &&
uri.port == public_uri.port
uri.host == public_uri.host &&
uri.port == public_uri.port
end
sig { params(uri: URI::Generic).returns(URI::Generic) }
def redirect_public_url(uri)
private def redirect_public_url(uri)
private_uri = @config_manager.document_server_private_uri
redirected_uri = uri
redirected_uri.scheme = private_uri.scheme

View File

@ -20,7 +20,9 @@
require 'test/unit'
require_relative 'proxy'
# Test case for ProxyManager resolving URIs that refer to public and non-public URLs.
class ProxyManagerRefersTests < Test::Unit::TestCase
# Mocked configuration manager for testing.
class MockedConfigurationManager < ConfigurationManager
def document_server_public_uri
URI('http://localhost')
@ -31,6 +33,7 @@ class ProxyManagerRefersTests < Test::Unit::TestCase
end
end
# Test case to ensure resolving a URI that refers to the public URI.
def test_resolves_a_uri_that_refers_to_the_public_uri
config_manager = MockedConfigurationManager.new
proxy_manager = ProxyManager.new(config_manager:)
@ -43,13 +46,16 @@ class ProxyManagerRefersTests < Test::Unit::TestCase
end
end
# Test case for ProxyManager resolving a URL that does not refer to the public URL.
class ProxyManagerDoesNotRefersTests < Test::Unit::TestCase
# Mocked configuration manager for testing.
class MockedConfigurationManager < ConfigurationManager
def document_server_public_uri
URI('http://localhost')
end
end
# Test case to ensure resolving a URL that does not refer to the public URL.
def test_resolves_a_url_that_does_not_refers_to_the_public_url
config_manager = MockedConfigurationManager.new
proxy_manager = ProxyManager.new(config_manager:)

View File

@ -27,6 +27,7 @@
var docEditor;
var config;
var versionHistory;
var innerAlert = function (message, inEditor) {
if (console && console.log)
@ -102,20 +103,20 @@
var onRequestInsertImage = function(event) {
docEditor.insertImage({ // insert an image into the file
"c": event.data.c,
<%= raw @file.get_insert_image %>
<%= raw @file.insert_image %>
})
};
// the user is trying to select document for comparing by clicking the Document from Storage button
var onRequestSelectDocument = function(event) {
var data = <%= raw @file.dataDocument.to_json %>;
var data = <%= raw @file.data_document.to_json %>;
data.c = event.data.c;
docEditor.setRequestedDocument(data); // select a document for comparing
};
// the user is trying to select recipients data by clicking the Mail merge button
var onRequestSelectSpreadsheet = function (event) {
var data = <%= raw @file.dataSpreadsheet.to_json %>;
var data = <%= raw @file.data_spreadsheet.to_json %>;
data.c = event.data.c;
docEditor.setRequestedSpreadsheet(data); // insert recipient data for mail merge into the file
};
@ -210,13 +211,34 @@
innerAlert(response.error)
return
}
document.location.reload();
onRequestHistory()
}
}
function onRequestHistory() {
fileData = <%= raw @file.to_json %>;
const req = new XMLHttpRequest()
req.open("POST", '/historyobj')
req.send(JSON.stringify(fileData))
req.onload = function () {
versionHistory = JSON.parse(req.response)
docEditor.refreshHistory(versionHistory.hist)
}
}
function onRequestHistoryData(event){
var ver = event.data;
var histData = versionHistory.histData;
docEditor.setHistoryData(histData[ver - 1]);
}
function onRequestHistoryClose(){
document.location.reload();
}
var сonnectEditor = function () {
config = <%= raw @file.get_config.to_json %>;
config = <%= raw @file.config.to_json %>;
config.width = "100%";
config.height = "100%";
@ -230,30 +252,16 @@
'onRequestInsertImage': onRequestInsertImage,
'onRequestSelectDocument': onRequestSelectDocument,
'onRequestSelectSpreadsheet': onRequestSelectSpreadsheet,
'onRequestRestore': onRequestRestore
'onRequestRestore': onRequestRestore,
'onRequestHistory': onRequestHistory,
'onRequestHistoryData': onRequestHistoryData,
'onRequestHistoryClose': onRequestHistoryClose
};
<%
history = @file.get_history
usersMentions = @file.get_users_mentions %>
usersMentions = @file.users_mentions %>
if (config.editorConfig.user.id) {
<% if history %>
// the user is trying to show the document version history
config.events['onRequestHistory'] = function () {
docEditor.refreshHistory(<%= raw history[:hist].to_json %>); // show the document version history
};
// the user is trying to click the specific document version in the document version history
config.events['onRequestHistoryData'] = function (event) {
var ver = event.data;
var histData = <%= raw history[:histData].to_json %>;
docEditor.setHistoryData(histData[ver - 1]); // send the link to the document for viewing the version history
};
// the user is trying to go back to the document from viewing the document version history
config.events['onRequestHistoryClose'] = function () {
document.location.reload();
};
<% end %>
// add mentions for not anonymous users
config.events['onRequestUsers'] = function (event) {
if (event && event.data){
@ -262,7 +270,7 @@
switch (c) {
case "info":
users = [];
var allUsers = <%= raw @file.get_users_info.to_json %>;
var allUsers = <%= raw @file.users_info.to_json %>;
for (var i = 0; i < event.data.id.length; i++) {
for (var j = 0; j < allUsers.length; j++) {
if (allUsers[j].id == event.data.id[i]) {
@ -273,10 +281,10 @@
}
break;
case "protect":
var users = <%= raw @file.get_users_protect.to_json %>;
var users = <%= raw @file.users_protect.to_json %>;
break;
default:
users = <%= raw @file.get_users_mentions.to_json %>;
users = <%= raw @file.users_mentions.to_json %>;
}
docEditor.setUsers({
"c": c,

View File

@ -62,7 +62,7 @@
<span class="select-user">Username</span>
<img id="info" class="info" data-id="user" src="assets/info.svg" />
<select class="select-user" id="user">
<% for user in Users.get_all_users() do %>
<% for user in Users.all_users() do %>
<option value="<%= user.id %>"><%= user.name ? user.name : "Anonymous" %></option>
<% end %>
</select>
@ -110,7 +110,7 @@
</span>
<span class="portal-descr">Please do NOT use this integration example on your own server without proper code modifications, it is intended for testing purposes only. In case you enabled this test example, disable it before going for production.</span>
<span class="portal-descr">You can open the same document using different users in different Web browser sessions, so you can check out multi-user editing functions.</span>
<% for user in Users.get_all_users() do %>
<% for user in Users.all_users() do %>
<div class="user-descr">
<b><%= user.name ? user.name : "Anonymous" %></b>
<ul>

View File

@ -34,6 +34,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no, minimal-ui" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="server-version" content="<%= ConfigurationManager.new.version %>" />
</head>
<body>

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
require_relative 'config/application'
Rails.application.initialize!

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
require_relative 'boot'
require 'active_model/railtie'
@ -9,11 +11,12 @@ Bundler.require(*Rails.groups)
require 'securerandom'
# Configuration for the Rails application.
class Application < Rails::Application
config.middleware.insert_before 0, Rack::Cors do
config.middleware.insert_before(0, Rack::Cors) do
allow do
origins '*'
resource '*', headers: :any, methods: %i[get post patch delete put options]
resource '*', headers: :any, methods: [:get, :post, :patch, :delete, :put, :options]
end
end
@ -31,6 +34,7 @@ class Application < Rails::Application
match '/asset', to: 'home#assets', via: 'get'
match '/download', to: 'home#download', via: 'get'
match '/downloadhistory', to: 'home#downloadhistory', via: 'get'
match '/historyobj', to: 'home#historyobj', via: 'post'
match '/editor', to: 'home#editor', via: 'get'
match '/files', to: 'home#files', via: 'get'
match '/index', to: 'home#index', via: 'get'

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
require 'bundler/setup'