diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 04e915b3b32..a8aa0331c6d 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -8,8 +8,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v1
- - uses: actions/setup-python@v1
+ - uses: actions/checkout@v3
+ - uses: actions/setup-python@v4
with:
python-version: "3.8"
diff --git a/frontend/src/components/dialog/repo-office-suite-dialog.js b/frontend/src/components/dialog/repo-office-suite-dialog.js
new file mode 100644
index 00000000000..74aeaa2f7a7
--- /dev/null
+++ b/frontend/src/components/dialog/repo-office-suite-dialog.js
@@ -0,0 +1,110 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { Button, Modal, ModalBody, ModalFooter, TabContent, TabPane, ModalHeader} from 'reactstrap';
+import makeAnimated from 'react-select/animated';
+import { userAPI } from '../../utils/user-api';
+import { gettext, isPro } from '../../utils/constants';
+import { Utils } from '../../utils/utils';
+import toaster from '../toast';
+import { SeahubSelect } from '../common/select';
+import '../../css/repo-office-suite-dialog.css';
+
+const propTypes = {
+ itemName: PropTypes.string.isRequired,
+ toggleDialog: PropTypes.func.isRequired,
+ submit: PropTypes.func.isRequired,
+ repoID: PropTypes.string.isRequired,
+};
+
+class OfficeSuiteDialog extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ selectedOption: null,
+ errorMsg: []
+ };
+ this.options = [];
+ }
+
+ handleSelectChange = (option) => {
+ this.setState({ selectedOption: option });
+ };
+
+ submit = () => {
+ let suite_id = this.state.selectedOption.value;
+ this.props.submit(suite_id);
+ };
+
+ componentDidMount() {
+ if (isPro) {
+ userAPI.getOfficeSuite(this.props.repoID).then((res) => {
+ this.updateOptions(res);
+ }).catch(error => {
+ let errMessage = Utils.getErrorMsg(error);
+ toaster.danger(errMessage);
+ });
+ }
+ }
+
+ updateOptions = (officeSuites) => {
+ officeSuites.data.suites_info.forEach(item => {
+ let option = {
+ value: item.id,
+ label: item.name,
+ is_selected: item.is_selected,
+ };
+ this.options.push(option);
+ });
+ let selectedOption = this.options.find(op => op.is_selected);
+ this.setState({ selectedOption });
+ };
+
+
+ renderOfficeSuiteContent = () => {
+ return (
+
+
+ {isPro &&
+
+
+
+ }
+
+
+ );
+ };
+
+ render() {
+ const { itemName: repoName } = this.props;
+ let title = gettext('{library_name} Office Suite');
+ title = title.replace('{library_name}', '' + Utils.HTMLescape(repoName) + '');
+ return (
+
+
+
+
+
+ {this.renderOfficeSuiteContent()}
+
+
+
+
+
+
+ );
+ }
+}
+
+OfficeSuiteDialog.propTypes = propTypes;
+
+export default OfficeSuiteDialog;
diff --git a/frontend/src/css/repo-office-suite-dialog.css b/frontend/src/css/repo-office-suite-dialog.css
new file mode 100644
index 00000000000..ad94b441c84
--- /dev/null
+++ b/frontend/src/css/repo-office-suite-dialog.css
@@ -0,0 +1,28 @@
+.repo-office-suite-dialog .repo-office-suite-dialog-content {
+ padding: 0;
+ min-height: 5.5rem;
+ min-width: 10rem;
+ display: flex;
+ flex-direction: column;
+}
+
+@media (min-width: 268px) {
+ .repo-office-suite-dialog .repo-office-suite-dialog-content {
+ flex-direction: column;
+ }
+}
+
+.repo-office-suite-dialog-content .repo-office-suite-dialog-main {
+ display: flex;
+ flex-basis: 48%;
+ padding: 1rem;
+}
+
+.repo-office-suite-dialog-content .repo-office-suite-dialog-main .tab-content {
+ flex: 1;
+}
+
+
+.repo-office-suite-dialog-content .repo-office-suite-dialog-main .repo-select-office-suite {
+ padding: 8px 0;
+}
\ No newline at end of file
diff --git a/frontend/src/models/repo-info.js b/frontend/src/models/repo-info.js
index a62570a1d08..9073456aa2c 100644
--- a/frontend/src/models/repo-info.js
+++ b/frontend/src/models/repo-info.js
@@ -21,6 +21,7 @@ class RepoInfo {
this.lib_need_decrypt = object.lib_need_decrypt;
this.last_modified= object.last_modified;
this.status = object.status;
+ this.enable_onlyoffice = object.enable_onlyoffice;
}
}
diff --git a/frontend/src/pages/my-libs/mylib-repo-list-item.js b/frontend/src/pages/my-libs/mylib-repo-list-item.js
index 1198be5bc0c..bcd05354968 100644
--- a/frontend/src/pages/my-libs/mylib-repo-list-item.js
+++ b/frontend/src/pages/my-libs/mylib-repo-list-item.js
@@ -22,7 +22,9 @@ import RepoAPITokenDialog from '../../components/dialog/repo-api-token-dialog';
import RepoSeaTableIntegrationDialog from '../../components/dialog/repo-seatable-integration-dialog';
import RepoShareAdminDialog from '../../components/dialog/repo-share-admin-dialog';
import LibOldFilesAutoDelDialog from '../../components/dialog/lib-old-files-auto-del-dialog';
+import OfficeSuiteDialog from '../../components/dialog/repo-office-suite-dialog';
import RepoMonitoredIcon from '../../components/repo-monitored-icon';
+import { userAPI } from '../../utils/user-api';
const propTypes = {
repo: PropTypes.object.isRequired,
@@ -57,6 +59,7 @@ class MylibRepoListItem extends React.Component {
isRepoShareAdminDialogOpen: false,
isRepoDeleted: false,
isOldFilesAutoDelDialogOpen: false,
+ isOfficeSuiteDialogShow: false,
};
}
@@ -137,6 +140,9 @@ class MylibRepoListItem extends React.Component {
case 'SeaTable integration':
this.onSeaTableIntegrationToggle();
break;
+ case 'Office Suite':
+ this.onOfficeSuiteToggle();
+ break;
default:
break;
}
@@ -249,6 +255,10 @@ class MylibRepoListItem extends React.Component {
this.setState({isSeaTableIntegrationShow: !this.state.isSeaTableIntegrationShow});
};
+ onOfficeSuiteToggle = () => {
+ this.setState({ isOfficeSuiteDialogShow: !this.state.isOfficeSuiteDialogShow });
+ };
+
toggleRepoShareAdminDialog = () => {
this.setState({isRepoShareAdminDialogOpen: !this.state.isRepoShareAdminDialogOpen});
};
@@ -298,6 +308,21 @@ class MylibRepoListItem extends React.Component {
this.onTransferToggle();
};
+ onOfficeSuiteChange = (suiteID) => {
+ let repoID = this.props.repo.repo_id;
+ userAPI.setOfficeSuite(repoID, suiteID).then(res => {
+ let message = gettext('Successfully change office suite.');
+ toaster.success(message);
+ }).catch(error => {
+ if (error.response) {
+ toaster.danger(error.response.data.error_msg || gettext('Error'), { duration: 3 });
+ } else {
+ toaster.danger(gettext('Failed. Please check the network.'), { duration: 3 });
+ }
+ });
+ this.onOfficeSuiteToggle();
+ };
+
onDeleteRepo = (repo) => {
seafileAPI.deleteRepo(repo.repo_id).then((res) => {
@@ -544,6 +569,17 @@ class MylibRepoListItem extends React.Component {
)}
+ {this.state.isOfficeSuiteDialogShow && (
+
+
+
+ )}
+
);
}
diff --git a/frontend/src/pages/my-libs/mylib-repo-menu.js b/frontend/src/pages/my-libs/mylib-repo-menu.js
index c811ab465dd..8262f747be7 100644
--- a/frontend/src/pages/my-libs/mylib-repo-menu.js
+++ b/frontend/src/pages/my-libs/mylib-repo-menu.js
@@ -1,7 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Dropdown, DropdownMenu, DropdownToggle, DropdownItem } from 'reactstrap';
-import { gettext, isPro, folderPermEnabled, enableRepoSnapshotLabel, enableResetEncryptedRepoPassword, isEmailConfigured, enableRepoAutoDel, enableSeaTableIntegration } from '../../utils/constants';
+import { gettext, isPro, folderPermEnabled, enableRepoSnapshotLabel, enableResetEncryptedRepoPassword, isEmailConfigured, enableRepoAutoDel, enableSeaTableIntegration, enableMultipleOfficeSuite } from '../../utils/constants';
import { Utils } from '../../utils/utils';
const propTypes = {
@@ -126,6 +126,9 @@ class MylibRepoMenu extends React.Component {
if (enableSeaTableIntegration) {
operations.push('SeaTable integration');
}
+ if (enableMultipleOfficeSuite && isPro) {
+ operations.push('Office Suite');
+ }
return operations;
};
@@ -186,6 +189,9 @@ class MylibRepoMenu extends React.Component {
case 'SeaTable integration':
translateResult = gettext('SeaTable integration');
break;
+ case 'Office Suite':
+ translateResult = gettext('Office Suite');
+ break;
default:
break;
}
diff --git a/frontend/src/utils/constants.js b/frontend/src/utils/constants.js
index 4ac5f7bb074..5a36b070cc3 100644
--- a/frontend/src/utils/constants.js
+++ b/frontend/src/utils/constants.js
@@ -86,6 +86,7 @@ export const ocmRemoteServers = window.app.pageOptions.ocmRemoteServers;
export const enableOCMViaWebdav = window.app.pageOptions.enableOCMViaWebdav;
export const enableSSOToThirdpartWebsite = window.app.pageOptions.enableSSOToThirdpartWebsite;
export const enableSeadoc = window.app.pageOptions.enableSeadoc;
+export const enableMultipleOfficeSuite = window.app.pageOptions.enableMultipleOfficeSuite;
export const curNoteMsg = window.app.pageOptions.curNoteMsg;
export const curNoteID = window.app.pageOptions.curNoteID;
diff --git a/frontend/src/utils/user-api.js b/frontend/src/utils/user-api.js
new file mode 100644
index 00000000000..a4fbbe13644
--- /dev/null
+++ b/frontend/src/utils/user-api.js
@@ -0,0 +1,54 @@
+import axios from 'axios';
+import cookie from 'react-cookies';
+import { siteRoot } from './constants';
+
+class UserAPI {
+
+ init({ server, username, password, token }) {
+ this.server = server;
+ this.username = username;
+ this.password = password;
+ this.token = token;
+ if (this.token && this.server) {
+ this.req = axios.create({
+ baseURL: this.server,
+ headers: { 'Authorization': 'Token ' + this.token },
+ });
+ }
+ return this;
+ }
+
+ initForSeahubUsage({ siteRoot, xcsrfHeaders }) {
+ if (siteRoot && siteRoot.charAt(siteRoot.length - 1) === '/') {
+ var server = siteRoot.substring(0, siteRoot.length - 1);
+ this.server = server;
+ } else {
+ this.server = siteRoot;
+ }
+
+ this.req = axios.create({
+ headers: {
+ 'X-CSRFToken': xcsrfHeaders,
+ }
+ });
+ return this;
+ }
+
+ getOfficeSuite(repoID) {
+ const url = this.server + '/api/v2.1/repos/' + repoID + '/office-suite/';
+ return this.req.get(url);
+ }
+
+ setOfficeSuite(repoID, suiteID) {
+ const url = this.server + '/api/v2.1/repos/' + repoID + '/office-suite/';
+ const form = new FormData();
+ form.append('suite_id', suiteID);
+ return this.req.put(url, form);
+ }
+}
+
+let userAPI = new UserAPI();
+let xcsrfHeaders = cookie.load('sfcsrftoken');
+userAPI.initForSeahubUsage({ siteRoot, xcsrfHeaders });
+
+export { userAPI };
diff --git a/frontend/src/utils/utils.js b/frontend/src/utils/utils.js
index 7d28667a944..8d61f0db0bf 100644
--- a/frontend/src/utils/utils.js
+++ b/frontend/src/utils/utils.js
@@ -1,4 +1,4 @@
-import { mediaUrl, gettext, serviceURL, siteRoot, isPro, fileAuditEnabled, canGenerateShareLink, canGenerateUploadLink, shareLinkPasswordMinLength, username, folderPermEnabled, onlyofficeConverterExtensions, enableOnlyoffice, enableSeadoc } from './constants';
+import { mediaUrl, gettext, serviceURL, siteRoot, isPro, fileAuditEnabled, canGenerateShareLink, canGenerateUploadLink, shareLinkPasswordMinLength, username, folderPermEnabled, onlyofficeConverterExtensions, enableSeadoc } from './constants';
import TextTranslation from './text-translation';
import React from 'react';
import toaster from '../components/toast';
@@ -640,7 +640,7 @@ export const Utils = {
list.push(HISTORY);
}
- if (permission == 'rw' && enableOnlyoffice &&
+ if (permission == 'rw' && currentRepoInfo.enable_onlyoffice &&
onlyofficeConverterExtensions.includes(this.getFileExtension(dirent.name, false))) {
list.push(ONLYOFFICE_CONVERT);
}
diff --git a/seahub/api2/endpoints/repo_office_suite.py b/seahub/api2/endpoints/repo_office_suite.py
new file mode 100644
index 00000000000..45b2ae02f34
--- /dev/null
+++ b/seahub/api2/endpoints/repo_office_suite.py
@@ -0,0 +1,88 @@
+import json
+
+from rest_framework.authentication import SessionAuthentication
+from rest_framework.permissions import IsAuthenticated
+from rest_framework.response import Response
+from rest_framework.views import APIView
+from rest_framework import status
+
+from seaserv import seafile_api
+
+from seahub.api2.throttling import UserRateThrottle
+from seahub.api2.authentication import TokenAuthentication
+from seahub.api2.utils import api_error
+from seahub.api2.permissions import IsProVersion
+
+from seahub.onlyoffice.models import RepoExtraConfig, REPO_OFFICE_CONFIG
+from seahub.settings import OFFICE_SUITE_LIST
+from seahub.utils.repo import get_repo_owner
+
+class OfficeSuiteConfig(APIView):
+
+ authentication_classes = (TokenAuthentication, SessionAuthentication)
+ permission_classes = (IsAuthenticated, IsProVersion)
+ throttle_classes = (UserRateThrottle,)
+
+ def get(self, request, repo_id):
+ if not request.user.permissions.can_choose_office_suite:
+ error_msg = 'Permission denied.'
+ return api_error(status.HTTP_403_FORBIDDEN, error_msg)
+
+ repo = seafile_api.get_repo(repo_id)
+ if not repo:
+ error_msg = 'Library %s not found.' % repo_id
+ return api_error(status.HTTP_404_NOT_FOUND, error_msg)
+
+ repo_owner = get_repo_owner(request, repo_id)
+ if '@seafile_group' in repo_owner:
+ error_msg = 'Department repo can not use this feature.'
+ return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
+
+ current_suite = RepoExtraConfig.objects.filter(repo_id=repo_id, config_type=REPO_OFFICE_CONFIG).first()
+ suites_info = []
+ for office_suite in OFFICE_SUITE_LIST:
+ suite_info = {}
+ suite_info['id'] = office_suite.get('id')
+ suite_info['name'] = office_suite.get('name')
+ suite_info['is_default'] = office_suite.get('is_default')
+ if current_suite:
+ config_details = json.loads(current_suite.config_details)
+ office_config = config_details.get('office_suite')
+ suite_info['is_selected'] = (True if office_config and office_config.get('suite_id') == office_suite.get('id') else False)
+ else:
+ suite_info['is_selected'] = office_suite.get('is_default')
+ suites_info.append(suite_info)
+
+ return Response({'suites_info': suites_info})
+
+ def put(self, request, repo_id):
+ # arguments check
+ suite_id = request.data.get('suite_id', '')
+ if suite_id not in ['collabora', 'onlyoffice']:
+ error_msg = 'suite_id invalid.'
+ return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
+
+ if not request.user.permissions.can_choose_office_suite:
+ error_msg = 'Permission denied.'
+ return api_error(status.HTTP_403_FORBIDDEN, error_msg)
+
+ # resource check
+ repo = seafile_api.get_repo(repo_id)
+ if not repo:
+ error_msg = 'Library %s not found.' % repo_id
+ return api_error(status.HTTP_404_NOT_FOUND, error_msg)
+
+ repo_owner = get_repo_owner(request, repo_id)
+ if '@seafile_group' in repo_owner:
+ error_msg = 'Department repo can not use this feature.'
+ return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
+
+ config_details = {
+ 'office_suite': {
+ 'suite_id': suite_id
+ }
+ }
+ RepoExtraConfig.objects.update_or_create(repo_id=repo_id, config_type=REPO_OFFICE_CONFIG,
+ defaults= {'config_details':json.dumps(config_details)} )
+
+ return Response({"success": True}, status=status.HTTP_200_OK)
diff --git a/seahub/api2/endpoints/repos.py b/seahub/api2/endpoints/repos.py
index 5a7606a4fdf..7f5b1a42db8 100644
--- a/seahub/api2/endpoints/repos.py
+++ b/seahub/api2/endpoints/repos.py
@@ -29,6 +29,7 @@
from seahub.settings import ENABLE_STORAGE_CLASSES
from seaserv import seafile_api
+from seahub.views.file import get_office_feature_by_repo
logger = logging.getLogger(__name__)
@@ -113,7 +114,8 @@ def get(self, request):
# do not return virtual repos
if r.is_virtual:
continue
-
+ enable_onlyoffice, _ = get_office_feature_by_repo(r)
+
repo_info = {
"type": "mine",
"repo_id": r.id,
@@ -132,6 +134,7 @@ def get(self, request):
"monitored": r.repo_id in monitored_repo_id_list,
"status": normalize_repo_status_code(r.status),
"salt": r.salt if r.enc_version >= 3 else '',
+ "enable_onlyoffice": enable_onlyoffice
}
if is_pro_version() and ENABLE_STORAGE_CLASSES:
@@ -183,6 +186,8 @@ def get(self, request):
owner_name = group_name if is_group_owned_repo else nickname_dict.get(owner_email, '')
owner_contact_email = '' if is_group_owned_repo else contact_email_dict.get(owner_email, '')
+ enable_onlyoffice, _ = get_office_feature_by_repo(r)
+
repo_info = {
"type": "shared",
"repo_id": r.repo_id,
@@ -201,6 +206,7 @@ def get(self, request):
"monitored": r.repo_id in monitored_repo_id_list,
"status": normalize_repo_status_code(r.status),
"salt": r.salt if r.enc_version >= 3 else '',
+ "enable_onlyoffice": enable_onlyoffice
}
if r.repo_id in repos_with_admin_share_to:
@@ -238,6 +244,7 @@ def get(self, request):
monitored_repo_id_list = []
for r in group_repos:
+ enable_onlyoffice, _ = get_office_feature_by_repo(r)
repo_info = {
"type": "group",
"group_id": r.group_id,
@@ -255,6 +262,7 @@ def get(self, request):
"monitored": r.repo_id in monitored_repo_id_list,
"status": normalize_repo_status_code(r.status),
"salt": r.salt if r.enc_version >= 3 else '',
+ "enable_onlyoffice": enable_onlyoffice
}
repo_info_list.append(repo_info)
@@ -283,6 +291,7 @@ def get(self, request):
for r in public_repos:
repo_owner = repo_id_owner_dict[r.repo_id]
+ enable_onlyoffice, _ = get_office_feature_by_repo(r)
repo_info = {
"type": "public",
"repo_id": r.repo_id,
@@ -300,6 +309,7 @@ def get(self, request):
"starred": r.repo_id in starred_repo_id_list,
"status": normalize_repo_status_code(r.status),
"salt": r.salt if r.enc_version >= 3 else '',
+ "enable_onlyoffice": enable_onlyoffice
}
repo_info_list.append(repo_info)
@@ -340,6 +350,7 @@ def get(self, request, repo_id):
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
username = request.user.username
+ enable_onlyoffice, _ = get_office_feature_by_repo(repo)
lib_need_decrypt = False
if repo.encrypted \
@@ -374,6 +385,7 @@ def get(self, request, repo_id):
"lib_need_decrypt": lib_need_decrypt,
"last_modified": timestamp_to_isoformat_timestr(repo.last_modify),
"status": normalize_repo_status_code(repo.status),
+ "enable_onlyoffice": enable_onlyoffice
}
return Response(result)
diff --git a/seahub/api2/views.py b/seahub/api2/views.py
index 590c73f0b6a..2484fe13d1c 100644
--- a/seahub/api2/views.py
+++ b/seahub/api2/views.py
@@ -106,6 +106,7 @@
ENABLE_RESET_ENCRYPTED_REPO_PASSWORD, SHARE_LINK_EXPIRE_DAYS_MAX, \
SHARE_LINK_EXPIRE_DAYS_MIN, SHARE_LINK_EXPIRE_DAYS_DEFAULT
from seahub.subscription.utils import subscription_check
+from seahub.views.file import get_office_feature_by_repo
try:
from seahub.settings import CLOUD_MODE
@@ -120,10 +121,6 @@
except ImportError:
ORG_MEMBER_QUOTA_DEFAULT = None
-try:
- from seahub.settings import ENABLE_OFFICE_WEB_APP
-except ImportError:
- ENABLE_OFFICE_WEB_APP = False
try:
from seahub.settings import ORG_MEMBER_QUOTA_ENABLED
@@ -2726,6 +2723,8 @@ def get(self, request, repo_id, format=None):
error_msg = 'Library %s not found.' % repo_id
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
+ _, ENABLE_OFFICE_WEB_APP = get_office_feature_by_repo(repo)
+
action = request.GET.get('action', 'view')
if action not in ('view', 'edit'):
error_msg = 'action invalid.'
diff --git a/seahub/base/accounts.py b/seahub/base/accounts.py
index 54d255b6931..798ae2f1988 100644
--- a/seahub/base/accounts.py
+++ b/seahub/base/accounts.py
@@ -446,6 +446,11 @@ def can_publish_repo(self):
return False
return self._get_perm_by_roles('can_publish_repo')
+
+ def can_choose_office_suite(self):
+ if not settings.ENABLE_MULTIPLE_OFFICE_SUITE:
+ return False
+ return self._get_perm_by_roles('can_choose_office_suite')
class AdminPermissions(object):
diff --git a/seahub/onlyoffice/models.py b/seahub/onlyoffice/models.py
index 9bc7a100471..cb0a8e91075 100644
--- a/seahub/onlyoffice/models.py
+++ b/seahub/onlyoffice/models.py
@@ -14,3 +14,15 @@ class OnlyOfficeDocKey(models.Model):
file_path = models.TextField()
repo_id_file_path_md5 = models.CharField(max_length=100, db_index=True, unique=True)
created_time = models.DateTimeField(default=datetime.datetime.now)
+
+
+REPO_OFFICE_CONFIG = 'office'
+class RepoExtraConfig(models.Model):
+
+ config_type = models.CharField(max_length=50)
+ repo_id = models.CharField(max_length=36, db_index=True)
+ config_details = models.TextField()
+
+ class Meta:
+ db_table = 'repo_extra_config'
+ unique_together = ('repo_id', 'config_type')
diff --git a/seahub/onlyoffice/settings.py b/seahub/onlyoffice/settings.py
index e632491da98..1cb69ab524f 100644
--- a/seahub/onlyoffice/settings.py
+++ b/seahub/onlyoffice/settings.py
@@ -1,5 +1,6 @@
# Copyright (c) 2012-2016 Seafile Ltd.
from django.conf import settings
+from seahub.settings import ENABLE_MULTIPLE_OFFICE_SUITE, OFFICE_SUITE_LIST, OFFICE_SUITE_ENABLED_FILE_TYPES, OFFICE_SUITE_ENABLED_EDIT_FILE_TYPES
ENABLE_ONLYOFFICE = getattr(settings, 'ENABLE_ONLYOFFICE', False)
ONLYOFFICE_APIJS_URL = getattr(settings, 'ONLYOFFICE_APIJS_URL', '')
@@ -44,3 +45,21 @@
".html", ".htm", ".mht", ".xml",
".pdf", ".djvu", ".fb2", ".epub", ".xps"
]
+
+
+if ENABLE_MULTIPLE_OFFICE_SUITE:
+ OFFICE_SUITE_ONLY_OFFICE = 'onlyoffice'
+ office_info = {}
+ for s in OFFICE_SUITE_LIST:
+ if s.get('id') == OFFICE_SUITE_ONLY_OFFICE:
+ office_info = s
+ break
+ ONLYOFFICE_APIJS_URL = office_info.get('ONLYOFFICE_APIJS_URL')
+ ONLYOFFICE_CONVERTER_URL = ONLYOFFICE_APIJS_URL and ONLYOFFICE_APIJS_URL.replace("/web-apps/apps/api/documents/api.js", "/ConvertService.ashx")
+ ONLYOFFICE_FORCE_SAVE = office_info.get('ONLYOFFICE_FORCE_SAVE', False)
+ ONLYOFFICE_JWT_SECRET = office_info.get('ONLYOFFICE_JWT_SECRET', '')
+ ONLYOFFICE_JWT_HEADER = office_info.get('ONLYOFFICE_JWT_HEADER', 'Authorization')
+ ONLYOFFICE_DESKTOP_EDITOR_HTTP_USER_AGENT = office_info.get('ONLYOFFICE_DESKTOP_EDITOR_HTTP_USER_AGENT', 'AscDesktopEditor')
+ VERIFY_ONLYOFFICE_CERTIFICATE = office_info.get('VERIFY_ONLYOFFICE_CERTIFICATE', True)
+ ONLYOFFICE_FILE_EXTENSION = OFFICE_SUITE_ENABLED_FILE_TYPES
+ ONLYOFFICE_EDIT_FILE_EXTENSION = OFFICE_SUITE_ENABLED_EDIT_FILE_TYPES
diff --git a/seahub/role_permissions/settings.py b/seahub/role_permissions/settings.py
index 479ea6fd371..9bb948d595f 100644
--- a/seahub/role_permissions/settings.py
+++ b/seahub/role_permissions/settings.py
@@ -46,6 +46,7 @@ def merge_roles(default, custom):
'can_publish_repo': True,
'upload_rate_limit': 0,
'download_rate_limit': 0,
+ 'can_choose_office_suite': True,
},
GUEST_USER: {
'can_add_repo': False,
@@ -68,6 +69,7 @@ def merge_roles(default, custom):
'can_publish_repo': False,
'upload_rate_limit': 0,
'download_rate_limit': 0,
+ 'can_choose_office_suite': False,
},
}
diff --git a/seahub/settings.py b/seahub/settings.py
index 1b3e9a0fa52..23886aee37d 100644
--- a/seahub/settings.py
+++ b/seahub/settings.py
@@ -892,6 +892,25 @@ def genpassword():
EX_PROPS_TABLE = ''
EX_EDITABLE_COLUMNS = []
+#############################
+# multi office suite support
+#############################
+ENABLE_MULTIPLE_OFFICE_SUITE = False
+OFFICE_SUITE_LIST = [
+ {
+ "id": "onlyoffice",
+ "name": "OnlyOffice",
+ "is_default": True,
+ },
+ {
+ "id": "collabora",
+ "name": "CollaboraOnline",
+ "is_default": False,
+ }
+]
+ROLES_DEFAULT_OFFCICE_SUITE = {}
+OFFICE_SUITE_ENABLED_FILE_TYPES = []
+OFFICE_SUITE_ENABLED_EDIT_FILE_TYPES = []
d = os.path.dirname
EVENTS_CONFIG_FILE = os.environ.get(
'EVENTS_CONFIG_FILE',
diff --git a/seahub/templates/base_for_react.html b/seahub/templates/base_for_react.html
index 37d702162be..d737b96ad80 100644
--- a/seahub/templates/base_for_react.html
+++ b/seahub/templates/base_for_react.html
@@ -150,6 +150,7 @@
canSetExProps: {% if can_set_ex_props %} true {% else %} false {% endif %},
enableSeaTableIntegration: {% if enable_seatable_integration %} true {% else %} false {% endif %},
isOrgContext: {% if org is not None %} true {% else %} false {% endif %},
+ enableMultipleOfficeSuite: {% if user.permissions.can_choose_office_suite %} true {% else %} false {% endif %},
}
};
diff --git a/seahub/urls.py b/seahub/urls.py
index c6c348cb392..61c1c56c1ac 100644
--- a/seahub/urls.py
+++ b/seahub/urls.py
@@ -200,6 +200,7 @@
from seahub.api2.endpoints.repo_auto_delete import RepoAutoDeleteView
from seahub.seadoc.views import sdoc_revision, sdoc_revisions, sdoc_to_docx
from seahub.ocm.settings import OCM_ENDPOINT
+from seahub.api2.endpoints.repo_office_suite import OfficeSuiteConfig
from seahub.ai.apis import LibrarySdocIndexes, Search, LibrarySdocIndex, TaskStatus, \
LibraryIndexState, QuestionAnsweringSearchInLibrary, FileDownloadToken
@@ -446,6 +447,7 @@
re_path(r'^api/v2.1/repos/(?P[-0-9a-f]{36})/upload-links/$', RepoUploadLinks.as_view(), name='api-v2.1-repo-upload-links'),
re_path(r'^api/v2.1/repos/(?P[-0-9a-f]{36})/upload-links/(?P[a-f0-9]+)/$', RepoUploadLink.as_view(), name='api-v2.1-repo-upload-link'),
re_path(r'^api/v2.1/repos/(?P[-0-9a-f]{36})/share-info/$', RepoShareInfoView.as_view(), name='api-v2.1-repo-share-info-view'),
+ re_path(r'^api/v2.1/repos/(?P[-0-9a-f]{36})/office-suite/$', OfficeSuiteConfig.as_view(), name='api-v2.1-repo-office-suite'),
## user:: repo-api-tokens
re_path(r'^api/v2.1/repos/(?P[-0-9a-f]{36})/repo-api-tokens/$', RepoAPITokensView.as_view(), name='api-v2.1-repo-api-tokens'),
@@ -961,7 +963,7 @@
path('saml2/complete/', auth_complete, name='saml2_complete'),
]
-if getattr(settings, 'ENABLE_ONLYOFFICE', False):
+if getattr(settings, 'ENABLE_ONLYOFFICE', False) or getattr(settings, 'ENABLE_MULTIPLE_OFFICE_SUITE', False):
urlpatterns += [
path('onlyoffice/', include('seahub.onlyoffice.urls')),
path('onlyoffice-api/', include('seahub.onlyoffice.api_urls')),
diff --git a/seahub/views/file.py b/seahub/views/file.py
index 556a46a6f04..73e6d2f8375 100644
--- a/seahub/views/file.py
+++ b/seahub/views/file.py
@@ -34,15 +34,16 @@
from seaserv import seafile_api, ccnet_api
from seaserv import get_repo, get_commits, \
get_file_id_by_path, get_commit, get_file_size, \
- seafserv_threaded_rpc
+ seafserv_threaded_rpc, get_org_id_by_repo_id
from seahub.settings import SITE_ROOT
from seahub.tags.models import FileUUIDMap
from seahub.wopi.utils import get_wopi_dict
from seahub.onlyoffice.utils import get_onlyoffice_dict
+from seahub.onlyoffice.models import RepoExtraConfig, REPO_OFFICE_CONFIG
from seahub.auth.decorators import login_required
from seahub.base.decorators import repo_passwd_set_required
-from seahub.base.accounts import ANONYMOUS_EMAIL
+from seahub.base.accounts import ANONYMOUS_EMAIL, User
from seahub.base.templatetags.seahub_tags import file_icon_filter
from seahub.share.models import FileShare, check_share_link_common
from seahub.share.decorators import share_link_audit, share_link_login_required
@@ -67,6 +68,7 @@
BadRequestException
from seahub.utils.file_op import check_file_lock, \
ONLINE_OFFICE_LOCK_OWNER, if_locked_by_online_office
+from seahub.utils.user_permissions import get_user_role
from seahub.views import check_folder_permission, \
get_unencry_rw_repos_by_user
from seahub.utils.repo import is_repo_owner, parse_repo_perm
@@ -89,33 +91,34 @@
FILE_ENCODING_TRY_LIST, MEDIA_URL, SEAFILE_COLLAB_SERVER, ENABLE_WATERMARK, \
SHARE_LINK_EXPIRE_DAYS_MIN, SHARE_LINK_EXPIRE_DAYS_MAX, SHARE_LINK_PASSWORD_MIN_LENGTH, \
SHARE_LINK_FORCE_USE_PASSWORD, SHARE_LINK_PASSWORD_STRENGTH_LEVEL, \
- SHARE_LINK_EXPIRE_DAYS_DEFAULT, ENABLE_SHARE_LINK_REPORT_ABUSE, SEADOC_SERVER_URL
+ SHARE_LINK_EXPIRE_DAYS_DEFAULT, ENABLE_SHARE_LINK_REPORT_ABUSE, SEADOC_SERVER_URL, \
+ ENABLE_MULTIPLE_OFFICE_SUITE, OFFICE_SUITE_LIST
# wopi
try:
- from seahub.settings import ENABLE_OFFICE_WEB_APP
+ from seahub.wopi.settings import ENABLE_OFFICE_WEB_APP
except ImportError:
ENABLE_OFFICE_WEB_APP = False
try:
- from seahub.settings import ENABLE_OFFICE_WEB_APP_EDIT
+ from seahub.wopi.settings import ENABLE_OFFICE_WEB_APP_EDIT
except ImportError:
ENABLE_OFFICE_WEB_APP_EDIT = False
try:
- from seahub.settings import OFFICE_WEB_APP_FILE_EXTENSION
+ from seahub.wopi.settings import OFFICE_WEB_APP_FILE_EXTENSION
except ImportError:
OFFICE_WEB_APP_FILE_EXTENSION = ()
try:
- from seahub.settings import OFFICE_WEB_APP_EDIT_FILE_EXTENSION
+ from seahub.wopi.settings import OFFICE_WEB_APP_EDIT_FILE_EXTENSION
except ImportError:
OFFICE_WEB_APP_EDIT_FILE_EXTENSION = ()
# onlyoffice
try:
- from seahub.settings import ENABLE_ONLYOFFICE
+ from seahub.onlyoffice.settings import ENABLE_ONLYOFFICE
except ImportError:
ENABLE_ONLYOFFICE = False
@@ -138,10 +141,62 @@
from seahub.thirdparty_editor.settings import ENABLE_THIRDPARTY_EDITOR
from seahub.thirdparty_editor.settings import THIRDPARTY_EDITOR_ACTION_URL_DICT
from seahub.thirdparty_editor.settings import THIRDPARTY_EDITOR_ACCESS_TOKEN_EXPIRATION
+from seahub.settings import ROLES_DEFAULT_OFFCICE_SUITE
# Get an instance of a logger
logger = logging.getLogger(__name__)
+
+def _check_feature(repo_id):
+ office_suite = RepoExtraConfig.objects.filter(repo_id=repo_id, config_type=REPO_OFFICE_CONFIG).first()
+ if office_suite:
+ repo_config_details = json.loads(office_suite.config_details)
+ office_config = repo_config_details.get('office_suite')
+ return office_config.get('suite_id') if office_config else None
+ return None
+
+def get_office_feature_by_repo(repo):
+ enable_onlyoffice, enable_office_app = False, False
+ if not ENABLE_MULTIPLE_OFFICE_SUITE:
+ return ENABLE_ONLYOFFICE, ENABLE_OFFICE_WEB_APP
+
+ if not OFFICE_SUITE_LIST:
+ return ENABLE_ONLYOFFICE, ENABLE_OFFICE_WEB_APP
+
+ org_id = get_org_id_by_repo_id(repo.repo_id)
+ if org_id > 0:
+ repo_owner = seafile_api.get_org_repo_owner(repo.repo_id)
+ else:
+ repo_owner = seafile_api.get_repo_owner(repo.repo_id)
+ if '@seafile_group' in repo_owner:
+ repo_feature = None
+ else:
+ repo_feature = _check_feature(repo.repo_id)
+
+ if not repo_feature and '@seafile_group' not in repo_owner:
+ user = User.objects.get(email=repo_owner)
+ role = get_user_role(user)
+ repo_feature = ROLES_DEFAULT_OFFCICE_SUITE.get(role)
+
+ if not repo_feature:
+ default_suite = {}
+ for s in OFFICE_SUITE_LIST:
+ if s.get('is_default'):
+ default_suite = s
+ break
+ if default_suite.get('id') == 'onlyoffice':
+ enable_onlyoffice = True
+ if default_suite.get('id') == 'collabora':
+ enable_office_app = True
+ else:
+ if repo_feature == 'onlyoffice':
+ enable_onlyoffice = True
+ if repo_feature == 'collabora':
+ enable_office_app = True
+
+ return enable_onlyoffice, enable_office_app
+
+
def gen_path_link(path, repo_name):
"""
Generate navigate paths and links in repo page.
@@ -335,6 +390,8 @@ def can_preview_file(file_name, file_size, repo):
filetype, fileext = get_file_type_and_ext(file_name)
+ ENABLE_ONLYOFFICE, ENABLE_OFFICE_WEB_APP = get_office_feature_by_repo(repo)
+
# Seafile defines 10 kinds of filetype:
# TEXT, MARKDOWN, IMAGE, DOCUMENT, SPREADSHEET, VIDEO, AUDIO, PDF, SVG
if filetype in (TEXT, MARKDOWN, IMAGE) or fileext in get_conf_text_ext():
@@ -389,7 +446,7 @@ def can_edit_file(file_name, file_size, repo):
"""Check whether Seafile supports edit file.
Returns (True, None) if Yes, otherwise (False, error_msg).
"""
-
+ ENABLE_ONLYOFFICE, ENABLE_OFFICE_WEB_APP = get_office_feature_by_repo(repo)
can_preview, err_msg = can_preview_file(file_name, file_size, repo)
if not can_preview:
return False, err_msg
@@ -486,6 +543,8 @@ def view_lib_file(request, repo_id, path):
file_id = seafile_api.get_file_id_by_path(repo_id, path)
if not file_id:
return render_error(request, _('File does not exist'))
+
+ ENABLE_ONLYOFFICE, ENABLE_OFFICE_WEB_APP = get_office_feature_by_repo(repo)
# permission check
username = request.user.username
@@ -938,6 +997,8 @@ def view_history_file_common(request, repo_id, ret_dict):
path = request.GET.get('p', '/')
path = normalize_file_path(path)
+ ENABLE_ONLYOFFICE, ENABLE_OFFICE_WEB_APP = get_office_feature_by_repo(repo)
+
commit_id = request.GET.get('commit_id', '')
if not commit_id:
raise Http404
@@ -1177,6 +1238,8 @@ def view_shared_file(request, fileshare):
obj_id = seafile_api.get_file_id_by_path(repo_id, path)
if not obj_id:
return render_error(request, _('File does not exist'))
+
+ ENABLE_ONLYOFFICE, ENABLE_OFFICE_WEB_APP = get_office_feature_by_repo(repo)
# permission check
shared_by = fileshare.username
@@ -1411,6 +1474,8 @@ def view_file_via_shared_dir(request, fileshare):
if not repo:
raise Http404
+ ENABLE_ONLYOFFICE, ENABLE_OFFICE_WEB_APP = get_office_feature_by_repo(repo)
+
# recourse check
# Get file path from frontend, and construct request file path
# with fileshare.path to real path, used to fetch file content by RPC.
diff --git a/seahub/wopi/settings.py b/seahub/wopi/settings.py
index 303795045d4..f688bfe27c4 100644
--- a/seahub/wopi/settings.py
+++ b/seahub/wopi/settings.py
@@ -29,3 +29,24 @@
# Path to a CA_BUNDLE file or directory with certificates of trusted CAs
OFFICE_WEB_APP_SERVER_CA = getattr(settings, 'OFFICE_WEB_APP_SERVER_CA', True)
+
+if settings.ENABLE_MULTIPLE_OFFICE_SUITE:
+ OFFICE_SUITE_COLLA = 'collabora'
+ office_info = {}
+ for s in settings.OFFICE_SUITE_LIST:
+ if s.get('id') == OFFICE_SUITE_COLLA:
+ office_info = s
+ break
+ OFFICE_SERVER_TYPE = office_info.get('OFFICE_SERVER_TYPE', 'collaboraoffice')
+ OFFICE_WEB_APP_BASE_URL = office_info.get('OFFICE_WEB_APP_BASE_URL', '')
+ WOPI_ACCESS_TOKEN_EXPIRATION = office_info.get('WOPI_ACCESS_TOKEN_EXPIRATION', 12 * 60 * 60)
+ OFFICE_WEB_APP_DISCOVERY_EXPIRATION = office_info.get('OFFICE_WEB_APP_DISCOVERY_EXPIRATION', 7 * 24 * 60 * 60)
+ OFFICE_WEB_APP_CLIENT_CERT = office_info.get('OFFICE_WEB_APP_CLIENT_CERT', '')
+ OFFICE_WEB_APP_CLIENT_KEY = office_info.get('OFFICE_WEB_APP_CLIENT_KEY', '')
+ OFFICE_WEB_APP_CLIENT_PEM = office_info.get('OFFICE_WEB_APP_CLIENT_PEM', '')
+ OFFICE_WEB_APP_SERVER_CA = office_info.get('OFFICE_WEB_APP_SERVER_CA', '')
+ ENABLE_OFFICE_WEB_APP_EDIT = office_info.get('ENABLE_OFFICE_WEB_APP_EDIT', False)
+ OFFICE_WEB_APP_FILE_EXTENSION = settings.OFFICE_SUITE_ENABLED_FILE_TYPES
+ OFFICE_WEB_APP_EDIT_FILE_EXTENSION = settings.OFFICE_SUITE_ENABLED_EDIT_FILE_TYPES
+
+
diff --git a/tests/seahub/role_permissions/test_utils.py b/tests/seahub/role_permissions/test_utils.py
index 4f0a03a780b..9a59479b93f 100644
--- a/tests/seahub/role_permissions/test_utils.py
+++ b/tests/seahub/role_permissions/test_utils.py
@@ -11,4 +11,4 @@ def test_get_available_role(self):
assert DEFAULT_USER in get_available_roles()
def test_get_enabled_role_permissions_by_role(self):
- assert len(list(get_enabled_role_permissions_by_role(DEFAULT_USER).keys())) == 20
+ assert len(list(get_enabled_role_permissions_by_role(DEFAULT_USER).keys())) == 21