diff --git a/.gitignore b/.gitignore index f3cef92b8a1..ba34bff1df8 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ Logs/ .eslintcache build/deploy/ /public/debuginfo.md +TestsResults/ diff --git a/.gitmodules b/.gitmodules index fb7ab6880bd..681950e4c45 100644 --- a/.gitmodules +++ b/.gitmodules @@ -2,3 +2,6 @@ path = products/ASC.Files/Server/DocStore url = https://github.com/ONLYOFFICE/document-templates branch = main/community-server +[submodule "common/Tests/Frontend.Translations.Tests/dictionaries"] + path = common/Tests/Frontend.Translations.Tests/dictionaries + url = https://github.com/ONLYOFFICE/dictionaries diff --git a/build/install/OneClickInstall/install-Debian.sh b/build/install/OneClickInstall/install-Debian.sh index d73049cf982..b00d4ee8e75 100644 --- a/build/install/OneClickInstall/install-Debian.sh +++ b/build/install/OneClickInstall/install-Debian.sh @@ -37,6 +37,13 @@ while [ "$1" != "" ]; do fi ;; + -skiphc | --skiphardwarecheck ) + if [ "$2" != "" ]; then + SKIP_HARDWARE_CHECK=$2 + shift + fi + ;; + -? | -h | --help ) echo " Usage $0 [PARAMETER] [[PARAMETER], ...]" echo " Parameters:" @@ -60,6 +67,10 @@ if [ -z "${LOCAL_SCRIPTS}" ]; then LOCAL_SCRIPTS="false"; fi +if [ -z "${SKIP_HARDWARE_CHECK}" ]; then + SKIP_HARDWARE_CHECK="false"; +fi + if [ $(dpkg-query -W -f='${Status}' curl 2>/dev/null | grep -c "ok installed") -eq 0 ]; then apt-get update; apt-get install -yq curl; diff --git a/build/install/OneClickInstall/install-Debian/install-preq.sh b/build/install/OneClickInstall/install-Debian/install-preq.sh index 2785e9b9f3d..5ae630b3dea 100644 --- a/build/install/OneClickInstall/install-Debian/install-preq.sh +++ b/build/install/OneClickInstall/install-Debian/install-preq.sh @@ -61,7 +61,7 @@ if [ "$(ls -A "$PRODUCT_DIR/services/kafka" 2> /dev/null)" == "" ]; then adduser --quiet --home ${PRODUCT_DIR}/services/kafka --system kafka fi cd ${PRODUCT_DIR}/services/kafka - curl https://downloads.apache.org/kafka/2.7.1/kafka_2.13-2.7.1.tgz -O + curl https://downloads.apache.org/kafka/2.7.2/kafka_2.13-2.7.2.tgz -O tar xzf kafka_*.tgz --strip 1 && rm -rf kafka_*.tgz chown -R kafka ${PRODUCT_DIR}/services/kafka cd - diff --git a/build/install/OneClickInstall/install-Debian/tools.sh b/build/install/OneClickInstall/install-Debian/tools.sh index c1631d0f0fe..f8a939253d7 100644 --- a/build/install/OneClickInstall/install-Debian/tools.sh +++ b/build/install/OneClickInstall/install-Debian/tools.sh @@ -6,6 +6,37 @@ command_exists () { type "$1" &> /dev/null; } +check_hardware () { + DISK_REQUIREMENTS=40960; + MEMORY_REQUIREMENTS=5500; + CORE_REQUIREMENTS=2; + + AVAILABLE_DISK_SPACE=$(df -m / | tail -1 | awk '{ print $4 }'); + + if [ ${AVAILABLE_DISK_SPACE} -lt ${DISK_REQUIREMENTS} ]; then + echo "Minimal requirements are not met: need at least $DISK_REQUIREMENTS MB of free HDD space" + exit 1; + fi + + TOTAL_MEMORY=$(free -m | grep -oP '\d+' | head -n 1); + + if [ ${TOTAL_MEMORY} -lt ${MEMORY_REQUIREMENTS} ]; then + echo "Minimal requirements are not met: need at least $MEMORY_REQUIREMENTS MB of RAM" + exit 1; + fi + + CPU_CORES_NUMBER=$(cat /proc/cpuinfo | grep processor | wc -l); + + if [ ${CPU_CORES_NUMBER} -lt ${CORE_REQUIREMENTS} ]; then + echo "The system does not meet the minimal hardware requirements. CPU with at least $CORE_REQUIREMENTS cores is required" + exit 1; + fi +} + +if [ "$SKIP_HARDWARE_CHECK" != "true" ]; then + check_hardware +fi + ARCH="$(dpkg --print-architecture)" if [ "$ARCH" != "amd64" ]; then echo "ONLYOFFICE ${product^^} doesn't support architecture '$ARCH'" diff --git a/build/install/OneClickInstall/install-RedHat.sh b/build/install/OneClickInstall/install-RedHat.sh index 16c5ba82a94..c82f0c5a772 100644 --- a/build/install/OneClickInstall/install-RedHat.sh +++ b/build/install/OneClickInstall/install-RedHat.sh @@ -47,6 +47,13 @@ while [ "$1" != "" ]; do fi ;; + -skiphc | --skiphardwarecheck ) + if [ "$2" != "" ]; then + SKIP_HARDWARE_CHECK=$2 + shift + fi + ;; + -? | -h | --help ) echo " Usage $0 [PARAMETER] [[PARAMETER], ...]" echo " Parameters:" @@ -69,6 +76,10 @@ if [ -z "${LOCAL_SCRIPTS}" ]; then LOCAL_SCRIPTS="false"; fi +if [ -z "${SKIP_HARDWARE_CHECK}" ]; then + SKIP_HARDWARE_CHECK="false"; +fi + cat > /etc/yum.repos.d/onlyoffice.repo < /dev/null)" == "" ]; then mkdir -p ${PRODUCT_DIR}/services/ getent passwd kafka >/dev/null || useradd -m -d ${PRODUCT_DIR}/services/kafka -s /sbin/nologin -p kafka kafka cd ${PRODUCT_DIR}/services/kafka - curl https://downloads.apache.org/kafka/2.7.1/kafka_2.13-2.7.1.tgz -O + curl https://downloads.apache.org/kafka/2.7.2/kafka_2.13-2.7.2.tgz -O tar xzf kafka_*.tgz --strip 1 && rm -rf kafka_*.tgz chown -R kafka ${PRODUCT_DIR}/services/kafka cd - diff --git a/build/install/OneClickInstall/install-RedHat/tools.sh b/build/install/OneClickInstall/install-RedHat/tools.sh new file mode 100644 index 00000000000..f17268e7e03 --- /dev/null +++ b/build/install/OneClickInstall/install-RedHat/tools.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +set -e + +check_hardware () { + DISK_REQUIREMENTS=40960; + MEMORY_REQUIREMENTS=5500; + CORE_REQUIREMENTS=2; + + AVAILABLE_DISK_SPACE=$(df -m / | tail -1 | awk '{ print $4 }'); + + if [ ${AVAILABLE_DISK_SPACE} -lt ${DISK_REQUIREMENTS} ]; then + echo "Minimal requirements are not met: need at least $DISK_REQUIREMENTS MB of free HDD space" + exit 1; + fi + + TOTAL_MEMORY=$(free -m | grep -oP '\d+' | head -n 1); + + if [ ${TOTAL_MEMORY} -lt ${MEMORY_REQUIREMENTS} ]; then + echo "Minimal requirements are not met: need at least $MEMORY_REQUIREMENTS MB of RAM" + exit 1; + fi + + CPU_CORES_NUMBER=$(cat /proc/cpuinfo | grep processor | wc -l); + + if [ ${CPU_CORES_NUMBER} -lt ${CORE_REQUIREMENTS} ]; then + echo "The system does not meet the minimal hardware requirements. CPU with at least $CORE_REQUIREMENTS cores is required" + exit 1; + fi +} + +if [ "$SKIP_HARDWARE_CHECK" != "true" ]; then + check_hardware +fi diff --git a/build/run.e2e.test.bat b/build/run.e2e.test.bat new file mode 100644 index 00000000000..4df80e2300b --- /dev/null +++ b/build/run.e2e.test.bat @@ -0,0 +1,71 @@ +@echo off + +PUSHD %~dp0 +call runasadmin.bat "%~dpnx0" + +if %errorlevel% == 0 ( +PUSHD %~dp0.. + + +echo "mode=" + + +REM call yarn wipe +call yarn install + +REM call yarn build +call yarn build:test + +REM call yarn wipe +call yarn deploy + + +REM copy nginx configurations to deploy folder +xcopy config\nginx\onlyoffice.conf build\deploy\nginx\ /E /R /Y +powershell -Command "(gc build\deploy\nginx\onlyoffice.conf) -replace '#', '' | Out-File -encoding ASCII build\deploy\nginx\onlyoffice.conf" + +xcopy config\nginx\sites-enabled\* build\deploy\nginx\sites-enabled\ /E /R /Y + +REM fix paths +powershell -Command "(gc build\deploy\nginx\sites-enabled\onlyoffice-calendar.conf) -replace 'ROOTPATH', '%~dp0deploy\products\ASC.Calendar\client' -replace '\\', '/' | Out-File -encoding ASCII build\deploy\nginx\sites-enabled\onlyoffice-calendar.conf" +powershell -Command "(gc build\deploy\nginx\sites-enabled\onlyoffice-crm.conf) -replace 'ROOTPATH', '%~dp0deploy\products\ASC.CRM\client' -replace '\\', '/' | Out-File -encoding ASCII build\deploy\nginx\sites-enabled\onlyoffice-crm.conf" +powershell -Command "(gc build\deploy\nginx\sites-enabled\onlyoffice-editor.conf) -replace 'ROOTPATH', '%~dp0deploy\products\ASC.Files\editor' -replace '\\', '/' | Out-File -encoding ASCII build\deploy\nginx\sites-enabled\onlyoffice-editor.conf" +powershell -Command "(gc build\deploy\nginx\sites-enabled\onlyoffice-files.conf) -replace 'ROOTPATH', '%~dp0deploy\products\ASC.Files\client' -replace '\\', '/' | Out-File -encoding ASCII build\deploy\nginx\sites-enabled\onlyoffice-files.conf" +powershell -Command "(gc build\deploy\nginx\sites-enabled\onlyoffice-login.conf) -replace 'ROOTPATH', '%~dp0deploy\studio\login' -replace '\\', '/' | Out-File -encoding ASCII build\deploy\nginx\sites-enabled\onlyoffice-login.conf" +powershell -Command "(gc build\deploy\nginx\sites-enabled\onlyoffice-mail.conf) -replace 'ROOTPATH', '%~dp0deploy\products\ASC.Mail\client' -replace '\\', '/' | Out-File -encoding ASCII build\deploy\nginx\sites-enabled\onlyoffice-mail.conf" +powershell -Command "(gc build\deploy\nginx\sites-enabled\onlyoffice-people.conf) -replace 'ROOTPATH', '%~dp0deploy\products\ASC.People\client' -replace '\\', '/' | Out-File -encoding ASCII build\deploy\nginx\sites-enabled\onlyoffice-people.conf" +powershell -Command "(gc build\deploy\nginx\sites-enabled\onlyoffice-projects.conf) -replace 'ROOTPATH', '%~dp0deploy\products\ASC.Projects\client' -replace '\\', '/' | Out-File -encoding ASCII build\deploy\nginx\sites-enabled\onlyoffice-projects.conf" +powershell -Command "(gc build\deploy\nginx\sites-enabled\onlyoffice-studio.conf) -replace 'ROOTPATH', '%~dp0deploy\studio\client' -replace '\\', '/' | Out-File -encoding ASCII build\deploy\nginx\sites-enabled\onlyoffice-studio.conf" + +REM restart nginx +echo service nginx stop +call sc stop nginx > nul + +REM sleep 5 seconds +call ping 127.0.0.1 -n 6 > nul + +echo service nginx start +call sc start nginx > nul + +REM sleep 5 seconds +call ping 127.0.0.1 -n 6 > nul + + +choice /t 10 /d Y /m "Do you want to start tests in sequential mode?" +if errorlevel 2 call yarn e2e.test +if errorlevel 1 call yarn e2e.test:sequential + + +if NOT %errorlevel% == 0 ( + echo Couldn't restarte Onlyoffice%%~nf service +) + +) + +echo. + +POPD + +if "%1"=="nopause" goto start +pause +:start \ No newline at end of file diff --git a/build/run.e2e.translations.tests.bat b/build/run.e2e.translations.tests.bat new file mode 100644 index 00000000000..aea5cf212c5 --- /dev/null +++ b/build/run.e2e.translations.tests.bat @@ -0,0 +1,71 @@ +@echo off + +PUSHD %~dp0 +call runasadmin.bat "%~dpnx0" + +if %errorlevel% == 0 ( +PUSHD %~dp0.. + + +echo "mode=" + + +REM call yarn wipe +call yarn install + +REM call yarn build +call yarn build:test.translation + +REM call yarn wipe +call yarn deploy + + +REM copy nginx configurations to deploy folder +xcopy config\nginx\onlyoffice.conf build\deploy\nginx\ /E /R /Y +powershell -Command "(gc build\deploy\nginx\onlyoffice.conf) -replace '#', '' | Out-File -encoding ASCII build\deploy\nginx\onlyoffice.conf" + +xcopy config\nginx\sites-enabled\* build\deploy\nginx\sites-enabled\ /E /R /Y + +REM fix paths +powershell -Command "(gc build\deploy\nginx\sites-enabled\onlyoffice-calendar.conf) -replace 'ROOTPATH', '%~dp0deploy\products\ASC.Calendar\client' -replace '\\', '/' | Out-File -encoding ASCII build\deploy\nginx\sites-enabled\onlyoffice-calendar.conf" +powershell -Command "(gc build\deploy\nginx\sites-enabled\onlyoffice-crm.conf) -replace 'ROOTPATH', '%~dp0deploy\products\ASC.CRM\client' -replace '\\', '/' | Out-File -encoding ASCII build\deploy\nginx\sites-enabled\onlyoffice-crm.conf" +powershell -Command "(gc build\deploy\nginx\sites-enabled\onlyoffice-editor.conf) -replace 'ROOTPATH', '%~dp0deploy\products\ASC.Files\editor' -replace '\\', '/' | Out-File -encoding ASCII build\deploy\nginx\sites-enabled\onlyoffice-editor.conf" +powershell -Command "(gc build\deploy\nginx\sites-enabled\onlyoffice-files.conf) -replace 'ROOTPATH', '%~dp0deploy\products\ASC.Files\client' -replace '\\', '/' | Out-File -encoding ASCII build\deploy\nginx\sites-enabled\onlyoffice-files.conf" +powershell -Command "(gc build\deploy\nginx\sites-enabled\onlyoffice-login.conf) -replace 'ROOTPATH', '%~dp0deploy\studio\login' -replace '\\', '/' | Out-File -encoding ASCII build\deploy\nginx\sites-enabled\onlyoffice-login.conf" +powershell -Command "(gc build\deploy\nginx\sites-enabled\onlyoffice-mail.conf) -replace 'ROOTPATH', '%~dp0deploy\products\ASC.Mail\client' -replace '\\', '/' | Out-File -encoding ASCII build\deploy\nginx\sites-enabled\onlyoffice-mail.conf" +powershell -Command "(gc build\deploy\nginx\sites-enabled\onlyoffice-people.conf) -replace 'ROOTPATH', '%~dp0deploy\products\ASC.People\client' -replace '\\', '/' | Out-File -encoding ASCII build\deploy\nginx\sites-enabled\onlyoffice-people.conf" +powershell -Command "(gc build\deploy\nginx\sites-enabled\onlyoffice-projects.conf) -replace 'ROOTPATH', '%~dp0deploy\products\ASC.Projects\client' -replace '\\', '/' | Out-File -encoding ASCII build\deploy\nginx\sites-enabled\onlyoffice-projects.conf" +powershell -Command "(gc build\deploy\nginx\sites-enabled\onlyoffice-studio.conf) -replace 'ROOTPATH', '%~dp0deploy\studio\client' -replace '\\', '/' | Out-File -encoding ASCII build\deploy\nginx\sites-enabled\onlyoffice-studio.conf" + +REM restart nginx +echo service nginx stop +call sc stop nginx > nul + +REM sleep 5 seconds +call ping 127.0.0.1 -n 6 > nul + +echo service nginx start +call sc start nginx > nul + +REM sleep 5 seconds +call ping 127.0.0.1 -n 6 > nul + +call yarn e2e.test:translation + +exit + + + +if NOT %errorlevel% == 0 ( + echo Couldn't restarte Onlyoffice%%~nf service +) + +) + +echo. + +POPD + +if "%1"=="nopause" goto start +pause +:start \ No newline at end of file diff --git a/build/run.translations.spellcheck.test.bat b/build/run.translations.spellcheck.test.bat new file mode 100644 index 00000000000..1364b5228d8 --- /dev/null +++ b/build/run.translations.spellcheck.test.bat @@ -0,0 +1,2 @@ +PUSHD %~dp0.. +dotnet test common\Tests\Frontend.Translations.Tests\Frontend.Translations.Tests.csproj --filter Name~SpellCheckTest -l:html -r TestsResults \ No newline at end of file diff --git a/build/run.translations.tests.bat b/build/run.translations.tests.bat new file mode 100644 index 00000000000..780e016dc67 --- /dev/null +++ b/build/run.translations.tests.bat @@ -0,0 +1,2 @@ +PUSHD %~dp0.. +dotnet test common\Tests\Frontend.Translations.Tests\Frontend.Translations.Tests.csproj --filter "TestCategory=FastRunning" -l:html -r TestsResults \ No newline at end of file diff --git a/common/ASC.Common/Web/MimeMapping.cs b/common/ASC.Common/Web/MimeMapping.cs index e3cdf33aa06..eeefcc7d187 100644 --- a/common/ASC.Common/Web/MimeMapping.cs +++ b/common/ASC.Common/Web/MimeMapping.cs @@ -158,7 +158,9 @@ static MimeMapping() AddMimeMapping(".doc", "application/msword"); AddMimeMapping(".docm", "application/vnd.ms-word.document.macroEnabled.12"); AddMimeMapping(".doct", "application/doct"); - AddMimeMapping(".docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"); + AddMimeMapping(".docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"); + AddMimeMapping(".docxf", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"); + AddMimeMapping(".docxf", "application/vnd.openxmlformats-officedocument.wordprocessingml.document.docxf"); AddMimeMapping(".dot", "application/msword"); AddMimeMapping(".dotm", "application/vnd.ms-word.template.macroEnabled.12"); AddMimeMapping(".dotx", "application/vnd.openxmlformats-officedocument.wordprocessingml.template"); @@ -429,7 +431,9 @@ static MimeMapping() AddMimeMapping(".oda", "application/oda"); AddMimeMapping(".odp", "application/vnd.oasis.opendocument.presentation"); AddMimeMapping(".ods", "application/vnd.oasis.opendocument.spreadsheet"); - AddMimeMapping(".odt", "application/vnd.oasis.opendocument.text"); + AddMimeMapping(".odt", "application/vnd.oasis.opendocument.text"); + AddMimeMapping(".oform", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"); + AddMimeMapping(".oform", "application/vnd.openxmlformats-officedocument.wordprocessingml.document.oform"); AddMimeMapping(".oga", "audio/ogg"); AddMimeMapping(".ogg", "video/ogg"); AddMimeMapping(".ogg", "audio/ogg"); @@ -710,7 +714,8 @@ static MimeMapping() AddMimeMapping(".wml", "text/vnd.wap.wml"); AddMimeMapping(".wmlc", "application/vnd.wap.wmlc"); AddMimeMapping(".wmls", "text/vnd.wap.wmlscript"); - AddMimeMapping(".wmlsc", "application/vnd.wap.wmlscriptc"); + AddMimeMapping(".wmlsc", "application/vnd.wap.wmlscriptc"); + AddMimeMapping(".woff2", "application/font-woff2"); AddMimeMapping(".word", "application/msword"); AddMimeMapping(".wp", "application/wordperfect"); AddMimeMapping(".wp5", "application/wordperfect"); diff --git a/common/ASC.Core.Common/Billing/License/License.cs b/common/ASC.Core.Common/Billing/License/License.cs index bb9205f898c..a20073bc137 100644 --- a/common/ASC.Core.Common/Billing/License/License.cs +++ b/common/ASC.Core.Common/Billing/License/License.cs @@ -65,7 +65,16 @@ public class License public string Signature { get; set; } - public bool? DiscEncryption { get; set; } + public bool? DiscEncryption { get; set; } + + [JsonPropertyName("users_count")] + public int DSUsersCount { get; set; } + + [JsonPropertyName("users_expire")] + public int DSUsersExpire { get; set; } + + [JsonPropertyName("connections")] + public int DSConnections { get; set; } public static License Parse(string licenseString) { diff --git a/common/ASC.Core.Common/Caching/CachedTenantService.cs b/common/ASC.Core.Common/Caching/CachedTenantService.cs index 04fc243e34b..1aa26b16daa 100644 --- a/common/ASC.Core.Common/Caching/CachedTenantService.cs +++ b/common/ASC.Core.Common/Caching/CachedTenantService.cs @@ -223,7 +223,11 @@ public IEnumerable GetTenants(string login, string passwordHash) public IEnumerable GetTenants(DateTime from, bool active = true) { return Service.GetTenants(from, active); - } + } + public IEnumerable GetTenants(List ids) + { + return Service.GetTenants(ids); + } public Tenant GetTenant(int id) { diff --git a/common/ASC.Core.Common/Caching/CachedUserService.cs b/common/ASC.Core.Common/Caching/CachedUserService.cs index 6674eeecc23..93037b012b1 100644 --- a/common/ASC.Core.Common/Caching/CachedUserService.cs +++ b/common/ASC.Core.Common/Caching/CachedUserService.cs @@ -61,7 +61,7 @@ public UserServiceCache( ICacheNotify cacheUserInfoItem, ICacheNotify cacheUserPhotoItem, ICacheNotify cacheGroupCacheItem, - ICacheNotify cacheUserGroupRefItem, + ICacheNotify cacheUserGroupRefItem, ICache cache) { TrustInterval = new TrustInterval(); @@ -302,7 +302,11 @@ private UserInfo GetUserForPersonal(int tenant, Guid id) public UserInfo GetUserByPasswordHash(int tenant, string login, string passwordHash) { return Service.GetUserByPasswordHash(tenant, login, passwordHash); - } + } + public IEnumerable GetUsersAllTenants(IEnumerable userIds) + { + return Service.GetUsersAllTenants(userIds); + } public UserInfo SaveUser(int tenant, UserInfo user) { diff --git a/common/ASC.Core.Common/Context/Impl/TenantManager.cs b/common/ASC.Core.Common/Context/Impl/TenantManager.cs index 5f7315fc568..1247aec31d8 100644 --- a/common/ASC.Core.Common/Context/Impl/TenantManager.cs +++ b/common/ASC.Core.Common/Context/Impl/TenantManager.cs @@ -155,7 +155,11 @@ public TenantManager( public List GetTenants(bool active = true) { return TenantService.GetTenants(default, active).ToList(); - } + } + public List GetTenants(List ids) + { + return TenantService.GetTenants(ids).ToList(); + } public Tenant GetTenant(int tenantId) { diff --git a/common/ASC.Core.Common/Core/ITenantService.cs b/common/ASC.Core.Common/Core/ITenantService.cs index 7c913d957f2..d7961a71904 100644 --- a/common/ASC.Core.Common/Core/ITenantService.cs +++ b/common/ASC.Core.Common/Core/ITenantService.cs @@ -39,7 +39,9 @@ public interface ITenantService { void ValidateDomain(string domain); - IEnumerable GetTenants(DateTime from, bool active = true); + IEnumerable GetTenants(DateTime from, bool active = true); + + IEnumerable GetTenants(List ids); IEnumerable GetTenants(string login, string passwordHash); diff --git a/common/ASC.Core.Common/Core/IUserService.cs b/common/ASC.Core.Common/Core/IUserService.cs index 1d5d53eefd2..330574e00d7 100644 --- a/common/ASC.Core.Common/Core/IUserService.cs +++ b/common/ASC.Core.Common/Core/IUserService.cs @@ -91,6 +91,8 @@ IQueryable GetUsers(int tenant, bool isAdmin, UserGroupRef SaveUserGroupRef(int tenant, UserGroupRef r); - void RemoveUserGroupRef(int tenant, Guid userId, Guid groupId, UserGroupRefType refType); + void RemoveUserGroupRef(int tenant, Guid userId, Guid groupId, UserGroupRefType refType); + + IEnumerable GetUsersAllTenants(IEnumerable userIds); } } diff --git a/common/ASC.Core.Common/Data/DbTenantService.cs b/common/ASC.Core.Common/Data/DbTenantService.cs index d16b4297209..245f4dd04fa 100644 --- a/common/ASC.Core.Common/Data/DbTenantService.cs +++ b/common/ASC.Core.Common/Data/DbTenantService.cs @@ -151,7 +151,14 @@ public IEnumerable GetTenants(DateTime from, bool active = true) } return q.Select(FromDbTenantToTenant).ToList(); - } + } + + public IEnumerable GetTenants(List ids) + { + var q = TenantsQuery(); + + return q.Where(r => ids.Contains(r.Id) && r.Status == TenantStatus.Active).Select(FromDbTenantToTenant).ToList(); + } public IEnumerable GetTenants(string login, string passwordHash) { @@ -245,7 +252,7 @@ public Tenant GetTenant(int id) public Tenant GetTenant(string domain) { if (string.IsNullOrEmpty(domain)) throw new ArgumentNullException("domain"); - + domain = domain.ToLowerInvariant(); return TenantsQuery() diff --git a/common/ASC.Core.Common/Data/DbUserService.cs b/common/ASC.Core.Common/Data/DbUserService.cs index a5531a084a4..829de8330e1 100644 --- a/common/ASC.Core.Common/Data/DbUserService.cs +++ b/common/ASC.Core.Common/Data/DbUserService.cs @@ -299,6 +299,14 @@ public UserInfo GetUserByPasswordHash(int tenant, string login, string passwordH } } + public IEnumerable GetUsersAllTenants(IEnumerable userIds) + { + var q = UserDbContext.Users + .Where(r => userIds.Contains(r.Id)) + .Where(r => !r.Removed); + return q.Select(FromUserToUserInfo).ToList(); + } + //todo: remove private void RegeneratePassword(int tenant, Guid userId) { diff --git a/common/ASC.Core.Common/EF/Model/FilesConverts.cs b/common/ASC.Core.Common/EF/Model/FilesConverts.cs index b1877edc77c..1c4a4477fa9 100644 --- a/common/ASC.Core.Common/EF/Model/FilesConverts.cs +++ b/common/ASC.Core.Common/EF/Model/FilesConverts.cs @@ -34,6 +34,20 @@ public static ModelBuilderWrapper AddFilesConverts(this ModelBuilderWrapper mode new FilesConverts { Input = ".docx", Output = ".pdf" }, new FilesConverts { Input = ".docx", Output = ".rtf" }, new FilesConverts { Input = ".docx", Output = ".txt" }, + + new FilesConverts { Input = ".docx", Output = ".docxf" }, + new FilesConverts { Input = ".docxf", Output = ".docx" }, + new FilesConverts { Input = ".docxf", Output = ".dotx" }, + new FilesConverts { Input = ".docxf", Output = ".epub" }, + new FilesConverts { Input = ".docxf", Output = ".fb2" }, + new FilesConverts { Input = ".docxf", Output = ".html" }, + new FilesConverts { Input = ".docxf", Output = ".odt" }, + new FilesConverts { Input = ".docxf", Output = ".oform" }, + new FilesConverts { Input = ".docxf", Output = ".ott" }, + new FilesConverts { Input = ".docxf", Output = ".pdf" }, + new FilesConverts { Input = ".docxf", Output = ".rtf" }, + new FilesConverts { Input = ".docxf", Output = ".txt" }, + new FilesConverts { Input = ".dot", Output = ".docx" }, new FilesConverts { Input = ".dot", Output = ".odt" }, new FilesConverts { Input = ".dot", Output = ".pdf" }, diff --git a/common/ASC.Core.Common/HostedSolution.cs b/common/ASC.Core.Common/HostedSolution.cs index ad6c7e3091a..371269fc21c 100644 --- a/common/ASC.Core.Common/HostedSolution.cs +++ b/common/ASC.Core.Common/HostedSolution.cs @@ -273,9 +273,12 @@ public void SetTariff(int tenant, Tariff tariff) public void SaveButton(int tariffId, string partnerId, string buttonUrl) { TariffService.SaveButton(tariffId, partnerId, buttonUrl); + } + public IEnumerable FindUsers(IEnumerable userIds) + { + return UserService.GetUsersAllTenants(userIds); } - private Tenant AddRegion(Tenant tenant) { if (tenant != null) diff --git a/common/ASC.Core.Common/Migrations/MySql/FilesDbContextMySql/20210309094212_FilesDbContextMySql.Designer.cs b/common/ASC.Core.Common/Migrations/MySql/FilesDbContextMySql/20210309094212_FilesDbContextMySql.Designer.cs index 9de687df45d..b41f77da589 100644 --- a/common/ASC.Core.Common/Migrations/MySql/FilesDbContextMySql/20210309094212_FilesDbContextMySql.Designer.cs +++ b/common/ASC.Core.Common/Migrations/MySql/FilesDbContextMySql/20210309094212_FilesDbContextMySql.Designer.cs @@ -127,7 +127,19 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) { Input = ".docx", Output = ".txt" - }, + }, + new { Input = ".docx", Output = ".docxf" }, + new { Input = ".docxf", Output = ".docx" }, + new { Input = ".docxf", Output = ".dotx" }, + new { Input = ".docxf", Output = ".epub" }, + new { Input = ".docxf", Output = ".fb2" }, + new { Input = ".docxf", Output = ".html" }, + new { Input = ".docxf", Output = ".odt" }, + new { Input = ".docxf", Output = ".oform" }, + new { Input = ".docxf", Output = ".ott" }, + new { Input = ".docxf", Output = ".pdf" }, + new { Input = ".docxf", Output = ".rtf" }, + new { Input = ".docxf", Output = ".txt" }, new { Input = ".dot", diff --git a/common/ASC.Core.Common/Migrations/MySql/FilesDbContextMySql/20210309094212_FilesDbContextMySql.cs b/common/ASC.Core.Common/Migrations/MySql/FilesDbContextMySql/20210309094212_FilesDbContextMySql.cs index a96446cbf86..39cd2738b67 100644 --- a/common/ASC.Core.Common/Migrations/MySql/FilesDbContextMySql/20210309094212_FilesDbContextMySql.cs +++ b/common/ASC.Core.Common/Migrations/MySql/FilesDbContextMySql/20210309094212_FilesDbContextMySql.cs @@ -115,7 +115,19 @@ protected override void Up(MigrationBuilder migrationBuilder) { ".dotm", ".rtf" }, { ".epub", ".docx" }, { ".docx", ".txt" }, - { ".docx", ".pdf" }, + { ".docx", ".pdf" }, + { ".docx", ".docxf" }, + { ".docxf",".docx" }, + { ".docxf",".dotx" }, + { ".docxf",".epub" }, + { ".docxf",".fb2" }, + { ".docxf",".html" }, + { ".docxf",".odt" }, + { ".docxf",".oform" }, + { ".docxf",".ott" }, + { ".docxf",".pdf" }, + { ".docxf",".rtf" }, + { ".docxf",".txt" }, { ".csv", ".pdf" }, { ".csv", ".xlsx" }, { ".doc", ".docx" }, diff --git a/common/ASC.Core.Common/Migrations/MySql/FilesDbContextMySql/MySqlFilesDbContextModelSnapshot.cs b/common/ASC.Core.Common/Migrations/MySql/FilesDbContextMySql/MySqlFilesDbContextModelSnapshot.cs index d803143ceba..408e939c13c 100644 --- a/common/ASC.Core.Common/Migrations/MySql/FilesDbContextMySql/MySqlFilesDbContextModelSnapshot.cs +++ b/common/ASC.Core.Common/Migrations/MySql/FilesDbContextMySql/MySqlFilesDbContextModelSnapshot.cs @@ -125,7 +125,19 @@ protected override void BuildModel(ModelBuilder modelBuilder) { Input = ".docx", Output = ".txt" - }, + }, + new { Input = ".docx", Output = ".docxf" }, + new { Input = ".docxf", Output = ".docx" }, + new { Input = ".docxf", Output = ".dotx" }, + new { Input = ".docxf", Output = ".epub" }, + new { Input = ".docxf", Output = ".fb2" }, + new { Input = ".docxf", Output = ".html" }, + new { Input = ".docxf", Output = ".odt" }, + new { Input = ".docxf", Output = ".oform" }, + new { Input = ".docxf", Output = ".ott" }, + new { Input = ".docxf", Output = ".pdf" }, + new { Input = ".docxf", Output = ".rtf" }, + new { Input = ".docxf", Output = ".txt" }, new { Input = ".dot", diff --git a/common/ASC.Core.Common/Migrations/Npgsql/FilesDbContextNpgsql/20200929101551_FilesDbContextNpgsql.Designer.cs b/common/ASC.Core.Common/Migrations/Npgsql/FilesDbContextNpgsql/20200929101551_FilesDbContextNpgsql.Designer.cs index 2a81df0a8ae..f61df8a3348 100644 --- a/common/ASC.Core.Common/Migrations/Npgsql/FilesDbContextNpgsql/20200929101551_FilesDbContextNpgsql.Designer.cs +++ b/common/ASC.Core.Common/Migrations/Npgsql/FilesDbContextNpgsql/20200929101551_FilesDbContextNpgsql.Designer.cs @@ -191,7 +191,19 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) { Input = ".dotx", Ouput = ".pdf" - }, + }, + new { Input = ".docx", Output = ".docxf" }, + new { Input = ".docxf", Output = ".docx" }, + new { Input = ".docxf", Output = ".dotx" }, + new { Input = ".docxf", Output = ".epub" }, + new { Input = ".docxf", Output = ".fb2" }, + new { Input = ".docxf", Output = ".html" }, + new { Input = ".docxf", Output = ".odt" }, + new { Input = ".docxf", Output = ".oform" }, + new { Input = ".docxf", Output = ".ott" }, + new { Input = ".docxf", Output = ".pdf" }, + new { Input = ".docxf", Output = ".rtf" }, + new { Input = ".docxf", Output = ".txt" }, new { Input = ".dotx", diff --git a/common/ASC.Core.Common/Migrations/Npgsql/FilesDbContextNpgsql/20200929101551_FilesDbContextNpgsql.cs b/common/ASC.Core.Common/Migrations/Npgsql/FilesDbContextNpgsql/20200929101551_FilesDbContextNpgsql.cs index 8fb029609a5..81961ba4044 100644 --- a/common/ASC.Core.Common/Migrations/Npgsql/FilesDbContextNpgsql/20200929101551_FilesDbContextNpgsql.cs +++ b/common/ASC.Core.Common/Migrations/Npgsql/FilesDbContextNpgsql/20200929101551_FilesDbContextNpgsql.cs @@ -118,7 +118,19 @@ protected override void Up(MigrationBuilder migrationBuilder) { ".dotm", ".rtf" }, { ".epub", ".docx" }, { ".docx", ".txt" }, - { ".docx", ".pdf" }, + { ".docx", ".pdf" }, + { ".docx", ".docxf" }, + { ".docxf",".docx" }, + { ".docxf",".dotx" }, + { ".docxf",".epub" }, + { ".docxf",".fb2" }, + { ".docxf",".html" }, + { ".docxf",".odt" }, + { ".docxf",".oform" }, + { ".docxf",".ott" }, + { ".docxf",".pdf" }, + { ".docxf",".rtf" }, + { ".docxf",".txt" }, { ".csv", ".pdf" }, { ".csv", ".xlsx" }, { ".doc", ".docx" }, diff --git a/common/ASC.Core.Common/Migrations/Npgsql/FilesDbContextNpgsql/FilesDbContextModelSnapshot.cs b/common/ASC.Core.Common/Migrations/Npgsql/FilesDbContextNpgsql/FilesDbContextModelSnapshot.cs index fbe654ae228..7ca6b4049b7 100644 --- a/common/ASC.Core.Common/Migrations/Npgsql/FilesDbContextNpgsql/FilesDbContextModelSnapshot.cs +++ b/common/ASC.Core.Common/Migrations/Npgsql/FilesDbContextNpgsql/FilesDbContextModelSnapshot.cs @@ -124,7 +124,19 @@ protected override void BuildModel(ModelBuilder modelBuilder) { Input = ".docx", Ouput = ".txt" - }, + }, + new { Input = ".docx", Output = ".docxf" }, + new { Input = ".docxf", Output = ".docx" }, + new { Input = ".docxf", Output = ".dotx" }, + new { Input = ".docxf", Output = ".epub" }, + new { Input = ".docxf", Output = ".fb2" }, + new { Input = ".docxf", Output = ".html" }, + new { Input = ".docxf", Output = ".odt" }, + new { Input = ".docxf", Output = ".oform" }, + new { Input = ".docxf", Output = ".ott" }, + new { Input = ".docxf", Output = ".pdf" }, + new { Input = ".docxf", Output = ".rtf" }, + new { Input = ".docxf", Output = ".txt" }, new { Input = ".dot", diff --git a/common/Tests/Frontend.Translations.Tests/Frontend.Translations.Tests.csproj b/common/Tests/Frontend.Translations.Tests/Frontend.Translations.Tests.csproj index 8c2f652f433..f45df7687af 100644 --- a/common/Tests/Frontend.Translations.Tests/Frontend.Translations.Tests.csproj +++ b/common/Tests/Frontend.Translations.Tests/Frontend.Translations.Tests.csproj @@ -7,9 +7,12 @@ + + + diff --git a/common/Tests/Frontend.Translations.Tests/LocalesTest.cs b/common/Tests/Frontend.Translations.Tests/LocalesTest.cs index cc3115448db..91ba7b0e985 100644 --- a/common/Tests/Frontend.Translations.Tests/LocalesTest.cs +++ b/common/Tests/Frontend.Translations.Tests/LocalesTest.cs @@ -1,14 +1,23 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; +using System.Security.Cryptography; +using System.Text; using System.Text.RegularExpressions; +using Frontend.Translations.Tests.Models; + using Newtonsoft.Json; using Newtonsoft.Json.Linq; using NUnit.Framework; +using UtfUnknown; + +using WeCantSpell.Hunspell; + namespace Frontend.Translations.Tests { public class Tests @@ -17,7 +26,7 @@ public static string BasePath { get { - return "..\\..\\..\\..\\..\\..\\"; + return Path.GetFullPath("..\\..\\..\\..\\..\\..\\"); } } @@ -28,10 +37,15 @@ public static string BasePath public List ModuleFolders { get; set; } public List> NotTranslatedToasts { get; set; } public List CommonTranslations { get; set; } + public List ParseJsonErrors { get; set; } + public List WrongEncodingJsonErrors { get; set; } - [SetUp] + [OneTimeSetUp] public void Setup() { + ParseJsonErrors = new List(); + WrongEncodingJsonErrors = new List(); + var packageJsonPath = Path.Combine(BasePath, @"package.json"); var jsonPackage = JObject.Parse(File.ReadAllText(packageJsonPath)); @@ -49,31 +63,63 @@ public void Setup() var translationFiles = from wsPath in Workspaces let clientDir = Path.Combine(BasePath, wsPath) - from file in Directory.EnumerateFiles(clientDir, "*.json", SearchOption.AllDirectories) - where file.Contains("public\\locales\\") - select file; + from filePath in Directory.EnumerateFiles(clientDir, "*.json", SearchOption.AllDirectories) + where filePath.Contains("public\\locales\\") + select Path.GetFullPath(filePath); TranslationFiles = new List(); foreach (var path in translationFiles) { - var jsonTranslation = JObject.Parse(File.ReadAllText(path)); + try + { + var result = CharsetDetector.DetectFromFile(path); - var translationFile = new TranslationFile(path, jsonTranslation.Properties() - .Select(p => new TranslationItem(p.Name, (string)p.Value)) - .ToList()); + if (result.Detected.EncodingName != "utf-8" + && result.Detected.EncodingName != "ascii") + { + WrongEncodingJsonErrors.Add( + new JsonEncodingError(path, result.Detected)); + } + + using (var md5 = MD5.Create()) + { + using (var stream = File.OpenRead(path)) + { + var hash = md5.ComputeHash(stream); + var md5hash = BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant(); - TranslationFiles.Add(translationFile); + stream.Position = 0; - /* Re-write by order */ + using var sr = new StreamReader(stream, Encoding.UTF8); + { + var jsonTranslation = JObject.Parse(sr.ReadToEnd()); - //var orderedList = jsonTranslation.Properties().OrderBy(t => t.Name); + var translationFile = new TranslationFile(path, jsonTranslation.Properties() + .Select(p => new TranslationItem(p.Name, (string)p.Value)) + .ToList(), md5hash); - //var result = new JObject(orderedList); + TranslationFiles.Add(translationFile); + } + + } + } - //var sortedJsonString = JsonConvert.SerializeObject(result, Formatting.Indented); + /* Re-write by order */ - //File.WriteAllText(path, sortedJsonString); + //var orderedList = jsonTranslation.Properties().OrderBy(t => t.Name); + + //var result = new JObject(orderedList); + + //var sortedJsonString = JsonConvert.SerializeObject(result, Formatting.Indented); + + //File.WriteAllText(path, sortedJsonString); + } + catch (Exception ex) + { + ParseJsonErrors.Add(new ParseJsonError(path, ex)); + Debug.WriteLine($"File path = {path} failed to parse with error: {ex.Message}"); + } } var javascriptFiles = (from wsPath in Workspaces @@ -204,7 +250,112 @@ from file in Directory.EnumerateFiles(clientDir, "*.jsx", SearchOption.AllDirect } [Test] - public void FullDublicatesTest() + [Category("FastRunning")] + public void ParseJsonTest() + { + Assert.AreEqual(0, ParseJsonErrors.Count, string.Join("\r\n", ParseJsonErrors.Select(e => $"File path = '{e.Path}' failed to parse with error: '{e.Exception.Message}'"))); + } + + [Test] + [Category("LongRunning")] + public void SpellCheckTest() + { + const string dictionariesPath = @"..\..\..\dictionaries"; + var i = 0; + var errorsCount = 0; + var message = $"Next keys have spell check issues:\r\n\r\n"; + + //var list = new List(); + + var groupByLng = TranslationFiles + .GroupBy(t => t.Language) + .Select(g => new + { + Language = g.Key, + Files = g.ToList() + }) + .ToList(); + + foreach (var group in groupByLng) + { + try + { + var language = SpellCheck.GetDictionaryLanguage(group.Language); + + //var spellCheckExclude = new SpellCheckExclude(group.Language); + + using (var dictionaryStream = File.OpenRead(Path.Combine(dictionariesPath, language, $"{language}.dic"))) + using (var affixStream = File.OpenRead(Path.Combine(dictionariesPath, language, $"{language}.aff"))) + { + var dictionary = WordList.CreateFromStreams(dictionaryStream, affixStream); + + foreach (var g in group.Files) + { + foreach (var item in g.Translations) + { + var result = SpellCheck.HasSpellIssues(item.Value, group.Language, dictionary); + + if (result.HasProblems) + { + message += $"{++i}. lng='{group.Language}' file='{g.FilePath}'\r\nkey='{item.Key}' value='{item.Value}'\r\nIncorrect words:\r\n{string.Join("\r\n", result.SpellIssues.Select(issue => $"'{issue.Word}' Suggestion: '{issue.Suggestions.FirstOrDefault()}'"))}\r\n\r\n"; + errorsCount++; + + + /*foreach (var word in result.SpellIssues + .Where(issue => issue.Suggestions.Any()) + .Select(issue => issue.Word)) + { + if (!spellCheckExclude.Excludes.Contains(word)) + { + spellCheckExclude.Excludes.Add(word); + } + }*/ + } + } + } + } + + //spellCheckExclude.Excludes.Sort(); + + //list.Add(spellCheckExclude); + } + catch (NotSupportedException) + { + // Skip not supported + continue; + } + } + + //string json = JsonConvert.SerializeObject(list, Formatting.Indented); + //File.WriteAllText("../../../spellcheck-excludes.json", json); + + Assert.AreEqual(0, errorsCount, message); + } + + [Test] + [Category("FastRunning")] + public void DublicatesFilesByMD5HashTest() + { + var skipHashes = new List() { + "bcba174a8dadc0ff97f37f9a2d816d88", + "2a506ed97d0fbd0858192b755ae122d0", + "ec73989085d4e1b984c1c9dca10524da" + }; + + var duplicatesByMD5 = TranslationFiles + .Where(t => !skipHashes.Contains(t.Md5Hash)) + .GroupBy(t => t.Md5Hash) + .Where(grp => grp.Count() > 1) + .Select(grp => new { Key = grp.Key, Count = grp.Count(), Paths = grp.ToList().Select(f => f.FilePath) }) + .OrderByDescending(itm => itm.Count) + .ToList(); + + Assert.AreEqual(0, duplicatesByMD5.Count, "Dublicates by MD5 hash:\r\n" + string.Join("\r\n", duplicatesByMD5.Select(d => $"\r\nMD5='{d.Key}':\r\n{string.Join("\r\n", d.Paths.Select(p => p))}'"))); + } + + [Test] + [Category("FastRunning")] + public void FullEnDublicatesTest() { var fullEnDuplicates = TranslationFiles .Where(file => file.Language == "en") @@ -220,7 +371,8 @@ public void FullDublicatesTest() } [Test] - public void DublicatesByContentTest() + [Category("FastRunning")] + public void EnDublicatesByContentTest() { var allRuTranslations = TranslationFiles .Where(file => file.Language == "ru") @@ -298,6 +450,7 @@ public static void SaveNotFoundLanguage(string existJsonPath, string notExistJso } [Test] + [Category("FastRunning")] public void NotAllLanguageTranslatedTest() { var groupedByLng = TranslationFiles @@ -331,7 +484,7 @@ public void NotAllLanguageTranslatedTest() { var lng = incompleteList[i]; - message += $"{i}. {lng.Issue}\r\n"; + message += $"\r\n\r\n{i}. {lng.Issue}\r\n"; var lngFilePaths = lng.Files.Select(f => f.FilePath).ToList(); @@ -374,6 +527,7 @@ public static void SaveNotFoundKeys(string pathToJson, List newKeys) } [Test] + [Category("FastRunning")] public void NotTranslatedKeysTest() { var message = $"Next languages are not equal 'en' by translated keys count:\r\n\r\n"; @@ -420,6 +574,7 @@ public void NotTranslatedKeysTest() } [Test] + [Category("FastRunning")] public void NotFoundKeysTest() { var allEnKeys = TranslationFiles @@ -430,7 +585,7 @@ public void NotFoundKeysTest() var allJsTranslationKeys = JavaScriptFiles .Where(f => !f.Path.Contains("Banner.js")) // skip Banner.js (translations from firebase) .SelectMany(j => j.TranslationKeys) - .Select(k => k.Replace("Common:", "").Replace("Translations:", "").Replace("Home:", "")) + .Select(k => k.Substring(k.IndexOf(":") + 1)) .Distinct(); var notFoundJsKeys = allJsTranslationKeys.Except(allEnKeys); @@ -441,6 +596,7 @@ public void NotFoundKeysTest() } [Test] + [Category("FastRunning")] public void UselessTranslationKeysTest() { var allEnKeys = TranslationFiles @@ -451,7 +607,7 @@ public void UselessTranslationKeysTest() var allJsTranslationKeys = JavaScriptFiles .SelectMany(j => j.TranslationKeys) - .Select(k => k.Replace("Common:", "").Replace("Translations:", "")) + .Select(k => k.Substring(k.IndexOf(":") + 1)) .Where(k => !k.StartsWith("Culture_")) .Distinct(); @@ -463,6 +619,7 @@ public void UselessTranslationKeysTest() } [Test] + [Category("FastRunning")] public void UselessModuleTranslationKeysTest() { var notFoundi18nKeys = new List>>(); @@ -528,7 +685,7 @@ public void UselessModuleTranslationKeysTest() { var list = lng.Translations .Select(t => t.Key) - .Except(notCommonKeys.Select(k => k.Replace("Translations:", ""))) + .Except(notCommonKeys.Select(k => k.Substring(k.IndexOf(":") + 1))) .ToList(); if (!list.Any()) @@ -544,6 +701,7 @@ public void UselessModuleTranslationKeysTest() } [Test] + [Category("FastRunning")] public void NotTranslatedCommonKeysTest() { var message = $"Some i18n-keys are not found in COMMON translations: \r\nKeys: \r\n\r\n"; @@ -642,6 +800,7 @@ public string GetWorkspace(string path) } [Test] + [Category("FastRunning")] public void EmptyValueKeysTest() { // Uncomment if new keys are available @@ -745,6 +904,7 @@ public void EmptyValueKeysTest() } [Test] + [Category("FastRunning")] public void LanguageTranslatedPercentTest() { var message = $"Next languages translated less then 100%:\r\n\r\n"; @@ -781,8 +941,8 @@ public void LanguageTranslatedPercentTest() exists = true; var translated = lng.TotalKeysCount == expectedTotalKeysCount - ? Math.Round(100f - (lng.EmptyKeysCount * 100f / expectedTotalKeysCount)) - : Math.Round(lng.TotalKeysCount * 100f / expectedTotalKeysCount); + ? Math.Round(100f - (lng.EmptyKeysCount * 100f / expectedTotalKeysCount), 1) + : Math.Round(lng.TotalKeysCount * 100f / expectedTotalKeysCount, 1); message += $"{++i}. Language '{lng.Language}' translated by '{translated}%'\r\n"; } @@ -791,6 +951,7 @@ public void LanguageTranslatedPercentTest() } [Test] + [Category("FastRunning")] public void NotTranslatedToastsTest() { var message = $"Next text not translated in toasts:\r\n\r\n"; @@ -813,6 +974,7 @@ public void NotTranslatedToastsTest() } [Test] + [Category("FastRunning")] public void WrongTranslationVariablesTest() { var message = $"Next keys have wrong variables:\r\n\r\n"; @@ -889,6 +1051,74 @@ public void WrongTranslationVariablesTest() Assert.AreEqual(0, errorsCount, message); } + [Test] + [Category("FastRunning")] + public void TranslationsEncodingTest() + { + /*//Convert to UTF-8 + foreach (var issue in WrongEncodingJsonErrors) + { + if (issue.DetectionDetail.Encoding == null) + continue; + + ConvertFileEncoding(issue.Path, issue.Path, issue.DetectionDetail.Encoding, Encoding.UTF8); + }*/ + + var message = $"Next files have encoding issues:\r\n\r\n"; + + Assert.AreEqual(0, WrongEncodingJsonErrors.Count, + message + string.Join("\r\n", WrongEncodingJsonErrors + .Select(e => $"File path = '{e.Path}' potentially wrong file encoding: {e.DetectionDetail.EncodingName}"))); + } + + /// + /// Converts a file from one encoding to another. + /// + /// the file to convert + /// the destination for the converted file + /// the original file encoding + /// the encoding to which the contents should be converted + public static void ConvertFileEncoding(string sourcePath, string destPath, + Encoding sourceEncoding, Encoding destEncoding) + { + // If the destination�s parent doesn�t exist, create it. + var parent = Path.GetDirectoryName(Path.GetFullPath(destPath)); + if (!Directory.Exists(parent)) + { + Directory.CreateDirectory(parent); + } + // If the source and destination encodings are the same, just copy the file. + if (sourceEncoding == destEncoding) + { + File.Copy(sourcePath, destPath, true); + return; + } + // Convert the file. + string tempName = null; + try + { + tempName = Path.GetTempFileName(); + using (StreamReader sr = new StreamReader(sourcePath, sourceEncoding, false)) + { + using (StreamWriter sw = new StreamWriter(tempName, false, destEncoding)) + { + int charsRead; + char[] buffer = new char[128 * 1024]; + while ((charsRead = sr.ReadBlock(buffer, 0, buffer.Length)) > 0) + { + sw.Write(buffer, 0, charsRead); + } + } + } + File.Delete(destPath); + File.Move(tempName, destPath); + } + finally + { + File.Delete(tempName); + } + } + /*[Test] public void TempTest() { diff --git a/common/Tests/Frontend.Translations.Tests/Models/JsonEncodingError.cs b/common/Tests/Frontend.Translations.Tests/Models/JsonEncodingError.cs new file mode 100644 index 00000000000..5f8235ad9f9 --- /dev/null +++ b/common/Tests/Frontend.Translations.Tests/Models/JsonEncodingError.cs @@ -0,0 +1,17 @@ +using UtfUnknown; + +namespace Frontend.Translations.Tests +{ + public class JsonEncodingError + { + public DetectionDetail DetectionDetail { get; } + + public string Path { get; } + + public JsonEncodingError(string path, DetectionDetail detail) + { + Path = path; + DetectionDetail = detail; + } + } +} diff --git a/common/Tests/Frontend.Translations.Tests/Models/ParseJsonError.cs b/common/Tests/Frontend.Translations.Tests/Models/ParseJsonError.cs new file mode 100644 index 00000000000..82d2c4095e2 --- /dev/null +++ b/common/Tests/Frontend.Translations.Tests/Models/ParseJsonError.cs @@ -0,0 +1,17 @@ +using System; + +namespace Frontend.Translations.Tests +{ + public class ParseJsonError + { + public Exception Exception { get; } + + public string Path { get; } + + public ParseJsonError(string path, Exception ex) + { + Path = path; + Exception = ex; + } + } +} diff --git a/common/Tests/Frontend.Translations.Tests/Models/SpellCheckExclude.cs b/common/Tests/Frontend.Translations.Tests/Models/SpellCheckExclude.cs new file mode 100644 index 00000000000..89e409f47dc --- /dev/null +++ b/common/Tests/Frontend.Translations.Tests/Models/SpellCheckExclude.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; + +namespace Frontend.Translations.Tests.Models +{ + public class SpellCheckExclude + { + public List Excludes { get; set; } + public string Language { get; set; } + public SpellCheckExclude(string language) + { + Language = language; + Excludes = new List(); + } + } +} diff --git a/common/Tests/Frontend.Translations.Tests/Models/SpellCheckResult.cs b/common/Tests/Frontend.Translations.Tests/Models/SpellCheckResult.cs new file mode 100644 index 00000000000..02d385a5b13 --- /dev/null +++ b/common/Tests/Frontend.Translations.Tests/Models/SpellCheckResult.cs @@ -0,0 +1,77 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; + +using Newtonsoft.Json; + +namespace Frontend.Translations.Tests.Models +{ + public class SpellCheckResult + { + private static Regex wordRegex = new Regex(@"[\p{L}-]+", RegexOptions.Multiline | RegexOptions.Compiled); + private static Regex regVariables = new Regex("\\{\\{([^\\{].?[^\\}]+)\\}\\}", RegexOptions.Compiled | RegexOptions.Multiline); + private static Regex htmlTags = new Regex("<[^>]*>", RegexOptions.Compiled | RegexOptions.Multiline); + private static List trademarks = new List() + { + "onlyoffice.com", "onlyoffice.eu", "Office Open XML", "ONLYOFFICE Desktop Editors", + "ONLYOFFICE Desktop", "ONLYOFFICE Documents", "Google Drive", "Twitter", "Facebook", "LinkedIn", "Google", + "Yandex", "Yandex.Disk", "Dropbox","OneDrive","ONLYOFFICE", "DocuSign", "e-mail", + "SharePoint", "Windows Phone", "Enterprise Edition", "AES-256" + }; + private static List exclusions = new List() + { + "ok","doc","docx","xls","xlsx","ppt","pptx","xml","ooxml","jpg","png","mb","ip", + "canvas","tag","Disk","Box","Dcs","zip","Android","Authenticator","iOS","Windows", + "Web","oform","WebDAV","kDrive", "Punycode","logo","sms","html","LDAP", + "Portal","Favicon","URL","QR", "email", "app", "api" + }; + + private static List excludes = File.Exists("../../../spellcheck-excludes.json") + ? JsonConvert.DeserializeObject>(File.ReadAllText("../../../spellcheck-excludes.json")) + : new List(); + + public SpellCheckResult(string text, string language) + { + Text = text; + Language = language; + + var sanitizedText = htmlTags.Replace(text, string.Empty); + + sanitizedText = regVariables.Replace(sanitizedText, string.Empty); + + foreach (var trademark in trademarks) + sanitizedText = sanitizedText.Replace(trademark, string.Empty); + + var lngExcludes = excludes + .Where(ex => ex.Language == language) + .SelectMany(ex => ex.Excludes) + .ToList(); + + Words = wordRegex.Matches(sanitizedText) + .Select(m => m.Value.Trim('-')) + .Where(w => !string.IsNullOrEmpty(w) + && !exclusions.Exists(t => + t.Equals(w, System.StringComparison.InvariantCultureIgnoreCase)) + && !lngExcludes.Exists(t => + t.Equals(w, System.StringComparison.InvariantCultureIgnoreCase))) + .ToList(); + + SpellIssues = new List(); + } + + public string Text { get; } + public string Language { get; } + public List Words { get; } + + public List SpellIssues { get; set; } + + public bool HasProblems + { + get + { + return SpellIssues.Any(); + } + } + } +} diff --git a/common/Tests/Frontend.Translations.Tests/Models/SpellIssue.cs b/common/Tests/Frontend.Translations.Tests/Models/SpellIssue.cs new file mode 100644 index 00000000000..e616e037111 --- /dev/null +++ b/common/Tests/Frontend.Translations.Tests/Models/SpellIssue.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; + +namespace Frontend.Translations.Tests.Models +{ + public class SpellIssue + { + public SpellIssue(string word, IEnumerable suggestions) + { + Word = word; + Suggestions = suggestions; + } + + public string Word { get; } + public IEnumerable Suggestions { get; } + } +} diff --git a/common/Tests/Frontend.Translations.Tests/Models/TranslationFile.cs b/common/Tests/Frontend.Translations.Tests/Models/TranslationFile.cs index 66d6f9cbd0a..46c23eead5b 100644 --- a/common/Tests/Frontend.Translations.Tests/Models/TranslationFile.cs +++ b/common/Tests/Frontend.Translations.Tests/Models/TranslationFile.cs @@ -5,7 +5,7 @@ namespace Frontend.Translations.Tests { public class TranslationFile { - public TranslationFile(string path, List translations) + public TranslationFile(string path, List translations, string md5hash = null) { FilePath = path.Replace("/", "\\"); @@ -14,6 +14,8 @@ public TranslationFile(string path, List translations) Language = Directory.GetParent(FilePath).Name; //FilePath.Substring(FilePath.IndexOf("locales\\") + 8, 2); Translations = translations; + + Md5Hash = md5hash; } public string FilePath { get; } @@ -23,5 +25,7 @@ public TranslationFile(string path, List translations) public string Language { get; } public List Translations { get; } + + public string Md5Hash { get; } } } diff --git a/common/Tests/Frontend.Translations.Tests/SpellCheck.cs b/common/Tests/Frontend.Translations.Tests/SpellCheck.cs new file mode 100644 index 00000000000..e3e776bda35 --- /dev/null +++ b/common/Tests/Frontend.Translations.Tests/SpellCheck.cs @@ -0,0 +1,88 @@ +using System; + +using WeCantSpell.Hunspell; + +namespace Frontend.Translations.Tests +{ + public static class SpellCheck + { + public static Models.SpellCheckResult HasSpellIssues(string text, string language, WordList dictionary) + { + var result = new Models.SpellCheckResult(text, language); + + foreach (var word in result.Words) + { + if (!dictionary.Check(word)) + { + result.SpellIssues.Add(new Models.SpellIssue(word, dictionary.Suggest(word))); + } + } + + return result; + } + + public static string GetDictionaryLanguage(string lng) + { + // az,bg,cs,de,el,en,en-US,es,fi,fr,it,ja,ko,lo,lv,nl,pl,pt,pt-BR,ro,ru,sk,sl,tr,uk,vi,zh-CN + switch (lng) + { + case "az": + return "az_Latn_AZ"; + case "bg": + return "bg_BG"; + case "cs": + return "cs_CZ"; + case "de": + return "de_DE"; + case "el": + return "el_GR"; + case "en": + return "en_GB"; + case "en-US": + return "en_US"; + case "es": + return "es_ES"; + //case "fi": + // return ""; + case "fr": + return "fr_FR"; + case "it": + return "it_IT"; + //case "ja": + // return ""; + case "ko": + return "ko_KR"; + //case "lo": + // return ""; + case "lv": + return "lv_LV"; + case "nl": + return "nl_NL"; + case "pl": + return "pl_PL"; + case "pt": + return "pt_PT"; + case "pt-BR": + return "pt_BR"; + case "ro": + return "ro_RO"; + case "ru": + return "ru_RU"; + case "sk": + return "sk_SK"; + case "sl": + return "sl_SI"; + case "tr": + return "tr_TR"; + case "uk": + return "uk_UA"; + case "vi": + return "vi_VN"; + //case "zh-CN": + // return ""; + default: + throw new NotSupportedException(); + } + } + } +} diff --git a/common/Tests/Frontend.Translations.Tests/dictionaries b/common/Tests/Frontend.Translations.Tests/dictionaries new file mode 160000 index 00000000000..8222c8ca5d0 --- /dev/null +++ b/common/Tests/Frontend.Translations.Tests/dictionaries @@ -0,0 +1 @@ +Subproject commit 8222c8ca5d017973a76197551958f4aac0f75f71 diff --git a/common/Tests/Frontend.Translations.Tests/spellcheck-excludes.json b/common/Tests/Frontend.Translations.Tests/spellcheck-excludes.json new file mode 100644 index 00000000000..31ad67da88d --- /dev/null +++ b/common/Tests/Frontend.Translations.Tests/spellcheck-excludes.json @@ -0,0 +1,1644 @@ +[ + { + "Excludes": [ + "A-Z", + "Abunəlikdən", + "Abunəliklər", + "abunəliyi", + "Abunəliyinin", + "abunəliyiniz", + "abunəliyinizi", + "açarınızı", + "açılmırsa", + "açın", + "addımdan", + "adı", + "admin", + "Admin", + "administratoru", + "Administratorlar", + "administratorları", + "administratorlarını", + "Adminlər", + "adminləri", + "Adminləri", + "akkauntlar", + "Akkauntları", + "akkaunt", + "Akkaunt", + "Akkauntu", + "Akkauntun", + "aktivdir", + "aktivləşdirildi", + "aktivləşdirilmişdi", + "aktivləşdirilmişdir", + "aktivləşdirmə", + "Aktivləşdirmə", + "aktivlşdirilməsi", + "alqoritmi", + "Arxiv", + "Arxivlər", + "arxivləşdirilməsi", + "Atlamaq", + "audentifikasiya", + "Audentifikasiya", + "Audio", + "Avto", + "avtorizasiya", + "Avtorizasiya", + "avtorizasiyası", + "Axtarın", + "Ayarlar", + "Ayarlara", + "ayarları", + "Ayarları", + "ayarlarını", + "bağlandıqdan", + "Bağlantını", + "bağlantısı", + "Bağlayın", + "Başlıqda", + "baxılan", + "Bərkidilənlərdən", + "Bərkidin", + "bildirişi", + "bildirişlər", + "bildirişləri", + "bilməyəcəksiniz", + "bilmirsinizsə", + "bitmişdir", + "Blankı", + "Blokdan", + "Bloklamaq", + "bloklanılır", + "Bloklanmış", + "bölməyə", + "Bolqar", + "boşdur", + "boşluqlar", + "brauzer", + "brauzeriniz", + "buferə", + "buferinə", + "Buluda", + "Buludunuz", + "buraxılışından", + "Çat", + "Çex", + "Çexiya", + "Cədvəllər", + "çıxarın", + "çıxma", + "ÇXR", + "Daxilolma", + "dayandırmağı", + "deaktiv", + "Deaktiv", + "deaktivləşdirin", + "deaktivləşdirmək", + "dəfəlik", + "dəqiqədir", + "dəstəklənmir", + "Dəvəetmə", + "dəvətnamə", + "Dəvətnamə", + "dəyiş", + "dəyişdir", + "Dəyişdir", + "dəyişdirə", + "dəyişdirildi", + "dəyişdirilməsindən", + "dəyişdirilmişdir", + "dəyişdirin", + "dəyişdirmə", + "Dəyişiklər", + "dəyişikləriniz", + "dəyişiklikləriniz", + "dəyişikliksiz", + "dəyişin", + "dialoqunda", + "dialoqunun", + "dilinizi", + "doğrulama", + "Doğrulama", + "doldurun", + "domen", + "Domen", + "domendə", + "düyməyə", + "E-məktublarınızı", + "e-məktubu", + "e-poçt", + "E-poçt", + "Ehtiyyat", + "elementinin", + "elementlərdən", + "elementlərin", + "Elementlərin", + "elementlərini", + "emailin", + "endirin", + "Endirin", + "etdiyiz", + "Etibarsız", + "əlaqələndirə", + "Əlaqələrinizi", + "əlçatan", + "əlçatandır", + "əminsinizmi", + "ərzindəki", + "faktorlu", + "favorit", + "Favorit", + "Favoritlərdən", + "fayla", + "Fayla", + "Fayldan", + "faylın", + "Faylın", + "faylından", + "Faylından", + "faylını", + "faylınız", + "faylınızı", + "fayllar", + "Fayllar", + "faylları", + "Faylları", + "faylların", + "fərdiləşdirilmiş", + "Fərdiləşdirmə", + "Filtr", + "Filtri", + "Fin", + "Finlandiya", + "fonlu", + "formalaşdır", + "Formanı", + "Formanın", + "format", + "formatına", + "formatını", + "formatlı", + "Formu", + "funksionallıq", + "generasiya", + "generasiyası", + "Genişlik", + "gəldiniz", + "gəlmisiniz", + "Girişə", + "Girişi", + "Girişin", + "göndəriləcəkdir", + "göndərilsinmi", + "Göndərin", + "görməmisinizsə", + "görsənmirsə", + "görünməsi", + "görünsün", + "görüntülənəcək", + "göstərmə", + "Güvənilən", + "Hesabımı", + "Hesabınız", + "hesabınızdan", + "Hesabınızı", + "Hesabınızın", + "hesabınızla", + "hesablardakı", + "hesablarını", + "həcmdən", + "Həmkarların", + "hərflər", + "hüquqlara", + "icazənin", + "İcra", + "İki", + "İlk", + "İlkin", + "importu", + "İndi", + "İnsanlar", + "instruksiyası", + "İnteqrasiya", + "interfeysindən", + "İnternet", + "inzibatçı", + "İnzibatçı", + "inzibatçıları", + "inzibatçının", + "inzibatçısı", + "İş", + "işarələ", + "İşə", + "işləyin", + "İspan", + "istədiyinizdən", + "istəyirsinizmi", + "İstifadə", + "İstifadəçi", + "İstifadəçilər", + "istifadəçilərə", + "İstifadəçilərə", + "istifadəçiləri", + "İstifadəçiləri", + "istifadəçilərin", + "İstifadəçilərin", + "istifadəçilərlə", + "İstifadəçinin", + "istifadəçisinə", + "istifadəd", + "İtalyan", + "İzn", + "kataloqu", + "Kataloqu", + "keç", + "keçiriləcəklər", + "klikəyin", + "klikləməklə", + "klikləyin", + "köçürə", + "köçürüldü", + "Köçürüldü", + "köçürülən", + "Köçürülmə", + "köçürülmədi", + "köçürülsünmü", + "Köçürülür", + "kodlar", + "kodlardan", + "kodları", + "kodlarından", + "kodlarını", + "konfiqurasiyasını", + "Kontaktın", + "kontekst", + "Kontekst", + "konvertasiya", + "Konvertasiya", + "konvertasiyasını", + "kopyalandı", + "kopyalayın", + "Krallıq", + "Laos", + "Latış", + "Latviya", + "link", + "Link", + "linki", + "Linki", + "linkin", + "Lisenziyanı", + "Lisenziyanız", + "lisenziyanızı", + "lisenziyası", + "lokal", + "Lokal", + "loqo", + "Loqo", + "Loqonu", + "loqosu", + "lütfən", + "Lütfən", + "manual", + "Manual", + "masaüstü", + "Masaüstü", + "Meksika", + "Mesaj", + "məktubundan", + "məlumatlarını", + "Məlumatlarınızı", + "Məxfilik", + "modul", + "Modul", + "Modullar", + "Modullara", + "Modullarda", + "modullarına", + "moduluna", + "modulundan", + "mövcuddursa", + "mu", + "müəyyənləşdirin", + "Niderland", + "Nöm", + "nömrəsinin", + "Növün", + "nüsxəsi", + "nüsxəsini", + "o", + "Ödənişsiz", + "olunmusunuz", + "Onlayn", + "or", + "Oxunmuş", + "oxunulmuş", + "özüvüzün", + "parametrindən", + "Parolun", + "Parolunuzu", + "Paylanılmış", + "Paylaş", + "paylaşa", + "paylaşılan", + "Paylaşılanlar", + "paylaşıldı", + "Paylaşılmış", + "paylaşım", + "paylaşın", + "Paylaşma", + "pəncərəsi", + "planlaşdırın", + "Plitkalar", + "poçta", + "poçtu", + "poçtun", + "poçtuna", + "poçtunuz", + "poçtunuzu", + "portala", + "Portala", + "portalda", + "Portalda", + "portaldan", + "Portaldan", + "portalı", + "Portalı", + "portalın", + "portalınız", + "Portalınıza", + "portalınızı", + "portalınızla", + "Portuqal", + "Portuqaliya", + "Potalın", + "presetlərdən", + "Profil", + "Profili", + "profilin", + "Profilin", + "profillər", + "Profillərə", + "Provayder", + "Provayderin", + "qayıt", + "qısamüddətli", + "Qolvuğu", + "qoş", + "qoşmaq", + "qoşul", + "qoşulanlar", + "qoşulun", + "Qoşulun", + "qovluğa", + "qovluğu", + "Qovluğu", + "Qovluğua", + "qovluğuna", + "qovluğunda", + "qovluğundan", + "qovluğunun", + "qovluq", + "Qovluq", + "qovluqda", + "Qovluqda", + "qovluqlar", + "Qovluqlar", + "quraşdır", + "quraşdırın", + "qurşağı", + "Qurşağı", + "qurun", + "qutunu", + "qutusu", + "qutusuna", + "qutusundan", + "qutusunu", + "razılaşırsınız", + "razılaşmalarınız", + "razılaşmasını", + "redaktorları", + "resurs", + "rezerv", + "Rezerv", + "Rumın", + "Şablon", + "Şablonları", + "şablonu", + "şablonunu", + "sadalananlar", + "Sadalananlar", + "Sadələşdirilmiş", + "salamlama", + "Salamlama", + "Satınalma", + "satışlarınızı", + "saxlama", + "saxlandığına", + "saxlanıldı", + "saxlanılmaya", + "saxlanılmışdır", + "saxlanmayan", + "saxlanmış", + "sazlamalar", + "sazlamaları", + "seçdiyiniz", + "seçdiyinizə", + "seçiminə", + "seçinr", + "seçməklə", + "seçsəz", + "sessiya", + "Sessiya", + "səhifəni", + "səhifəyə", + "şəkillərdən", + "sənədinə", + "Sənədlərim", + "Sənədlərimə", + "Şərhi", + "Şərhlər", + "şifrə", + "Şifrə", + "Şifrədə", + "şifrədən", + "Şifrələmə", + "şifrələndiyi", + "şifrələnir", + "şifrələnmə", + "Şifrələnmiş", + "şifrələnmişdir", + "şifrəni", + "Şifrəni", + "şifrənin", + "Şifrənin", + "Şifrlənmiş", + "sil", + "silə", + "silin", + "Silin", + "silindi", + "silindiyi", + "silinəcək", + "silinəcəkdir", + "silinəcəklər", + "silinir", + "Silinir", + "silinməsi", + "silinmiş", + "silinmişdir", + "silinsin", + "silsəz", + "simvol", + "simvoldur", + "simvollardan", + "simvolun", + "Siyasətimizə", + "siyasətimizlə", + "sıfırla", + "sıfırlamaq", + "sıfırlanacaq", + "sıfırlayın", + "Sındırılmamış", + "sıxlaşdırılacaq", + "skan", + "Slovakiya", + "Sloven", + "Sloveniya", + "Slovak", + "Söndürülüb", + "sorğula", + "Sorğunu", + "soyad", + "Soyad", + "Status", + "statusuna", + "statusunda", + "statusunuzu", + "suallarınıza", + "sürüşdürün", + "süzgəc", + "süzgəci", + "süzgəcə", + "Susmaya", + "tabda", + "tanıdın", + "tapıla", + "tapılmadı", + "Tapşırıqları", + "tarixçəsinə", + "tarixləri", + "telefonunuz", + "Telefonunuz", + "teqini", + "təlimatı", + "təlimatını", + "təlimatlar", + "Təlimatlar", + "təlimatları", + "təmizləndi", + "təmizləyərək", + "təmizləyin", + "Təqdimatlar", + "Tərcümədə", + "təsdiqdən", + "Təsdiqləmə", + "təsdiqlənmə", + "təsdiqlənməsi", + "təsdiqləyin", + "tətbiqdən", + "Tətbiqə", + "tətbiqindən", + "tətbiqinizdən", + "Tətbiqləri", + "tipini", + "unutmusunuz", + "ünvanının", + "üsuldur", + "üzrəsiniz", + "Uzunluq", + "veb", + "Veb", + "Vebsaytın", + "Verilənlərin", + "verilməyən", + "versiyaları", + "versiyalarını", + "versiyanı", + "Versiyanı", + "Video", + "Vyetnam", + "went", + "xanada", + "Xanada", + "Xanadan", + "xəta", + "xətası", + "yaddaşda", + "yandırıldı", + "Yapon", + "yaradılma", + "yarat", + "Yaratdığınız", + "Yazdığınız", + "yazma", + "yazmanın", + "Yedəkləmə", + "yedəkləyin", + "yeniləmisinizsə", + "yeniləndi", + "Yeniləndi", + "Yenilənmə", + "yenilənmələr", + "yenilənmələri", + "yenilənməsi", + "yenilənmişdir", + "yeniləyin", + "Yenisini", + "yerdəyişdirildi", + "Yerdəyişmə", + "Yerdəyişmənin", + "yerdəyişməsi", + "yerləşdir", + "yerləşdirilsinmi", + "yerləşdirin", + "Yerləşdirmə", + "yerləşdirmək", + "yoldaşlarınız", + "yönləndiriləcəksiniz", + "yoxdurmu", + "yoxlayın", + "yüklə", + "yüklədiyiniz", + "Yükləmələr", + "yüklənə", + "Yüklənəcək", + "Yüklənildi", + "yüklənilmiş", + "Yüklənir", + "Yüklənmə", + "yüklənməsi", + "yükləyə", + "yükləyin", + "Yükləyin", + "Z-A" + ], + "Language": "az" + }, + { + "Excludes": [ + "A-Я", + "Я-A", + "авторизацията", + "Азърбайджан", + "Азърбайджански", + "активаия", + "вленове", + "Двуфакторно", + "Двуфакторното", + "Домените", + "др", + "ѝ", + "имейл", + "Имейл", + "имейла", + "имейли", + "имейлите", + "Имейлът", + "интернет", + "кликнете", + "Кликнете", + "клипборда", + "локация", + "макс", + "Нидерландия", + "Нулирай", + "нулирана", + "Нулиране", + "пренасочете", + "Принтирай", + "САЩ", + "Суздай", + "уебсайта", + "Фавикона", + "Финализираща", + "Mестоположението" + ], + "Language": "bg" + }, + { + "Excludes": [ + "Adresar", + "Archives", + "autentizátoru", + "Change", + "cloud", + "cloudy", + "ČLR", + "Default", + "desktopové", + "Domain", + "E-mail", + "em", + "formulare", + "Formulare", + "Info", + "Laosština", + "max", + "meziverze", + "mozne", + "nahrat", + "nasdíleli", + "nasdílen", + "Neprolomitelný", + "Online", + "ová", + "ovou", + "ovým", + "Pending", + "použijt", + "Prazdny", + "prezentaec", + "Private", + "Reassign", + "Restore", + "sablona", + "Sablona", + "sablonu", + "schranky", + "slozky", + "Spreadsheet", + "stazeni", + "textoveho", + "ů", + "Ucet", + "Ver", + "Vytvorit", + "Your", + "zkopírovany" + ], + "Language": "cs" + }, + { + "Excludes": [ + "Abbestellungsbestätigung", + "Authentifiktionsanwendung", + "auto", + "Azerbaijan", + "Azerbaijani", + "Back-up-Codes", + "Bestätiguns-E-Mail", + "Canvas-Tag", + "Cloud", + "Cloud-Konten", + "Community", + "Desktopversion", + "DOCX-Format", + "Drittanbieterdienst", + "Drittanbieterdienste", + "Drittanbieterinformationen", + "Dutch", + "Einmalkennwort", + "Filterungsoptionen", + "German", + "Germany", + "gesendt", + "hochgeladenden", + "konviertiert", + "Latin", + "max", + "Netherlands", + "PNG-Format", + "Portaladministratoren", + "Portalbenutzer", + "Portalbesitzer", + "Portaldaten", + "Portallogos", + "Portalverwalter", + "PPTX-Dateien", + "Punycode-Domänen", + "QR-Code", + "Registrierungsanfrage", + "Registrierungsdaten", + "Registrierungsemail", + "Registrierungsfehler", + "Tiles", + "usw", + "Verbindungs-URL", + "XLSX-Format", + "ZIP-Datei" + ], + "Language": "de" + }, + { + "Excludes": [ + "A-Ω", + "αδειάζοντάς", + "Αζερική", + "αναγνωσμένου", + "αναγνωσμένων", + "ανακατευθυνθείτε", + "αντιγράφηκαν", + "αντιγράφηκε", + "Αντιγράφηκε", + "απενεργοποιημένοι", + "Έκδ", + "επαληθευτή", + "Επαληθευτής", + "Λαοτινή", + "ΛΔΚ", + "Λετονικά", + "λπ", + "Μεταφορτώθηκε", + "μεταφορτώνετε", + "Ξεκαρφίτσωμα", + "πάροχος", + "περιήγησής", + "προεπιλεγμένου", + "Προεπισκόπηση", + "υποφάκελό", + "υποφακέλους", + "ψήφιο", + "Ω-A", + "Desktop", + "Check-in", + "Office", + "Enterprise" + ], + "Language": "el" + }, + { + "Excludes": [ + "Azerbaijani", + "Center", + "dialog", + "etc", + "favorite", + "favorites", + "Favorites", + "Flds", + "max", + "teammates", + "tel", + "Ver" + ], + "Language": "en" + }, + { + "Excludes": [ + "A-Z", + "administardor", + "autenticación", + "Autenticación", + "Azerbaiyano", + "borrelo", + "Chat", + "cmabiado", + "DC", + "dodocumentos", + "E-mail", + "establecza", + "Favicono", + "Laosiano", + "Letón", + "multimedia", + "Multimedia", + "personalizable", + "portapapeles", + "preajustes", + "Reasignación", + "Resetear", + "resetearán", + "RPC", + "sobrescritura", + "subcarpeta", + "subcarpetas", + "tel", + "users", + "Z-A" + ], + "Language": "es" + }, + { + "Excludes": [ + "Сompte", + "could", + "Désépingler", + "Download", + "Editeurs", + "elements", + "Epingler", + "Folder", + "loaded", + "not", + "Onedrive", + "Rétiré", + "RPC", + "selection", + "status", + "Status" + ], + "Language": "fr" + }, + { + "Excludes": [ + "all", + "archivo", + "blanco", + "cloud", + "dall", + "dell", + "desde", + "Desde", + "Disconnetti", + "Docusign", + "En", + "Enterprise", + "Gender", + "log", + "nell", + "OnlyOffice", + "People", + "plantilla", + "PRC", + "preset", + "scannerizza", + "sottocartella", + "sull", + "texto" + ], + "Language": "it" + }, + { + "Excludes": [ + "iOS의", + "고객님께서는", + "고객님의", + "기본값으로", + "넣으시겠습니", + "넣으시겠습니h", + "다이얼로그", + "데스크탑", + "도메인명은", + "드롭다운하여", + "드롭하세요", + "디렉토리", + "디렉토리에서", + "라오스어", + "라이센스", + "라이센스가", + "라이센스에", + "라이센스입니다", + "로", + "로그인용", + "로드", + "로드하지", + "로컬", + "룸", + "를", + "리드", + "리디렉션됩니다", + "리셋됩니다", + "리소스를", + "리소스에", + "리워드를", + "마이그레이션", + "복사되었습니다", + "복사되지", + "본사본", + "비활성화되었습니다", + "사용자명", + "생성자", + "설정공유", + "세션", + "섹션", + "섹션에", + "섹션에서", + "섹션은", + "섹션을", + "섹션의", + "소셜", + "손실될", + "스캔하거나", + "스토리지에", + "슬로바키아어", + "아제르바이잔", + "아제르바이잔어", + "아카이브", + "안함", + "액티브", + "앱", + "앱에", + "앱에서", + "앱을", + "앱의", + "업데이트되었습니다", + "업로그", + "연락주세요", + "으로", + "을", + "이동되는", + "이동되었습니다", + "이동됩니다", + "인증자", + "임베드", + "임베딩", + "재할당", + "재할당하세요", + "지원팀에", + "컨텍스트", + "콜라보레이션", + "콜라보레이션을", + "클라우드", + "클라우드가", + "템플릿", + "템플릿은", + "파비콘", + "파싱되었습니다", + "파싱된", + "파일명", + "페이지당", + "포털명은", + "폴란드어", + "퓨니코드", + "프라이빗", + "필요헌", + "회사명" + ], + "Language": "ko" + }, + { + "Excludes": [ + "A-Z", + "apakšmapi", + "apakšmapju", + "Atiestatiet", + "Atiestatīt", + "atiestatīta", + "autentifikatora", + "Autentifikatora", + "autentifikatoru", + "cilnē", + "creation", + "Deaktivizējiet", + "deaktivizēt", + "Deaktivizēt", + "deaktivizēts", + "Documenti", + "dzēšiet", + "e", + "E", + "e-pasta", + "E-pasta", + "e-pasts", + "E-pasts", + "e-pastu", + "e-pastus", + "kopīgoja", + "Kopīgojiet", + "kopīgojis", + "kopīgojuši", + "kopīgošana", + "Kopīgot", + "kopīgoti", + "kopīgots", + "Kopīgots", + "kopīgotu", + "ĶTR", + "Laosiešu", + "Overwrite", + "pārdevēts", + "parsēts", + "Pārvaldī", + "Phone", + "Priekšskatīt", + "so", + "tālr", + "Type", + "update", + "utt", + "version", + "with", + "Z-A" + ], + "Language": "lv" + }, + { + "Excludes": [ + "еekstbestand", + "A-Z", + "aankoopvragen", + "activatielink", + "Activatielink", + "algorithm", + "Backup", + "Beheerdersinstellingen", + "browservenster", + "cloud", + "clouds", + "code-genererende", + "Contactinformatie", + "contextmenu", + "creation", + "desktop-app", + "dialoogvenster", + "Documentbeheer", + "doelmap", + "domeinnaam", + "emailadres", + "Enterprise", + "enz", + "Filterinstellingen", + "Flds", + "functionaliteits", + "hoofdletters", + "inloggegevens", + "Inloginstellingen", + "internetverbinding", + "klembord", + "ladenr", + "login", + "Login", + "max", + "photo", + "portaalbeheerder", + "Portaalconfiguratie", + "portaalfuncties", + "portaalgebeurtenissen", + "portaalgebruikers", + "portaalgegevens", + "portaalnaam", + "Privékamer", + "proefperiode", + "QR-code", + "real-time", + "selecteerte", + "spreadsheet", + "Spreadsheet", + "Spreadsheets", + "Standaardwaarden", + "submap", + "submappen", + "supportEmail", + "teamleden", + "tekstbestand", + "tijdzone", + "Tijdzone", + "tussenversies", + "Twee-factor", + "Two-factor", + "Uitnodigingslink", + "Unbreakable", + "Verbindingsurl", + "voorinstellingen", + "wachtwoord", + "Wachtwoord", + "Webversie", + "weergegeven", + "welkomstpagina", + "Welkomstpagina", + "XLSX-formaat", + "Z-A" + ], + "Language": "nl" + }, + { + "Excludes": [ + "Check-in", + "presetów", + "przekonwertowane", + "przekonwertowany", + "Przekonwertuj" + ], + "Language": "pl" + }, + { + "Excludes": [ + "A-Z", + "About", + "Accesso", + "administadores", + "an", + "Azeri", + "blank", + "Center", + "Create", + "Customização", + "customizado", + "customizados", + "customizar", + "Customizar", + "Departmento", + "Desafixar", + "desktop", + "editors", + "existing", + "form", + "Form", + "from", + "From", + "guardadass", + "header", + "Help", + "internet", + "Lao", + "Login", + "Master", + "max", + "Onedrive", + "page", + "PRC", + "reatribuir", + "Reatribuir", + "s", + "scan", + "seção", + "subpasta", + "Support", + "Team", + "tel", + "text", + "the", + "website", + "Z-A" + ], + "Language": "pt" + }, + { + "Excludes": [ + "Admin", + "Admins", + "Azerbaijão", + "Check-in", + "desconhcido", + "desktop", + "Image", + "Log", + "max", + "PRC", + "Team", + "tel", + "Template", + "upload", + "Upload", + "Uploads" + ], + "Language": "pt-BR" + }, + { + "Excludes": [ + "A-Z", + "aceeaşi", + "Acţiuni", + "Administratiorii", + "Afisare", + "aplicațiirol", + "arhiveaza", + "at", + "Azeră", + "be", + "Bifaţi", + "citie", + "clipboard", + "cloud", + "conditiile", + "confidentialitate", + "Confidenţialitate", + "contactaţi", + "Coreeană", + "Coş", + "Coşul", + "could", + "cunoştinţă", + "depăşeşte", + "Descărare", + "descideți", + "Dezacticarea", + "disponiile", + "Doriţi", + "Dumneavostră", + "echip", + "editatree", + "Email-ul", + "Engliză", + "Femenin", + "fincțiile", + "fișerul", + "fișerului", + "Fişierul", + "gererat", + "gererate", + "imaginui", + "Încarcat", + "Instructions", + "Instrucţiuni", + "Instructiunile", + "Intenet", + "intoduceți", + "loaded", + "luaţi", + "max", + "not", + "optiunea", + "părăsiţi", + "parsat", + "Păstați", + "Permiteţi", + "portivit", + "Portugeză", + "posesirului", + "Reasinarea", + "redirecţionaţi", + "refizat", + "resetate", + "si", + "sînt", + "SMS-ul", + "Specificaţi", + "ştergeţi", + "Subdosar", + "subdosarul", + "success", + "suprimati", + "Ver", + "veriune", + "versiunei", + "Veţi", + "vir", + "Z-A" + ], + "Language": "ro" + }, + { + "Excludes": [ + "URL-адрес", + "QR-код", + "десктопных", + "десктопные", + "десктопного", + "десктопное", + "ов", + "отсканируйте", + "Псевдодомен", + "подпапок", + "предустановок", + "расшаренную", + "расшаренные", + "расшаренный", + "сконвертирован", + "сконвертированы", + "сконвертировать", + "Сконвертировать", + "Cменить", + "Кастомизация" + ], + "Language": "ru" + }, + { + "Excludes": [ + "able", + "access", + "admina", + "admini", + "Admini", + "admins", + "aiOS", + "aktívného", + "all", + "any", + "autentifikátora", + "Azerbajdžančina", + "be", + "can", + "certain", + "characters", + "cloud", + "cloudy", + "ČĽR", + "Common", + "contain", + "contains", + "create", + "deaktivované", + "Deaktivovaní", + "deaktivovaný", + "Deaktivovaný", + "Dvojfaktorová", + "Dvojfaktorové", + "dvojfaktorovú", + "Edirori", + "files", + "Flds", + "following", + "Full", + "granted", + "jplatí", + "kartě", + "Laoština", + "Lotyščina", + "našomCentre", + "neaktívného", + "Neprelomiteľný", + "Odkliknutím", + "om", + "Only", + "Otevřít", + "ová", + "ové", + "ovým", + "pozvánie", + "pripnutie", + "Resetovať", + "section", + "shared", + "soubor", + "soubory", + "special", + "there", + "they", + "this", + "This", + "title", + "upload", + "users", + "Users", + "using", + "ve", + "very", + "vlastnika", + "Vybrat", + "way", + "will", + "within", + "You", + "Začiarknutím" + ], + "Language": "sk" + }, + { + "Excludes": [ + "aktivacijo", + "Audio", + "avtentikacija", + "avtentikacijo", + "Avtentikator", + "Azerbejdžan", + "Azerbejdžansko", + "creation", + "Deaktivirajte", + "deaktiviranje", + "deaktivirano", + "direktorij", + "direktorija", + "dostopati", + "dvofaktorska", + "Dvofaktorska", + "dvofaktorsko", + "e", + "E-mail", + "Enterprise", + "Generirajte", + "generirano", + "itd", + "kompresirane", + "konfigurirajte", + "Konfigurirajte", + "Kontaktiraj", + "kontaktirajte", + "LRK", + "Mail", + "max", + "neshranjene", + "Odblokiraj", + "ogledane", + "ov", + "podmap", + "podmapo", + "Ponastavi", + "ponastavljena", + "prednastavitev", + "prenešene", + "pro", + "skenirajte", + "tel", + "u", + "Yvaš" + ], + "Language": "sl" + }, + { + "Excludes": [ + "a", + "A-Z", + "aboneliğiniz", + "açamıyorsanız", + "aktivasyon", + "Aktivasyon", + "and", + "arayüzünden", + "Audio", + "Bulutunuz", + "Çekya", + "Çevrimiçi", + "ÇHC", + "Çökp", + "creation", + "değişiklikleriniz", + "Değişiklikleriniz", + "dökümanlar", + "domain", + "domainler", + "Dsylr", + "e", + "e-posta", + "E-posta", + "E-postalarınızı", + "e-postanız", + "e-postanızı", + "e-postası", + "e-postaya", + "e-postayı", + "E-postayı", + "erişilemeyecekler", + "Etkinleştir", + "etkinleştirildi", + "formatında", + "görüntülediğiniz", + "ila", + "ı", + "ınız", + "Klsrlr", + "kopyalanmayacaktır", + "Laos", + "Laosça", + "Letonca", + "Letonya", + "manuel", + "Modüllerde", + "ndaki", + "ndeki", + "nu", + "öğeler", + "öğeleri", + "Öğeleri", + "öğelerin", + "öğelerini", + "Önizleme", + "orijinalinden", + "portala", + "portaldan", + "portalı", + "Portalı", + "portalınız", + "portalınıza", + "Portalınıza", + "Portalınızı", + "sekmede", + "sonlandır", + "vb", + "yüklediyseniz", + "yükleyebileceklerdir", + "Z-A" + ], + "Language": "tr" + }, + { + "Excludes": [ + "Вер", + "Виберати", + "відключений", + "Відключено", + "Відключити", + "відображалися", + "Відображати", + "відображатися", + "відображаються", + "відображення", + "Двофакторна", + "двофакторну", + "деактивовано", + "деактивувати", + "Деактивувати", + "Деактивує", + "дн", + "ел", + "єднаний", + "єтнам", + "єтнамська", + "заготівок", + "Запам", + "зберігтися", + "ім", + "Ім", + "іменемe", + "існуючим", + "КНР", + "медіафайл", + "Місцезнаходження", + "місцерозташування", + "Настроюване", + "настроюваний", + "Настроюваний", + "Настроювані", + "неможна", + "Обов", + "панікоді", + "Папка", + "папки", + "Папки", + "папку", + "Папку", + "папок", + "папці", + "Перепідключитися", + "підпапки", + "підпапок", + "пов", + "прив", + "cтворення", + "тел", + "Фавікон", + "Фіналізувати", + "язаних", + "язати", + "язкове", + "ятати" + ], + "Language": "uk" + }, + { + "Excludes": [ + "Azerbaijan", + "Brazil", + "Bungari", + "Change", + "Documents", + "Flds", + "file", + "internet", + "độnge", + "Edit", + "ệp", + "Hy", + "Latinh", + "Latvia", + "menu", + "media", + "Mỹ", + "Mexico", + "Photo", + "Romania", + "tab", + "v", + "Video", + "Ukraina", + "Slovenia", + "Slovak", + "Slovakia", + "Z" + ], + "Language": "vi" + } +] \ No newline at end of file diff --git a/common/services/ASC.ApiSystem/Classes/CommonMethods.cs b/common/services/ASC.ApiSystem/Classes/CommonMethods.cs index 377bd9d2154..84519d0df1f 100644 --- a/common/services/ASC.ApiSystem/Classes/CommonMethods.cs +++ b/common/services/ASC.ApiSystem/Classes/CommonMethods.cs @@ -299,11 +299,25 @@ public string GetClientIp() //return null; } - public bool ValidateRecaptcha(string response, string ip) + public bool ValidateRecaptcha(string response, RecaptchaType recaptchaType, string ip) { try - { - var data = string.Format("secret={0}&remoteip={1}&response={2}", Configuration["recaptcha:private-key"], ip, response); + { + string privateKey; + switch (recaptchaType) + { + case RecaptchaType.AndroidV2: + privateKey = Configuration["recaptcha:private-key:android"]; + break; + case RecaptchaType.iOSV2: + privateKey = Configuration["recaptcha:private-key:ios"]; + break; + default: + privateKey = Configuration["recaptcha:private-key"]; + break; + } + + var data = string.Format("secret={0}&remoteip={1}&response={2}", privateKey, ip, response); var url = Configuration["recaptcha:verify-url"] ?? "https://www.recaptcha.net/recaptcha/api/siteverify"; var webRequest = (HttpWebRequest)WebRequest.Create(url); diff --git a/common/services/ASC.ApiSystem/Controllers/PeopleController.cs b/common/services/ASC.ApiSystem/Controllers/PeopleController.cs new file mode 100644 index 00000000000..23505d66403 --- /dev/null +++ b/common/services/ASC.ApiSystem/Controllers/PeopleController.cs @@ -0,0 +1,140 @@ +/* + * + * (c) Copyright Ascensio System Limited 2010-2021 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ + + + + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +using ASC.ApiSystem.Classes; +using ASC.Common; +using ASC.Common.Caching; +using ASC.Common.Logging; +using ASC.Core; +using ASC.Core.Users; +using ASC.Web.Studio.Utility; + +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; + +namespace ASC.ApiSystem.Controllers +{ + [Scope] + [ApiController] + [Route("[controller]")] + public class PeopleController : ControllerBase + { + private ILog Log { get; } + private HostedSolution HostedSolution { get; } + private UserFormatter UserFormatter { get; } + private ICache Cache { get; } + private CoreSettings CoreSettings { get; } + private CommonLinkUtility CommonLinkUtility { get; } + public IHttpContextAccessor HttpContextAccessor { get; } + + public PeopleController( + IOptionsMonitor option, + IOptionsSnapshot hostedSolutionOptions, + UserFormatter userFormatter, + ICache cache, + CoreSettings coreSettings, + CommonLinkUtility commonLinkUtility, + IHttpContextAccessor httpContextAccessor) + { + Log = option.Get("ASC.ApiSystem.People"); + HostedSolution = hostedSolutionOptions.Value; + UserFormatter = userFormatter; + Cache = cache; + CoreSettings = coreSettings; + CommonLinkUtility = commonLinkUtility; + HttpContextAccessor = httpContextAccessor; + } + + #region For TEST api + + [HttpGet("test")] + public IActionResult Check() + { + return Ok(new + { + value = "Portal api works" + }); + } + + #endregion + + #region API methods + + [HttpPost("find")] + [AllowCrossSiteJson] + public IActionResult Find(IEnumerable userIds) + { + var sw = Stopwatch.StartNew(); + userIds = userIds ?? new List(); + + var users = HostedSolution.FindUsers(userIds); + + var result = users.Select(user => new + { + id = user.ID, + name = UserFormatter.GetUserName(user), + email = user.Email, + + link = GetUserProfileLink(user) + }); + + Log.DebugFormat("People find {0} / {1}; Elapsed {2} ms", result.Count(), userIds.Count(), sw.ElapsedMilliseconds); + sw.Stop(); + + return Ok(new + { + result + }); + } + + #endregion + + #region private methods + + private string GetTenantDomain(int tenantId) + { + var domain = Cache.Get(tenantId.ToString()); + if (string.IsNullOrEmpty(domain)) + { + var tenant = HostedSolution.GetTenant(tenantId); + domain = tenant.GetTenantDomain(CoreSettings); + Cache.Insert(tenantId.ToString(), domain, TimeSpan.FromMinutes(10)); + } + return domain; + } + + private string GetUserProfileLink(UserInfo user) + { + var tenantDomain = GetTenantDomain(user.Tenant); + return string.Format("{0}{1}{2}/{3}", + HttpContextAccessor.HttpContext.Request.Scheme, + Uri.SchemeDelimiter, + tenantDomain, + "Products/People/Profile.aspx?" + CommonLinkUtility.GetUserParamsPair(user)); + } + + #endregion + } +} \ No newline at end of file diff --git a/common/services/ASC.ApiSystem/Controllers/PortalController.cs b/common/services/ASC.ApiSystem/Controllers/PortalController.cs index 81d95688a11..2f895237a6f 100644 --- a/common/services/ASC.ApiSystem/Controllers/PortalController.cs +++ b/common/services/ASC.ApiSystem/Controllers/PortalController.cs @@ -671,12 +671,12 @@ private bool CheckRecaptcha(TenantModel model, string clientIP, Stopwatch sw, ou { Log.DebugFormat("PortalName = {0}; Elapsed ms. ValidateRecaptcha via app key: {1}. {2}", model.PortalName, model.AppKey, sw.ElapsedMilliseconds); return true; - } - - var data = string.Format("{0} {1} {2} {3} {4}", model.PortalName, model.FirstName, model.LastName, model.Email, model.Phone); + } + + var data = string.Format("{0} {1} {2} {3} {4} {5}", model.PortalName, model.FirstName, model.LastName, model.Email, model.Phone, model.RecaptchaType); /*** validate recaptcha ***/ - if (!CommonMethods.ValidateRecaptcha(model.RecaptchaResponse, clientIP)) + if (!CommonMethods.ValidateRecaptcha(model.RecaptchaResponse, model.RecaptchaType, clientIP)) { Log.DebugFormat("PortalName = {0}; Elapsed ms. ValidateRecaptcha error: {1} {2}", model.PortalName, sw.ElapsedMilliseconds, data); sw.Stop(); diff --git a/common/services/ASC.ApiSystem/Models/RecaptchaType.cs b/common/services/ASC.ApiSystem/Models/RecaptchaType.cs new file mode 100644 index 00000000000..8193e74eda2 --- /dev/null +++ b/common/services/ASC.ApiSystem/Models/RecaptchaType.cs @@ -0,0 +1,25 @@ +/* + * + * (c) Copyright Ascensio System Limited 2010-2021 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ + +namespace ASC.ApiSystem.Models +{ + public enum RecaptchaType + { + Default = 0, + AndroidV2 = 1, + iOSV2 = 2 + } +} \ No newline at end of file diff --git a/common/services/ASC.ApiSystem/Models/TenantModel.cs b/common/services/ASC.ApiSystem/Models/TenantModel.cs index dde90598f04..e95d6200c7a 100644 --- a/common/services/ASC.ApiSystem/Models/TenantModel.cs +++ b/common/services/ASC.ApiSystem/Models/TenantModel.cs @@ -73,7 +73,9 @@ public class TenantModel : IModel [StringLength(32)] public string Phone { get; set; } - public string RecaptchaResponse { get; set; } + public string RecaptchaResponse { get; set; } + + public RecaptchaType RecaptchaType { get; set; } [StringLength(20)] public string Region { get; set; } diff --git a/config/appsettings.json b/config/appsettings.json index 52bf8197336..492e506b1c9 100644 --- a/config/appsettings.json +++ b/config/appsettings.json @@ -56,15 +56,15 @@ "enable": [ "box", "dropboxv2", "docusign", "google", "onedrive", "sharepoint", "nextcloud", "owncloud", "webdav", "kdrive", "yandex" ] }, "docservice": { - "coauthor-docs": [ ".pptx", ".ppsx", ".xlsx", ".csv", ".docx", ".txt" ], - "commented-docs": [ ".docx", ".xlsx", ".pptx" ], + "coauthor-docs": [ ".pptx", ".ppsx", ".xlsx", ".csv", ".docx", ".docxf", ".oform", ".txt" ], + "commented-docs": [ ".docx", ".docxf", ".xlsx", ".pptx" ], "convert-docs": [ ".pptm", ".ppt", ".ppsm", ".pps", ".potx", ".potm", ".pot", ".odp", ".fodp", ".otp", ".xlsm", ".xls", ".xltx", ".xltm", ".xlt", ".ods", ".fods", ".ots", ".docm", ".doc", ".dotx", ".dotm", ".dot", ".odt", ".fodt", ".ott", ".rtf" ], - "edited-docs": [ ".pptx", ".pptm", ".ppt", ".ppsx", ".ppsm", ".pps", ".potx", ".potm", ".pot", ".odp", ".fodp", ".otp", ".xlsx", ".xlsm", ".xls", ".xltx", ".xltm", ".xlt", ".ods", ".fods", ".ots", ".csv", ".docx", ".docm", ".doc", ".dotx", ".dotm", ".dot", ".odt", ".fodt", ".ott", ".txt", ".rtf", ".mht", ".html", ".htm" ], - "encrypted-docs": [ ".docx", ".xlsx", ".pptx" ], - "formfilling-docs": [ ".docx" ], + "edited-docs": [ ".pptx", ".pptm", ".ppt", ".ppsx", ".ppsm", ".pps", ".potx", ".potm", ".pot", ".odp", ".fodp", ".otp", ".xlsx", ".xlsm", ".xls", ".xltx", ".xltm", ".xlt", ".ods", ".fods", ".ots", ".csv", ".docx", ".docxf", ".oform", ".docm", ".doc", ".dotx", ".dotm", ".dot", ".odt", ".fodt", ".ott", ".txt", ".rtf", ".mht", ".html", ".htm" ], + "encrypted-docs": [ ".docx", ".docxf", ".xlsx", ".pptx", ".oform" ], + "formfilling-docs": [ ".oform" ], "customfilter-docs": [ ".xlsx" ], - "reviewed-docs": [ ".docx" ], - "viewed-docs": [ ".pptx", ".pptm", ".ppt", ".ppsx", ".ppsm", ".pps", ".potx", ".potm", ".pot", ".odp", ".fodp", ".otp", ".gslides", ".xlsx", ".xlsm", ".xls", ".xltx", ".xltm", ".xlt", ".ods", ".fods", ".ots", ".gsheet", ".csv", ".docx", ".docm", ".doc", ".dotx", ".dotm", ".dot", ".odt", ".fodt", ".ott", ".gdoc", ".txt", ".rtf", ".mht", ".html", ".htm", ".epub", ".pdf", ".djvu", ".xps" ], + "reviewed-docs": [ ".docx", ".docxf" ], + "viewed-docs": [ ".pptx", ".pptm", ".ppt", ".ppsx", ".ppsm", ".pps", ".potx", ".potm", ".pot", ".odp", ".fodp", ".otp", ".gslides", ".xlsx", ".xlsm", ".xls", ".xltx", ".xltm", ".xlt", ".ods", ".fods", ".ots", ".gsheet", ".csv", ".docx", ".docxf", ".oform", ".docm", ".doc", ".dotx", ".dotm", ".dot", ".odt", ".fodt", ".ott", ".gdoc", ".txt", ".rtf", ".mht", ".html", ".htm", ".epub", ".pdf", ".djvu", ".xps" ], "secret": { "value": "", "header": "" @@ -98,7 +98,7 @@ "url": "", "internal": "" }, - "cultures": "en-US,ru-RU,de-DE,it-IT,fr-FR,pt-BR", + "cultures": "de,en,fr,it,pt-BR,ru", "url-shortener": { "value": "/sh/", "internal": "http://localhost:9999/" diff --git a/config/workbox-config.js b/config/workbox-config.js index 6cefc2f8a8c..dfb11242346 100644 --- a/config/workbox-config.js +++ b/config/workbox-config.js @@ -1,9 +1,7 @@ module.exports = { - globDirectory: 'build/deploy/', - globPatterns: [ - "**/*.{js,css,woff2,svg}" - ], - globIgnores: ['**/remoteEntry.js'], - swSrc: 'packages/asc-web-common/utils/sw-template.js', - swDest: 'build/deploy/public/sw.js' -}; \ No newline at end of file + globDirectory: "build/deploy/", + globPatterns: ["**/*.{js,css,woff2,svg}"], + globIgnores: ["**/remoteEntry.js"], + swSrc: "packages/asc-web-common/sw/template.js", + swDest: "build/deploy/public/sw.js", +}; diff --git a/frontend.code-workspace b/frontend.code-workspace index d1bb1ea5310..ecd0bea263f 100644 --- a/frontend.code-workspace +++ b/frontend.code-workspace @@ -52,7 +52,8 @@ "settings": { "window.zoomLevel": 0, "editor.formatOnSave": true, - "editor.defaultFormatter": "esbenp.prettier-vscode" + "editor.defaultFormatter": "esbenp.prettier-vscode", + "liveServer.settings.multiRootWorkspaceName": "✨ appserver" }, "extensions": { "recommendations": ["folke.vscode-monorepo-workspace"] diff --git a/lerna.json b/lerna.json index 3507f9b0421..60d8c002566 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "1.0.1", + "version": "1.1.0", "npmClient": "yarn", "packages": [ "packages/asc-web-components", diff --git a/package.json b/package.json index f15dbfe41d1..45545b122cf 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,6 @@ "packages/asc-web-components", "packages/asc-web-common", "packages/browserslist-config-asc", - "packages/debug-info", "web/ASC.Web.Login", "web/ASC.Web.Client", "web/ASC.Web.Editor", @@ -18,16 +17,18 @@ "products/ASC.Calendar/Client" ], "scripts": { - "build": "lerna run build --parallel --ignore @appserver/common --ignore @appserver/components --ignore @appserver/browserslist-config-asc --ignore @appserver/debug-info", + "build": "lerna run build --parallel --ignore @appserver/common --ignore @appserver/components --ignore @appserver/browserslist-config-asc", "build:personal": "lerna run build --parallel --scope {@appserver/studio,@appserver/people,@appserver/files,@appserver/editor}", + "build:test": "lerna run build:test --parallel --ignore @appserver/common --ignore @appserver/components --ignore @appserver/browserslist-config-asc --ignore @appserver/debug-info", + "build:test.translation": "lerna run build:test.translation --parallel --ignore @appserver/common --ignore @appserver/components --ignore @appserver/browserslist-config-asc --ignore @appserver/debug-info", "bump": "lerna version --no-push --no-git-tag-version", "clean": "lerna run clean --parallel", - "deploy": "shx rm -rf build/deploy/products && shx rm -rf build/deploy/public && shx rm -rf build/deploy/studio && lerna run deploy --parallel --ignore @appserver/common --ignore @appserver/components --ignore @appserver/browserslist-config-asc --ignore @appserver/debug-info && shx cp -r public build/deploy", + "deploy": "shx rm -rf build/deploy/products && shx rm -rf build/deploy/public && shx rm -rf build/deploy/studio && lerna run deploy --parallel --ignore @appserver/common --ignore @appserver/components --ignore @appserver/browserslist-config-asc && shx cp -r public build/deploy", "deploy:personal": "shx rm -rf build/deploy/products && shx rm -rf build/deploy/public && shx rm -rf build/deploy/studio && lerna run deploy --parallel --scope {@appserver/studio,@appserver/people,@appserver/files,@appserver/editor} && shx cp -r public build/deploy", - "serve": "lerna run serve --parallel --ignore @appserver/common --ignore @appserver/components --ignore @appserver/browserslist-config-asc --ignore @appserver/debug-info", - "start": "lerna run start --parallel --ignore @appserver/common --ignore @appserver/components --ignore @appserver/browserslist-config-asc --ignore @appserver/debug-info", + "serve": "lerna run serve --parallel --ignore @appserver/common --ignore @appserver/components --ignore @appserver/browserslist-config-asc", + "start": "lerna run start --parallel --ignore @appserver/common --ignore @appserver/components --ignore @appserver/browserslist-config-asc", "start:personal": "lerna run start --parallel --scope {@appserver/studio,@appserver/people,@appserver/files,@appserver/editor}", - "start-prod": "lerna run start-prod --parallel --ignore @appserver/common --ignore @appserver/components --ignore @appserver/browserslist-config-asc --ignore @appserver/debug-info", + "start-prod": "lerna run start-prod --parallel --ignore @appserver/common --ignore @appserver/components --ignore @appserver/browserslist-config-asc", "storybook": "yarn workspace @appserver/components storybook", "storybook-build": "yarn workspace @appserver/components run storybook-build", "sw-build": "workbox injectManifest config/workbox-config.js && yarn sw-modify && yarn sw-minimize", @@ -47,9 +48,14 @@ "sw-studio-replace": "replace-in-files --string='studio/client/' --replacement='/' build/deploy/public/sw.js", "test": "yarn workspace @appserver/components test", "wipe": "shx rm -rf node_modules yarn.lock web/**/node_modules products/**/node_modules", - "debug-info": "node packages/debug-info --unreleased-only --template debuginfo --output public/debuginfo.md" + "debug-info": "auto-changelog --unreleased-only --template debuginfo --output public/debuginfo.md", + "e2e.test": "lerna run test:sequential --parallel --scope {@appserver/people,@appserver/files,@appserver/login}", + "e2e.test:sequential": "yarn workspace @appserver/people test:sequential && yarn workspace @appserver/files test:sequential && yarn workspace @appserver/login test:sequential", + "e2e.test:model": "yarn workspace @appserver/people test:model && yarn workspace @appserver/files test:model && yarn workspace @appserver/login test:model", + "e2e.test:translation": "lerna run test:translation --parallel --scope {@appserver/people,@appserver/files}" }, "devDependencies": { + "auto-changelog": "https://github.com/ONLYOFFICE/auto-changelog.git#master", "lerna": "3.22.1", "replace-in-files-cli": "^1.0.0", "shx": "^0.3.3", diff --git a/packages/asc-web-common/api/files/index.js b/packages/asc-web-common/api/files/index.js index 579fa359a4c..8c151ce09a9 100644 --- a/packages/asc-web-common/api/files/index.js +++ b/packages/asc-web-common/api/files/index.js @@ -277,8 +277,8 @@ export function deleteFolder(folderId, deleteAfter, immediately) { return request(options); } -export function createFile(folderId, title) { - const data = { title }; +export function createFile(folderId, title, templateId) { + const data = { title, templateId }; const options = { method: "post", url: `/files/${folderId}/file`, @@ -706,6 +706,8 @@ export function setEncryptionKeys(keys) { const data = { publicKey: keys.publicKey, privateKeyEnc: keys.privateKeyEnc, + enable: keys.enable, + update: keys.update, }; return request({ method: "put", @@ -789,3 +791,46 @@ export function getPresignedUri(fileId) { url: `files/file/${fileId}/presigned`, }); } + +export function checkFillFormDraft(fileId) { + return request({ + method: "post", + url: `files/masterform/${fileId}/checkfillformdraft`, + data: { fileId }, + }); +} + +export function fileCopyAs(fileId, destTitle, destFolderId, enableExternalExt) { + return request({ + method: "post", + url: `files/file/${fileId}/copyas`, + data: { + destTitle, + destFolderId, + enableExternalExt, + }, + }); +} + +export function getEditHistory(fileId, doc) { + return request({ + method: "get", + url: `files/file/${fileId}/edit/history?doc=${doc}`, + }); +} + +export function getEditDiff(fileId, version, doc) { + return request({ + method: "get", + url: `files/file/${fileId}/edit/diff?version=${version}&doc=${doc}`, + }); +} + +export function restoreDocumentsVersion(fileId, version, doc) { + const options = { + method: "get", + url: `files/file/${fileId}/restoreversion?version=${version}&doc=${doc}`, + }; + + return request(options); +} diff --git a/packages/asc-web-common/api/user/index.js b/packages/asc-web-common/api/user/index.js index 403efc073e7..3f8da8671f5 100644 --- a/packages/asc-web-common/api/user/index.js +++ b/packages/asc-web-common/api/user/index.js @@ -1,9 +1,10 @@ import { request, setWithCredentialsStatus } from "../client"; -export function login(userName, passwordHash) { +export function login(userName, passwordHash, session) { const data = { userName, passwordHash, + session, }; return request({ diff --git a/packages/asc-web-common/components/FilterInput/sub-components/CloseButton.js b/packages/asc-web-common/components/FilterInput/sub-components/CloseButton.js index 43aed58c4e3..6c190ce38be 100644 --- a/packages/asc-web-common/components/FilterInput/sub-components/CloseButton.js +++ b/packages/asc-web-common/components/FilterInput/sub-components/CloseButton.js @@ -5,7 +5,7 @@ import IconButton from "@appserver/components/icon-button"; const CloseButton = (props) => { //console.log("CloseButton render"); - const { className, isDisabled, onClick } = props; + const { className, isDisabled, onClick, isClickable } = props; return (
{ isFill={true} isDisabled={isDisabled} onClick={!isDisabled ? onClick : undefined} + isClickable={isClickable} />
); diff --git a/packages/asc-web-common/components/FilterInput/sub-components/FilterBlock.js b/packages/asc-web-common/components/FilterInput/sub-components/FilterBlock.js index 0b77f55d16f..2aa0c325575 100644 --- a/packages/asc-web-common/components/FilterInput/sub-components/FilterBlock.js +++ b/packages/asc-web-common/components/FilterInput/sub-components/FilterBlock.js @@ -251,7 +251,7 @@ class FilterItem extends React.Component { isDisabled={isDisabled} isClickable={true} > - + ); diff --git a/packages/asc-web-common/components/Loaders/DialogLoader/DialogLoader.js b/packages/asc-web-common/components/Loaders/DialogLoader/DialogLoader.js index a94b6b8b1e0..8679b77e6d8 100644 --- a/packages/asc-web-common/components/Loaders/DialogLoader/DialogLoader.js +++ b/packages/asc-web-common/components/Loaders/DialogLoader/DialogLoader.js @@ -2,7 +2,7 @@ import React from "react"; import Loaders from "../../Loaders"; import StyledDialogLoader from "./StyledDialogLoader"; -const DialogLoader = () => { +const DialogLoader = ({ bodyHeight = "150px" }) => { return (
@@ -14,7 +14,7 @@ const DialogLoader = () => { />
- +
diff --git a/packages/asc-web-common/components/Loaders/RowLoader/RowLoader.js b/packages/asc-web-common/components/Loaders/RowLoader/RowLoader.js index f04f9f4c042..7fb8ca01dc3 100644 --- a/packages/asc-web-common/components/Loaders/RowLoader/RowLoader.js +++ b/packages/asc-web-common/components/Loaders/RowLoader/RowLoader.js @@ -67,7 +67,7 @@ const RowLoader = ({ id, className, style, isRectangle, ...rest }) => { /> )} - + -
+ { @@ -100,6 +110,8 @@ export function regDesktop( break; } }; + + console.log("Created window.onSystemMessage", window.onSystemMessage); } export function relogin() { diff --git a/packages/asc-web-common/package.json b/packages/asc-web-common/package.json index f62d4ce4f50..d56a42c5b09 100644 --- a/packages/asc-web-common/package.json +++ b/packages/asc-web-common/package.json @@ -1,6 +1,6 @@ { "name": "@appserver/common", - "version": "1.0.1", + "version": "1.1.0", "private": true, "scripts": { "build": "echo 'skip it'", diff --git a/packages/asc-web-common/store/AuthStore.js b/packages/asc-web-common/store/AuthStore.js index 64979ea5e47..7c2584b2b38 100644 --- a/packages/asc-web-common/store/AuthStore.js +++ b/packages/asc-web-common/store/AuthStore.js @@ -148,9 +148,9 @@ class AuthStore { return [settingsModuleWrapper]; }; - login = async (user, hash) => { + login = async (user, hash, session = true) => { try { - const response = await api.user.login(user, hash); + const response = await api.user.login(user, hash, session); if (!response || (!response.token && !response.tfa)) throw response.error.message; diff --git a/packages/asc-web-common/sw/helper.js b/packages/asc-web-common/sw/helper.js new file mode 100644 index 00000000000..016a01d5490 --- /dev/null +++ b/packages/asc-web-common/sw/helper.js @@ -0,0 +1,11 @@ +import registerSW from "./register"; +import unregisterSW from "./unregister"; + +window.SW = { + register: registerSW, + unregister: unregisterSW, +}; + +export { unregisterSW as registerSW, unregisterSW }; +// TODO: Replace 'unregisterSW as registerSW' to 'registerSW' when sw.js is needed +//export { registerSW, unregisterSW }; diff --git a/packages/asc-web-common/sw/register.js b/packages/asc-web-common/sw/register.js new file mode 100644 index 00000000000..bb219565923 --- /dev/null +++ b/packages/asc-web-common/sw/register.js @@ -0,0 +1,131 @@ +import React from "react"; +import ReactDOM from "react-dom"; + +import { Workbox } from "workbox-window"; +import SnackBar from "@appserver/components/snackbar"; +import i18n from "i18next"; +import { useTranslation, initReactI18next } from "react-i18next"; +import Backend from "i18next-http-backend"; +import { LANGUAGE } from "../constants"; +import { loadLanguagePath } from "../utils"; + +i18n + .use(Backend) + .use(initReactI18next) + .init({ + lng: localStorage.getItem(LANGUAGE) || "en", + fallbackLng: "en", + load: "currentOnly", + //debug: true, + + interpolation: { + escapeValue: false, // not needed for react as it escapes by default + format: function (value, format) { + if (format === "lowercase") return value.toLowerCase(); + return value; + }, + }, + + backend: { + loadPath: loadLanguagePath(""), + }, + + react: { + useSuspense: false, + }, + }); + +const SnackBarWrapper = (props) => { + const { t, ready } = useTranslation("Common", { i18n }); + + if (ready) { + const barConfig = { + parentElementId: "snackbar", + text: t("Common:NewVersionAvailable"), + btnText: t("Common:Load"), + onAction: () => props.onButtonClick(), + opacity: 1, + countDownTime: 5 * 60 * 1000, + }; + + return ; + } + return <>; +}; + +export default function () { + if ( + process.env.NODE_ENV !== "production" && + !("serviceWorker" in navigator) + ) { + console.log("SKIP registerSW because of DEV mode"); + return; + } + + const wb = new Workbox(`/sw.js`); + + const showSkipWaitingPrompt = (event) => { + console.log( + `A new service worker has installed, but it can't activate` + + `until all tabs running the current version have fully unloaded.` + ); + + function refresh() { + wb.addEventListener("controlling", () => { + localStorage.removeItem("sw_need_activation"); + window.location.reload(); + }); + + // This will postMessage() to the waiting service worker. + wb.messageSkipWaiting(); + } + + try { + const snackbarNode = document.createElement("div"); + snackbarNode.id = "snackbar"; + document.body.appendChild(snackbarNode); + + ReactDOM.render( + { + snackbarNode.remove(); + refresh(); + }} + />, + document.getElementById("snackbar") + ); + + localStorage.setItem("sw_need_activation", true); + } catch (e) { + console.error("showSkipWaitingPrompt", e); + refresh(); + } + }; + + window.addEventListener("beforeunload", async () => { + if (localStorage.getItem("sw_need_activation")) { + localStorage.removeItem("sw_need_activation"); + wb.messageSkipWaiting(); + } + }); + + // Add an event listener to detect when the registered + // service worker has installed but is waiting to activate. + wb.addEventListener("waiting", showSkipWaitingPrompt); + + wb.register() + .then((reg) => { + console.log("Successful service worker registration", reg); + + if (!window.swUpdateTimer) { + console.log("SW timer checks for updates every hour"); + window.swUpdateTimer = setInterval(() => { + console.log("SW update timer check"); + reg.update().catch((e) => { + console.error("SW update timer FAILED", e); + }); + }, 60 * 60 * 1000); + } + }) + .catch((err) => console.error("Service worker registration failed", err)); +} diff --git a/packages/asc-web-common/utils/sw-template.js b/packages/asc-web-common/sw/template.js similarity index 100% rename from packages/asc-web-common/utils/sw-template.js rename to packages/asc-web-common/sw/template.js diff --git a/packages/asc-web-common/sw/unregister.js b/packages/asc-web-common/sw/unregister.js new file mode 100644 index 00000000000..535b260f0d3 --- /dev/null +++ b/packages/asc-web-common/sw/unregister.js @@ -0,0 +1,55 @@ +function clearCaches() { + try { + caches?.keys()?.then(function (keyList) { + return Promise.all( + keyList.map(function (key) { + if ( + key.startsWith("workbox-") || + key.startsWith("wb6-") || + key.startsWith("appserver-") + ) { + return caches.delete(key); + } + }) + ); + }); + } catch (error) { + console.error("clearCaches failed", error); + } +} +export default function () { + if ( + process.env.NODE_ENV !== "production" && + !("serviceWorker" in navigator) + ) { + console.log("SKIP registerSW because of DEV mode"); + return; + } + + clearCaches(); + + return navigator.serviceWorker + .getRegistrations() + .then(function (registrations) { + for (let registration of registrations) { + registration + .unregister() + .then(function () { + return self.clients?.matchAll() || []; + }) + .then(function (clients) { + clients.forEach((client) => { + if (client.url && "navigate" in client) { + client.navigate(client.url); + } + }); + }) + .catch((err) => { + console.error(err); + }); + } + }) + .catch((err) => { + console.error(err); + }); +} diff --git a/packages/asc-web-common/utils/index.js b/packages/asc-web-common/utils/index.js index e5baaec7faf..895a34ab128 100644 --- a/packages/asc-web-common/utils/index.js +++ b/packages/asc-web-common/utils/index.js @@ -305,3 +305,20 @@ export function isRetina() { if (window.matchMedia && window.matchMedia(mediaQuery).matches) return true; return false; } + +export function convertLanguage(key) { + switch (key) { + case "en-US": + return "en"; + case "ru-RU": + return "ru"; + case "de-DE": + return "de"; + case "it-IT": + return "it"; + case "fr-FR": + return "fr"; + } + + return key; +} diff --git a/packages/asc-web-common/utils/sw-helper.js b/packages/asc-web-common/utils/sw-helper.js deleted file mode 100644 index 23122a73ee9..00000000000 --- a/packages/asc-web-common/utils/sw-helper.js +++ /dev/null @@ -1,138 +0,0 @@ -import React from "react"; -import ReactDOM from "react-dom"; - -import { Workbox } from "workbox-window"; -import SnackBar from "@appserver/components/snackbar"; -import i18n from "i18next"; -import { useTranslation, initReactI18next } from "react-i18next"; -import Backend from "i18next-http-backend"; -import { LANGUAGE } from "../constants"; -import { loadLanguagePath } from "./"; - -i18n - .use(Backend) - .use(initReactI18next) - .init({ - lng: localStorage.getItem(LANGUAGE) || "en", - fallbackLng: "en", - load: "currentOnly", - //debug: true, - - interpolation: { - escapeValue: false, // not needed for react as it escapes by default - format: function (value, format) { - if (format === "lowercase") return value.toLowerCase(); - return value; - }, - }, - - backend: { - loadPath: loadLanguagePath(""), - }, - - react: { - useSuspense: false, - }, - }); - -const SnackBarWrapper = (props) => { - const { t, ready } = useTranslation("Common", { i18n }); - - if (ready) { - const barConfig = { - parentElementId: "snackbar", - text: t("NewVersionAvailable"), - btnText: t("Load"), - onAction: () => props.onButtonClick(), - opacity: 1, - countDownTime: 5 * 60 * 1000, - }; - - return ; - } - return <>; -}; - -const registerSW = () => { - return; //TODO: Enable service-worker after fix of infinite reloading (Bug 53063) - - /* - if (process.env.NODE_ENV === "production" && "serviceWorker" in navigator) { - const wb = new Workbox(`/sw.js`); - - const showSkipWaitingPrompt = (event) => { - console.log( - `A new service worker has installed, but it can't activate` + - `until all tabs running the current version have fully unloaded.` - ); - - function refresh() { - wb.addEventListener("controlling", () => { - localStorage.removeItem("sw_need_activation"); - window.location.reload(); - }); - - // This will postMessage() to the waiting service worker. - wb.messageSkipWaiting(); - } - - try { - const snackbarNode = document.createElement("div"); - snackbarNode.id = "snackbar"; - document.body.appendChild(snackbarNode); - - ReactDOM.render( - { - snackbarNode.remove(); - refresh(); - }} - />, - document.getElementById("snackbar") - ); - - localStorage.setItem("sw_need_activation", true); - } catch (e) { - console.error("showSkipWaitingPrompt", e); - refresh(); - } - }; - - window.addEventListener("beforeunload", async () => { - if (localStorage.getItem("sw_need_activation")) { - localStorage.removeItem("sw_need_activation"); - wb.messageSkipWaiting(); - } - }); - - // Add an event listener to detect when the registered - // service worker has installed but is waiting to activate. - wb.addEventListener("waiting", showSkipWaitingPrompt); - - wb.register() - .then((reg) => { - console.log("Successful service worker registration", reg); - - if (!window.swUpdateTimer) { - console.log("SW timer checks for updates every hour"); - window.swUpdateTimer = setInterval(() => { - console.log("SW update timer check"); - reg.update().catch((e) => { - console.error("SW update timer FAILED", e); - }); - }, 60 * 60 * 1000); - } - }) - .catch((err) => console.error("Service worker registration failed", err)); - } else { - console.log("SKIP registerSW because of DEV mode"); - } - - */ -}; - -window.SW = { - registerSW: registerSW, -}; - -export { registerSW }; diff --git a/packages/asc-web-components/.storybook/main.js b/packages/asc-web-components/.storybook/main.js index 95ab720207b..8f22feafcba 100644 --- a/packages/asc-web-components/.storybook/main.js +++ b/packages/asc-web-components/.storybook/main.js @@ -2,11 +2,25 @@ module.exports = { stories: ["../**/*.stories.@(js|mdx)"], addons: [ "@storybook/addon-links", - "@storybook/addon-docs", "@storybook/addon-essentials", "@storybook/addon-actions", "@storybook/addon-controls", "@storybook/addon-viewport", "@storybook/addon-contexts/register", + { + name: "@storybook/addon-docs", + options: { + babelOptions: { + plugins: [ + [ + "@babel/plugin-proposal-private-property-in-object", + { + loose: true, + }, + ], + ], + }, + }, + }, ], }; diff --git a/packages/asc-web-components/avatar-editor/index.js b/packages/asc-web-components/avatar-editor/index.js index 35148d8a034..cc5778177fb 100644 --- a/packages/asc-web-components/avatar-editor/index.js +++ b/packages/asc-web-components/avatar-editor/index.js @@ -113,6 +113,7 @@ class AvatarEditor extends React.Component { saveButtonLoading, useModalDialog, cancelButtonLabel, + maxSizeLabel, } = this.props; return useModalDialog ? ( @@ -146,6 +147,7 @@ class AvatarEditor extends React.Component { unknownTypeError={unknownTypeError} maxSizeFileError={maxSizeFileError} unknownError={unknownError} + maxSizeLabel={maxSizeLabel} /> @@ -179,6 +181,7 @@ class AvatarEditor extends React.Component { maxSizeFileError={maxSizeFileError} unknownError={unknownError} useModalDialog={false} + maxSizeLabel={maxSizeLabel} />