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:
parent
678eedf95f
commit
d59e563e8c
85
CHANGELOG.md
85
CHANGELOG.md
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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');
|
||||
|
@ -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`),
|
||||
|
19
build/sql/onlyoffice.upgradev127.sql
Normal file
19
build/sql/onlyoffice.upgradev127.sql
Normal 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 ;
|
@ -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" />
|
||||
|
@ -57,6 +57,9 @@ namespace ASC.Core.Common.Contracts
|
||||
|
||||
[DataMember]
|
||||
public Dictionary<string, string> StorageParams { get; set; }
|
||||
|
||||
[DataMember]
|
||||
public bool Dump { get; set; }
|
||||
}
|
||||
|
||||
[DataContract]
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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 });
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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.");
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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>
|
@ -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)));
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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])
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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"}}
|
||||
};
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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>();
|
||||
}
|
||||
}
|
||||
|
@ -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" />
|
||||
|
@ -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)));
|
||||
|
@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
@ -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";
|
||||
}
|
||||
}
|
@ -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];
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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.
|
||||
|
23
common/ASC.Data.Storage/IDataStoreValidator.cs
Normal file
23
common/ASC.Data.Storage/IDataStoreValidator.cs
Normal 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);
|
||||
}
|
||||
}
|
@ -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)))
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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")))
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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)));
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -37,7 +37,8 @@ namespace ASC.Api.Migration
|
||||
{
|
||||
/// <summary>
|
||||
/// Migration API.
|
||||
/// </summary>
|
||||
/// </summary>
|
||||
/// <name>migration</name>
|
||||
public class MigrationApi : Interfaces.IApiEntryPoint
|
||||
{
|
||||
/// <summary>
|
||||
|
@ -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);
|
||||
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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");
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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];
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
@ -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" />
|
||||
|
@ -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"))
|
||||
};
|
||||
|
@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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 = ?",
|
||||
};
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
};
|
@ -3,6 +3,7 @@
|
||||
"core.machinekey": "1123askdasjklasbnd",
|
||||
"sql": {
|
||||
"host": "127.0.0.1",
|
||||
"port": 3306,
|
||||
"user": "root",
|
||||
"password": "111111",
|
||||
"database": "onlyoffice"
|
||||
|
@ -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" />
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
59
web/core/ASC.Web.Core/WebItemStoreValidator.cs
Normal file
59
web/core/ASC.Web.Core/WebItemStoreValidator.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -70,6 +70,7 @@ namespace ASC.Web.Studio.Masters.MasterResources
|
||||
FilesLinkUtility.FileDownloadUrlString,
|
||||
FilesLinkUtility.FileViewUrlString,
|
||||
FilesLinkUtility.FileWebViewerUrlString,
|
||||
FilesLinkUtility.FileWebFillingUrlString,
|
||||
FilesLinkUtility.FileWebViewerExternalUrlString,
|
||||
FilesLinkUtility.FileWebEditorUrlString,
|
||||
FilesLinkUtility.FileWebEditorExternalUrlString,
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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));
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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))
|
||||
{
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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)); });
|
||||
|
@ -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 {
|
||||
|
@ -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) {
|
||||
|
@ -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: [],
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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
Loading…
x
Reference in New Issue
Block a user