1
0
mirror of https://github.com/ONLYOFFICE/CommunityServer.git synced 2025-04-18 13:24:01 +03:00

Update to v12.7.0

This commit is contained in:
JenkinsRobo 2024-10-04 13:39:22 +00:00
parent 678eedf95f
commit d59e563e8c
172 changed files with 2444 additions and 1212 deletions

View File

@ -1,5 +1,90 @@
# Change log
## Version 12.7.0
### General portal changes
* Using the latest version of the Facebook API without specifying a version number. Previously, version 2.7 was used, which is outdated.
* Added Zoom login provider.
* Added the mysql port setting in the UrlShortener service config.
* Added methods for getting entities in parts and sorting by id.
* Fixed the dark theme issues: the deeplink dialog window for opening a file on mobile devices; the "On top" button in entity lists on a narrow screen; the letter container in the Mail module; the dialog window for mentioning a user via @ in ckeditor.
* Removed font styles when pasting text from MS Word to ckeditor.
* Disabled Facebook until the application is validated.
* Disabled Google Drive until the application is validated.
* Increased the length of the short link.
* Fixed the issue with indicating an incorrect maximum number of characters in the hint. TenantDomainValidator: A subdomain can be up to 255 characters long, but if you have multiple levels in your subdomain, each level can only be 63 characters long (Bug 66512).
* Fixed the issue when the /ajaxpro/ASC.Web.Studio.UserControls.Common.PollForm.PollForm,ASC.Web.Studio.ashx method allows voting through BS Turbo Intruder multiple times (Bug 66500).
* Fixed the issue when the api/2.0/settings/customnavigation/getall method is available to users without administrator rights (Bug 66647).
* Fixed errors in the Favorites folder after changing the settings for connecting a third-party storage, if its file was added to Favorites. (Bug 66624).
* Fixed issue when the /api/2.0/settings/security method is available to users without administrator rights. (Bug 66663).
* Fixed the issue with missing backend validation for the Success probability. (Bug 66667).
* Fixed the typo in the link to the Organization profile page (?type=organisation_profile). (Bug 66715).
* Fixed the issue when the /api/2.0/settings/security/loginSettings method allows seting values of more than 4 characters for Brute Force Protection in Workspace. (Bug 66756).
* Fixed the issue when the /ajaxpro/ASP.usercontrols_management_cookiesettings_cookiesettings_ascx,ASC.ashx method allows setting a value of more than 4 characters for Session Lifetime in Workspace. (Bug 66709).
* Fixed the "Could not resolve current tenant" error when exporting a large number of contacts. (Bug 62984).
* Fixed the issue when contact avatars are available via direct link when access is closed for the user. (Bug 66708).
* Fixed the issue when the /fckuploader method allows users without access to the Community module to upload images. (Bug 66710).
* Fixed the issue when a user without access to the mail server can use the mailserver/domains/common method. (Bug 67100).
* Fixed the translation of the Unblock/Check-in button in Chinese. (Bug 67464).
* Fixed the issue when the /ajaxpro/ASC.Web.Studio.UserControls.Common.PollForm.PollForm,ASC.Web.Studio.ashx method allows a user with limited access to the Community module to vote. (Bug 67465).
* Fixed the issue with the white background of a document title on the Deeplink page with the dark interface theme. (Bug 67529).
* Fixed the issue with the "index was outside the bounds of the array" error when generating reports. (Bug 67986).
* Added a loader in the manager when saving .docxf via Save as PDF form. (Bug 66529).
* Fixed the issue when a user without administrator rights can download the backup file via a direct link. (Bug 68162).
* Switched FFmpeg-installer to use pre-downloaded file. (Bug 67421).
* Updated MySQL to v8.0.37. (Bug 68348).
* Fixed the issue with missing the Save as PDF form button in the context menu of the .oform file. (Bug 68646).
* Fixed the issue when the /app/onlyoffice/CommunityServer/data/Products/Files/00/00/01/temp/ folder is added to the backup file. (Bug 68392).
* Fixed the issue when the value "undefined" appears in the URL after selecting yourself as a responsible for a project. (Bug 68942).
* Fixed the issue when the /api/2.0/portal/usedspace method is available to users without administrator rights in Worksapce. (Bug 68990).
* Fixed the issue with the advanced sorting list button in the Recent folder. (Bug 68707).
* Fixed the issue with missing the link to download the temporary Backup file after refreshing the page. (Bug 67877).
* Fixed the issue with upgrading Workspace to Enterprise Edition (Bug 68561).
* Fixed the issue with null values of imported data in a link to a third-party resource instead of a warning. (Bug 69914).
* Fixed the issue when the link to change email remains active after sending a message to change email to another address. (Bug 68836).
* Fixed the issue when the file list does not scroll when selecting with the left mouse button held down. (Bug 68654).
* Replaced Twitter icon with actual X icon. (Bug 70255).
* Fixed the issue when files without an extension are not included in the backup archive. (Bug 70294).
* Fixed the "DOMNodeInserted" error while creating message. (Bug 70295).
* Fixed hanging the Mail module when changing the number of displayed letters /page_size=1000/. (Bug 68786).
* Fixed the issue when documents are not available after performing restore. (Bug 70348).
* Fixed the issue when the POST /api/2.0/settings/rebranding/company method allows passing to the About window the email and website addresses using punycode. (Bug 70257).
* Fixed the issue when the /api/2.0/settings/tfaappcodes method allows viewing Backup codes after disabling two-factor authentication in Workspace. (Bug 70390).
* Fixed the issue when the PUT /api/2.0/portal/portalrename method allows renaming a portal with a space before the name. (Bug 70391).
* Fixed the issue with the Enterprise Edition license. (Bug 70051).
* Fixed the issue when the /api/2.0/portal/getshortenlink method allows substituting a malicious site into the shortlink. (Bug 70392).
* Fixed the issue when the PUT /api/2.0/people/{ID} method allows allows changing a type of a deactivated user. (Bug 70395).
* Fixed the issue when the api/2.0/group/{groupid}/manager method allows assigning a blocked user as a group manager. (Bug 70418).
* Fixed the issue with a timeout expected when entering an incorrect login/password for the mail/gmail mailbox. (Bug 68740).
* Fixed the issue when marking a To-do task as completed or uncompleted changes its start time (DTSTART label). (Bug 68970).
* Fixed the issue with missing logos of the social network (X) Twitter in the dark theme of the portal. (Bug 70457).
### Documents module
* Updated the list of the editors error codes.
* The djvu, oxps, pdf, xps formats will be sent to the editor with the documentType=pdf indication (editor 8.0 or higher is required). The docxf and oform formats will be sent to the editor with the documentType=pdf indication (editor 8.1 or higher is required).
* The Protected panel is hidden for the anonymous so that he cannot set a password for the file.
* When sharing via an external link for viewing, the chat is not available and the username is not requested.
* User avatars are transferred to the editor for authorized users if there is access to the People module.
* In a mobile browser, when opening the editor, the ONLYOFFICE logo will not be displayed if the license allows (editor 8.1 or higher is required).
* Files in djvu and oform formats can be converted to pdf (editor 7.1 or higher is required).
* When creating a form, an empty PDF form file will be opened for editing. In the context menu of any PDF file there are the following buttons: edit (the mode as for docxf with changing all contents and saving), fill in (the mode as for oform with filling in fields and saving) and view (the mode of opening for viewing with the ability to fill in without saving) (editor 8.1 or higher is required). The document manager does not distinguish a PDF file from our PDF form.
* For the docxf file, the "Save as PDF form" button is kept in the context menu. A similar button is added to the context menu of the oform file.
* Creating a PDF form from docx works through conversion, editor 8.1 or higher is required.
* Support for referencing a portal file in a spreadsheet in the IMPORTRANGE formula (editor 8.1 or higher is required).
* Added the sr-Cyrl-RS empty file.
* Updated empty file templates in English.
* Added the version parameter to GetPresignedUri.
* Added the shardkey parameter for requests to editors.
### Control Panel
* Fixed the issue with missing the link to download the temporary Backup file after refreshing the page. (Bug 70341).
* Fixed the issue when a curl request to the Control Panel stops the service on Windows. (Bug 70049).
* Fixed the issue when the description of the dark theme logo indicates that the logo is for the light theme. (Bug 68858).
* Added a checkbox when creating a backup for migration to DocSpace.
## Version 12.6.0
### General portal changes

View File

@ -263,7 +263,7 @@ case "$1" in
rm -f /etc/nginx/sites-enabled/{{package_sysname}}-apisystem
# disable apparmor mysql. need for best perfomance mysql
if which apparmor_parser && [ ! -f /etc/apparmor.d/disable/usr.sbin.mysqld ]; then
if which apparmor_parser && [ ! -f /etc/apparmor.d/disable/usr.sbin.mysqld ] && [ -f /etc/apparmor.d/disable/ ]; then
ln -sf /etc/apparmor.d/usr.sbin.mysqld /etc/apparmor.d/disable/
apparmor_parser -R /etc/apparmor.d/usr.sbin.mysqld || true
fi
@ -283,13 +283,13 @@ END
[ -f /usr/lib/python3.$(python3 -c 'import sys; print(sys.version_info.minor)')/EXTERNALLY-MANAGED ] && \
rm /usr/lib/python3.$(python3 -c 'import sys; print(sys.version_info.minor)')/EXTERNALLY-MANAGED
python3 -m pip install --upgrade pip
python3 -m pip install --upgrade requests
python3 -m pip install --upgrade setuptools
python3 -m pip install --upgrade radicale==3.0.5
python3 -m pip install --upgrade $DIR/Tools/radicale/plugins/app_auth_plugin/.
python3 -m pip install --upgrade $DIR/Tools/radicale/plugins/app_store_plugin/.
python3 -m pip install --upgrade $DIR/Tools/radicale/plugins/app_rights_plugin/.
for pkg in radicale==3.0.5 requests setuptools importlib_metadata; do
dpkg -l python3-"${pkg%%=*}" &>/dev/null || pip_packages+=("$pkg")
done
export PIP_ROOT_USER_ACTION=ignore
python3 -m pip install --upgrade "${pip_packages[@]}" \
"$DIR/Tools/radicale/plugins/"{app_auth_plugin,app_store_plugin,app_rights_plugin}/. || true
#configure elasticsearch
service elasticsearch stop
@ -452,7 +452,7 @@ EOF
sed '/web\.controlpanel\.url/s/\(value\s*=\s*\"\)[^\"]*\"/\1\/controlpanel\/\"/' -i ${APP_ROOT_DIR}/web.appsettings.config;
sed '/web\.controlpanel\.url/s/\(value\s*=\s*\"\)[^\"]*\"/\1\/controlpanel\/\"/' -i ${APP_SERVICES_DIR}/TeamLabSvc/TeamLabSvc.exe.config;
if systemctl is-active {{package_sysname}}ControlPanel | grep -q ^active; then
if systemctl is-active {{package_sysname}}ControlPanel &>/dev/null; then
service {{package_sysname}}ControlPanel restart >/dev/null 2>&1
fi
fi
@ -522,7 +522,7 @@ CipherString = $CIPHERSTRING \n" >> $OPENSSL_CONF
service nginx restart >/dev/null 2>&1
service mysql restart >/dev/null 2>&1 || true # ignore errors
if systemctl is-active monoserve | grep -q "active"; then
if systemctl is-active monoserve &>/dev/null; then
curl --silent --output /dev/null http://127.0.0.1/api/2.0/warmup/restart.json || true
fi

View File

@ -111,7 +111,7 @@ IFS="$OLD_IFS"
rm -rf "$RPM_BUILD_ROOT"
%files -f onlyoffice.list
%attr(-, root, root) /usr/bin/*.sh
%attr(744, root, root) /usr/bin/*.sh
%attr(-, %{package_sysname}, %{package_sysname}) %dir /var/log/%{package_sysname}/
%attr(-, root, root) /usr/lib/systemd/system/*.service
%attr(-, root, root) %{nginx_conf_d}/%{package_sysname}.conf
@ -160,7 +160,7 @@ find "$DIR/controlpanel/www/config" -type f -name "*.json" -exec sed -i "s_\(\"c
package_services=("monoserve" "%{package_sysname}ControlPanel")
for SVC in "${package_services[@]}"; do
if systemctl is-active "$SVC" | grep -q ^active; then
if systemctl is-active "$SVC" &>/dev/null; then
systemctl restart "$SVC"
fi
done
@ -175,12 +175,13 @@ fi
DIR="/var/www/%{package_sysname}"
python3 -m pip install --upgrade pip
python3 -m pip install --upgrade requests
python3 -m pip install --upgrade radicale==3.0.5
python3 -m pip install --upgrade $DIR/Tools/radicale/plugins/app_auth_plugin/.
python3 -m pip install --upgrade $DIR/Tools/radicale/plugins/app_store_plugin/.
python3 -m pip install --upgrade $DIR/Tools/radicale/plugins/app_rights_plugin/.
for pkg in radicale==3.0.5 requests setuptools importlib_metadata; do
rpm -q python3-"${pkg%%=*}" &>/dev/null || pip_packages+=("$pkg")
done
export PIP_ROOT_USER_ACTION=ignore
python3 -m pip install --upgrade "${pip_packages[@]}" \
"$DIR/Tools/radicale/plugins/"{app_auth_plugin,app_store_plugin,app_rights_plugin}/. || true
systemctl restart %{package_sysname}Radicale
@ -191,7 +192,7 @@ APP_ROOT_DIR="$DIR/WebStudio";
sed '/web\.talk/s/value=\"\S*\"/value=\"true\"/g' -i ${APP_ROOT_DIR}/web.appsettings.config
if systemctl is-active monoserve | grep -q "active"; then
if systemctl is-active monoserve &>/dev/null; then
systemctl restart monoserve
fi
@ -256,7 +257,7 @@ fi
for SVC in monoserve %{package_sysname}Thumb %{package_sysname}ThumbnailBuilder
do
if systemctl is-active $SVC | grep -q "active"; then
if systemctl is-active $SVC &>/dev/null; then
systemctl restart $SVC
fi
done
@ -310,7 +311,7 @@ APP_INDEX_DIR="${APP_DATA_DIR}/Index/v${ELASTIC_SEARCH_VERSION}"
LOG_DIR="/var/log/%{package_sysname}"
#import common ssl certificates
mozroots --import --sync --machine --quiet
mozroots --import --sync --machine --quiet || cert-sync /etc/pki/tls/certs/ca-bundle.crt || true
mkdir -p /etc/mono/registry/LocalMachine
mkdir -p /usr/share/.mono/keypairs
mkdir -p /var/cache/nginx/%{package_sysname}
@ -368,15 +369,12 @@ if [ $1 -ge 2 ]; then
fi
fi
if [ $1 -eq 1 ]; then
if /usr/share/elasticsearch/bin/elasticsearch-plugin list | grep -q "ingest-attachment"; then
/usr/share/elasticsearch/bin/elasticsearch-plugin remove ingest-attachment
fi
/usr/share/elasticsearch/bin/elasticsearch-plugin install -s -b ingest-attachment
if /usr/share/elasticsearch/bin/elasticsearch-plugin list | grep -q "ingest-attachment"; then
/usr/share/elasticsearch/bin/elasticsearch-plugin remove ingest-attachment
fi
/usr/share/elasticsearch/bin/elasticsearch-plugin install -s -b ingest-attachment
if [ -f ${ELASTIC_SEARCH_CONF_PATH}.rpmnew ]; then
cp -rf ${ELASTIC_SEARCH_CONF_PATH}.rpmnew ${ELASTIC_SEARCH_CONF_PATH};
fi
@ -454,7 +452,7 @@ if [ -d /etc/elasticsearch/ ]; then
chmod g+ws /etc/elasticsearch/
fi
if systemctl is-active elasticsearch | grep -q "active"; then
if systemctl is-active elasticsearch &>/dev/null; then
#Checking that the elastic is not currently being updated
if [[ $(find /usr/share/elasticsearch/lib/ -name "elasticsearch-[0-9]*.jar" | wc -l) -eq 1 ]]; then
systemctl restart elasticsearch.service
@ -506,13 +504,13 @@ if [ $1 -ge 2 ]; then
systemctl restart $SVC
fi
done
if systemctl is-active %{package_sysname}AutoCleanUp | grep -q "active"; then
if systemctl is-active %{package_sysname}AutoCleanUp &>/dev/null; then
systemctl disable %{package_sysname}AutoCleanUp
systemctl stop %{package_sysname}AutoCleanUp
fi
fi
if systemctl is-active monoserve | grep -q "active"; then
if systemctl is-active monoserve &>/dev/null; then
curl --silent --output /dev/null http://127.0.0.1/api/2.0/warmup/restart.json || true
fi
@ -549,11 +547,11 @@ if [ ! -z $ELASTIC_SEARCH_VERSION ]; then
$DIR/elasticsearch-plugin install -s -b ingest-attachment
if ! systemctl is-active elasticsearch | grep -q "inactive"; then
if systemctl is-active elasticsearch &>/dev/null; then
systemctl restart elasticsearch
fi
if ! systemctl is-active %{package_sysname}Index | grep -q "inactive"; then
if systemctl is-active %{package_sysname}Index &>/dev/null; then
systemctl restart %{package_sysname}Index
fi
fi

View File

@ -239,6 +239,7 @@ BEGIN
insert into files_converts (input, output) values ('.csv', '.xlsx');
insert into files_converts (input, output) values ('.csv', '.xltm');
insert into files_converts (input, output) values ('.csv', '.xltx');
insert into files_converts (input, output) values ('.djvu', '.pdf');
insert into files_converts (input, output) values ('.doc', '.docm');
insert into files_converts (input, output) values ('.doc', '.docx');
insert into files_converts (input, output) values ('.doc', '.dotm');
@ -494,6 +495,7 @@ BEGIN
insert into files_converts (input, output) values ('.odt', '.pdf');
insert into files_converts (input, output) values ('.odt', '.rtf');
insert into files_converts (input, output) values ('.odt', '.txt');
insert into files_converts (input, output) values ('.oform', '.pdf');
insert into files_converts (input, output) values ('.ott', '.docm');
insert into files_converts (input, output) values ('.ott', '.docx');
insert into files_converts (input, output) values ('.ott', '.dotm');

View File

@ -2464,7 +2464,7 @@ CREATE TABLE IF NOT EXISTS `wiki_pages_history` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `short_links` (
`id` INT(21) NOT NULL AUTO_INCREMENT,
`id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
`short` VARCHAR(12) COLLATE utf8_bin NULL DEFAULT NULL,
`link` TEXT NULL,
PRIMARY KEY (`id`),

View File

@ -0,0 +1,19 @@
DELIMITER DLM00
DROP PROCEDURE IF EXISTS upgrade127 DLM00
CREATE PROCEDURE upgrade127()
BEGIN
INSERT IGNORE INTO `files_converts` (`input`, `output`) VALUES ('.djvu', '.pdf');
INSERT IGNORE INTO `files_converts` (`input`, `output`) VALUES ('.oform', '.pdf');
DELETE FROM `files_converts` WHERE `output`='.docxf';
ALTER TABLE `short_links` CHANGE COLUMN `id` `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT;
END DLM00
CALL upgrade127() DLM00
DELIMITER ;

View File

@ -83,6 +83,7 @@
<Compile Include="Core\PartnerStatus.cs" />
<Compile Include="Core\PartnerType.cs" />
<Compile Include="Core\DBResourceManager.cs" />
<Compile Include="Core\StringExtension.cs" />
<Compile Include="Data\DbLoginEventsManager.cs" />
<Compile Include="Data\DbSettingsManager.cs" />
<Compile Include="Encryption\EncryprtionStatus.cs" />

View File

@ -57,6 +57,9 @@ namespace ASC.Core.Common.Contracts
[DataMember]
public Dictionary<string, string> StorageParams { get; set; }
[DataMember]
public bool Dump { get; set; }
}
[DataContract]

View File

@ -1,123 +1,160 @@
/*
*
* (c) Copyright Ascensio System Limited 2010-2023
*
* 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.Globalization;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
namespace System
{
public static class StringExtension
{
private static readonly Regex reStrict = new Regex(@"^(([^<>()[\]\\.,;:\s@\""]+"
+ @"(\.[^<>()[\]\\.,;:\s@\""]+)*)|(\"".+\""))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}"
+ @"\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$");
public static string HtmlEncode(this string str)
{
return !string.IsNullOrEmpty(str) ? HttpUtility.HtmlEncode(str) : str;
}
/// <summary>
/// Replace ' on
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
public static string ReplaceSingleQuote(this string str)
{
return str == null ? null : str.Replace("'", "");
}
public static bool TestEmailRegex(this string emailAddress)
{
emailAddress = (emailAddress ?? "").Trim();
return !string.IsNullOrEmpty(emailAddress) && reStrict.IsMatch(emailAddress);
}
public static string GetMD5Hash(this string str)
{
var bytes = Encoding.Unicode.GetBytes(str);
var CSP = new MD5CryptoServiceProvider();
var byteHash = CSP.ComputeHash(bytes);
return byteHash.Aggregate(String.Empty, (current, b) => current + String.Format("{0:x2}", b));
}
public static int EnumerableComparer(this string x, string y)
{
var xIndex = 0;
var yIndex = 0;
while (xIndex < x.Length)
{
if (yIndex >= y.Length)
return 1;
if (char.IsDigit(x[xIndex]) && char.IsDigit(y[yIndex]))
{
var xBuilder = new StringBuilder();
while (xIndex < x.Length && char.IsDigit(x[xIndex]))
{
xBuilder.Append(x[xIndex++]);
}
var yBuilder = new StringBuilder();
while (yIndex < y.Length && char.IsDigit(y[yIndex]))
{
yBuilder.Append(y[yIndex++]);
}
long xValue;
if (!long.TryParse(xBuilder.ToString(), out xValue))
{
xValue = Int64.MaxValue;
}
long yValue;
if (!long.TryParse(yBuilder.ToString(), out yValue))
{
yValue = Int64.MaxValue;
}
int difference;
if ((difference = xValue.CompareTo(yValue)) != 0)
return difference;
}
else
{
int difference;
if ((difference = string.Compare(x[xIndex].ToString(CultureInfo.InvariantCulture), y[yIndex].ToString(CultureInfo.InvariantCulture), StringComparison.InvariantCultureIgnoreCase)) != 0)
return difference;
xIndex++;
yIndex++;
}
}
if (yIndex < y.Length)
return -1;
return 0;
}
}
}
/*
*
* (c) Copyright Ascensio System Limited 2010-2023
*
* 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.Globalization;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
namespace System
{
public static class StringExtension
{
private static readonly Regex reStrict = new Regex(@"^(([^<>()[\]\\.,;:\s@\""]+"
+ @"(\.[^<>()[\]\\.,;:\s@\""]+)*)|(\"".+\""))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}"
+ @"\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$");
public static string HtmlEncode(this string str)
{
return !string.IsNullOrEmpty(str) ? HttpUtility.HtmlEncode(str) : str;
}
/// <summary>
/// Replace ' on
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
public static string ReplaceSingleQuote(this string str)
{
return str == null ? null : str.Replace("'", "");
}
public static bool TestEmailRegex(this string emailAddress)
{
emailAddress = (emailAddress ?? "").Trim();
return !string.IsNullOrEmpty(emailAddress) && reStrict.IsMatch(emailAddress);
}
public static bool TestEmailPunyCode(this string emailAddress)
{
var toTest = reStrict.Match(emailAddress);
for (int i = 0; i < toTest.Groups.Count; ++i)
{
var value = toTest.Groups[i].Value;
if (!string.IsNullOrEmpty(value) && TestPunyCode(value))
{
return true;
}
}
return false;
}
public static bool TestPunyCode(this string data)
{
var idn = new IdnMapping();
try
{
var punyCode = idn.GetAscii(data);
var domain2 = idn.GetUnicode(punyCode);
if (!string.Equals(punyCode, domain2))
{
return true;
}
}
catch (ArgumentException)
{
}
return false;
}
public static string GetMD5Hash(this string str)
{
var bytes = Encoding.Unicode.GetBytes(str);
var CSP = new MD5CryptoServiceProvider();
var byteHash = CSP.ComputeHash(bytes);
return byteHash.Aggregate(String.Empty, (current, b) => current + String.Format("{0:x2}", b));
}
public static int EnumerableComparer(this string x, string y)
{
var xIndex = 0;
var yIndex = 0;
while (xIndex < x.Length)
{
if (yIndex >= y.Length)
return 1;
if (char.IsDigit(x[xIndex]) && char.IsDigit(y[yIndex]))
{
var xBuilder = new StringBuilder();
while (xIndex < x.Length && char.IsDigit(x[xIndex]))
{
xBuilder.Append(x[xIndex++]);
}
var yBuilder = new StringBuilder();
while (yIndex < y.Length && char.IsDigit(y[yIndex]))
{
yBuilder.Append(y[yIndex++]);
}
long xValue;
if (!long.TryParse(xBuilder.ToString(), out xValue))
{
xValue = Int64.MaxValue;
}
long yValue;
if (!long.TryParse(yBuilder.ToString(), out yValue))
{
yValue = Int64.MaxValue;
}
int difference;
if ((difference = xValue.CompareTo(yValue)) != 0)
return difference;
}
else
{
int difference;
if ((difference = string.Compare(x[xIndex].ToString(CultureInfo.InvariantCulture), y[yIndex].ToString(CultureInfo.InvariantCulture), StringComparison.InvariantCultureIgnoreCase)) != 0)
return difference;
xIndex++;
yIndex++;
}
}
if (yIndex < y.Length)
return -1;
return 0;
}
}
}

View File

@ -55,6 +55,14 @@ namespace ASC.Core
get { return tenantService.IsDocspace; }
}
public int DocspaceDefaultQuota
{
get
{
return int.TryParse(ConfigurationManagerExtension.AppSettings["docspace.default.quota"], out var quota) ? quota : -3;
}
}
public HostedSolution(ConnectionStringSettings connectionString)
: this(connectionString, null)
{
@ -217,7 +225,7 @@ namespace ASC.Core
public void SetTariff(int tenant, bool paid)
{
var quota = quotaService.GetTenantQuotas().FirstOrDefault(q => paid ? q.NonProfit : q.Trial);
var quota = quotaService.GetTenantQuotas().FirstOrDefault(q => paid ? q.NonProfit : IsDocspace ? q.Id == DocspaceDefaultQuota : q.Trial);
if (quota != null)
{
tariffService.SetTariff(tenant, new Tariff { QuotaId = quota.Id, DueDate = DateTime.MaxValue, Quantity = 1 });

View File

@ -103,7 +103,9 @@ namespace ASC.Core.Tenants
public static DateTime GetExpiresTime(int tenantId)
{
var settingsTenant = GetForTenant(tenantId);
var expires = settingsTenant.IsDefault() ? DateTime.UtcNow.AddYears(1) : DateTime.UtcNow.AddMinutes(settingsTenant.LifeTime);
var expires = settingsTenant.IsDefault() ?
DateTime.UtcNow.AddYears(1) :
settingsTenant.LifeTime == 0 ? DateTime.MaxValue : DateTime.UtcNow.AddMinutes(settingsTenant.LifeTime);
return expires;
}
}

View File

@ -23,21 +23,34 @@ namespace ASC.Core.Tenants
{
public class TenantDomainValidator
{
private static readonly Regex ValidDomain = new Regex("^[a-z0-9]([a-z0-9-]){1,98}[a-z0-9]$",
RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
private static readonly Regex ValidDomain;
public static readonly int MinLength;
private const int MaxLength = 100;
public static readonly int MaxLength;
static TenantDomainValidator()
{
MaxLength = 63;
if (int.TryParse(ConfigurationManagerExtension.AppSettings["web.alias.max"], out var defaultMaxLength))
{
MaxLength = Math.Max(3, Math.Min(MaxLength, defaultMaxLength));
}
MinLength = 6;
int defaultMinLength;
if (int.TryParse(ConfigurationManagerExtension.AppSettings["web.alias.min"], out defaultMinLength))
if (int.TryParse(ConfigurationManagerExtension.AppSettings["web.alias.min"], out var defaultMinLength))
{
MinLength = Math.Max(1, Math.Min(MaxLength, defaultMinLength));
}
var defaultRegexPattern = ConfigurationManagerExtension.AppSettings["web.alias.regex"];
if (string.IsNullOrEmpty(defaultRegexPattern))
{
defaultRegexPattern = $"^[a-z0-9]([a-z0-9-]){{1,{MaxLength - 2}}}[a-z0-9]$";
}
ValidDomain = new Regex(defaultRegexPattern, RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
}
public static void ValidateDomainLength(string domain)
@ -51,7 +64,7 @@ namespace ASC.Core.Tenants
public static void ValidateDomainCharacters(string domain)
{
if (!ValidDomain.IsMatch(domain))
if (!ValidDomain.IsMatch(domain) || domain.TestPunyCode())
{
throw new TenantIncorrectCharsException("Domain contains invalid characters.");
}

View File

@ -61,7 +61,7 @@ namespace ASC.Data.Backup {
}
/// <summary>
/// Looks up a localized string similar to The backup file is invalid. Please, use a file created in ONLYOFFICE v11.5 or later..
/// Looks up a localized string similar to The backup file is invalid. Please use the file created in the same portal, version 11.5 or higher..
/// </summary>
public static string BackupNotFound {
get {

View File

@ -59,6 +59,6 @@
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=5.0.6.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="BackupNotFound" xml:space="preserve">
<value>The backup file is invalid. Please, use a file created in ONLYOFFICE v11.5 or later.</value>
<value>The backup file is invalid. Please use the file created in the same portal, version 11.5 or higher.</value>
</data>
</root>

View File

@ -109,12 +109,12 @@ namespace ASC.Data.Backup
foreach (var domain in domainList)
{
files.AddRange(store
.ListFilesRelative(domain, "\\", "*.*", true)
.ListFilesRelative(domain, "\\", "*", true)
.Select(x => new FileBackupInfo(domain, module, x)));
}
files.AddRange(store
.ListFilesRelative(string.Empty, "\\", "*.*", true)
.ListFilesRelative(string.Empty, "\\", "*", true)
.Where(x => domainList.All(domain => x.IndexOf(string.Format("{0}/", domain)) == -1))
.Select(x => new FileBackupInfo(string.Empty, module, x)));
}

View File

@ -102,7 +102,7 @@ namespace ASC.Data.Backup.Service
}
if (item == null)
{
item = new BackupProgressItem(false, request.TenantId, request.UserId, request.StorageType, request.StorageBasePath) { BackupMail = request.BackupMail, StorageParams = request.StorageParams };
item = new BackupProgressItem(false, request.TenantId, request.UserId, request.StorageType, request.StorageBasePath, request.Dump) { BackupMail = request.BackupMail, StorageParams = request.StorageParams };
tasks.Add(item);
}
return ToBackupProgress(item);
@ -121,7 +121,7 @@ namespace ASC.Data.Backup.Service
}
if (item == null)
{
item = new BackupProgressItem(true, schedule.TenantId, Guid.Empty, schedule.StorageType, schedule.StorageBasePath) { BackupMail = schedule.BackupMail, StorageParams = schedule.StorageParams };
item = new BackupProgressItem(true, schedule.TenantId, Guid.Empty, schedule.StorageType, schedule.StorageBasePath, CoreContext.Configuration.Standalone) { BackupMail = schedule.BackupMail, StorageParams = schedule.StorageParams };
schedulerTasks.Add(item);
}
}
@ -131,7 +131,24 @@ namespace ASC.Data.Backup.Service
{
lock (tasks.SynchRoot)
{
return ToBackupProgress(tasks.GetItems().OfType<BackupProgressItem>().FirstOrDefault(t => t.TenantId == tenantId));
var progressItem = tasks.GetItems().OfType<BackupProgressItem>().FirstOrDefault(t => t.TenantId == tenantId);
if (progressItem == null)
{
return null;
}
if (progressItem.IsCompleted)
{
var repo = BackupStorageFactory.GetBackupRepository();
var record = repo.GetBackupRecord((Guid)progressItem.Id);
if (record != null && (record.Removed || (record.ExpiresOn != DateTime.MinValue && record.ExpiresOn < DateTime.UtcNow)))
{
tasks.Remove(progressItem);
return null;
}
}
return ToBackupProgress(progressItem);
}
}
@ -261,6 +278,7 @@ namespace ASC.Data.Backup.Service
private bool IsScheduled { get; set; }
public int TenantId { get; private set; }
private Guid UserId { get; set; }
private bool Dump { get; set; }
private BackupStorageType StorageType { get; set; }
private string StorageBasePath { get; set; }
public bool BackupMail { get; set; }
@ -275,7 +293,7 @@ namespace ASC.Data.Backup.Service
public double Percentage { get; set; }
public bool IsCompleted { get; set; }
public BackupProgressItem(bool isScheduled, int tenantId, Guid userId, BackupStorageType storageType, string storageBasePath)
public BackupProgressItem(bool isScheduled, int tenantId, Guid userId, BackupStorageType storageType, string storageBasePath, bool dump)
{
Id = Guid.NewGuid();
IsScheduled = isScheduled;
@ -283,6 +301,7 @@ namespace ASC.Data.Backup.Service
UserId = userId;
StorageType = storageType;
StorageBasePath = storageBasePath;
Dump = dump;
}
public void RunJob()
@ -300,7 +319,7 @@ namespace ASC.Data.Backup.Service
try
{
CoreContext.TenantManager.SetCurrentTenant(TenantId);
var backupTask = new BackupPortalTask(Log, TenantId, configPaths[currentRegion], tempFile, limit);
var backupTask = new BackupPortalTask(Log, TenantId, configPaths[currentRegion], tempFile, limit, Dump);
var backupStorage = BackupStorageFactory.GetBackupStorage(StorageType, TenantId, StorageParams);
var writer = ZipWriteOperatorFactory.GetWriteOperator(StorageBasePath, backupName, TempFolder, UserId, backupStorage as IGetterWriteOperator);
backupTask.WriteOperator = writer;

View File

@ -65,7 +65,7 @@ namespace ASC.Data.Backup.Storage
public BackupRecord GetBackupRecord(Guid id)
{
var select = new SqlQuery("backup_backup")
.Select("id", "tenant_id", "is_scheduled", "name", "storage_type", "storage_base_path", "storage_path", "created_on", "expires_on", "storage_params","hash")
.Select("id", "tenant_id", "is_scheduled", "name", "storage_type", "storage_base_path", "storage_path", "created_on", "expires_on", "storage_params","hash", "removed")
.Where("id", id);
using (var db = GetDbManager())
@ -77,7 +77,7 @@ namespace ASC.Data.Backup.Storage
public BackupRecord GetBackupRecord(string hash, int tenant)
{
var select = new SqlQuery("backup_backup")
.Select("id", "tenant_id", "is_scheduled", "name", "storage_type", "storage_base_path", "storage_path", "created_on", "expires_on", "storage_params", "hash")
.Select("id", "tenant_id", "is_scheduled", "name", "storage_type", "storage_base_path", "storage_path", "created_on", "expires_on", "storage_params", "hash", "removed")
.Where("hash", hash)
.Where("tenant_id", tenant);
@ -90,7 +90,7 @@ namespace ASC.Data.Backup.Storage
public List<BackupRecord> GetExpiredBackupRecords()
{
var select = new SqlQuery("backup_backup")
.Select("id", "tenant_id", "is_scheduled", "name", "storage_type", "storage_base_path", "storage_path", "created_on", "expires_on", "storage_params", "hash")
.Select("id", "tenant_id", "is_scheduled", "name", "storage_type", "storage_base_path", "storage_path", "created_on", "expires_on", "storage_params", "hash", "removed")
.Where(!Exp.Eq("expires_on", DateTime.MinValue) & Exp.Le("expires_on", DateTime.UtcNow))
.Where(Exp.Eq("removed", false));
@ -103,7 +103,7 @@ namespace ASC.Data.Backup.Storage
public List<BackupRecord> GetScheduledBackupRecords()
{
var select = new SqlQuery("backup_backup")
.Select("id", "tenant_id", "is_scheduled", "name", "storage_type", "storage_base_path", "storage_path", "created_on", "expires_on", "storage_params", "hash")
.Select("id", "tenant_id", "is_scheduled", "name", "storage_type", "storage_base_path", "storage_path", "created_on", "expires_on", "storage_params", "hash", "removed")
.Where(Exp.Eq("is_scheduled", true))
.Where(Exp.Eq("removed", false));
@ -121,7 +121,7 @@ namespace ASC.Data.Backup.Storage
private List<BackupRecord> GetBackupRecordsByTenantIdInternal(int tenantId, bool? visible = null)
{
var select = new SqlQuery("backup_backup")
.Select("id", "tenant_id", "is_scheduled", "name", "storage_type", "storage_base_path", "storage_path", "created_on", "expires_on", "storage_params", "hash")
.Select("id", "tenant_id", "is_scheduled", "name", "storage_type", "storage_base_path", "storage_path", "created_on", "expires_on", "storage_params", "hash", "removed")
.Where("tenant_id", tenantId);
if (visible.HasValue)
@ -240,7 +240,8 @@ namespace ASC.Data.Backup.Storage
CreatedOn = Convert.ToDateTime(row[7]),
ExpiresOn = Convert.ToDateTime(row[8]),
StorageParams = JsonConvert.DeserializeObject<Dictionary<string, string>>(Convert.ToString(row[9])),
Hash = Convert.ToString(row[10])
Hash = Convert.ToString(row[10]),
Removed = Convert.ToBoolean(row[11])
};
}

View File

@ -24,7 +24,6 @@ using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
using ASC.Common;
using ASC.Common.Data;
@ -52,8 +51,9 @@ namespace ASC.Data.Backup.Tasks
public string BackupFilePath { get; private set; }
public int Limit { get; private set; }
private bool Dump { get; set; }
private bool ForMigration { get; set; }
public BackupPortalTask(ILog logger, int tenantId, string fromConfigPath, string toFilePath, int limit)
public BackupPortalTask(ILog logger, int tenantId, string fromConfigPath, string toFilePath, int limit, bool dump)
: base(logger, tenantId, fromConfigPath)
{
if (string.IsNullOrEmpty(toFilePath))
@ -61,49 +61,65 @@ namespace ASC.Data.Backup.Tasks
BackupFilePath = toFilePath;
Limit = limit;
Dump = CoreContext.Configuration.Standalone;
Dump = dump && CoreContext.Configuration.Standalone;
ForMigration = !dump && CoreContext.Configuration.Standalone;
backupMailServerFilesService = new BackupMailServerFilesService(Logger);
}
public override void RunJob()
{
Logger.DebugFormat("begin backup {0}", TenantId);
using (WriteOperator)
{
if (Dump)
if (ForMigration)
{
DoDump(WriteOperator);
var ids = CoreContext.TenantManager.GetTenants().Select(t => t.TenantId);
SetStepsCount(ids.Count());
foreach (var id in ids)
{
Backup(id, $"{id}/");
}
}
else
{
var dbFactory = new DbFactory(ConfigPath);
var modulesToProcess = GetModulesToProcess().ToList();
var fileGroups = GetFilesGroup(dbFactory);
var stepscount = ProcessStorage ? fileGroups.Count : 0;
SetStepsCount(modulesToProcess.Count + stepscount);
foreach (var module in modulesToProcess)
{
DoBackupModule(WriteOperator, dbFactory, module);
}
if (!IgnoredModules.Contains(ModuleName.Mail))
{
var paths = DoBackupMailTable(WriteOperator);
if (paths.Count != 0)
{
DoBackupMailFiles(WriteOperator, paths);
}
}
if (ProcessStorage)
{
DoBackupStorage(WriteOperator, fileGroups);
}
SetStepsCount(1);
Backup(TenantId);
}
}
Logger.DebugFormat("end backup {0}", TenantId);
}
public void Backup(int tenantId, string prefix = "")
{
Logger.DebugFormat("begin backup {0}", tenantId);
if (Dump)
{
DoDump(WriteOperator);
}
else
{
var modulesToProcess = GetModulesToProcess().ToList();
var count = modulesToProcess.Select(m => m.Tables.Count(t => !IgnoredTables.Contains(t.Name) && t.InsertMethod != InsertMethod.None)).Sum();
IEnumerable<BackupFileInfo> files = null;
if (ProcessStorage)
{
files = GetFiles(tenantId);
count += files.Count();
}
var completedCount = 0;
var dbFactory = new DbFactory(ConfigPath);
foreach (var module in modulesToProcess)
{
completedCount = DoBackupModule(WriteOperator, dbFactory, module, completedCount, count, tenantId, prefix);
}
if (ProcessStorage)
{
DoBackupStorage(WriteOperator, files, completedCount, count, prefix: prefix);
}
}
Logger.DebugFormat("end backup {0}", tenantId);
}
private void DoDump(IDataWriteOperator writer)
@ -143,25 +159,20 @@ namespace ASC.Data.Backup.Tasks
writer.WriteEntry(KeyHelper.GetDumpKey(), stream);
}
var files = new List<BackupFileInfo>();
IEnumerable<BackupFileInfo> files = null;
var count = databases.Select(d => d.Value.Count * 4).Sum(); // (schema + data) * (dump + zip)
var completedCount = count;
var stepscount = 0;
foreach (var db in databases)
{
stepscount += db.Value.Count * 4;// (schema + data) * (dump + zip)
}
if (ProcessStorage)
{
var tenants = CoreContext.TenantManager.GetTenants(false).Select(r => r.TenantId);
foreach (var t in tenants)
{
files.AddRange(GetFiles(t));
}
stepscount += files.Count * 2 + 1;
Logger.Debug("files:" + files.Count);
files = GetFilesTenants(tenants);
Logger.Debug("files:" + files.Count());
count += files.Count();
}
SetStepsCount(stepscount);
SetStepsCount(1);
foreach (var db in databases)
{
@ -175,7 +186,7 @@ namespace ASC.Data.Backup.Tasks
if (ProcessStorage)
{
DoDumpStorage(writer, files);
DoBackupStorage(writer, files, completedCount, count, true);
}
}
@ -239,9 +250,20 @@ namespace ASC.Data.Backup.Tasks
}
}
private IEnumerable<BackupFileInfo> GetFilesTenants(IEnumerable<int> tenantIds)
{
foreach (var tenantId in tenantIds)
{
var files = GetFiles(tenantId);
foreach (var file in files)
{
yield return file;
}
}
}
private IEnumerable<BackupFileInfo> GetFiles(int tenantId)
{
var files = GetFilesToProcess(tenantId).ToList();
var files = GetFilesToProcess(tenantId);
var exclude = new List<string>();
using (var db = new DbManager("default"))
@ -255,7 +277,8 @@ namespace ASC.Data.Backup.Tasks
exclude.AddRange(db.ExecuteList(query).Select(r => Convert.ToString(r[0])));
}
files = files.Where(f => !exclude.Any(e => f.Path.Replace('\\', '/').Contains(string.Format("/file_{0}/", e)))).ToList();
files = files.Where(f => !exclude.Any(e => f.Path.Replace('\\', '/').Contains(string.Format("/file_{0}/", e))));
files = files.Where(f => !f.Path.Contains("temp"));
return files;
}
@ -506,81 +529,6 @@ namespace ASC.Data.Backup.Tasks
}
}
private void DoDumpStorage(IDataWriteOperator writer, IReadOnlyList<BackupFileInfo> files)
{
Logger.Debug("begin backup storage");
var dir = Path.GetDirectoryName(BackupFilePath);
var subDir = Path.Combine(dir, Path.GetFileNameWithoutExtension(BackupFilePath));
for (var i = 0; i < files.Count; i += TasksLimit)
{
var storageDir = Path.Combine(subDir, KeyHelper.GetStorage());
if (!Directory.Exists(storageDir))
{
Directory.CreateDirectory(storageDir);
}
var tasks = new List<Task>(TasksLimit);
for (var j = 0; j < TasksLimit && i + j < files.Count; j++)
{
var t = files[i + j];
tasks.Add(Task.Run(() => DoDumpFile(t, storageDir)));
}
Task.WaitAll(tasks.ToArray());
ArchiveDir(writer, subDir);
Directory.Delete(storageDir, true);
}
var restoreInfoXml = new XElement("storage_restore", files.Select(file => (object)file.ToXElement()).ToArray());
var tmpPath = Path.Combine(subDir, KeyHelper.GetStorageRestoreInfoZipKey());
Directory.CreateDirectory(Path.GetDirectoryName(tmpPath));
using (var tmpFile = new FileStream(tmpPath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read, 4096, FileOptions.DeleteOnClose))
{
restoreInfoXml.WriteTo(tmpFile);
writer.WriteEntry(KeyHelper.GetStorageRestoreInfoZipKey(), tmpFile);
}
SetStepCompleted();
Directory.Delete(subDir, true);
Logger.Debug("end backup storage");
}
private async Task DoDumpFile(BackupFileInfo file, string dir)
{
var storage = StorageFactory.GetStorage(ConfigPath, file.Tenant.ToString(), file.Module);
var filePath = Path.Combine(dir, file.GetZipKey());
var dirName = Path.GetDirectoryName(filePath);
Logger.DebugFormat("backup file {0}", filePath);
if (!Directory.Exists(dirName) && !string.IsNullOrEmpty(dirName))
{
Directory.CreateDirectory(dirName);
}
if (!WorkContext.IsMono && filePath.Length > MaxLength)
{
filePath = @"\\?\" + filePath;
}
using (var fileStream = storage.GetReadStream(file.Domain, file.Path))
using (var tmpFile = File.OpenWrite(filePath))
{
await fileStream.CopyToAsync(tmpFile);
}
SetStepCompleted();
}
private void ArchiveDir(IDataWriteOperator writer, string subDir)
{
Logger.DebugFormat("archive dir start {0}", subDir);
@ -600,48 +548,11 @@ namespace ASC.Data.Backup.Tasks
Logger.DebugFormat("archive dir end {0}", subDir);
}
private List<IGrouping<string, BackupFileInfo>> GetFilesGroup(DbFactory dbFactory)
{
var files = GetFilesToProcess(TenantId).ToList();
var exclude = new List<string>();
using (var db = dbFactory.OpenConnection())
{
using (var command = db.CreateCommand())
{
command.CommandText = "select storage_path from backup_backup where tenant_id = " + TenantId + " and storage_type = 0 and storage_path is not null and removed = 0";
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
exclude.Add(reader.GetString(0));
}
}
}
using (var command = db.CreateCommand())
{
command.CommandText = "select id from files_file where tenant_id = " + TenantId + " and title like '%tar.gz' and content_length > 1073741824";
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
exclude.Add(reader.GetString(0));
}
}
}
}
files = files.Where(f => !exclude.Any(e => f.Path.Replace('\\', '/').Contains(string.Format("/file_{0}/", e)))).ToList();
return files.GroupBy(file => file.Module).ToList();
}
private void DoBackupModule(IDataWriteOperator writer, DbFactory dbFactory, IModuleSpecifics module)
private int DoBackupModule(IDataWriteOperator writer, DbFactory dbFactory, IModuleSpecifics module, int completedCount, int count, int tenantId, string prefix = "")
{
Logger.DebugFormat("begin saving data for module {0}", module.ModuleName);
var tablesToProcess = module.Tables.Where(t => !IgnoredTables.Contains(t.Name) && t.InsertMethod != InsertMethod.None).ToList();
var tablesCount = tablesToProcess.Count;
var tablesProcessed = 0;
var tablesProcessed = completedCount;
using (var connection = dbFactory.OpenConnection())
{
@ -660,7 +571,7 @@ namespace ASC.Data.Backup.Tasks
{
var t = (TableInfo)state;
var dataAdapter = dbFactory.CreateDataAdapter();
dataAdapter.SelectCommand = module.CreateSelectCommand(connection.Fix(), TenantId, t, Limit, offset).WithTimeout(600);
dataAdapter.SelectCommand = module.CreateSelectCommand(connection.Fix(), tenantId, t, Limit, offset).WithTimeout(600);
counts = ((DbDataAdapter)dataAdapter).Fill(data);
offset += Limit;
} while (counts == Limit);
@ -687,19 +598,20 @@ namespace ASC.Data.Backup.Tasks
data.WriteXml(file, XmlWriteMode.WriteSchema);
data.Clear();
writer.WriteEntry(KeyHelper.GetTableZipKey(module, data.TableName), file);
writer.WriteEntry(prefix + KeyHelper.GetTableZipKey(module, data.TableName), file);
}
Logger.DebugFormat("end saving table {0}", table.Name);
}
SetCurrentStepProgress((int)((++tablesProcessed * 100) / (double)tablesCount));
SetCurrentStepProgress((int)((++tablesProcessed * 100) / (double)count));
}
}
Logger.DebugFormat("end saving data for module {0}", module.ModuleName);
return tablesProcessed;
}
private List<string> DoBackupMailTable(IDataWriteOperator writer)
private List<string> DoBackupMailTable(IDataWriteOperator writer, int tenantId)
{
Logger.DebugFormat("begin saving data for MailTable");
@ -717,7 +629,7 @@ namespace ASC.Data.Backup.Tasks
Logger.Debug($"DoBackupMailTable: dbconnection: {dbconnection}.");
});
dbManager.ExecuteList($"SELECT name FROM mail_server_domain WHERE tenant={TenantId} AND is_verified=1").ForEach(r =>
dbManager.ExecuteList($"SELECT name FROM mail_server_domain WHERE tenant={tenantId} AND is_verified=1").ForEach(r =>
{
var findedDomain = Convert.ToString(r[0]);
@ -739,7 +651,7 @@ namespace ASC.Data.Backup.Tasks
if (module.findValues["domain"].Count == 0)
{
Logger.Debug($"DoBackupMailTable: TenantId {TenantId} hasn`t any custom domain.");
Logger.Debug($"DoBackupMailTable: TenantId {tenantId} hasn`t any custom domain.");
return result;
}
@ -857,18 +769,34 @@ namespace ASC.Data.Backup.Tasks
Logger.DebugFormat($"end saving {filesCount} files for Mail Server");
}
private void DoBackupStorage(IDataWriteOperator writer, List<IGrouping<string, BackupFileInfo>> fileGroups)
private void DoBackupStorage(IDataWriteOperator writer, IEnumerable<BackupFileInfo> files, int completedCount, int count, bool dump = false, string prefix = "")
{
Logger.Debug("begin backup storage");
foreach (var group in fileGroups)
{
var filesProcessed = 0;
var filesCount = group.Count();
var filesProcessed = completedCount;
foreach (var file in group)
using (var tmpFile = TempStream.Create())
{
var bytes = Encoding.UTF8.GetBytes("<storage_restore>");
tmpFile.Write(bytes, 0, bytes.Length);
var storages = new Dictionary<string, IDataStore>();
foreach (var file in files)
{
var storage = StorageFactory.GetStorage(ConfigPath, TenantId.ToString(), group.Key);
if (!storages.TryGetValue(file.Module + file.Tenant.ToString(), out var storage))
{
storage = StorageFactory.GetStorage(ConfigPath, file.Tenant.ToString(), file.Module);
storages.Add(file.Module + file.Tenant.ToString(), storage);
}
var path = file.GetZipKey();
if (dump)
{
path = Path.Combine(prefix, Path.DirectorySeparatorChar + "storage", path);
}
else
{
path = Path.Combine(prefix, path).Replace("\\", "/");
}
var file1 = file;
Stream fileStream = null;
ActionInvoker.Try(state =>
@ -878,25 +806,20 @@ namespace ASC.Data.Backup.Tasks
}, file, 5, error => Logger.WarnFormat("can't backup file ({0}:{1}): {2}", file1.Module, file1.Path, error));
if (fileStream != null)
{
writer.WriteEntry(file1.GetZipKey(), fileStream);
writer.WriteEntry(path, fileStream);
fileStream.Dispose();
}
SetCurrentStepProgress((int)(++filesProcessed * 100 / (double)filesCount));
SetCurrentStepProgress((int)(++filesProcessed * 100 / (double)count));
var restoreInfoXml = file.ToXElement();
restoreInfoXml.WriteTo(tmpFile);
}
bytes = Encoding.UTF8.GetBytes("</storage_restore>");
tmpFile.Write(bytes, 0, bytes.Length);
writer.WriteEntry(prefix + KeyHelper.GetStorageRestoreInfoZipKey(), tmpFile);
}
var restoreInfoXml = new XElement(
"storage_restore",
fileGroups
.SelectMany(group => group.Select(file => (object)file.ToXElement()))
.ToArray());
using (var tmpFile = TempStream.Create())
{
restoreInfoXml.WriteTo(tmpFile);
writer.WriteEntry(KeyHelper.GetStorageRestoreInfoZipKey(), tmpFile);
}
Logger.Debug("end backup storage");
}
}

View File

@ -42,16 +42,16 @@ namespace ASC.Data.Backup.Tasks
var dbFactory = new DbFactory(ConfigPath);
foreach (var module in modulesToProcess)
{
DoDeleteModule(dbFactory, module);
DoDeleteModule(dbFactory, module, TenantId);
}
if (ProcessStorage)
{
DoDeleteStorage();
DoDeleteStorage(TenantId);
}
Logger.DebugFormat("end delete {0}", TenantId);
}
private void DoDeleteModule(DbFactory dbFactory, IModuleSpecifics module)
private void DoDeleteModule(DbFactory dbFactory, IModuleSpecifics module, int tenantId)
{
Logger.DebugFormat("begin delete data for module ({0})", module.ModuleName);
int tablesCount = module.Tables.Count();
@ -63,7 +63,7 @@ namespace ASC.Data.Backup.Tasks
ActionInvoker.Try(state =>
{
var t = (TableInfo)state;
module.CreateDeleteCommand(connection.Fix(), TenantId, t).WithTimeout(120).ExecuteNonQuery();
module.CreateDeleteCommand(connection.Fix(), tenantId, t).WithTimeout(120).ExecuteNonQuery();
}, table, 5, onFailure: error => { throw ThrowHelper.CantDeleteTable(table.Name, error); });
SetCurrentStepProgress((int)((++tablesProcessed * 100) / (double)tablesCount));
}
@ -71,14 +71,14 @@ namespace ASC.Data.Backup.Tasks
Logger.DebugFormat("end delete data for module ({0})", module.ModuleName);
}
private void DoDeleteStorage()
private void DoDeleteStorage(int tenantId)
{
Logger.Debug("begin delete storage");
List<string> storageModules = StorageFactory.GetModuleList(ConfigPath).Where(IsStorageModuleAllowed).ToList();
int modulesProcessed = 0;
foreach (string module in storageModules)
{
IDataStore storage = StorageFactory.GetStorage(ConfigPath, TenantId.ToString(), module);
IDataStore storage = StorageFactory.GetStorage(ConfigPath, tenantId.ToString(), module);
List<string> domains = StorageFactory.GetDomainList(ConfigPath, module).ToList();
foreach (var domain in domains)
{

View File

@ -69,7 +69,7 @@ namespace ASC.Data.Backup.Tasks.Modules
new TableInfo("mail_contact_info", "tenant", "id") {UserIDColumns = new[] {"id_user"}},
new TableInfo("mail_filter", "tenant", "id") {UserIDColumns = new[] {"id_user"}},
new TableInfo("mail_folder", "tenant", "id") {UserIDColumns = new[] {"id_user"}},
new TableInfo("mail_folder", "tenant") {UserIDColumns = new[] {"id_user"}},
new TableInfo("mail_user_folder", "tenant", "id") {UserIDColumns = new[] {"id_user"}},
new TableInfo("mail_user_folder_x_mail", "tenant") {UserIDColumns = new[] {"id_user"}}
};

View File

@ -103,26 +103,31 @@ namespace ASC.Data.Backup.Tasks
protected IEnumerable<BackupFileInfo> GetFilesToProcess(int tenantId)
{
var files = new List<BackupFileInfo>();
foreach (var module in StorageFactory.GetModuleList(ConfigPath).Where(IsStorageModuleAllowed))
{
var store = StorageFactory.GetStorage(ConfigPath, tenantId.ToString(), module);
var domains = StorageFactory.GetDomainList(ConfigPath, module).ToArray();
var domains = StorageFactory.GetDomainList(ConfigPath, module, false);
foreach (var domain in domains)
var files = store.ListFilesRelative(string.Empty, "\\", "*", true)
.Where(path => domains.All(domain => !path.Contains(domain + "/")))
.Select(path => new BackupFileInfo(string.Empty, module, path, tenantId));
foreach (var file in files)
{
files.AddRange(
store.ListFilesRelative(domain, "\\", "*.*", true)
.Select(path => new BackupFileInfo(domain, module, path, tenantId)));
yield return file;
}
files.AddRange(
store.ListFilesRelative(string.Empty, "\\", "*.*", true)
.Where(path => domains.All(domain => !path.Contains(domain + "/")))
.Select(path => new BackupFileInfo(string.Empty, module, path, tenantId)));
}
foreach (var domain in StorageFactory.GetDomainList(ConfigPath, module))
{
files = store.ListFilesRelative(domain, "\\", "*", true)
.Select(path => new BackupFileInfo(domain, module, path, tenantId));
return files.Distinct();
foreach (var file in files)
{
yield return file;
}
}
}
}
protected bool IsStorageModuleAllowed(string storageModuleName)

View File

@ -110,23 +110,7 @@ namespace ASC.Data.Backup.Tasks
}
RestoreMailTable(dataReader);
try
{
using (var dbManager = new DbManager("default", 100000))
{
//set new domain name in dns record
dbManager.ExecuteNonQuery("update mail_server_dns set mx=(select hostname from mail_mailbox_server where id_provider=-1 limit 1)");
dbManager.ExecuteNonQuery("update mail_mailbox set id_smtp_server = (select id from mail_mailbox_server where id_provider = -1 and type = 'smtp' limit 1), id_in_server = (select id from mail_mailbox_server where id_provider = -1 and type = 'imap' limit 1) where is_server_mailbox=1");
}
}
catch (Exception ex)
{
Logger.Error(ex);
}
var backupRepository = BackupStorageFactory.GetBackupRepository();
var backupRepository = BackupStorageFactory.GetBackupRepository();
backupRepository.MigrationBackupRecords(TenantId, _columnMapper.GetTenantMapping(), ConfigPath);
}
@ -196,6 +180,14 @@ namespace ASC.Data.Backup.Tasks
var restoreTask = new RestoreMailTableTask(Logger, dataReader, ConfigPath, dbconnection);
restoreTask.RunJob();
}
using (var dbManager = new DbManager("default", 100000))
{
//set new domain name in dns record
dbManager.ExecuteNonQuery("update mail_server_dns set mx=(select hostname from mail_mailbox_server where id_provider=-1 limit 1)");
dbManager.ExecuteNonQuery("update mail_mailbox set id_smtp_server = (select id from mail_mailbox_server where id_provider = -1 and type = 'smtp' limit 1), id_in_server = (select id from mail_mailbox_server where id_provider = -1 and type = 'imap' limit 1) where is_server_mailbox=1");
}
}
catch (Exception ex)
{

View File

@ -22,6 +22,7 @@ using System.Linq;
using ASC.Common.Data;
using ASC.Common.Logging;
using ASC.Core;
using ASC.Core.Tenants;
using ASC.Data.Backup.Extensions;
using ASC.Data.Backup.Tasks.Modules;
@ -64,7 +65,7 @@ namespace ASC.Data.Backup.Tasks
Logger.DebugFormat("begin transfer {0}", TenantId);
var fromDbFactory = new DbFactory(ConfigPath);
var toDbFactory = new DbFactory(ToConfigPath);
string tenantAlias = GetTenantAlias(fromDbFactory);
string tenantAlias = GetTenantAlias(fromDbFactory, TenantId);
string backupFilePath = GetBackupFilePath(tenantAlias);
var columnMapper = new ColumnMapper();
try
@ -80,7 +81,7 @@ namespace ASC.Data.Backup.Tasks
SetStepsCount(ProcessStorage ? 3 : 2);
//save db data to temporary file
var backupTask = new BackupPortalTask(Logger, TenantId, ConfigPath, backupFilePath, Limit) { ProcessStorage = false };
var backupTask = new BackupPortalTask(Logger, TenantId, ConfigPath, backupFilePath, Limit, CoreContext.Configuration.Standalone) { ProcessStorage = false };
backupTask.ProgressChanged += (sender, args) => SetCurrentStepProgress(args.Progress);
backupTask.WriteOperator = ZipWriteOperatorFactory.GetDefaultWriteOperator(backupFilePath);
@ -102,7 +103,7 @@ namespace ASC.Data.Backup.Tasks
//transfer files
if (ProcessStorage)
{
DoTransferStorage(columnMapper);
DoTransferStorage(columnMapper, TenantId);
}
SaveTenant(toDbFactory, tenantAlias, TenantStatus.Active);
@ -136,14 +137,14 @@ namespace ASC.Data.Backup.Tasks
}
}
private void DoTransferStorage(ColumnMapper columnMapper)
private void DoTransferStorage(ColumnMapper columnMapper, int tenantId)
{
Logger.Debug("begin transfer storage");
var fileGroups = GetFilesToProcess(TenantId).GroupBy(file => file.Module).ToList();
var fileGroups = GetFilesToProcess(tenantId).GroupBy(file => file.Module).ToList();
int groupsProcessed = 0;
foreach (var group in fileGroups)
{
var baseStorage = StorageFactory.GetStorage(ConfigPath, TenantId.ToString(), group.Key);
var baseStorage = StorageFactory.GetStorage(ConfigPath, tenantId.ToString(), group.Key);
var destStorage = StorageFactory.GetStorage(ToConfigPath, columnMapper.GetTenantMapping().ToString(), group.Key);
var utility = new CrossModuleTransferUtility(baseStorage, destStorage);
@ -210,11 +211,11 @@ namespace ASC.Data.Backup.Tasks
}
}
private string GetTenantAlias(DbFactory dbFactory)
private string GetTenantAlias(DbFactory dbFactory, int tenantId)
{
using (var connection = dbFactory.OpenConnection())
{
var commandText = "select alias from tenants_tenants where id = " + TenantId;
var commandText = "select alias from tenants_tenants where id = " + tenantId;
return connection.CreateCommand(commandText).WithTimeout(120).ExecuteScalar<string>();
}
}

View File

@ -82,6 +82,7 @@
<Compile Include="DiscStorage\MappedPath.cs" />
<Compile Include="Extensions.cs" />
<Compile Include="GoogleCloud\GoogleCloudStorage.cs" />
<Compile Include="IDataStoreValidator.cs" />
<Compile Include="IDataStore.cs" />
<Compile Include="IQuotaController.cs" />
<Compile Include="Migration\IService.cs" />

View File

@ -41,6 +41,7 @@ namespace ASC.Data.Storage
internal DataList _dataList;
internal string _tenant;
internal Dictionary<string, TimeSpan> _domainsExpires = new Dictionary<string, TimeSpan>();
internal Dictionary<string, IDataStoreValidator> _domainsValidators = new Dictionary<string, IDataStoreValidator>();
public IQuotaController QuotaController { get; set; }
@ -49,6 +50,23 @@ namespace ASC.Data.Storage
return _domainsExpires.ContainsKey(domain) ? _domainsExpires[domain] : _domainsExpires[string.Empty];
}
public IDataStoreValidator GetValidator(string domain)
{
return _domainsValidators.ContainsKey(domain) ? _domainsValidators[domain] : _domainsValidators[string.Empty];
}
protected IDataStoreValidator CreateValidator(string type, string param)
{
if (string.IsNullOrEmpty(type))
{
return null;
}
var validatorType = Type.GetType(type, false);
return validatorType == null ? null : (IDataStoreValidator)Activator.CreateInstance(validatorType, param);
}
public Uri GetUri(string path)
{
return GetUri(string.Empty, path);
@ -230,7 +248,7 @@ namespace ASC.Data.Storage
public abstract Uri Move(string srcdomain, string srcpath, string newdomain, string newpath, Guid ownerId, bool quotaCheckFileSize = true);
public abstract Uri SaveTemp(string domain, out string assignedPath, Stream stream);
public abstract string[] ListDirectoriesRelative(string domain, string path, bool recursive);
public abstract string[] ListFilesRelative(string domain, string path, string pattern, bool recursive);
public abstract IEnumerable<string> ListFilesRelative(string domain, string path, string pattern, bool recursive);
public abstract bool IsFile(string domain, string path);
public abstract Task<bool> IsFileAsync(string domain, string path);
public abstract bool IsDirectory(string domain, string path);
@ -292,7 +310,7 @@ namespace ASC.Data.Storage
public Uri[] ListFiles(string domain, string path, string pattern, bool recursive)
{
var filePaths = ListFilesRelative(domain, path, pattern, recursive);
var filePaths = ListFilesRelative(domain, path, pattern, recursive).ToArray();
return Array.ConvertAll(
filePaths,
x => GetUri(domain, Path.Combine(PathUtils.Normalize(path), x)));

View File

@ -88,5 +88,19 @@ namespace ASC.Data.Storage.Configuration
get { return (bool)this[Schema.PUBLIC]; }
set { this[Schema.PUBLIC] = value; }
}
[ConfigurationProperty(Schema.VALIDATORTYPE)]
public string ValidatorType
{
get { return (string)this[Schema.VALIDATORTYPE]; }
set { this[Schema.VALIDATORTYPE] = value; }
}
[ConfigurationProperty(Schema.VALIDATORPARAMS)]
public string ValidatorParams
{
get { return (string)this[Schema.VALIDATORPARAMS]; }
set { this[Schema.VALIDATORPARAMS] = value; }
}
}
}

View File

@ -136,5 +136,19 @@ namespace ASC.Data.Storage.Configuration
get { return (bool)this[Schema.ATTACHMENT]; }
set { this[Schema.ATTACHMENT] = value; }
}
[ConfigurationProperty(Schema.VALIDATORTYPE)]
public string ValidatorType
{
get { return (string)this[Schema.VALIDATORTYPE]; }
set { this[Schema.VALIDATORTYPE] = value; }
}
[ConfigurationProperty(Schema.VALIDATORPARAMS)]
public string ValidatorParams
{
get { return (string)this[Schema.VALIDATORPARAMS]; }
set { this[Schema.VALIDATORPARAMS] = value; }
}
}
}

View File

@ -49,5 +49,7 @@ namespace ASC.Data.Storage.Configuration
public const string DISABLEDENCRYPTION = "disableEncryption";
public const string CACHE = "cache";
public const string ATTACHMENT = "attachment";
public const string VALIDATORTYPE = "validatorType";
public const string VALIDATORPARAMS = "validatorParams";
}
}

View File

@ -47,19 +47,26 @@ namespace ASC.Data.Storage.DiscStorage
_cache = moduleConfig.Cache;
_dataList = new DataList(moduleConfig);
_attachment = moduleConfig.Attachment;
//Add default
_mappedPaths.Add(string.Empty, new MappedPath(tenant, moduleConfig.AppendTenant, PathUtils.Normalize(moduleConfig.Path), handlerConfig.GetProperties()));
_domainsExpires.Add(string.Empty, moduleConfig.Expires);
_domainsValidators.Add(string.Empty, CreateValidator(moduleConfig.ValidatorType, moduleConfig.ValidatorParams));
foreach (DomainConfigurationElement domain in moduleConfig.Domains)
{
_mappedPaths.Add(domain.Name, new MappedPath(tenant, moduleConfig.AppendTenant, domain.Path, handlerConfig.GetProperties()));
}
//Add default
_mappedPaths.Add(string.Empty, new MappedPath(tenant, moduleConfig.AppendTenant, PathUtils.Normalize(moduleConfig.Path), handlerConfig.GetProperties()));
//Make expires
_domainsExpires =
moduleConfig.Domains.Cast<DomainConfigurationElement>().Where(x => x.Expires != TimeSpan.Zero).
ToDictionary(x => x.Name,
y => y.Expires);
_domainsExpires.Add(string.Empty, moduleConfig.Expires);
if (domain.Expires != TimeSpan.Zero)
{
_domainsExpires.Add(domain.Name, domain.Expires);
}
if (!string.IsNullOrEmpty(domain.ValidatorType))
{
_domainsValidators.Add(domain.Name, CreateValidator(domain.ValidatorType, domain.ValidatorParams));
}
}
var settings = moduleConfig.DisabledEncryption ? new EncryptionSettings() : EncryptionSettings.Load();
@ -543,7 +550,6 @@ namespace ASC.Data.Storage.DiscStorage
throw new NotSupportedException("This operation supported only on s3 storage");
}
public override string[] ListDirectoriesRelative(string domain, string path, bool recursive)
{
if (path == null) throw new ArgumentNullException("path");
@ -561,7 +567,7 @@ namespace ASC.Data.Storage.DiscStorage
return new string[0];
}
public override string[] ListFilesRelative(string domain, string path, string pattern, bool recursive)
public override IEnumerable<string> ListFilesRelative(string domain, string path, string pattern, bool recursive)
{
if (path == null) throw new ArgumentNullException("path");
@ -570,10 +576,9 @@ namespace ASC.Data.Storage.DiscStorage
if (!string.IsNullOrEmpty(targetDir) && !targetDir.EndsWith(Path.DirectorySeparatorChar.ToString())) targetDir += Path.DirectorySeparatorChar;
if (Directory.Exists(targetDir))
{
var entries = Directory.GetFiles(targetDir, pattern, recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly);
return Array.ConvertAll(
entries,
x => x.Substring(targetDir.Length));
var entries = Directory.EnumerateFiles(targetDir, pattern, recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly)
.Select(e => e.Substring(targetDir.Length));
return entries;
}
return new string[0];
}

View File

@ -79,17 +79,26 @@ namespace ASC.Data.Storage.GoogleCloud
_cache = moduleConfig.Cache;
_dataList = new DataList(moduleConfig);
_attachment = moduleConfig.Attachment;
_domainsExpires =
moduleConfig.Domains.Cast<DomainConfigurationElement>().Where(x => x.Expires != TimeSpan.Zero).
ToDictionary(x => x.Name,
y => y.Expires);
_domainsExpires.Add(string.Empty, moduleConfig.Expires);
_domainsAcl = moduleConfig.Domains.Cast<DomainConfigurationElement>().ToDictionary(x => x.Name,
y => GetGoogleCloudAcl(y.Acl));
_moduleAcl = GetGoogleCloudAcl(moduleConfig.Acl);
_domainsAcl = new Dictionary<string, PredefinedObjectAcl>();
_domainsExpires.Add(string.Empty, moduleConfig.Expires);
_domainsValidators.Add(string.Empty, CreateValidator(moduleConfig.ValidatorType, moduleConfig.ValidatorParams));
foreach (DomainConfigurationElement domain in moduleConfig.Domains)
{
_domainsAcl.Add(domain.Name, GetGoogleCloudAcl(domain.Acl));
if (domain.Expires != TimeSpan.Zero)
{
_domainsExpires.Add(domain.Name, domain.Expires);
}
if (!string.IsNullOrEmpty(domain.ValidatorType))
{
_domainsValidators.Add(domain.Name, CreateValidator(domain.ValidatorType, domain.ValidatorParams));
}
}
}
public override IDataStore Configure(IDictionary<string, string> props)
@ -529,10 +538,10 @@ namespace ASC.Data.Storage.GoogleCloud
return items.Where(x => x.Name.IndexOf('/', MakePath(domain, path + "/").Length) == -1);
}
public override string[] ListFilesRelative(string domain, string path, string pattern, bool recursive)
public override IEnumerable<string> ListFilesRelative(string domain, string path, string pattern, bool recursive)
{
return GetObjects(domain, path, recursive).Where(x => Wildcard.IsMatch(pattern, Path.GetFileName(x.Name)))
.Select(x => x.Name.Substring(MakePath(domain, path + "/").Length).TrimStart('/')).ToArray();
.Select(x => x.Name.Substring(MakePath(domain, path + "/").Length).TrimStart('/'));
}
public override bool IsFile(string domain, string path)

View File

@ -39,6 +39,8 @@ namespace ASC.Data.Storage
TimeSpan GetExpire(string domain);
IDataStoreValidator GetValidator(string domain);
///<summary>
/// Get absolute Uri for html links to handler
///</summary>
@ -279,7 +281,7 @@ namespace ASC.Data.Storage
///<param name="pattern">Wildcard mask (*. jpg for example)</param>
///<param name="recursive">iterate subdirectories or not</param>
///<returns></returns>
string[] ListFilesRelative(string domain, string path, string pattern, bool recursive);
IEnumerable<string> ListFilesRelative(string domain, string path, string pattern, bool recursive);
///<summary>
/// Checks whether a file exists. On s3 it took long time.

View File

@ -0,0 +1,23 @@
/*
*
* (c) Copyright Ascensio System Limited 2010-2023
*
* 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.Data.Storage
{
public interface IDataStoreValidator
{
bool Validate(string path);
}
}

View File

@ -71,21 +71,28 @@ namespace ASC.Data.Storage.RackspaceCloud
_cache = moduleConfig.Cache;
_dataList = new DataList(moduleConfig);
_attachment = moduleConfig.Attachment;
_domains.AddRange(
moduleConfig.Domains.Cast<DomainConfigurationElement>().Select(x => string.Format("{0}/", x.Name)));
//Make acl
_domainsExpires =
moduleConfig.Domains.Cast<DomainConfigurationElement>().Where(x => x.Expires != TimeSpan.Zero).
ToDictionary(x => x.Name,
y => y.Expires);
_domainsExpires.Add(string.Empty, moduleConfig.Expires);
_domainsAcl = moduleConfig.Domains.Cast<DomainConfigurationElement>().ToDictionary(x => x.Name,
y => y.Acl);
_moduleAcl = moduleConfig.Acl;
_domainsAcl = new Dictionary<string, ACL>();
_domainsExpires.Add(string.Empty, moduleConfig.Expires);
_domainsValidators.Add(string.Empty, CreateValidator(moduleConfig.ValidatorType, moduleConfig.ValidatorParams));
foreach (DomainConfigurationElement domain in moduleConfig.Domains)
{
_domains.Add(string.Format("{0}/", domain.Name));
_domainsAcl.Add(domain.Name, domain.Acl);
if (domain.Expires != TimeSpan.Zero)
{
_domainsExpires.Add(domain.Name, domain.Expires);
}
if (!string.IsNullOrEmpty(domain.ValidatorType))
{
_domainsValidators.Add(domain.Name, CreateValidator(domain.ValidatorType, domain.ValidatorParams));
}
}
}
private string MakePath(string domain, string path)
@ -534,13 +541,11 @@ namespace ASC.Data.Storage.RackspaceCloud
.Select(x => x.Name.Substring(MakePath(domain, path + "/").Length)).ToArray();
}
public override string[] ListFilesRelative(string domain, string path, string pattern, bool recursive)
public override IEnumerable<string> ListFilesRelative(string domain, string path, string pattern, bool recursive)
{
var paths = new List<String>();
var client = GetClient();
paths = client.ListObjects(_private_container, null, null, null, MakePath(domain, path), _region).Select(x => x.Name).ToList();
var paths = client.ListObjects(_private_container, null, null, null, MakePath(domain, path), _region).Select(x => x.Name);
return paths
.Where(x => Wildcard.IsMatch(pattern, Path.GetFileName(x)))

View File

@ -92,19 +92,28 @@ namespace ASC.Data.Storage.S3
_cache = moduleConfig.Cache;
_dataList = new DataList(moduleConfig);
_attachment = moduleConfig.Attachment;
_domains.AddRange(
moduleConfig.Domains.Cast<DomainConfigurationElement>().Select(x => string.Format("{0}/", x.Name)));
//Make expires
_domainsExpires =
moduleConfig.Domains.Cast<DomainConfigurationElement>().Where(x => x.Expires != TimeSpan.Zero).
ToDictionary(x => x.Name,
y => y.Expires);
_domainsExpires.Add(string.Empty, moduleConfig.Expires);
_domainsAcl = moduleConfig.Domains.Cast<DomainConfigurationElement>().ToDictionary(x => x.Name,
y => GetS3Acl(y.Acl));
_moduleAcl = GetS3Acl(moduleConfig.Acl);
_domainsAcl = new Dictionary<string, S3CannedACL>();
_domainsExpires.Add(string.Empty, moduleConfig.Expires);
_domainsValidators.Add(string.Empty, CreateValidator(moduleConfig.ValidatorType, moduleConfig.ValidatorParams));
foreach (DomainConfigurationElement domain in moduleConfig.Domains)
{
_domains.Add(string.Format("{0}/", domain.Name));
_domainsAcl.Add(domain.Name, GetS3Acl(domain.Acl));
if (domain.Expires != TimeSpan.Zero)
{
_domainsExpires.Add(domain.Name, domain.Expires);
}
if (!string.IsNullOrEmpty(domain.ValidatorType))
{
_domainsValidators.Add(domain.Name, CreateValidator(domain.ValidatorType, domain.ValidatorParams));
}
}
}
private S3CannedACL GetDomainACL(string domain)
@ -745,6 +754,7 @@ namespace ASC.Data.Storage.S3
public override string[] ListDirectoriesRelative(string domain, string path, bool recursive)
{
return GetS3Objects(domain, path)
.Where(x => x.Key.EndsWith("/"))
.Select(x => x.Key.Substring((MakePath(domain, path) + "/").Length))
.ToArray();
}
@ -936,12 +946,12 @@ namespace ASC.Data.Storage.S3
return policyBase64;
}
public override string[] ListFilesRelative(string domain, string path, string pattern, bool recursive)
public override IEnumerable<string> ListFilesRelative(string domain, string path, string pattern, bool recursive)
{
return GetS3Objects(domain, path)
.Where(x => !x.Key.EndsWith("/"))
.Where(x => Wildcard.IsMatch(pattern, Path.GetFileName(x.Key)))
.Select(x => x.Key.Substring((MakePath(domain, path) + "/").Length).TrimStart('/'))
.ToArray();
.Select(x => x.Key.Substring((MakePath(domain, path) + "/").Length).TrimStart('/'));
}
private bool CheckKey(string domain, string key)

View File

@ -71,14 +71,26 @@ namespace ASC.Data.Storage.Selectel
_cache = moduleConfig.Cache;
_dataList = new DataList(moduleConfig);
_attachment = moduleConfig.Attachment;
_domainsExpires = moduleConfig.Domains.Cast<DomainConfigurationElement>()
.Where(x => x.Expires != TimeSpan.Zero)
.ToDictionary(x => x.Name, y => y.Expires);
_domainsExpires.Add(String.Empty, moduleConfig.Expires);
_domainsAcl = moduleConfig.Domains.Cast<DomainConfigurationElement>().ToDictionary(x => x.Name, y => y.Acl);
_moduleAcl = moduleConfig.Acl;
_domainsAcl = new Dictionary<string, ACL>();
_domainsExpires.Add(string.Empty, moduleConfig.Expires);
_domainsValidators.Add(string.Empty, CreateValidator(moduleConfig.ValidatorType, moduleConfig.ValidatorParams));
foreach (DomainConfigurationElement domain in moduleConfig.Domains)
{
_domainsAcl.Add(domain.Name, domain.Acl);
if (domain.Expires != TimeSpan.Zero)
{
_domainsExpires.Add(domain.Name, domain.Expires);
}
if (!string.IsNullOrEmpty(domain.ValidatorType))
{
_domainsValidators.Add(domain.Name, CreateValidator(domain.ValidatorType, domain.ValidatorParams));
}
}
}
public override IDataStore Configure(IDictionary<String, String> props)
@ -505,9 +517,9 @@ namespace ASC.Data.Storage.Selectel
.Select(x => x.Name.Substring(MakePath(domain, path + "/").Length)).ToArray();
}
public override string[] ListFilesRelative(string domain, string path, string pattern, bool recursive)
public override IEnumerable<string> ListFilesRelative(string domain, string path, string pattern, bool recursive)
{
var paths = new List<String>();
IEnumerable<string> paths = null;
var client = GetClient().Result;
if (recursive)
@ -521,7 +533,7 @@ namespace ASC.Data.Storage.Selectel
return paths
.Where(x => Wildcard.IsMatch(pattern, Path.GetFileName(x)))
.Select(x => x.Substring(MakePath(domain, path + "/").Length).TrimStart('/')).ToArray();
.Select(x => x.Substring(MakePath(domain, path + "/").Length).TrimStart('/'));
}
public override bool IsFile(string domain, string path)

View File

@ -122,7 +122,7 @@ namespace ASC.Data.Storage
.Select(x => x.Name);
}
public static IEnumerable<string> GetDomainList(string configpath, string modulename)
public static IEnumerable<string> GetDomainList(string configpath, string modulename, bool onlyVisible = true)
{
var section = GetSection(configpath);
if (section == null)
@ -134,7 +134,7 @@ namespace ASC.Data.Storage
.Cast<ModuleConfigurationElement>()
.Single(x => x.Name.Equals(modulename, StringComparison.OrdinalIgnoreCase))
.Domains.Cast<DomainConfigurationElement>()
.Where(x => x.Visible)
.Where(x => !onlyVisible || x.Visible)
.Select(x => x.Name);
}

View File

@ -26,6 +26,7 @@ using System.Web.Routing;
using ASC.Common.Logging;
using ASC.Core;
using ASC.Core.Users;
using ASC.Security.Cryptography;
namespace ASC.Data.Storage.DiscStorage
@ -103,6 +104,12 @@ namespace ASC.Data.Storage.DiscStorage
}
}
if (_module == "backup" && (!SecurityContext.IsAuthenticated || !CoreContext.UserManager.GetUsers(SecurityContext.CurrentAccount.ID).IsAdmin()))
{
context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
return;
}
var storage = StorageFactory.GetStorage(CoreContext.TenantManager.GetCurrentTenant().TenantId.ToString(CultureInfo.InvariantCulture), _module);
var auth = context.Request[Constants.QUERY_AUTH];
@ -127,6 +134,13 @@ namespace ASC.Data.Storage.DiscStorage
return;
}
var validator = storage.GetValidator(_domain);
if (validator != null && !validator.Validate(path))
{
context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
return;
}
if (storage.IsContentDispositionAsAttachment)
{
if (!headers.Any(h => h.StartsWith("Content-Disposition")))

View File

@ -137,12 +137,12 @@ namespace ASC.Data.Storage
var crossModuleTransferUtility = new CrossModuleTransferUtility(oldStore, store);
string[] files;
IEnumerable<string> files;
foreach (var domain in domains)
{
Status = module + domain;
Log.DebugFormat("Domain: {0}", domain);
files = oldStore.ListFilesRelative(domain, "\\", "*.*", true);
files = oldStore.ListFilesRelative(domain, "\\", "*", true);
foreach (var file in files)
{
@ -153,7 +153,7 @@ namespace ASC.Data.Storage
Log.DebugFormat("Module:{0},Domain:", module);
files = oldStore.ListFilesRelative(string.Empty, "\\", "*.*", true)
files = oldStore.ListFilesRelative(string.Empty, "\\", "*", true)
.Where(path => domains.All(domain => !path.Contains(domain + "/")))
.ToArray();

View File

@ -55,11 +55,9 @@ namespace ASC.Api.CRM
int successProbability,
DealMilestoneStatus stageType)
{
if (!(CRMSecurity.IsAdmin)) throw CRMSecurity.CreateSecurityException();
if (!CRMSecurity.IsAdmin) throw CRMSecurity.CreateSecurityException();
if (string.IsNullOrEmpty(title)) throw new ArgumentException();
if (successProbability < 0) successProbability = 0;
if (string.IsNullOrEmpty(title) || successProbability < 0 || successProbability > 100) throw new ArgumentException();
var dealMilestone = new DealMilestone
{
@ -103,11 +101,9 @@ namespace ASC.Api.CRM
int successProbability,
DealMilestoneStatus stageType)
{
if (!(CRMSecurity.IsAdmin)) throw CRMSecurity.CreateSecurityException();
if (!CRMSecurity.IsAdmin) throw CRMSecurity.CreateSecurityException();
if (id <= 0 || string.IsNullOrEmpty(title)) throw new ArgumentException();
if (successProbability < 0) successProbability = 0;
if (id <= 0 || string.IsNullOrEmpty(title) || successProbability < 0 || successProbability > 100) throw new ArgumentException();
var curDealMilestoneExist = DaoFactory.DealMilestoneDao.IsExist(id);
if (!curDealMilestoneExist) throw new ItemNotFoundException();

View File

@ -159,7 +159,7 @@ namespace ASC.Api.CRM
{
var storage = StorageFactory.GetStorage("", "crm");
const string path = "default/";
var files = storage.ListFilesRelative("voip", path, "*.*", true)
var files = storage.ListFilesRelative("voip", path, "*", true)
.Select(filePath => new
{
path = CommonLinkUtility.GetFullAbsolutePath(storage.GetUri("voip", Path.Combine(path, filePath)).ToString()),
@ -415,7 +415,7 @@ namespace ASC.Api.CRM
path = "default/" + audioType.ToLower();
store = StorageFactory.GetStorage("", "crm");
filePaths = store.ListFilesRelative("voip", path, "*.*", true);
filePaths = store.ListFilesRelative("voip", path, "*", true);
result.AddRange(
filePaths.Select(filePath =>
GetVoipUpload(store.GetUri("voip", Path.Combine(path, filePath)), Path.GetFileName(filePath), type, true)));

View File

@ -66,10 +66,6 @@ using SecurityContext = ASC.Core.SecurityContext;
namespace ASC.Api.Calendar
{
/// <summary>
/// Access to the calendars.
/// </summary>
///<name>calendar</name>
public class iCalApiContentResponse : IApiContentResponce
{
private readonly Stream _stream;
@ -135,6 +131,10 @@ namespace ASC.Api.Calendar
}
}
/// <summary>
/// Access to the calendars.
/// </summary>
/// <name>calendar</name>
public class CalendarApi : IApiEntryPoint, IDisposable
{
public static bool IsPersonal

View File

@ -631,10 +631,10 @@ namespace ASC.Api.Documents
public Configuration OpenEdit(String fileId, int version, String doc)
{
Configuration configuration;
var file = DocumentServiceHelper.GetParams(fileId, version, doc, true, true, true, out configuration);
var file = DocumentServiceHelper.GetParams(fileId, version, doc, true, true, false, true, out configuration);
if (configuration.EditorConfig.ModeWrite && FileConverter.MustConvert(file))
{
file = DocumentServiceHelper.GetParams(file.ID, file.Version, doc, false, false, false, out configuration);
file = DocumentServiceHelper.GetParams(file.ID, file.Version, doc, false, false, false, false, out configuration);
}
configuration.Type = Configuration.EditorType.External;
@ -1066,12 +1066,13 @@ namespace ASC.Api.Documents
/// <param type="System.String, System" method="url" name="fileId">File ID</param>
/// <param type="System.String, System" name="destFolderId">Destination folder ID</param>
/// <param type="System.String, System" name="destTitle">Destination file title</param>
/// <param type="System.Boolean, System" name="toForm">Convert to form</param>
/// <returns>Copied file</returns>
/// <path>api/2.0/files/file/{fileId}/copyas</path>
/// <httpMethod>POST</httpMethod>
/// <requiresAuthorization>false</requiresAuthorization>
[Create("file/{fileId}/copyas", false)] // NOTE: This method doesn't require auth!!!
public FileWrapper CopyFileAs(string fileId, string destFolderId, string destTitle)
public FileWrapper CopyFileAs(string fileId, string destFolderId, string destTitle, bool toForm)
{
var file = _fileStorageService.GetFile(fileId, -1);
var ext = FileUtility.GetFileExtension(file.Title);
@ -1082,7 +1083,7 @@ namespace ASC.Api.Documents
return CreateFile(destFolderId, destTitle, fileId, true);
}
using (var fileStream = FileConverter.Exec(file, destExt))
using (var fileStream = FileConverter.Exec(file, destExt, toForm))
{
return InsertFile(destFolderId, fileStream, destTitle, true);
}
@ -1189,13 +1190,14 @@ namespace ASC.Api.Documents
/// <short>Get file download link</short>
/// <category>Files</category>
/// <param type="System.String, System" name="fileId">File ID</param>
/// <param type="System.Int32, System" method="url" name="version">File version</param>
/// <returns>File download link</returns>
/// <path>api/2.0/files/file/{fileId}/presigneduri</path>
/// <httpMethod>GET</httpMethod>
[Read("file/{fileId}/presigneduri")]
public string GetPresignedUri(String fileId)
public string GetPresignedUri(String fileId, int version)
{
var file = _fileStorageService.GetFile(fileId, -1);
var file = _fileStorageService.GetFile(fileId, version);
return PathProvider.GetFileStreamUrl(file);
}

View File

@ -37,7 +37,8 @@ namespace ASC.Api.Migration
{
/// <summary>
/// Migration API.
/// </summary>
/// </summary>
/// <name>migration</name>
public class MigrationApi : Interfaces.IApiEntryPoint
{
/// <summary>

View File

@ -167,10 +167,15 @@ namespace ASC.Api.Portal
/// <httpMethod>PUT</httpMethod>
/// <visible>false</visible>
[Update("getshortenlink")]
public String GetShortenLink(string link)
public string GetShortenLink(string link)
{
try
{
if (!link.StartsWith(CommonLinkUtility.ServerRootPath))
{
throw new ArgumentException("the link should be to this portal");
}
return UrlShortener.Instance.GetShortenLink(link);
}
catch (Exception ex)
@ -194,6 +199,8 @@ namespace ASC.Api.Portal
[Read("usedspace")]
public double GetUsedSpace()
{
SecurityContext.DemandPermissions(SecutiryConstants.EditPortalSettings);
return Math.Round(
CoreContext.TenantManager.FindTenantQuotaRows(CoreContext.TenantManager.GetCurrentTenant().TenantId)
.Where(q => !string.IsNullOrEmpty(q.Tag) && new Guid(q.Tag) != Guid.Empty)
@ -825,19 +832,20 @@ namespace ASC.Api.Portal
/// <param type="ASC.Core.Common.Contracts.BackupStorageType, ASC.Core.Common.Contracts" name="storageType">Storage type ("Documents", "ThridpartyDocuments", "CustomCloud", "Local", "DataStore", or "ThirdPartyConsumer")</param>
/// <param type="System.Collections.Generic.IEnumerable{ASC.Api.Collections.ItemKeyValuePair{System.String, System.String}}, System.Collections.Generic" name="storageParams">Storage parameters</param>
/// <param type="System.Boolean, System" name="backupMail">Specifies if the mails will be included into the backup or not</param>
/// /// <param type="System.Boolean, System" name="dump">Specifies if a dump will be created or not</param>
/// <category>Backup</category>
/// <returns type="ASC.Core.Common.Contracts.BackupProgress, ASC.Core.Common">Backup progress</returns>
/// <path>api/2.0/portal/startbackup</path>
/// <httpMethod>POST</httpMethod>
[Create("startbackup")]
public BackupProgress StartBackup(BackupStorageType storageType, IEnumerable<ItemKeyValuePair<string, string>> storageParams, bool backupMail)
public BackupProgress StartBackup(BackupStorageType storageType, IEnumerable<ItemKeyValuePair<string, string>> storageParams, bool backupMail, bool dump)
{
if (CoreContext.Configuration.Standalone)
{
TenantExtra.DemandControlPanelPermission();
}
return backupHandler.StartBackup(storageType, storageParams.ToDictionary(r => r.Key, r => r.Value), backupMail);
return backupHandler.StartBackup(storageType, storageParams.ToDictionary(r => r.Key, r => r.Value), backupMail, dump);
}
/// <summary>
@ -1040,14 +1048,14 @@ namespace ASC.Api.Portal
SecurityContext.DemandPermissions(SecutiryConstants.EditPortalSettings);
if (String.IsNullOrEmpty(alias)) throw new ArgumentException();
if (string.IsNullOrEmpty(alias) || alias.Any(char.IsWhiteSpace)) throw new ArgumentException();
var tenant = CoreContext.TenantManager.GetCurrentTenant();
var user = CoreContext.UserManager.GetUsers(SecurityContext.CurrentAccount.ID);
var localhost = CoreContext.Configuration.BaseDomain == "localhost" || tenant.TenantAlias == "localhost";
var newAlias = alias.ToLowerInvariant();
var newAlias = alias.Trim().ToLowerInvariant();
var oldAlias = tenant.TenantAlias;
var oldVirtualRootPath = CommonLinkUtility.GetFullAbsolutePath("~").TrimEnd('/');
@ -1068,7 +1076,7 @@ namespace ASC.Api.Portal
ApiSystemHelper.AddTenantToCache(newAlias);
}
tenant.TenantAlias = alias;
tenant.TenantAlias = newAlias;
tenant = CoreContext.TenantManager.SaveTenant(tenant);

View File

@ -25,7 +25,9 @@ namespace ASC.Api.Sample
{
/// <summary>
/// Sample CRUD API.
/// </summary>
/// </summary>
/// <name>sample</name>
/// <visible>false</visible>
public class SampleApi : IApiEntryPoint
{
/// <summary>

View File

@ -610,15 +610,15 @@ namespace ASC.Api.Security
{
SecurityContext.DemandPermissions(SecutiryConstants.EditPortalSettings);
if (attemptsCount < 1)
if (attemptsCount < 1 || attemptsCount > 9999)
{
throw new ArgumentOutOfRangeException("attemptsCount");
}
if (checkPeriod < 1)
if (checkPeriod < 1 || checkPeriod > 9999)
{
throw new ArgumentOutOfRangeException("checkPeriod");
}
if (blockTime < 1)
if (blockTime < 1 || blockTime > 9999)
{
throw new ArgumentOutOfRangeException("blockTime");
}

View File

@ -58,7 +58,7 @@ namespace ASC.Api.Settings
private static string GetCommunityVersion()
{
return ConfigurationManagerExtension.AppSettings["version.number"] ?? "12.5.0";
return ConfigurationManagerExtension.AppSettings["version.number"] ?? "12.7.0";
}
private static string GetDocumentVersion()

View File

@ -444,6 +444,8 @@ namespace ASC.Api.Settings
[Read("security")]
public IEnumerable<SecurityWrapper> GetWebItemSecurityInfo(IEnumerable<string> ids)
{
SecurityContext.DemandPermissions(SecutiryConstants.EditPortalSettings);
if (ids == null || !ids.Any())
{
ids = WebItemManager.Instance.GetItemsAll().Select(i => i.ID.ToString());
@ -687,6 +689,8 @@ namespace ASC.Api.Settings
[Read("security/administrator/{productid}")]
public IEnumerable<EmployeeWraper> GetProductAdministrators(Guid productid)
{
SecurityContext.DemandPermissions(SecutiryConstants.EditPortalSettings);
return WebItemSecurity.GetProductAdministrators(productid)
.Select(EmployeeWraper.Get)
.ToList();
@ -707,6 +711,8 @@ namespace ASC.Api.Settings
[Read("security/administrator")]
public object IsProductAdministrator(Guid productid, Guid userid)
{
SecurityContext.DemandPermissions(SecutiryConstants.EditPortalSettings);
var result = WebItemSecurity.IsProductAdministrator(productid, userid);
return new { ProductId = productid, UserId = userid, Administrator = result, };
}
@ -1367,7 +1373,7 @@ namespace ASC.Api.Settings
{
var currentUser = CoreContext.UserManager.GetUsers(SecurityContext.CurrentAccount.ID);
if (!TfaAppAuthSettings.IsVisibleSettings || !TfaAppUserSettings.EnableForUser(currentUser.ID))
if (!TfaAppAuthSettings.IsVisibleSettings || !TfaAppAuthSettings.Enable || !TfaAppUserSettings.EnableForUser(currentUser.ID))
throw new Exception(Resource.TfaAppNotAvailable);
if (currentUser.IsOutsider())
@ -1784,6 +1790,8 @@ namespace ASC.Api.Settings
[Read("customnavigation/getall")]
public List<CustomNavigationItem> GetCustomNavigationItems()
{
SecurityContext.DemandPermissions(SecutiryConstants.EditPortalSettings);
return CustomNavigationSettings.Load().Items;
}
@ -1798,6 +1806,8 @@ namespace ASC.Api.Settings
[Read("customnavigation/getsample")]
public CustomNavigationItem GetCustomNavigationItemSample()
{
SecurityContext.DemandPermissions(SecutiryConstants.EditPortalSettings);
return CustomNavigationItem.GetSample();
}
@ -1813,6 +1823,8 @@ namespace ASC.Api.Settings
[Read("customnavigation/get/{id}")]
public CustomNavigationItem GetCustomNavigationItem(Guid id)
{
SecurityContext.DemandPermissions(SecutiryConstants.EditPortalSettings);
return CustomNavigationSettings.Load().Items.FirstOrDefault(item => item.Id == id);
}
@ -2368,6 +2380,16 @@ namespace ASC.Api.Settings
DemandRebrandingPermission();
if (!settings.Email.TestEmailRegex() || settings.Email.TestEmailPunyCode())
{
throw new ArgumentException("invalid email");
}
if (!Uri.TryCreate(settings.Site, UriKind.Absolute, out var uri) || uri.DnsSafeHost.TestPunyCode())
{
throw new ArgumentException("invalid url");
}
settings.IsLicensor = false; //TODO: CoreContext.TenantManager.GetTenantQuota(TenantProvider.CurrentTenantID).Branding && settings.IsLicensor
settings.SaveForDefaultTenant();

View File

@ -731,6 +731,9 @@ namespace ASC.Api.Employee
if (CoreContext.UserManager.IsSystemUser(user.ID))
throw new SecurityException();
if (user.Status != EmployeeStatus.Active)
throw new Exception("The user is suspended");
var self = SecurityContext.CurrentAccount.ID.Equals(user.ID);
var resetDate = new DateTime(1900, 01, 01);

View File

@ -324,14 +324,17 @@ namespace ASC.Api.Employee
public GroupWrapperFull SetManager(Guid groupid, Guid userid)
{
var group = GetGroupInfo(groupid);
if (CoreContext.UserManager.UserExists(userid))
{
CoreContext.UserManager.SetDepartmentManager(group.ID, userid);
}
else
{
var user = CoreContext.UserManager.GetUsers(userid);
if (CoreContext.UserManager.IsSystemUser(user.ID))
throw new ItemNotFoundException("user not found");
}
if (user.Status != EmployeeStatus.Active)
throw new Exception("The user is suspended");
CoreContext.UserManager.SetDepartmentManager(group.ID, userid);
return GetById(groupid);
}

View File

@ -151,6 +151,14 @@ namespace ASC.Specific.GlobalFilters
return products[module];
}
}
if (method.Name == "mailserver")
{
var module = "mail";
if (products.ContainsKey(module))
{
return products[module];
}
}
if (products.ContainsKey(method.Name))
{
return products[method.Name];

View File

@ -204,7 +204,7 @@ namespace ASC.Files.ThumbnailBuilder
var thumbnailAspect = config.ThumbnailAspect;
var thumbnail = GetThumbnailData(thumbnailAspect);
var spreadsheetLayout = GetSpreadsheetLayout(thumbnailAspect);
var operationResultProgress = DocumentServiceConnector.GetConvertedUri(fileUri, fileExtension, toExtension, docKey, null, null, thumbnail, spreadsheetLayout, false, out url, out _);
var operationResultProgress = DocumentServiceConnector.GetConvertedUri(fileUri, fileExtension, toExtension, docKey, null, null, thumbnail, spreadsheetLayout, false, false, out url, out _);
operationResultProgress = Math.Min(operationResultProgress, 100);
return operationResultProgress == 100;

View File

@ -52,7 +52,8 @@ namespace ASC.Mail.Core.Dao
{
var query = Query()
.Where(MailboxServerTable.Columns.ProviderId, providerId)
.Where(MailboxServerTable.Columns.IsUserData, isUserData);
.Where(MailboxServerTable.Columns.IsUserData, isUserData)
.GroupBy(MailboxServerTable.Columns.Type, MailboxServerTable.Columns.Port);
return Db.ExecuteList(query)
.ConvertAll(ToMailboxServer);

View File

@ -78,7 +78,8 @@ namespace ASC.MessagingSystem.DbSender
// messages with action code < 2000 are related to login-history
if ((int)message.Action < 2000) return true;
return message.Action == MessageAction.UserSentPasswordChangeInstructions;
return message.Action == MessageAction.UserSentPasswordChangeInstructions
|| message.Action == MessageAction.UserSentEmailChangeInstructions;
}

View File

@ -87,6 +87,7 @@
<Compile Include="LoginProviders\EncryptionLoginProvider.cs" />
<Compile Include="LoginProviders\BoxLoginProvider.cs" />
<Compile Include="LoginProviders\AppleIdLoginProvider.cs" />
<Compile Include="LoginProviders\ZoomLoginProvider.cs" />
<Compile Include="LoginProviders\MicrosoftLoginProvider.cs" />
<Compile Include="LoginProviders\TelegramLoginProvider.cs" />
<Compile Include="LoginProviders\GosUslugiLoginProvider.cs" />

View File

@ -28,13 +28,13 @@ namespace ASC.FederatedLogin.LoginProviders
{
public class FacebookLoginProvider : BaseLoginProvider<FacebookLoginProvider>
{
private const string FacebookProfileUrl = "https://graph.facebook.com/v2.7/me?fields=email,id,birthday,link,first_name,last_name,gender,timezone,locale";
private const string FacebookProfileUrl = "https://graph.facebook.com/me?fields=email,id,birthday,link,first_name,last_name,gender";
public override string AccessTokenUrl { get { return "https://graph.facebook.com/v2.7/oauth/access_token"; } }
public override string AccessTokenUrl { get { return "https://graph.facebook.com/oauth/access_token"; } }
public override string RedirectUri { get { return this["facebookRedirectUrl"]; } }
public override string ClientID { get { return this["facebookClientId"]; } }
public override string ClientSecret { get { return this["facebookClientSecret"]; } }
public override string CodeUrl { get { return "https://www.facebook.com/v2.7/dialog/oauth/"; } }
public override string CodeUrl { get { return "https://www.facebook.com/dialog/oauth/"; } }
public override string Scopes { get { return "email,public_profile"; } }
public FacebookLoginProvider() { }
@ -69,8 +69,6 @@ namespace ASC.FederatedLogin.LoginProviders
Gender = jProfile.Value<string>("gender"),
EMail = jProfile.Value<string>("email"),
Id = jProfile.Value<string>("id"),
TimeZone = jProfile.Value<string>("timezone"),
Locale = jProfile.Value<string>("locale"),
Provider = ProviderConstants.Facebook,
Avatar = string.Format("http://graph.facebook.com/{0}/picture?type=large", jProfile.Value<string>("id"))
};

View File

@ -0,0 +1,250 @@
/*
*
* (c) Copyright Ascensio System Limited 2010-2023
*
* 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.Linq;
using System.Text;
using System.Threading;
using System.Web;
using ASC.Core.Common.Configuration;
using ASC.FederatedLogin.Helpers;
using ASC.FederatedLogin.Profile;
using Newtonsoft.Json;
namespace ASC.FederatedLogin.LoginProviders
{
public class ZoomLoginProvider : BaseLoginProvider<ZoomLoginProvider>
{
public override string AccessTokenUrl => "https://zoom.us/oauth/token";
public override string RedirectUri => this["zoomRedirectUrl"];
public override string ClientID => this["zoomClientId"];
public override string ClientSecret => this["zoomClientSecret"];
public override string CodeUrl => "https://zoom.us/oauth/authorize";
public override string Scopes => "";
// used in ZoomService
public const string ApiUrl = "https://api.zoom.us/v2";
private const string UserProfileUrl = "https://api.zoom.us/v2/users/me";
public ZoomLoginProvider() { }
public ZoomLoginProvider(string name, int order, Dictionary<string, Prop> props, Dictionary<string, Prop> additional = null)
: base(name, order, props, additional)
{
}
public override LoginProfile ProcessAuthoriztion(HttpContext context, IDictionary<string, string> @params)
{
try
{
var error = context.Request["error"];
if (!string.IsNullOrEmpty(error))
{
if (error == "access_denied")
{
error = "Canceled at provider";
}
throw new Exception(error);
}
var code = context.Request["code"];
if (string.IsNullOrEmpty(code))
{
var additionalArgs = new Dictionary<string, string>(@params);
OAuth20TokenHelper.RequestCode<ZoomLoginProvider>(context, Scopes, additionalArgs);
}
var token = GetAccessToken(code);
return GetLoginProfile(token);
}
catch (ThreadAbortException)
{
throw;
}
catch (Exception ex)
{
return new LoginProfile() { AuthorizationError = ex.Message };
}
}
// used in ZoomService
public OAuth20Token GetAccessToken(string code, string redirectUri = null, string codeVerifier = null)
{
var clientPair = $"{ClientID}:{ClientSecret}";
var base64ClientPair = Convert.ToBase64String(Encoding.UTF8.GetBytes(clientPair));
var body = new Dictionary<string, string>
{
{ "code", code },
{ "grant_type", "authorization_code" },
{ "redirect_uri", redirectUri ?? RedirectUri }
};
if (codeVerifier != null)
{
body.Add("code_verifier", codeVerifier);
}
var json = RequestHelper.PerformRequest(AccessTokenUrl, "application/x-www-form-urlencoded", "POST",
body: string.Join("&", body.Select(kv => $"{HttpUtility.UrlEncode(kv.Key)}={HttpUtility.UrlEncode(kv.Value)}")),
headers: new Dictionary<string, string> { { "Authorization", $"Basic {base64ClientPair}" } }
);
return OAuth20Token.FromJson(json);
}
public override LoginProfile GetLoginProfile(string accessToken)
{
if (string.IsNullOrEmpty(accessToken))
{
throw new Exception("Login failed");
}
var (loginProfile, _) = RequestProfile(accessToken);
return loginProfile;
}
public (LoginProfile, ZoomProfile) GetLoginProfileAndRaw(string accessToken)
{
if (string.IsNullOrEmpty(accessToken))
{
throw new Exception("Login failed");
}
var (loginProfile, raw) = RequestProfile(accessToken);
return (loginProfile, raw);
}
public LoginProfile GetMinimalProfile(string uid)
{
return new LoginProfile
{
Id = uid,
Provider = ProviderConstants.Zoom
};
}
private (LoginProfile, ZoomProfile) ProfileFromZoom(string zoomProfile)
{
var jsonProfile = JsonConvert.DeserializeObject<ZoomProfile>(zoomProfile);
var profile = new LoginProfile
{
Id = jsonProfile.Id,
Avatar = jsonProfile.PicUrl?.ToString(),
EMail = jsonProfile.Email,
FirstName = jsonProfile.FirstName,
LastName = jsonProfile.LastName,
Locale = jsonProfile.Language,
TimeZone = jsonProfile.Timezone,
DisplayName = jsonProfile.DisplayName,
Provider = ProviderConstants.Zoom
};
return (profile, jsonProfile);
}
private (LoginProfile, ZoomProfile) RequestProfile(string accessToken)
{
var json = RequestHelper.PerformRequest(UserProfileUrl, headers: new Dictionary<string, string> { { "Authorization", "Bearer " + accessToken } });
var (loginProfile, jsonProfile) = ProfileFromZoom(json);
return (loginProfile, jsonProfile);
}
public class ZoomProfile
{
[JsonProperty("id")]
public string Id { get; set; }
[JsonProperty("first_name")]
public string FirstName { get; set; }
[JsonProperty("last_name")]
public string LastName { get; set; }
[JsonProperty("display_name")]
public string DisplayName { get; set; }
[JsonProperty("email")]
public string Email { get; set; }
[JsonProperty("role_name")]
public string RoleName { get; set; }
[JsonProperty("pmi")]
public long Pmi { get; set; }
[JsonProperty("use_pmi")]
public bool UsePmi { get; set; }
[JsonProperty("personal_meeting_url")]
public Uri PersonalMeetingUrl { get; set; }
[JsonProperty("timezone")]
public string Timezone { get; set; }
[JsonProperty("created_at")]
public DateTimeOffset CreatedAt { get; set; }
[JsonProperty("last_login_time")]
public DateTimeOffset LastLoginTime { get; set; }
[JsonProperty("pic_url")]
public Uri PicUrl { get; set; }
[JsonProperty("jid")]
public string Jid { get; set; }
[JsonProperty("account_id")]
public string AccountId { get; set; }
[JsonProperty("language")]
public string Language { get; set; }
[JsonProperty("phone_country")]
public string PhoneCountry { get; set; }
[JsonProperty("phone_number")]
public string PhoneNumber { get; set; }
[JsonProperty("status")]
public string Status { get; set; }
[JsonProperty("job_title")]
public string JobTitle { get; set; }
[JsonProperty("location")]
public string Location { get; set; }
[JsonProperty("account_number")]
public long AccountNumber { get; set; }
[JsonProperty("cluster")]
public string Cluster { get; set; }
[JsonProperty("user_created_at")]
public DateTimeOffset UserCreatedAt { get; set; }
}
}
}

View File

@ -31,5 +31,6 @@ namespace ASC.FederatedLogin
public const string Encryption = "e2e";
public const string AppleId = "appleid";
public const string Microsoft = "microsoft";
public const string Zoom = "zoom";
}
}

View File

@ -1,20 +1,20 @@
/*
*
* (c) Copyright Ascensio System Limited 2010-2023
*
* 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.
*
*/
/*
*
* (c) Copyright Ascensio System Limited 2010-2023
*
* 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.
*
*/
const queryConsts = require('./sqlConsts');
const shortUrl = require('./urlShortener');
const auth = require('../middleware/auth');
@ -67,17 +67,25 @@ function make(req, res) {
res.end();
return;
}
key = shortUrl.encode(result[0].id);
key = result[0].short;
log.info("already created shortlink (" + key + ") for " + link);
} else {
if (urls.find(r => r === link)) {
processError(new Error('Link is already being made'), res, 500);
return;
}
result = yield query(queryConsts.insert, [link]);
key = shortUrl.encode(result.insertId);
log.info("creted new shortlink (" + key + ") for " + link);
yield query(queryConsts.update, [key, result.insertId]);
while(true)
{
var key = shortUrl.GenerateRandomKey();
var id = shortUrl.decode(key);
var result = yield query(queryConsts.find, [id]);
if(!result.length){
result = yield query(queryConsts.insert, [id, key, link]);
log.info("creted new shortlink (" + key + ") for " + link);
break;
}
}
}
urls = urls.filter((item) => item !== link);

View File

@ -15,9 +15,8 @@
*/
module.exports = {
exists: "SELECT short,id FROM short_links WHERE link = ?",
insert: "INSERT INTO short_links SET link = ?",
update: "UPDATE short_links SET short = ? WHERE id = ?",
find: "SELECT link FROM short_links WHERE id = ?",
module.exports = {
exists: "SELECT short,id FROM short_links WHERE link = ?",
insert: "INSERT INTO short_links SET id = ?, short = ?, link = ?",
find: "SELECT link FROM short_links WHERE id = ?",
};

View File

@ -1,20 +1,20 @@
/*
*
* (c) Copyright Ascensio System Limited 2010-2023
*
* 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.
*
*/
/*
*
* (c) Copyright Ascensio System Limited 2010-2023
*
* 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.
*
*/
/*
* ShortURL (https://github.com/delight-im/ShortURL)
* Copyright (c) delight.im (https://www.delight.im/)
@ -39,19 +39,23 @@
//'23456789bcdfghjkmnpqrstvwxyzBCDFGHJKLMNPQRSTVWXYZ-_'
var _alphabet = "5XzpDt6wZRdsTrJkSY_cgPyxN4j-fnb9WKBF8vh3GH72QqmLVCM",
_base = _alphabet.length,
_initial = _base * _base;
_base = _alphabet.length;
function getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max) + 1;
return Math.floor(Math.random() * (max - min) + min);
}
module.exports = {
encode: function(num) {
num += _initial;
var str = '';
while (num > 0) {
str = _alphabet.charAt(num % _base) + str;
num = Math.floor(num / _base);
GenerateRandomKey: function() {
var length = 10;
var result = "";
for(var i = 0; i < length; i++){
result += _alphabet.charAt(getRandomInt(0,50));
}
return str;
return result;
},
decode: function(str) {
@ -61,7 +65,7 @@ module.exports = {
if (index < 0) return null;
num = num * _base + index;
}
return num - _initial;
return num;
}
};

View File

@ -3,6 +3,7 @@
"core.machinekey": "1123askdasjklasbnd",
"sql": {
"host": "127.0.0.1",
"port": 3306,
"user": "root",
"password": "111111",
"database": "onlyoffice"

View File

@ -107,6 +107,7 @@
<Compile Include="Helpers\ApiSystemHelper.cs" />
<Compile Include="Jabber\FireBase.cs" />
<Compile Include="Mail\MailServiceHelper.cs" />
<Compile Include="WebItemStoreValidator.cs" />
<Compile Include="Sms\SmsKeyStorage.cs" />
<Compile Include="Sms\SmsProvider.cs" />
<Compile Include="Sms\SmsSender.cs" />
@ -150,7 +151,6 @@
<Compile Include="IAddon.cs" />
<Compile Include="Extensions\EnumExtension.cs" />
<Compile Include="Extensions\ProductModuleExtension.cs" />
<Compile Include="Extensions\StringExtension.cs" />
<Compile Include="Extensions\UserInfoExtension.cs" />
<Compile Include="Helpers\GrammaticalHelper.cs" />
<Compile Include="Helpers\ImageHelpers.cs" />

View File

@ -220,8 +220,8 @@ namespace ASC.Web.Core
if (lifeTime > 0)
{
settings.Index = settings.Index + 1;
settings.LifeTime = lifeTime;
settings.Index++;
settings.LifeTime = lifeTime > 9999 ? 9999 : lifeTime;
}
else
{

View File

@ -85,6 +85,7 @@ namespace ASC.Web.Core.Files
/// <param name="region">Four letter language codes</param>
/// <param name="thumbnail">Thumbnail settings</param>
/// <param name="spreadsheetLayout"></param>
/// <param name="toForm">To form</param>
/// <param name="isAsync">Perform conversions asynchronously</param>
/// <param name="signatureSecret">Secret key to generate the token</param>
/// <param name="convertedDocumentUri">Uri to the converted document</param>
@ -106,6 +107,7 @@ namespace ASC.Web.Core.Files
string region,
ThumbnailData thumbnail,
SpreadsheetLayout spreadsheetLayout,
bool toForm,
bool isAsync,
string signatureSecret,
out string convertedDocumentUri,
@ -121,8 +123,13 @@ namespace ASC.Web.Core.Files
documentRevisionId = string.IsNullOrEmpty(documentRevisionId)
? documentUri
: documentRevisionId;
documentRevisionId = GenerateRevisionId(documentRevisionId);
documentConverterUrl = FilesLinkUtility.AddQueryString(documentConverterUrl, new Dictionary<string, string>() {
{ FilesLinkUtility.ShardKey, documentRevisionId }
});
var request = (HttpWebRequest)WebRequest.Create(documentConverterUrl);
request.Method = "POST";
request.ContentType = "application/json";
@ -147,6 +154,11 @@ namespace ASC.Web.Core.Files
body.Password = password;
}
if (toForm)
{
body.Pdf = new ConvertionPdf { form = true };
}
if (!string.IsNullOrEmpty(signatureSecret))
{
var payload = new Dictionary<string, object>
@ -247,6 +259,10 @@ namespace ASC.Web.Core.Files
MetaData meta,
string signatureSecret)
{
documentTrackerUrl = FilesLinkUtility.AddQueryString(documentTrackerUrl, new Dictionary<string, string>() {
{ FilesLinkUtility.ShardKey, documentRevisionId }
});
var request = (HttpWebRequest)WebRequest.Create(documentTrackerUrl);
request.Method = "POST";
request.ContentType = "application/json";
@ -338,6 +354,10 @@ namespace ASC.Web.Core.Files
if (string.IsNullOrEmpty(requestKey) && string.IsNullOrEmpty(scriptUrl))
throw new ArgumentException("requestKey or inputScript is empty");
docbuilderUrl = FilesLinkUtility.AddQueryString(docbuilderUrl, new Dictionary<string, string>() {
{ FilesLinkUtility.ShardKey, requestKey }
});
var request = (HttpWebRequest)WebRequest.Create(docbuilderUrl);
request.Method = "POST";
request.ContentType = "application/json";
@ -678,6 +698,15 @@ namespace ASC.Web.Core.Files
}
}
[Serializable]
[DataContract(Name = "pdf", Namespace = "")]
[DebuggerDisplay("form {form}")]
public class ConvertionPdf
{
[DataMember(Name = "form")]
public bool form;
}
[Serializable]
[DataContract(Name = "Converion", Namespace = "")]
[DebuggerDisplay("{Title} from {FileType} to {OutputType} ({Key})")]
@ -713,6 +742,9 @@ namespace ASC.Web.Core.Files
[DataMember(Name = "region", IsRequired = true)]
public string Region { get; set; }
[DataMember(Name = "pdf", EmitDefaultValue = false)]
public ConvertionPdf Pdf { get; set; }
[DataMember(Name = "token", EmitDefaultValue = false)]
public string Token { get; set; }
}
@ -790,23 +822,11 @@ namespace ASC.Web.Core.Files
string errorMessage;
switch (code)
{
case ErrorCode.VkeyUserCountExceed:
errorMessage = "user count exceed";
case ErrorCode.SizeLimit:
errorMessage = "size limit exceeded";
break;
case ErrorCode.VkeyKeyExpire:
errorMessage = "signature expire";
break;
case ErrorCode.VkeyEncrypt:
errorMessage = "encrypt signature";
break;
case ErrorCode.UploadCountFiles:
errorMessage = "count files";
break;
case ErrorCode.UploadExtension:
errorMessage = "extension";
break;
case ErrorCode.UploadContentLength:
errorMessage = "upload length";
case ErrorCode.OutputType:
errorMessage = "output format not defined";
break;
case ErrorCode.Vkey:
errorMessage = "document signature";
@ -839,12 +859,8 @@ namespace ASC.Web.Core.Files
public enum ErrorCode
{
VkeyUserCountExceed = -22,
VkeyKeyExpire = -21,
VkeyEncrypt = -20,
UploadCountFiles = -11,
UploadExtension = -10,
UploadContentLength = -9,
SizeLimit = -10,
OutputType = -9,
Vkey = -8,
TaskQueue = -6,
ConvertPassword = -5,

View File

@ -365,7 +365,7 @@ namespace ASC.Web.Core.Files
{ FileType.Presentation, ConfigurationManagerExtension.AppSettings["files.docservice.internal-ppt"] ?? ".pptx" }
};
public static readonly string MasterFormExtension = ConfigurationManagerExtension.AppSettings["files.docservice.internal-form"] ?? ".docxf";
public static readonly string MasterFormExtension = ConfigurationManagerExtension.AppSettings["files.docservice.internal-form"] ?? ".pdf";
public enum CsvDelimiter
{

View File

@ -16,7 +16,9 @@
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Web;
@ -53,6 +55,8 @@ namespace ASC.Web.Core.Files
public const string Anchor = "anchor";
public const string LinkId = "linkid";
public const string FolderShareKey = "share";
public const string VersionShort = "ver";
public const string ShardKey = "shardkey";
public static string FileHandlerPath
{
@ -260,6 +264,11 @@ namespace ASC.Web.Core.Files
get { return FileWebEditorUrlString + "&" + Action + "=view"; }
}
public static string FileWebFillingUrlString
{
get { return FileWebEditorUrlString + "&" + Action + "=fill"; }
}
public static string GetFileWebViewerUrlForMobile(object fileId, int fileVersion)
{
var viewerUrl = CommonLinkUtility.ToAbsolute("~/../Products/Files/") + EditorPage + "?" + FileId + "={0}";
@ -288,6 +297,11 @@ namespace ASC.Web.Core.Files
return string.Format(FileWebEditorUrlString, HttpUtility.UrlEncode(fileId.ToString()));
}
public static string GetFileWebFillUrl(object fileId)
{
return string.Format(FileWebFillingUrlString, HttpUtility.UrlEncode(fileId.ToString()));
}
public static string FileCustomProtocolEditorUrlString
{
get { return "oo-office:" + CommonLinkUtility.GetFullAbsolutePath(FileWebEditorUrlString); }
@ -446,5 +460,40 @@ namespace ASC.Web.Core.Files
{
return "DocKey_" + key;
}
public static string AddQueryString(string uri, Dictionary<string, string> queryString)
{
var uriToBeAppended = uri;
var anchorText = string.Empty;
var anchorIndex = uri.IndexOf('#');
if (anchorIndex != -1)
{
anchorText = uriToBeAppended.Substring(anchorIndex);
uriToBeAppended = uriToBeAppended.Substring(0, anchorIndex);
}
var queryIndex = uriToBeAppended.IndexOf('?');
var hasQuery = queryIndex != -1;
var sb = new StringBuilder();
sb.Append(uriToBeAppended);
foreach (var parameter in queryString)
{
if (parameter.Value == null)
{
continue;
}
sb.Append(hasQuery ? '&' : '?');
sb.Append(HttpUtility.UrlEncode(parameter.Key));
sb.Append('=');
sb.Append(HttpUtility.UrlEncode(parameter.Value));
hasQuery = true;
}
sb.Append(anchorText);
return sb.ToString();
}
}
}

View File

@ -0,0 +1,59 @@
/*
*
* (c) Copyright Ascensio System Limited 2010-2023
*
* 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 ASC.Core;
using ASC.Data.Storage;
namespace ASC.Web.Core
{
public class WebItemStoreValidator : IDataStoreValidator
{
internal Guid WebItemID;
public WebItemStoreValidator(string webItemID)
{
if (!Guid.TryParse(webItemID, out WebItemID))
{
throw new ArgumentException();
}
}
public virtual bool Validate(string path)
{
if (!SecurityContext.IsAuthenticated)
{
return false;
}
var product = WebItemManager.Instance[WebItemID];
return product != null && !product.IsDisabled();
}
}
public class WebItemStoreAdminValidator : WebItemStoreValidator
{
public WebItemStoreAdminValidator(string webItemID) : base(webItemID) { }
public override bool Validate(string path)
{
return base.Validate(path) && WebItemSecurity.IsProductAdministrator(WebItemID, SecurityContext.CurrentAccount.ID);
}
}
}

View File

@ -41,17 +41,23 @@ namespace ASC.Web.Studio.Core.Backup
#region backup
[AjaxMethod]
public BackupProgress StartBackup(BackupStorageType storageType, Dictionary<string, string> storageParams, bool backupMail)
public BackupProgress StartBackup(BackupStorageType storageType, Dictionary<string, string> storageParams, bool backupMail, bool dump)
{
DemandPermissionsBackup();
if (!CoreContext.Configuration.Standalone && dump)
{
throw new ArgumentException("backup can not start as dump");
}
var backupRequest = new StartBackupRequest
{
TenantId = GetCurrentTenantId(),
UserId = SecurityContext.CurrentAccount.ID,
BackupMail = backupMail,
StorageType = storageType,
StorageParams = storageParams
StorageParams = storageParams,
Dump = dump
};
switch (storageType)

View File

@ -204,7 +204,6 @@ namespace ASC.Web.Studio.Core
if (!viewerIsAdmin)
{
StudioNotifyService.Instance.SendEmailChangeInstructions(user, email);
MessageService.Send(HttpContext.Current.Request, MessageAction.UserSentEmailChangeInstructions, user.DisplayUserName(false));
}
else
{

View File

@ -229,7 +229,11 @@ namespace ASC.Web.Studio.Core.Notify
public void SendEmailChangeInstructions(UserInfo user, string email)
{
var confirmationUrl = CommonLinkUtility.GetConfirmationUrl(email, ConfirmType.EmailChange, SecurityContext.CurrentAccount.ID);
var auditEventDate = DateTime.UtcNow;
var hash = auditEventDate.ToString("s");
var confirmationUrl = CommonLinkUtility.GetConfirmationUrl(email, ConfirmType.EmailChange, hash);
Func<string> greenButtonText = () => WebstudioNotifyPatternResource.ButtonChangeEmail;
@ -244,6 +248,10 @@ namespace ASC.Web.Studio.Core.Notify
new[] { EMailSenderName },
TagValues.GreenButton(greenButtonText, confirmationUrl),
new TagValue(CommonTags.Culture, user.GetCulture().Name));
var displayUserName = user.DisplayUserName(false);
MessageService.Send(HttpContext.Current.Request, auditEventDate, MessageAction.UserSentEmailChangeInstructions, MessageTarget.Create(user.ID), displayUserName);
}
public void SendEmailActivationInstructions(UserInfo user, string email)

View File

@ -21,6 +21,7 @@ using System.Web;
using ASC.Common.Web;
using ASC.Core;
using ASC.Data.Storage;
using ASC.Web.Core;
using ASC.Web.Core.Files;
using ASC.Web.Studio.Core;
using ASC.Web.Studio.PublicResources;
@ -40,6 +41,9 @@ namespace ASC.Web.Studio.HttpHandlers
}
var storeDomain = context.Request["esid"];
DemandStoreAccess(storeDomain);
var itemID = context.Request["iid"] ?? "";
var file = context.Request.Files["upload"];
@ -119,5 +123,36 @@ namespace ASC.Web.Studio.HttpHandlers
);
}
}
private void DemandStoreAccess(string domain)
{
if (string.IsNullOrEmpty(domain)) return;
var baseDomain = domain.Split('_')[0];
foreach (var item in WebItemManager.Instance.GetItemsAll())
{
if (item.GetSysName().EndsWith(baseDomain))
{
if (item.IsDisabled())
{
throw new HttpException(403, "Access denied.");
}
if (item.IsSubItem())
{
var parentItemID = WebItemManager.Instance.GetParentItemID(item.ID);
var parentItem = WebItemManager.Instance[parentItemID];
if (parentItem.IsDisabled())
{
throw new HttpException(403, "Access denied.");
}
}
break;
}
}
}
}
}

View File

@ -70,6 +70,7 @@ namespace ASC.Web.Studio.Masters.MasterResources
FilesLinkUtility.FileDownloadUrlString,
FilesLinkUtility.FileViewUrlString,
FilesLinkUtility.FileWebViewerUrlString,
FilesLinkUtility.FileWebFillingUrlString,
FilesLinkUtility.FileWebViewerExternalUrlString,
FilesLinkUtility.FileWebEditorUrlString,
FilesLinkUtility.FileWebEditorExternalUrlString,

View File

@ -190,19 +190,19 @@ namespace ASC.Web.CRM.Classes
? BuildFileDirectory(contactID)
: (String.IsNullOrEmpty(tmpDirName) ? BuildFileTmpDirectory(contactID) : BuildFileTmpDirectory(tmpDirName));
var filesPaths = Global.GetStore().ListFilesRelative("", directoryPath, BuildFileName(contactID, photoSize) + "*", false);
var filesPaths = Global.GetStore().ListFilesRelative("", directoryPath, BuildFileName(contactID, photoSize) + "*", false).ToList();
if (filesPaths.Length == 0 && photoSize == _bigSize)
if (!filesPaths.Any() && photoSize == _bigSize)
{
filesPaths = Global.GetStore().ListFilesRelative("", directoryPath, BuildFileName(contactID, _oldBigSize) + "*", false);
filesPaths = Global.GetStore().ListFilesRelative("", directoryPath, BuildFileName(contactID, _oldBigSize) + "*", false).ToList();
}
if (filesPaths.Length == 0)
if (!filesPaths.Any())
{
return String.Empty;
}
return Path.Combine(directoryPath, filesPaths[0]);
return Path.Combine(directoryPath, filesPaths.First());
}
private static PhotoData FromDataStore(Size photoSize, String tmpDirName)

View File

@ -19,6 +19,7 @@ using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using ASC.Common.Logging;
using ASC.CRM.Core.Dao;
@ -140,12 +141,12 @@ namespace ASC.Web.CRM.Classes
try
{
var photoPaths = Global.GetStore().ListFilesRelative("", directoryPath, OrganisationLogoImgName + "*", false);
if (photoPaths.Length == 0)
var photoPaths = Global.GetStore().ListFilesRelative("", directoryPath, OrganisationLogoImgName + "*", false).ToList();
if (!photoPaths.Any())
return 0;
byte[] bytes;
using (var photoTmpStream = dataStore.GetReadStream(Path.Combine(directoryPath, photoPaths[0])))
using (var photoTmpStream = dataStore.GetReadStream(Path.Combine(directoryPath, photoPaths.First())))
{
bytes = Global.ToByteArray(photoTmpStream);
}

View File

@ -172,7 +172,7 @@
</ul>
</li>
<li class="menu-sub-item menu-item<% if (CurrentPage == "settings_invoice_items" || CurrentPage == "settings_invoice_tax" || CurrentPage == "settings_organisation_profile")
<li class="menu-sub-item menu-item<% if (CurrentPage == "settings_invoice_items" || CurrentPage == "settings_invoice_tax" || CurrentPage == "settings_organization_profile")
{ %> open<% } %>">
<div class="sub-list">
<span class="expander"></span>
@ -191,9 +191,9 @@
<span class="menu-item-label inner-text"><%= CRMCommonResource.InvoiceTaxes %></span>
</a>
</li>
<li class="menu-sub-item<% if (CurrentPage == "settings_organisation_profile")
<li class="menu-sub-item<% if (CurrentPage == "settings_organization_profile")
{ %> active<% } %>">
<a class="menu-item-label outer-text text-overflow" href="Settings.aspx?type=organisation_profile" title="<%= CRMCommonResource.OrganisationProfile %>">
<a class="menu-item-label outer-text text-overflow" href="Settings.aspx?type=organization_profile" title="<%= CRMCommonResource.OrganisationProfile %>">
<span class="menu-item-label inner-text"><%= CRMCommonResource.OrganisationProfile %></span>
</a>
</li>

View File

@ -282,7 +282,12 @@ namespace ASC.CRM.Core.Dao
public List<Cases> GetAllCases()
{
return GetCases(String.Empty, 0, null, null, 0, 0, new OrderBy(SortedByType.Title, true));
return GetAllCases(0, 0);
}
public List<Cases> GetAllCases(int from, int count)
{
return GetCases(String.Empty, 0, null, null, from, count, new OrderBy(SortedByType.Id, true));
}
public int GetCasesCount()
@ -472,22 +477,31 @@ namespace ASC.CRM.Core.Dao
if (0 < from && from < int.MaxValue) sqlQuery.SetFirstResult(from);
if (0 < count && count < int.MaxValue) sqlQuery.SetMaxResults(count);
sqlQuery.OrderBy("is_closed", true);
if (orderBy != null && Enum.IsDefined(typeof(SortedByType), orderBy.SortedBy))
{
switch ((SortedByType)orderBy.SortedBy)
{
case SortedByType.Title:
sqlQuery.OrderBy("is_closed", true);
sqlQuery.OrderBy("title", orderBy.IsAsc);
break;
case SortedByType.CreateBy:
sqlQuery.OrderBy("is_closed", true);
sqlQuery.OrderBy("create_by", orderBy.IsAsc);
break;
case SortedByType.DateAndTime:
sqlQuery.OrderBy("is_closed", true);
sqlQuery.OrderBy("create_on", orderBy.IsAsc);
break;
case SortedByType.Id:
sqlQuery.OrderBy("id", orderBy.IsAsc);
break;
}
}
else
{
sqlQuery.OrderBy("is_closed", true);
}
return Db.ExecuteList(sqlQuery).ConvertAll(ToCases);
}

View File

@ -190,8 +190,13 @@ namespace ASC.CRM.Core.Dao
public List<Contact> GetAllContacts()
{
return GetContacts(String.Empty, new List<string>(), -1, -1, ContactListViewType.All, DateTime.MinValue, DateTime.MinValue, 0, 0,
new OrderBy(ContactSortedByType.DisplayName, true));
return GetAllContacts(0 ,0);
}
public List<Contact> GetAllContacts(int from, int count)
{
return GetContacts(string.Empty, new List<string>(), -1, -1, ContactListViewType.All, DateTime.MinValue, DateTime.MinValue, from, count,
new OrderBy(ContactSortedByType.Id, true));
}
public int GetContactsCount(String searchText,
@ -857,6 +862,9 @@ namespace ASC.CRM.Core.Dao
sqlQuery.OrderBy("last_event_modifed_on", orderBy.IsAsc);
sqlQuery.OrderBy("create_on", orderBy.IsAsc);
break;
case ContactSortedByType.Id:
sqlQuery.OrderBy("id", orderBy.IsAsc);
break;
default:
sqlQuery.OrderBy("display_name", orderBy.IsAsc);
break;

View File

@ -198,7 +198,12 @@ namespace ASC.CRM.Core.Dao
public List<ContactInfo> GetAll()
{
return GetList(0, null, null, null);
return GetAll(0, 0);
}
public List<ContactInfo> GetAll(int from, int count)
{
return GetList(0, null, null, null, from, count);
}
public List<ContactInfo> GetAll(int[] contactID)
@ -213,7 +218,7 @@ namespace ASC.CRM.Core.Dao
return Db.ExecuteList(sqlQuery).ConvertAll(row => ToContactInfo(row));
}
public virtual List<ContactInfo> GetList(int contactID, ContactInfoType? infoType, int? categoryID, bool? isPrimary)
public virtual List<ContactInfo> GetList(int contactID, ContactInfoType? infoType, int? categoryID, bool? isPrimary, int? from = null, int? count = null)
{
SqlQuery sqlQuery = GetSqlQuery(null);
@ -229,10 +234,13 @@ namespace ASC.CRM.Core.Dao
if (isPrimary.HasValue)
sqlQuery.Where(Exp.Eq("is_primary", isPrimary.Value));
sqlQuery.OrderBy("type", true);
// sqlQuery.OrderBy("category", true);
// sqlQuery.OrderBy("is_primary", true);
if (from.HasValue && 0 < from && from < int.MaxValue)
sqlQuery.SetFirstResult(from.Value);
if (count.HasValue && 0 < count && count < int.MaxValue)
sqlQuery.SetMaxResults(count.Value);
sqlQuery.OrderBy(from.HasValue || count.HasValue ? "id" : "type", true);
return Db.ExecuteList(sqlQuery).ConvertAll(row => ToContactInfo(row));
}

View File

@ -237,7 +237,12 @@ namespace ASC.CRM.Core.Dao
public List<Deal> GetAllDeals()
{
return GetDeals(String.Empty,
return GetAllDeals(0, 0);
}
public List<Deal> GetAllDeals(int from, int count)
{
return GetDeals(string.Empty,
Guid.Empty,
0,
null,
@ -246,9 +251,9 @@ namespace ASC.CRM.Core.Dao
null,
DateTime.MinValue,
DateTime.MinValue,
0,
0,
new OrderBy(DealSortedByType.Stage, true));
from,
count,
new OrderBy(DealSortedByType.Id, true));
}
private Exp WhereConditional(
@ -614,6 +619,9 @@ namespace ASC.CRM.Core.Dao
case DealSortedByType.DateAndTime:
sqlQuery.OrderBy("close_date", orderBy.IsAsc);
break;
case DealSortedByType.Id:
sqlQuery.OrderBy("tblDeal.id", orderBy.IsAsc);
break;
default:
throw new ArgumentException();
}

View File

@ -291,6 +291,9 @@ namespace ASC.CRM.Core.Dao
.OrderBy("case when c_tbl.display_name is null then 1 else 0 end, c_tbl.display_name", orderBy.IsAsc)
.OrderBy(invoicesTableAlias + ".number", true);
break;
case InvoiceSortedByType.Id:
sqlQuery.OrderBy(invoicesTableAlias + ".id", orderBy.IsAsc);
break;
default:
sqlQuery.OrderBy(invoicesTableAlias + ".number", true);
break;

View File

@ -99,11 +99,26 @@ namespace ASC.CRM.Core.Dao
public virtual List<InvoiceItem> GetAll()
{
return GetAllInDb();
return GetAll(0, 0);
}
public virtual List<InvoiceItem> GetAllInDb()
public virtual List<InvoiceItem> GetAll(int from, int count)
{
return Db.ExecuteList(GetInvoiceItemSqlQuery(null)).ConvertAll(ToInvoiceItem);
var sqlQuery = GetInvoiceItemSqlQuery(null);
if (0 < from && from < int.MaxValue)
{
sqlQuery.SetFirstResult(from);
}
if (0 < count && count < int.MaxValue)
{
sqlQuery.SetMaxResults(count);
}
sqlQuery.OrderBy("id", true);
return Db.ExecuteList(sqlQuery).ConvertAll(ToInvoiceItem);
}
public virtual List<InvoiceItem> GetByID(int[] ids)
@ -171,6 +186,9 @@ namespace ASC.CRM.Core.Dao
case InvoiceItemSortedByType.Created:
sqlQuery.OrderBy("create_on", orderBy.IsAsc);
break;
case InvoiceItemSortedByType.Id:
sqlQuery.OrderBy("id", orderBy.IsAsc);
break;
default:
sqlQuery.OrderBy("title", true);
break;

View File

@ -415,15 +415,21 @@ namespace ASC.CRM.Core.Dao
public List<RelationshipEvent> GetAllItems()
{
return GetItems(String.Empty,
return GetAllItems(0, 0);
}
public List<RelationshipEvent> GetAllItems(int from, int count)
{
return GetItems(string.Empty,
EntityType.Any,
0,
Guid.Empty,
0,
DateTime.MinValue,
DateTime.MinValue,
0,
0, null);
from,
count,
new OrderBy(RelationshipEventByType.Id, true));
}
public List<RelationshipEvent> GetItems(
@ -529,6 +535,9 @@ namespace ASC.CRM.Core.Dao
case RelationshipEventByType.Created:
sqlQuery.OrderBy("create_on", orderBy.IsAsc);
break;
case RelationshipEventByType.Id:
sqlQuery.OrderBy("id", orderBy.IsAsc);
break;
}
else
sqlQuery.OrderBy("create_on", false);

View File

@ -150,11 +150,24 @@ namespace ASC.CRM.Core.Dao
public List<Task> GetAllTasks()
{
return Db.ExecuteList(
GetTaskQuery(null)
.OrderBy("deadline", true)
.OrderBy("title", true))
.ConvertAll(row => ToTask(row)).FindAll(CRMSecurity.CanAccessTo);
return GetAllTasks(0, 0);
}
public List<Task> GetAllTasks(int from, int count)
{
var sqlQuery = GetTaskQuery(null);
if (0 < from && from < int.MaxValue)
sqlQuery.SetFirstResult(from);
if (0 < count && count < int.MaxValue)
sqlQuery.SetMaxResults(count);
sqlQuery.OrderBy("id", true);
return Db.ExecuteList(sqlQuery)
.ConvertAll(row => ToTask(row))
.FindAll(CRMSecurity.CanAccessTo);
}
public void ExecAlert(IEnumerable<int> ids)
@ -647,6 +660,9 @@ namespace ASC.CRM.Core.Dao
.OrderBy(aliasPrefix + "deadline", true)
.OrderBy(aliasPrefix + "title", true);
break;
case TaskSortedByType.Id:
sqlQuery.OrderBy(aliasPrefix + "id", orderBy.IsAsc);
break;
}
}
else

View File

@ -24,7 +24,8 @@ namespace ASC.CRM.Core
Category,
DeadLine,
Contact,
ContactManager
ContactManager,
Id
}
public enum DealSortedByType
@ -33,7 +34,8 @@ namespace ASC.CRM.Core
Responsible,
Stage,
BidValue,
DateAndTime
DateAndTime,
Id
}
public enum RelationshipEventByType
@ -41,7 +43,8 @@ namespace ASC.CRM.Core
Created,
CreateBy,
Category,
Content
Content,
Id
}
public enum ContactSortedByType
@ -51,14 +54,16 @@ namespace ASC.CRM.Core
Created,
FirstName,
LastName,
History
History,
Id
}
public enum SortedByType
{
DateAndTime,
Title,
CreateBy
CreateBy,
Id
}
public enum InvoiceSortedByType
@ -67,7 +72,8 @@ namespace ASC.CRM.Core
IssueDate,
Contact,
DueDate,
Status
Status,
Id
}
public enum InvoiceItemSortedByType
@ -76,7 +82,7 @@ namespace ASC.CRM.Core
Price,
Quantity,
SKU,
Created
Created,
Id
}
}

View File

@ -313,7 +313,7 @@ namespace ASC.Web.CRM.Resources {
}
/// <summary>
/// Looks up a localized string similar to Twitter.
/// Looks up a localized string similar to X (Twitter).
/// </summary>
public static string ContactInfoType_Twitter {
get {

View File

@ -143,7 +143,7 @@
<value>Skype</value>
</data>
<data name="ContactInfoType_Twitter" xml:space="preserve">
<value>Twitter</value>
<value>X (Twitter)</value>
</data>
<data name="ContactInfoType_VK" xml:space="preserve">
<value>VK</value>

View File

@ -143,7 +143,7 @@
<value>Skype</value>
</data>
<data name="ContactInfoType_Twitter" xml:space="preserve">
<value>Twitter</value>
<value>X (Twitter)</value>
</data>
<data name="ContactInfoType_VK" xml:space="preserve">
<value>VK</value>

View File

@ -240,7 +240,7 @@ namespace ASC.Web.CRM
CommonContainerHolder.Controls.Add(LoadControl(InvoiceTaxesView.Location));
break;
case "organisation_profile":
case "organization_profile":
PageTitle = CRMCommonResource.OrganisationProfile;
CommonContainerHolder.Controls.Add(LoadControl(OrganisationProfile.Location));

View File

@ -54,14 +54,14 @@ namespace ASC.Web.CRM.Classes
{
public static readonly ICache Cache = AscCache.Default;
public static String GetStateCacheKey(string key)
public static string GetStateCacheKey(string key)
{
return String.Format("{0}:crm:queue:exporttocsv", key);
return string.Format("{0}:crm:queue:exporttocsv", key);
}
public static String GetCancelCacheKey(string key)
public static string GetCancelCacheKey(string key)
{
return String.Format("{0}:crm:queue:exporttocsv:cancel", key);
return string.Format("{0}:crm:queue:exporttocsv:cancel", key);
}
public static ExportDataOperation Get(string key)
@ -76,9 +76,9 @@ namespace ASC.Web.CRM.Classes
public static bool CheckCancelFlag(string key)
{
var fromCache = Cache.Get<String>(GetCancelCacheKey(key));
var fromCache = Cache.Get<string>(GetCancelCacheKey(key));
return !String.IsNullOrEmpty(fromCache);
return !string.IsNullOrEmpty(fromCache);
}
public static void SetCancelFlag(string key)
@ -144,13 +144,7 @@ namespace ASC.Web.CRM.Classes
public override bool Equals(object obj)
{
if (obj == null) return false;
var exportDataOperation = obj as ExportDataOperation;
if (exportDataOperation == null) return false;
return Id == exportDataOperation.Id;
return obj != null && obj is ExportDataOperation exportDataOperation && Id == exportDataOperation.Id;
}
public override int GetHashCode()
@ -168,7 +162,8 @@ namespace ASC.Web.CRM.Classes
Percentage = Percentage,
IsCompleted = IsCompleted,
FileName = FileName,
FileUrl = FileUrl
FileUrl = FileUrl,
CurrentPart = CurrentPart
};
return cloneObj;
@ -190,16 +185,18 @@ namespace ASC.Web.CRM.Classes
public string FileUrl { get; set; }
public string CurrentPart { get; set; }
#endregion
#region Private Methods
private static String WrapDoubleQuote(String value)
private static string WrapDoubleQuote(string value)
{
return "\"" + value.Trim().Replace("\"", "\"\"") + "\"";
}
private static String DataTableToCsv(DataTable dataTable)
private static string DataTableToCsv(DataTable dataTable)
{
var result = new StringBuilder();
@ -294,10 +291,53 @@ namespace ASC.Web.CRM.Classes
ExportDataCache.Insert((string)Id, (ExportDataOperation)Clone());
}
private void ExportAllData(DaoFactory daoFactory)
private void ClearStorage(string pattern)
{
if (_dataStore.IsDirectory("export", string.Empty))
{
_dataStore.DeleteFiles("export", string.Empty, pattern, true);
}
}
private Uri ZipStorageData()
{
using (var stream = TempStream.Create())
{
using (var zipStream = new ZipOutputStream(stream))
{
zipStream.IsStreamOwner = false;
var files = _dataStore.ListFilesRelative("export", string.Empty, "*.csv", true);
foreach (var path in files)
{
var fileName = Path.GetFileName(path);
var zipEntry = GetNewZipEntry(fileName);
zipStream.PutNextEntry(zipEntry);
using (var fileStream = _dataStore.GetReadStream("export", path))
{
fileStream.CopyTo(zipStream);
}
}
zipStream.Finish();
stream.Position = 0;
}
return _dataStore.Save("export", FileName, stream);
}
}
private void ExportAllData(DaoFactory daoFactory)
{
try
{
ClearStorage("*");
var contactDao = daoFactory.ContactDao;
var contactInfoDao = daoFactory.ContactInfoDao;
var dealDao = daoFactory.DealDao;
@ -313,81 +353,174 @@ namespace ASC.Web.CRM.Classes
_totalCount += historyDao.GetAllItemsCount();
_totalCount += invoiceItemDao.GetInvoiceItemsCount();
using (var zipStream = new ZipOutputStream(stream))
{
zipStream.IsStreamOwner = false;
var limit = 10000;
var offset = 0;
zipStream.PutNextEntry(GetNewZipEntry(CRMContactResource.Contacts + ".csv"));
var contactData = contactDao.GetAllContacts();
var contactInfos = new StringDictionary();
contactInfoDao.GetAll()
.ForEach(item =>
{
var contactInfoKey = String.Format("{0}_{1}_{2}", item.ContactID, (int)item.InfoType, item.Category);
if (contactInfos.ContainsKey(contactInfoKey))
{
contactInfos[contactInfoKey] += "," + item.Data;
}
else
{
contactInfos.Add(contactInfoKey, item.Data);
}
});
while (true)
{
var invoiceItemData = invoiceItemDao.GetAll(offset * limit, limit);
if (invoiceItemData.Count == 0)
{
break;
}
CurrentPart = CRMCommonResource.ProductsAndServices + "_" + offset + ".csv";
using (var zipEntryData = new MemoryStream(Encoding.UTF8.GetBytes(ExportInvoiceItemsToCsv(invoiceItemData, daoFactory))))
{
_ = _dataStore.Save("export", CurrentPart, zipEntryData);
}
offset++;
}
offset = 0;
while (true)
{
var casesData = casesDao.GetAllCases(offset * limit, limit);
if (casesData.Count == 0)
{
break;
}
CurrentPart = CRMCommonResource.CasesModuleName + "_" + offset + ".csv";
using (var zipEntryData = new MemoryStream(Encoding.UTF8.GetBytes(ExportCasesToCsv(casesData, daoFactory))))
{
_ = _dataStore.Save("export", CurrentPart, zipEntryData);
}
offset++;
}
offset = 0;
while (true)
{
var taskData = taskDao.GetAllTasks(offset * limit, limit);
if (taskData.Count == 0)
{
break;
}
CurrentPart = CRMCommonResource.TaskModuleName + "_" + offset + ".csv";
using (var zipEntryData = new MemoryStream(Encoding.UTF8.GetBytes(ExportTasksToCsv(taskData, daoFactory))))
{
_ = _dataStore.Save("export", CurrentPart, zipEntryData);
}
offset++;
}
offset = 0;
while (true)
{
var dealData = dealDao.GetAllDeals(offset * limit, limit);
if (dealData.Count == 0)
{
break;
}
CurrentPart = CRMCommonResource.DealModuleName + "_" + offset + ".csv";
using (var zipEntryData = new MemoryStream(Encoding.UTF8.GetBytes(ExportDealsToCsv(dealData, daoFactory))))
{
_ = _dataStore.Save("export", CurrentPart, zipEntryData);
}
offset++;
}
offset = 0;
while (true)
{
var historyData = historyDao.GetAllItems(offset * limit, limit);
if (historyData.Count == 0)
{
break;
}
CurrentPart = CRMCommonResource.History + "_" + offset + ".csv";
using (var zipEntryData = new MemoryStream(Encoding.UTF8.GetBytes(ExportHistoryToCsv(historyData, daoFactory))))
{
_ = _dataStore.Save("export", CurrentPart, zipEntryData);
}
offset++;
}
var contactInfos = new StringDictionary();
offset = 0;
while (true)
{
var contactInfoData = contactInfoDao.GetAll(offset * limit, limit);
if (contactInfoData.Count == 0)
{
break;
}
CurrentPart = "ContactInfo_" + offset;
contactInfoData.ForEach(item =>
{
var contactInfoKey = string.Format("{0}_{1}_{2}", item.ContactID, (int)item.InfoType, item.Category);
if (contactInfos.ContainsKey(contactInfoKey))
{
contactInfos[contactInfoKey] += "," + item.Data;
}
else
{
contactInfos.Add(contactInfoKey, item.Data);
}
});
offset++;
}
offset = 0;
while (true)
{
var contactData = contactDao.GetAllContacts(offset * limit, limit);
if (contactData.Count == 0)
{
break;
}
CurrentPart = CRMContactResource.Contacts + "_" + offset + ".csv";
using (var zipEntryData = new MemoryStream(Encoding.UTF8.GetBytes(ExportContactsToCsv(contactData, contactInfos, daoFactory))))
{
zipEntryData.CopyTo(zipStream);
_ = _dataStore.Save("export", CurrentPart, zipEntryData);
}
zipStream.PutNextEntry(GetNewZipEntry(CRMCommonResource.DealModuleName + ".csv"));
var dealData = dealDao.GetAllDeals();
using (var zipEntryData = new MemoryStream(Encoding.UTF8.GetBytes(ExportDealsToCsv(dealData, daoFactory))))
{
zipEntryData.CopyTo(zipStream);
}
zipStream.PutNextEntry(GetNewZipEntry(CRMCommonResource.CasesModuleName + ".csv"));
var casesData = casesDao.GetAllCases();
using (var zipEntryData = new MemoryStream(Encoding.UTF8.GetBytes(ExportCasesToCsv(casesData, daoFactory))))
{
zipEntryData.CopyTo(zipStream);
}
zipStream.PutNextEntry(GetNewZipEntry(CRMCommonResource.TaskModuleName + ".csv"));
var taskData = taskDao.GetAllTasks();
using (var zipEntryData = new MemoryStream(Encoding.UTF8.GetBytes(ExportTasksToCsv(taskData, daoFactory))))
{
zipEntryData.CopyTo(zipStream);
}
zipStream.PutNextEntry(GetNewZipEntry(CRMCommonResource.History + ".csv"));
var historyData = historyDao.GetAllItems();
using (var zipEntryData = new MemoryStream(Encoding.UTF8.GetBytes(ExportHistoryToCsv(historyData, daoFactory))))
{
zipEntryData.CopyTo(zipStream);
}
zipStream.PutNextEntry(GetNewZipEntry(CRMCommonResource.ProductsAndServices + ".csv"));
var invoiceItemData = invoiceItemDao.GetAll();
using (var zipEntryData = new MemoryStream(Encoding.UTF8.GetBytes(ExportInvoiceItemsToCsv(invoiceItemData, daoFactory))))
{
zipEntryData.CopyTo(zipStream);
}
zipStream.Finish();
stream.Position = 0;
offset++;
}
if (_dataStore.IsDirectory("export", string.Empty))
{
_dataStore.DeleteFiles("export", string.Empty, "*.*", true);
}
CurrentPart = null;
FileUrl = CommonLinkUtility.GetFullAbsolutePath(_dataStore.Save("export", FileName, stream).ToString());
FileUrl = CommonLinkUtility.GetFullAbsolutePath(ZipStorageData().ToString());
_notifyClient.SendAboutExportCompleted(_author.ID, FileName, FileUrl);
}
finally
{
ClearStorage("*.csv");
}
}
private void ExportPartData(DaoFactory daoFactory)
@ -401,18 +534,16 @@ namespace ASC.Web.CRM.Classes
if (_totalCount == 0)
throw new ArgumentException(CRMErrorsResource.ExportToCSVDataEmpty);
if (items is List<Contact>)
if (items is List<Contact> contacts)
{
var contactInfoDao = daoFactory.ContactInfoDao;
var contacts = (List<Contact>)items;
var contactInfos = new StringDictionary();
contactInfoDao.GetAll(contacts.Select(item => item.ID).ToArray())
.ForEach(item =>
{
var contactInfoKey = String.Format("{0}_{1}_{2}", item.ContactID,
var contactInfoKey = string.Format("{0}_{1}_{2}", item.ContactID,
(int)item.InfoType,
item.Category);
@ -424,33 +555,34 @@ namespace ASC.Web.CRM.Classes
fileContent = ExportContactsToCsv(contacts, contactInfos, daoFactory);
}
else if (items is List<Deal>)
else if (items is List<Deal> deals)
{
fileContent = ExportDealsToCsv((List<Deal>)items, daoFactory);
fileContent = ExportDealsToCsv(deals, daoFactory);
}
else if (items is List<ASC.CRM.Core.Entities.Cases>)
else if (items is List<ASC.CRM.Core.Entities.Cases> cases)
{
fileContent = ExportCasesToCsv((List<ASC.CRM.Core.Entities.Cases>)items, daoFactory);
fileContent = ExportCasesToCsv(cases, daoFactory);
}
else if (items is List<RelationshipEvent>)
else if (items is List<RelationshipEvent> events)
{
fileContent = ExportHistoryToCsv((List<RelationshipEvent>)items, daoFactory);
fileContent = ExportHistoryToCsv(events, daoFactory);
}
else if (items is List<Task>)
else if (items is List<Task> tasks)
{
fileContent = ExportTasksToCsv((List<Task>)items, daoFactory);
fileContent = ExportTasksToCsv(tasks, daoFactory);
}
else if (items is List<InvoiceItem>)
else if (items is List<InvoiceItem> invoices)
{
fileContent = ExportInvoiceItemsToCsv((List<InvoiceItem>)items, daoFactory);
fileContent = ExportInvoiceItemsToCsv(invoices, daoFactory);
}
else
{
throw new ArgumentException();
}
FileUrl = SaveCsvFileInMyDocument(FileName, fileContent);
}
private String ExportContactsToCsv(IReadOnlyCollection<Contact> contacts, StringDictionary contactInfos, DaoFactory daoFactory)
private string ExportContactsToCsv(IReadOnlyCollection<Contact> contacts, StringDictionary contactInfos, DaoFactory daoFactory)
{
var key = (string)Id;
var listItemDao = daoFactory.ListItemDao;
@ -512,21 +644,21 @@ namespace ASC.Web.CRM.Classes
foreach (ContactInfoType infoTypeEnum in Enum.GetValues(typeof(ContactInfoType)))
foreach (Enum categoryEnum in Enum.GetValues(ContactInfo.GetCategory(infoTypeEnum)))
{
var localTitle = String.Format("{1} ({0})", categoryEnum.ToLocalizedString().ToLower(), infoTypeEnum.ToLocalizedString());
var localTitle = string.Format("{1} ({0})", categoryEnum.ToLocalizedString().ToLower(), infoTypeEnum.ToLocalizedString());
if (infoTypeEnum == ContactInfoType.Address)
dataTable.Columns.AddRange((from AddressPart addressPartEnum in Enum.GetValues(typeof(AddressPart))
select new DataColumn
{
Caption = String.Format(localTitle + " {0}", addressPartEnum.ToLocalizedString().ToLower()),
ColumnName = String.Format("contactInfo_{0}_{1}_{2}", (int)infoTypeEnum, categoryEnum, (int)addressPartEnum)
Caption = string.Format(localTitle + " {0}", addressPartEnum.ToLocalizedString().ToLower()),
ColumnName = string.Format("contactInfo_{0}_{1}_{2}", (int)infoTypeEnum, categoryEnum, (int)addressPartEnum)
}).ToArray());
else
dataTable.Columns.Add(new DataColumn
{
Caption = localTitle,
ColumnName = String.Format("contactInfo_{0}_{1}", (int)infoTypeEnum, categoryEnum)
ColumnName = string.Format("contactInfo_{0}_{1}", (int)infoTypeEnum, categoryEnum)
});
}
@ -581,22 +713,22 @@ namespace ASC.Web.CRM.Classes
var compPersType = (isCompany) ? CRMContactResource.Company : CRMContactResource.Person;
var contactTags = String.Empty;
var contactTags = string.Empty;
if (tags.ContainsKey(contact.ID))
contactTags = String.Join(",", tags[contact.ID].OrderBy(x => x));
contactTags = string.Join(",", tags[contact.ID].OrderBy(x => x));
String firstName;
String lastName;
string firstName;
string lastName;
String companyName;
String title;
string companyName;
string title;
if (contact is Company)
{
firstName = String.Empty;
lastName = String.Empty;
title = String.Empty;
firstName = string.Empty;
lastName = string.Empty;
title = string.Empty;
companyName = ((Company)contact).CompanyName;
}
else
@ -607,7 +739,7 @@ namespace ASC.Web.CRM.Classes
lastName = people.LastName;
title = people.JobTitle;
companyName = String.Empty;
companyName = string.Empty;
if (people.CompanyID > 0)
{
@ -619,7 +751,7 @@ namespace ASC.Web.CRM.Classes
}
}
var contactStatus = String.Empty;
var contactStatus = string.Empty;
if (contact.StatusID > 0)
{
@ -630,7 +762,7 @@ namespace ASC.Web.CRM.Classes
contactStatus = listItem.Title;
}
var contactType = String.Empty;
var contactType = string.Empty;
if (contact.ContactTypeID > 0)
{
@ -641,7 +773,7 @@ namespace ASC.Web.CRM.Classes
contactType = listItem.Title;
}
var dataRowItems = new List<String>
var dataRowItems = new List<string>
{
compPersType,
firstName,
@ -657,7 +789,7 @@ namespace ASC.Web.CRM.Classes
foreach (ContactInfoType infoTypeEnum in Enum.GetValues(typeof(ContactInfoType)))
foreach (Enum categoryEnum in Enum.GetValues(ContactInfo.GetCategory(infoTypeEnum)))
{
var contactInfoKey = String.Format("{0}_{1}_{2}", contact.ID,
var contactInfoKey = string.Format("{0}_{1}_{2}", contact.ID,
(int)infoTypeEnum,
Convert.ToInt32(categoryEnum));
@ -668,12 +800,12 @@ namespace ASC.Web.CRM.Classes
if (infoTypeEnum == ContactInfoType.Address)
{
if (!String.IsNullOrEmpty(columnValue))
if (!string.IsNullOrEmpty(columnValue))
{
var addresses = JArray.Parse(String.Concat("[", columnValue, "]"));
var addresses = JArray.Parse(string.Concat("[", columnValue, "]"));
dataRowItems.AddRange((from AddressPart addressPartEnum in Enum.GetValues(typeof(AddressPart))
select String.Join(",", addresses.Select(item => (String)item.SelectToken(addressPartEnum.ToString().ToLower())).ToArray())).ToArray());
select string.Join(",", addresses.Select(item => (string)item.SelectToken(addressPartEnum.ToString().ToLower())).ToArray())).ToArray());
}
else
{
@ -695,7 +827,7 @@ namespace ASC.Web.CRM.Classes
return DataTableToCsv(dataTable);
}
private String ExportDealsToCsv(IEnumerable<Deal> deals, DaoFactory daoFactory)
private string ExportDealsToCsv(IEnumerable<Deal> deals, DaoFactory daoFactory)
{
var key = (string)Id;
var tagDao = daoFactory.TagDao;
@ -815,12 +947,12 @@ namespace ASC.Web.CRM.Classes
Percentage += 1.0 * 100 / _totalCount;
var contactTags = String.Empty;
var contactTags = string.Empty;
if (tags.ContainsKey(deal.ID))
contactTags = String.Join(",", tags[deal.ID].OrderBy(x => x));
contactTags = string.Join(",", tags[deal.ID].OrderBy(x => x));
String bidType;
string bidType;
switch (deal.BidType)
{
@ -848,7 +980,7 @@ namespace ASC.Web.CRM.Classes
var currentDealMilestone = dealMilestoneDao.GetByID(deal.DealMilestoneID);
var currentDealMilestoneStatus = currentDealMilestone.Status.ToLocalizedString();
var contactTitle = String.Empty;
var contactTitle = string.Empty;
if (deal.ContactID != 0)
contactTitle = contactDao.GetByID(deal.ContactID).GetTitle();
@ -887,7 +1019,7 @@ namespace ASC.Web.CRM.Classes
return DataTableToCsv(dataTable);
}
private String ExportCasesToCsv(IEnumerable<ASC.CRM.Core.Entities.Cases> cases, DaoFactory daoFactory)
private string ExportCasesToCsv(IEnumerable<ASC.CRM.Core.Entities.Cases> cases, DaoFactory daoFactory)
{
var key = (string)Id;
var tagDao = daoFactory.TagDao;
@ -941,10 +1073,10 @@ namespace ASC.Web.CRM.Classes
Percentage += 1.0 * 100 / _totalCount;
var contactTags = String.Empty;
var contactTags = string.Empty;
if (tags.ContainsKey(item.ID))
contactTags = String.Join(",", tags[item.ID].OrderBy(x => x));
contactTags = string.Join(",", tags[item.ID].OrderBy(x => x));
var dataRow = dataTable.Rows.Add(new object[]
{
@ -959,7 +1091,7 @@ namespace ASC.Web.CRM.Classes
return DataTableToCsv(dataTable);
}
private String ExportHistoryToCsv(IEnumerable<RelationshipEvent> events, DaoFactory daoFactory)
private string ExportHistoryToCsv(IEnumerable<RelationshipEvent> events, DaoFactory daoFactory)
{
var key = (string)Id;
var listItemDao = daoFactory.ListItemDao;
@ -1003,6 +1135,8 @@ namespace ASC.Web.CRM.Classes
}
});
var categories = new Dictionary<int, string>();
foreach (var item in events)
{
if (ExportDataCache.CheckCancelFlag(key))
@ -1016,7 +1150,7 @@ namespace ASC.Web.CRM.Classes
Percentage += 1.0 * 100 / _totalCount;
var entityTitle = String.Empty;
var entityTitle = string.Empty;
if (item.EntityID > 0)
switch (item.EntityType)
@ -1025,19 +1159,19 @@ namespace ASC.Web.CRM.Classes
var casesObj = casesDao.GetByID(item.EntityID);
if (casesObj != null)
entityTitle = String.Format("{0}: {1}", CRMCasesResource.Case,
entityTitle = string.Format("{0}: {1}", CRMCasesResource.Case,
casesObj.Title);
break;
case EntityType.Opportunity:
var dealObj = dealDao.GetByID(item.EntityID);
if (dealObj != null)
entityTitle = String.Format("{0}: {1}", CRMDealResource.Deal,
entityTitle = string.Format("{0}: {1}", CRMDealResource.Deal,
dealObj.Title);
break;
}
var contactTitle = String.Empty;
var contactTitle = string.Empty;
if (item.ContactID > 0)
{
@ -1047,15 +1181,24 @@ namespace ASC.Web.CRM.Classes
contactTitle = contactObj.GetTitle();
}
var categoryTitle = String.Empty;
var categoryTitle = string.Empty;
if (item.CategoryID > 0)
{
var categoryObj = listItemDao.GetByID(item.CategoryID);
if (categoryObj != null)
categoryTitle = categoryObj.Title;
if (categories.ContainsKey(item.CategoryID))
{
categoryTitle = categories[item.CategoryID];
}
else
{
var categoryObj = listItemDao.GetByID(item.CategoryID);
if (categoryObj != null)
{
categories.Add(item.CategoryID, categoryObj.Title);
categoryTitle = categoryObj.Title;
}
}
}
else if (item.CategoryID == (int)HistoryCategorySystem.TaskClosed)
categoryTitle = HistoryCategorySystem.TaskClosed.ToLocalizedString();
@ -1078,7 +1221,7 @@ namespace ASC.Web.CRM.Classes
return DataTableToCsv(dataTable);
}
private String ExportTasksToCsv(IEnumerable<Task> tasks, DaoFactory daoFactory)
private string ExportTasksToCsv(IEnumerable<Task> tasks, DaoFactory daoFactory)
{
var key = (string)Id;
var listItemDao = daoFactory.ListItemDao;
@ -1137,6 +1280,8 @@ namespace ASC.Web.CRM.Classes
}
});
var categories = new Dictionary<int, string>();
foreach (var item in tasks)
{
if (ExportDataCache.CheckCancelFlag(key))
@ -1150,7 +1295,7 @@ namespace ASC.Web.CRM.Classes
Percentage += 1.0 * 100 / _totalCount;
var entityTitle = String.Empty;
var entityTitle = string.Empty;
if (item.EntityID > 0)
switch (item.EntityType)
@ -1159,17 +1304,17 @@ namespace ASC.Web.CRM.Classes
var caseObj = casesDao.GetByID(item.EntityID);
if (caseObj != null)
entityTitle = String.Format("{0}: {1}", CRMCasesResource.Case, caseObj.Title);
entityTitle = string.Format("{0}: {1}", CRMCasesResource.Case, caseObj.Title);
break;
case EntityType.Opportunity:
var dealObj = dealDao.GetByID(item.EntityID);
if (dealObj != null)
entityTitle = String.Format("{0}: {1}", CRMDealResource.Deal, dealObj.Title);
entityTitle = string.Format("{0}: {1}", CRMDealResource.Deal, dealObj.Title);
break;
}
var contactTitle = String.Empty;
var contactTitle = string.Empty;
if (item.ContactID > 0)
{
@ -1179,6 +1324,23 @@ namespace ASC.Web.CRM.Classes
contactTitle = contact.GetTitle();
}
var category = string.Empty;
if (categories.ContainsKey(item.CategoryID))
{
category = categories[item.CategoryID];
}
else
{
var categoryObj = listItemDao.GetByID(item.CategoryID);
if (categoryObj != null)
{
categories.Add(item.CategoryID, categoryObj.Title);
category = categoryObj.Title;
}
}
dataTable.Rows.Add(new object[]
{
item.Title,
@ -1191,7 +1353,7 @@ namespace ASC.Web.CRM.Classes
item.IsClosed
? CRMTaskResource.TaskStatus_Closed
: CRMTaskResource.TaskStatus_Open,
listItemDao.GetByID(item.CategoryID).Title,
category,
entityTitle,
item.AlertValue.ToString(CultureInfo.InvariantCulture)
});
@ -1200,7 +1362,7 @@ namespace ASC.Web.CRM.Classes
return DataTableToCsv(dataTable);
}
private String ExportInvoiceItemsToCsv(IEnumerable<InvoiceItem> invoiceItems, DaoFactory daoFactory)
private string ExportInvoiceItemsToCsv(IEnumerable<InvoiceItem> invoiceItems, DaoFactory daoFactory)
{
var key = (string)Id;
var taxes = daoFactory.InvoiceTaxDao.GetAll();
@ -1303,7 +1465,7 @@ namespace ASC.Web.CRM.Classes
return DataTableToCsv(dataTable);
}
private static String SaveCsvFileInMyDocument(String title, String data)
private static string SaveCsvFileInMyDocument(string title, string data)
{
string fileUrl;
@ -1407,7 +1569,7 @@ namespace ASC.Web.CRM.Classes
partialDataExport ? SecurityContext.CurrentAccount.ID : Guid.Empty);
}
public static String ExportItems(FilterObject filterObject, string fileName)
public static string ExportItems(FilterObject filterObject, string fileName)
{
var operation = new ExportDataOperation(filterObject, fileName);

View File

@ -144,7 +144,7 @@ namespace ASC.Web.CRM.Classes
var revisionId = DocumentServiceConnector.GenerateRevisionId(Guid.NewGuid().ToString());
string urlToFile;
DocumentServiceConnector.GetConvertedUri(externalUri, FormatDocx, FormatPdf, revisionId, null, null, null, null, false, out urlToFile, out _);
DocumentServiceConnector.GetConvertedUri(externalUri, FormatDocx, FormatPdf, revisionId, null, null, null, null, false, false, out urlToFile, out _);
Log.DebugFormat("PdfCreator. GetUrlToFile. urlToFile = {0}", urlToFile);
return urlToFile;
@ -159,7 +159,7 @@ namespace ASC.Web.CRM.Classes
var revisionId = DocumentServiceConnector.GenerateRevisionId(Guid.NewGuid().ToString());
string urlToFile;
DocumentServiceConnector.GetConvertedUri(externalUri, FormatDocx, FormatPdf, revisionId, null, null, null, null, true, out urlToFile, out _);
DocumentServiceConnector.GetConvertedUri(externalUri, FormatDocx, FormatPdf, revisionId, null, null, null, null, false, true, out urlToFile, out _);
return new ConverterData
{
@ -178,7 +178,7 @@ namespace ASC.Web.CRM.Classes
}
string urlToFile;
DocumentServiceConnector.GetConvertedUri(data.StorageUrl, FormatDocx, FormatPdf, data.RevisionId, null, null, null, null, true, out urlToFile, out _);
DocumentServiceConnector.GetConvertedUri(data.StorageUrl, FormatDocx, FormatPdf, data.RevisionId, null, null, null, null, false, true, out urlToFile, out _);
if (string.IsNullOrEmpty(urlToFile))
{

View File

@ -127,6 +127,8 @@ namespace ASC.Forum
public class ForumDataProvider
{
private static readonly object syncRoot = new object();
internal static IDbManager DbManager
{
get
@ -526,23 +528,33 @@ namespace ASC.Forum
public static void PollVote(int tenantID, int pollID, List<int> variantIDs)
{
using (var tr = DbManager.Connection.BeginTransaction())
lock (syncRoot)
{
var answerID = DbManager.ExecuteScalar<int>(new SqlInsert("forum_answer")
.InColumnValue("id", 0)
.InColumnValue("TenantID", tenantID)
.InColumnValue("user_id", SecurityContext.CurrentAccount.ID)
.InColumnValue("create_date", DateTime.UtcNow)
.InColumnValue("question_id", pollID)
.Identity(0, 0, true));
foreach (var vid in variantIDs)
using (var tr = DbManager.Connection.BeginTransaction())
{
DbManager.ExecuteNonQuery(new SqlInsert("forum_answer_variant").InColumns("answer_id", "variant_id")
.Values(answerID, vid));
}
var answerID = DbManager.ExecuteScalar<int>(new SqlQuery("forum_answer").Select("id")
.Where("TenantID", tenantID)
.Where("user_id", SecurityContext.CurrentAccount.ID)
.Where("question_id", pollID));
tr.Commit();
if (answerID > 0) return;
answerID = DbManager.ExecuteScalar<int>(new SqlInsert("forum_answer")
.InColumnValue("id", 0)
.InColumnValue("TenantID", tenantID)
.InColumnValue("user_id", SecurityContext.CurrentAccount.ID)
.InColumnValue("create_date", DateTime.UtcNow)
.InColumnValue("question_id", pollID)
.Identity(0, 0, true));
foreach (var vid in variantIDs)
{
DbManager.ExecuteNonQuery(new SqlInsert("forum_answer_variant").InColumns("answer_id", "variant_id")
.Values(answerID, vid));
}
tr.Commit();
}
}
}

View File

@ -28,6 +28,7 @@ using AjaxPro;
using ASC.Core;
using ASC.Forum;
using ASC.Web.Community.Modules.Forum.UserControls.Resources;
using ASC.Web.Core;
using ASC.Web.Studio.Controls.Common;
using ASC.Web.Studio.UserControls.Common.PollForm;
using ASC.Web.Studio.Utility;
@ -41,6 +42,20 @@ namespace ASC.Web.UserControls.Forum
public bool VoteCallback(string pollID, List<string> selectedVariantIDs, string additionalParams, out string errorMessage)
{
var product = WebItemManager.Instance[WebItemManager.CommunityProductID];
if (product == null || product.IsDisabled())
{
errorMessage = ForumUCResource.ErrorAccessDenied;
return false;
}
var module = WebItemManager.Instance[Community.Forum.ForumManager.ModuleID];
if (module == null || module.IsDisabled())
{
errorMessage = ForumUCResource.ErrorAccessDenied;
return false;
}
errorMessage = "";
int idQuestion = Convert.ToInt32(additionalParams.Split(',')[1]);
var _forumManager = Community.Forum.ForumManager.Settings.ForumManager;

View File

@ -20,8 +20,9 @@ using System.Collections.Generic;
using System.Linq;
using ASC.Core;
using ASC.Web.Community.Modules.News.Resources;
using ASC.Web.Community.Modules.Forum.UserControls.Resources;
using ASC.Web.Community.News.Code.DAO;
using ASC.Web.Core;
using ASC.Web.Studio.UserControls.Common.PollForm;
namespace ASC.Web.Community.News.Code
@ -32,6 +33,20 @@ namespace ASC.Web.Community.News.Code
public bool VoteCallback(string pollID, List<string> selectedVariantIDs, string additionalParams, out string errorMessage)
{
var product = WebItemManager.Instance[WebItemManager.CommunityProductID];
if (product == null || product.IsDisabled())
{
errorMessage = ForumUCResource.ErrorAccessDenied;
return false;
}
var module = WebItemManager.Instance[NewsModule.ModuleId];
if (module == null || module.IsDisabled())
{
errorMessage = ForumUCResource.ErrorAccessDenied;
return false;
}
errorMessage = string.Empty;
var userAnswersIDs = new List<long>();
selectedVariantIDs.ForEach(strId => { if (!string.IsNullOrEmpty(strId)) userAnswersIDs.Add(Convert.ToInt64(strId)); });

View File

@ -26,9 +26,14 @@ label .checkbox {
.select-action span,
.select-action div {
cursor: default !important;
}
.popup-modal {
display: none;
}
body.select-action .mainPageContent{
position: relative;
}
.popup-modal {
display: none;
}
body .may-row-to,
body .may-drop-to {

View File

@ -1221,11 +1221,13 @@ window.ASC.Files.ChunkUploads = (function () {
entryId: file.data.id
}, false, copyFileAs);
}
var copyFileAs = function (params) {
Teamlab.copyDocFileAs(null, params.templateId,
{
destFolderId: params.folderID,
destTitle: params.fileTitle
destTitle: params.fileTitle,
toForm: true
},
{
success: function (_, data) {

View File

@ -27,9 +27,9 @@ window.ASC.Files.Converter = (function () {
/* Methods*/
var checkCanOpenEditor = function (fileId, fileTitle, version, forEdit) {
if (!ASC.Files.Utility.MustConvert(fileTitle) || forEdit == false) {
if (forEdit == false || !ASC.Files.Utility.CanWebEdit(fileTitle)) {
var checkCanOpenEditor = function (fileId, fileTitle, version, forEdit, forFill) {
if (!ASC.Files.Utility.MustConvert(fileTitle) || (forEdit || forFill) == false) {
if ((forEdit || forFill) == false || !ASC.Files.Utility.CanWebEdit(fileTitle)) {
var result = ASC.Files.Actions.checkViewFile(fileId, forEdit ? null : version);
ASC.Files.UI.updateMainContentHeader();
@ -43,7 +43,7 @@ window.ASC.Files.Converter = (function () {
ASC.Files.Folders.showVersions(fileObj);
}
result = ASC.Files.Actions.checkEditFile(fileId);
result = ASC.Files.Actions.checkEditFile(fileId, undefined, forFill);
ASC.Files.UI.updateMainContentHeader();
return result;
@ -200,7 +200,7 @@ window.ASC.Files.Converter = (function () {
};
var showToConvert = function (selectedElements) {
selectedElements = selectedElements || jq("#filesMainContent .file-row:has(.checkbox input:checked)");
selectedElements = selectedElements || jq("#filesMainContent .file-row:not(.error-entry):has(.checkbox input:checked)");
var selectedFiles = {
documents: [],

View File

@ -107,7 +107,8 @@ window.FileChoisePopup = (function () {
Teamlab.copyDocFileAs(null, params.templateId,
{
destFolderId: params.folderID,
destTitle: params.fileTitle
destTitle: params.fileTitle,
toForm: true
},
{
success: function (_, data) {

View File

@ -247,6 +247,8 @@ namespace ASC.Files.Core.Data
private void SelectFilesAndFoldersForShare(FileEntry entry, ICollection<string> files, ICollection<string> folders, ICollection<int> foldersInt)
{
if (!string.IsNullOrEmpty(entry.Error)) return;
object folderId;
if (entry.FileEntryType == FileEntryType.File)
{

View File

@ -117,6 +117,11 @@ namespace ASC.Web.Files
get { return (Request[FilesLinkUtility.Action] ?? "").Equals("view", StringComparison.InvariantCultureIgnoreCase); }
}
private bool RequestFill
{
get { return (Request[FilesLinkUtility.Action] ?? "").Equals("fill", StringComparison.InvariantCultureIgnoreCase); }
}
private int RequestVersion
{
get { return string.IsNullOrEmpty(Request[FilesLinkUtility.Version]) ? -1 : Convert.ToInt32(Request[FilesLinkUtility.Version]); }
@ -204,15 +209,14 @@ namespace ASC.Web.Files
Response.AppendHeader("Pragma", "no-cache");
Response.AppendHeader("Expires", "0");
DocServiceApiUrl += (DocServiceApiUrl.Contains("?") ? "&" : "?") + "ver=" + HttpUtility.UrlEncode(ClientSettings.ResetCacheKey + ResetCacheKey);
DocServiceApiUrl = FilesLinkUtility.AddQueryString(DocServiceApiUrl, new Dictionary<string, string>() {
{ FilesLinkUtility.VersionShort, ClientSettings.ResetCacheKey + ResetCacheKey },
{ FilesLinkUtility.ShardKey, _configuration?.Document?.Key }
});
if (_configuration != null && !string.IsNullOrEmpty(_configuration.DocumentType))
{
var ext = FileUtility.GetFileExtension(_configuration.Document.Info.File.Title);
var filename = Services.DocumentService.Configuration.PdfType.Contains(ext) ? "pdf" : _configuration.DocumentType;
Favicon = WebImageSupplier.GetAbsoluteWebPath("logo/" + filename + ".ico");
Favicon = WebImageSupplier.GetAbsoluteWebPath("logo/" + _configuration.DocumentType + ".ico");
}
}
@ -231,7 +235,7 @@ namespace ASC.Web.Files
var app = ThirdPartySelector.GetAppByFileId(RequestFileId);
if (app == null)
{
file = DocumentServiceHelper.GetParams(RequestFileId, RequestVersion, RequestShareLinkKey, editPossible, !RequestView, true, out _configuration);
file = DocumentServiceHelper.GetParams(RequestFileId, RequestVersion, RequestShareLinkKey, editPossible, !RequestView, RequestFill, true, out _configuration);
if (_valideShareLink)
{
_configuration.Document.SharedLinkKey += RequestShareLinkKey;
@ -253,7 +257,7 @@ namespace ASC.Web.Files
bool editable;
_thirdPartyApp = true;
file = app.GetFile(RequestFileId, out editable);
file = DocumentServiceHelper.GetParams(file, true, editPossible ? FileShare.ReadWrite : FileShare.Read, false, editable, editable, editable, true, out _configuration);
file = DocumentServiceHelper.GetParams(file, true, editPossible ? FileShare.ReadWrite : FileShare.Read, false, editable, editable, editable, editable, true, out _configuration);
_configuration.Document.Url = app.GetFileStreamUrl(file);
_configuration.EditorConfig.Customization.GobackUrl = string.Empty;
@ -275,7 +279,7 @@ namespace ASC.Web.Files
Title = Global.ReplaceInvalidCharsAndTruncate(fileTitle)
};
file = DocumentServiceHelper.GetParams(file, true, FileShare.Read, false, false, false, false, false, out _configuration);
file = DocumentServiceHelper.GetParams(file, true, FileShare.Read, false, false, false, false, false, false, out _configuration);
_configuration.Document.Permissions.Edit = editPossible && !CoreContext.Configuration.Standalone;
_configuration.Document.Permissions.Rename = false;
_configuration.Document.Permissions.Review = false;
@ -353,7 +357,7 @@ namespace ASC.Web.Files
? string.Empty
: "#message/" + HttpUtility.UrlEncode(string.Format(FilesCommonResource.MessageFillFormDraftCreated, folderIfNew.Title));
Response.Redirect(FilesLinkUtility.GetFileWebEditorUrl(file.ID) + comment);
Response.Redirect(FilesLinkUtility.GetFileWebFillUrl(file.ID) + comment);
return;
}
else if (!EntryManager.CheckFillFormDraft(file))
@ -464,6 +468,10 @@ namespace ASC.Web.Files
{
_configuration.EditorConfig.FileChoiceUrl = CommonLinkUtility.GetFullAbsolutePath(FileChoice.GetUrlForEditor);
}
if (RequestFill)
{
_linkToEdit = CommonLinkUtility.GetFullAbsolutePath(FilesLinkUtility.GetFileWebEditorUrl(file.ID));
}
}
else
{

Some files were not shown because too many files have changed in this diff Show More