1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-05-11 09:24:38 +00:00

Multi office suite ()

* initiate

* update

* test

* Update file.py

* update

* update

* update

* optimize fronted

* optimize

* update

* update

* update

* Update test_utils.py

* update

* remove-useless-code

* Update repo_office_suite.py

* optimize ui and sql

* optimize

* update

* add is pro version for api

* Update settings.py

* update

* Update models.py

* add-repo-owner-validation

---------

Co-authored-by: 孙永强 <11704063+s-yongqiang@user.noreply.gitee.com>
This commit is contained in:
Ranjiwei 2025-01-02 11:22:25 +08:00 committed by GitHub
parent 1b89e8e2f1
commit 037c3f3a3d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 472 additions and 29 deletions

View File

@ -0,0 +1,112 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Button, Modal, ModalHeader, ModalBody, ModalFooter, TabContent, TabPane } 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 (
<React.Fragment>
<div className="repo-office-suite-dialog-main">
<TabContent>
{isPro &&
<TabPane role="tabpanel" id="office-suite-panel">
<SeahubSelect
isClearable
maxMenuHeight={200}
hideSelectedOptions={true}
components={makeAnimated()}
placeholder={gettext('Select a office suite')}
options={this.options}
onChange={this.handleSelectChange}
value={this.state.selectedOption}
className="repo-select-office-suite"
/>
</TabPane>}
</TabContent>
</div>
</React.Fragment>
);
};
render() {
const { itemName: repoName } = this.props;
let title = gettext('{library_name} Office Suite');
title = title.replace('{library_name}', '<span class="op-target text-truncate mx-1">' + Utils.HTMLescape(repoName) + '</span>');
return (
<Modal isOpen={true} toggle={this.props.toggleDialog} className="repo-office-suite-dialog">
<ModalHeader toggle={this.props.toggleDialog}>
<span dangerouslySetInnerHTML={{ __html: title }} className="d-flex mw-100"></span>
</ModalHeader>
<ModalBody className="repo-office-suite-dialog-content" role="tablist">
{this.renderOfficeSuiteContent()}
</ModalBody>
<ModalFooter>
<Button color="secondary" onClick={this.props.toggleDialog}>{gettext('Cancel')}</Button>
<Button color="primary" onClick={this.submit}>{gettext('Submit')}</Button>
</ModalFooter>
</Modal>
);
}
}
OfficeSuiteDialog.propTypes = propTypes;
export default OfficeSuiteDialog;

View File

@ -0,0 +1,30 @@
.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;
}

View File

@ -23,6 +23,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;
}
}

View File

@ -20,6 +20,7 @@ import Rename from '../../components/rename';
import MylibRepoMenu from './mylib-repo-menu';
import RepoAPITokenDialog from '../../components/dialog/repo-api-token-dialog';
import RepoShareAdminDialog from '../../components/dialog/repo-share-admin-dialog';
import OfficeSuiteDialog from '../../components/dialog/repo-office-suite-dialog';
import RepoMonitoredIcon from '../../components/repo-monitored-icon';
import { LIST_MODE } from '../../components/dir-view-mode/constants';
import { userAPI } from '../../utils/user-api';
@ -57,6 +58,7 @@ class MylibRepoListItem extends React.Component {
isAPITokenDialogShow: false,
isRepoShareAdminDialogOpen: false,
isRepoDeleted: false,
isOfficeSuiteDialogShow: false,
};
}
@ -128,6 +130,9 @@ class MylibRepoListItem extends React.Component {
case 'Share Admin':
this.toggleRepoShareAdminDialog();
break;
case 'Office Suite':
this.onOfficeSuiteToggle();
break;
default:
break;
}
@ -221,6 +226,10 @@ class MylibRepoListItem extends React.Component {
this.setState({ isAPITokenDialogShow: !this.state.isAPITokenDialogShow });
};
onOfficeSuiteToggle = () => {
this.setState({ isOfficeSuiteDialogShow: !this.state.isOfficeSuiteDialogShow });
};
toggleRepoShareAdminDialog = () => {
this.setState({ isRepoShareAdminDialogOpen: !this.state.isRepoShareAdminDialogOpen });
};
@ -266,6 +275,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) => {
@ -548,6 +572,17 @@ class MylibRepoListItem extends React.Component {
</ModalPortal>
)}
{this.state.isOfficeSuiteDialogShow && (
<ModalPortal>
<OfficeSuiteDialog
repoID={repo.repo_id}
itemName={repo.repo_name}
submit={this.onOfficeSuiteChange}
toggleDialog={this.onOfficeSuiteToggle}
/>
</ModalPortal>
)}
</Fragment>
);
}

View File

@ -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 } from '../../utils/constants';
import { gettext, isPro, folderPermEnabled, enableRepoSnapshotLabel, enableResetEncryptedRepoPassword, isEmailConfigured, enableMultipleOfficeSuite } from '../../utils/constants';
import { Utils } from '../../utils/utils';
const propTypes = {
@ -120,6 +120,9 @@ class MylibRepoMenu extends React.Component {
if (this.props.isPC && enableRepoSnapshotLabel) {
operations.push('Label Current State');
}
if (enableMultipleOfficeSuite && isPro) {
operations.push('Office Suite');
}
return operations;
};
@ -174,6 +177,9 @@ class MylibRepoMenu extends React.Component {
case 'SeaTable integration':
translateResult = gettext('SeaTable integration');
break;
case 'Office Suite':
translateResult = gettext('Office Suite');
break;
default:
break;
}

View File

@ -90,6 +90,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;

View File

@ -63,6 +63,18 @@ class UserAPI {
form.append('reshare', reshare);
return this.req.put(url, form);
}
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();

View File

@ -1,4 +1,4 @@
import { mediaUrl, gettext, serviceURL, siteRoot, isPro, fileAuditEnabled, canGenerateShareLink, canGenerateUploadLink, shareLinkPasswordMinLength, username, folderPermEnabled, onlyofficeConverterExtensions, enableOnlyoffice, enableSeadoc, enableFileTags, enableRepoSnapshotLabel,
import { mediaUrl, gettext, serviceURL, siteRoot, isPro, fileAuditEnabled, canGenerateShareLink, canGenerateUploadLink, shareLinkPasswordMinLength, username, folderPermEnabled, onlyofficeConverterExtensions, enableSeadoc, enableFileTags, enableRepoSnapshotLabel,
enableResetEncryptedRepoPassword, isEmailConfigured, isSystemStaff } from './constants';
import TextTranslation from './text-translation';
import React from 'react';
@ -664,7 +664,7 @@ export const Utils = {
list.push(HISTORY);
}
if (permission == 'rw' && enableOnlyoffice &&
if (permission == 'rw' && currentRepoInfo.enable_onlyoffice &&
onlyofficeConverterExtensions.includes(Utils.getFileExtension(dirent.name, false))) {
list.push(ONLYOFFICE_CONVERT);
}

View File

@ -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)

View File

@ -36,6 +36,7 @@ from seahub.avatar.templatetags.avatar_tags import api_avatar_url
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__)
@ -125,6 +126,8 @@ class ReposView(APIView):
continue
url, _, _ = api_avatar_url(email)
enable_onlyoffice, _ = get_office_feature_by_repo(r)
repo_info = {
"type": "mine",
"repo_id": r.id,
@ -144,6 +147,7 @@ class ReposView(APIView):
"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:
@ -199,6 +203,8 @@ class ReposView(APIView):
owner_contact_email = '' if is_group_owned_repo else contact_email_dict.get(owner_email, '')
url, _, _ = api_avatar_url(owner_email)
enable_onlyoffice, _ = get_office_feature_by_repo(r)
repo_info = {
"type": "shared",
"repo_id": r.repo_id,
@ -218,6 +224,7 @@ class ReposView(APIView):
"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:
@ -259,7 +266,7 @@ class ReposView(APIView):
if is_wiki_repo(r):
continue
enable_onlyoffice, _ = get_office_feature_by_repo(r)
repo_info = {
"type": "group",
"group_id": r.group_id,
@ -277,6 +284,7 @@ class ReposView(APIView):
"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)
@ -310,6 +318,7 @@ class ReposView(APIView):
repo_owner = repo_id_owner_dict[r.repo_id]
url, _, _ = api_avatar_url(repo_owner)
enable_onlyoffice, _ = get_office_feature_by_repo(r)
repo_info = {
"type": "public",
"repo_id": r.repo_id,
@ -328,6 +337,7 @@ class ReposView(APIView):
"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)
@ -369,6 +379,7 @@ class RepoView(APIView):
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 \
@ -405,6 +416,7 @@ class RepoView(APIView):
"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)

View File

@ -110,6 +110,7 @@ from seahub.settings import THUMBNAIL_EXTENSION, THUMBNAIL_ROOT, \
from seahub.subscription.utils import subscription_check
from seahub.organizations.models import OrgAdminSettings, DISABLE_ORG_ENCRYPTED_LIBRARY
from seahub.seadoc.utils import get_seadoc_file_uuid, gen_seadoc_image_parent_path, get_seadoc_asset_upload_link
from seahub.views.file import get_office_feature_by_repo
try:
from seahub.settings import CLOUD_MODE
@ -124,11 +125,6 @@ try:
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
except ImportError:
@ -2828,6 +2824,8 @@ class OwaFileView(APIView):
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.'

View File

@ -450,6 +450,11 @@ class UserPermissions(object):
if not settings.ENABLE_WIKI:
return False
return self._get_perm_by_roles('can_publish_wiki')
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):

View File

@ -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):
repo_id = models.CharField(max_length=36, db_index=True)
config_type = models.CharField(max_length=50)
config_details = models.TextField()
class Meta:
db_table = 'repo_extra_config'
unique_together = ('repo_id', 'config_type')

View File

@ -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,23 @@ EXT_DOCUMENT = [
".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

View File

@ -25,7 +25,7 @@ from django.views.decorators.csrf import csrf_exempt
from seaserv import seafile_api
from seahub.onlyoffice.models import OnlyOfficeDocKey
from seahub.onlyoffice.settings import VERIFY_ONLYOFFICE_CERTIFICATE, ONLYOFFICE_JWT_SECRET
from seahub.onlyoffice.utils import get_onlyoffice_dict, get_doc_key_by_repo_id_file_path
from seahub.onlyoffice.utils import delete_doc_key, get_file_info_by_doc_key
@ -40,6 +40,7 @@ from seahub.views import check_folder_permission
from seahub.utils.file_types import SPREADSHEET
# Get an instance of a logger
logger = logging.getLogger('onlyoffice')
@ -506,4 +507,3 @@ class OnlyofficeGetReferenceData(APIView):
}
result['token'] = jwt.encode(result, ONLYOFFICE_JWT_SECRET)
return Response({'data': result})

View File

@ -48,7 +48,8 @@ DEFAULT_ENABLED_ROLE_PERMISSIONS = {
'upload_rate_limit': 0,
'download_rate_limit': 0,
'monthly_rate_limit': '',
'monthly_rate_limit_per_user': ''
'monthly_rate_limit_per_user': '',
'can_choose_office_suite': True,
},
GUEST_USER: {
'can_add_repo': False,
@ -73,7 +74,8 @@ DEFAULT_ENABLED_ROLE_PERMISSIONS = {
'upload_rate_limit': 0,
'download_rate_limit': 0,
'monthly_rate_limit': '',
'monthly_rate_limit_per_user': ''
'monthly_rate_limit_per_user': '',
'can_choose_office_suite': False,
},
}

View File

@ -973,6 +973,26 @@ ENABLE_METADATA_MANAGEMENT = False
METADATA_SERVER_URL = ''
METADATA_SERVER_SECRET_KEY = ''
#############################
# 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 = []
# file tags
ENABLE_FILE_TAGS = True

View File

@ -158,6 +158,7 @@
enableMetadataManagement: {% if enable_metadata_management %} true {% else %} false {% endif %},
enableFileTags: {% if enable_file_tags %} true {% else %} false {% endif %},
enableShowAbout: {% if enable_show_about %} true {% else %} false {% endif %},
enableMultipleOfficeSuite: {% if user.permissions.can_choose_office_suite %} true {% else %} false {% endif %},
showWechatSupportGroup: {% if show_wechat_support_group %} true {% else %} false {% endif %},
baiduMapKey: '{{ baidu_map_key }}',
googleMapKey: '{{ google_map_key }}',

View File

@ -212,6 +212,7 @@ from seahub.api2.endpoints.wiki2 import Wikis2View, Wiki2View, Wiki2ConfigView,
from seahub.api2.endpoints.subscription import SubscriptionView, SubscriptionPlansView, SubscriptionLogsView
from seahub.api2.endpoints.user_list import UserListView
from seahub.api2.endpoints.seahub_io import SeahubIOStatus
from seahub.api2.endpoints.repo_office_suite import OfficeSuiteConfig
urlpatterns = [
@ -463,6 +464,7 @@ urlpatterns = [
re_path(r'^api/v2.1/repos/(?P<repo_id>[-0-9a-f]{36})/upload-links/(?P<token>[a-f0-9]+)/$', RepoUploadLink.as_view(), name='api-v2.1-repo-upload-link'),
re_path(r'^api/v2.1/repos/(?P<repo_id>[-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<repo_id>[-0-9a-f]{36})/image-rotate/$', RepoImageRotateView.as_view(), name='api-v2.1-repo-image-rotate-view'),
re_path(r'^api/v2.1/repos/(?P<repo_id>[-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<repo_id>[-0-9a-f]{36})/repo-api-tokens/$', RepoAPITokensView.as_view(), name='api-v2.1-repo-api-tokens'),
@ -1006,7 +1008,7 @@ if getattr(settings, 'ENABLE_MULTI_ADFS', False) or getattr(settings, 'ENABLE_AD
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')),

View File

@ -1150,7 +1150,6 @@ def react_fake_view(request, **kwargs):
'enable_metadata_management': ENABLE_METADATA_MANAGEMENT,
'enable_file_tags': settings.ENABLE_FILE_TAGS,
'enable_show_about': settings.ENABLE_SHOW_ABOUT
}
if ENABLE_METADATA_MANAGEMENT:

View File

@ -34,17 +34,18 @@ from django.template.defaultfilters import filesizeformat
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.share.utils import check_share_link_user_access
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.auth import SESSION_MOBILE_LOGIN_KEY
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
@ -66,6 +67,7 @@ from seahub.utils.http import json_response, \
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, is_repo_admin
@ -87,33 +89,34 @@ from seahub.settings import FILE_ENCODING_LIST, FILE_PREVIEW_MAX_SIZE, \
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, \
ENABLE_METADATA_MANAGEMENT, BAIDU_MAP_KEY, GOOGLE_MAP_KEY, GOOGLE_MAP_ID
ENABLE_METADATA_MANAGEMENT, BAIDU_MAP_KEY, GOOGLE_MAP_KEY, GOOGLE_MAP_ID, 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
@ -130,6 +133,7 @@ except ImportError:
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__)
@ -140,6 +144,57 @@ FILE_TYPE_FOR_NEW_FILE_LINK = [
MARKDOWN
]
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.
@ -333,6 +388,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():
@ -387,7 +444,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
@ -474,7 +531,7 @@ def convert_repo_path_when_can_not_view_file(request, repo_id, path):
@login_required
@repo_passwd_set_required
def view_lib_file(request, repo_id, path):
# resource check
repo = seafile_api.get_repo(repo_id)
if not repo:
@ -484,6 +541,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
@ -926,6 +985,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
@ -1176,6 +1237,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
@ -1430,6 +1493,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.
@ -1671,6 +1736,7 @@ def view_raw_file(request, repo_id, file_path):
repo = get_repo(repo_id)
if not repo:
raise Http404
file_path = file_path.rstrip('/')
if file_path[0] != '/':
@ -1734,7 +1800,7 @@ def download_file(request, repo_id, obj_id):
repo = get_repo(repo_id)
if not repo:
raise Http404
if repo.encrypted and not seafile_api.is_password_set(repo_id, username):
reverse_url = reverse('lib_view', args=[repo_id, repo.name, ''])
return HttpResponseRedirect(reverse_url)
@ -1922,6 +1988,7 @@ def office_convert_get_page(request, repo_id, commit_id, path, filename):
"""
if not HAS_OFFICE_CONVERTER:
raise Http404
if not _OFFICE_PAGE_PATTERN.match(filename):
return HttpResponseForbidden()

View File

@ -1,6 +1,5 @@
# Copyright (c) 2012-2016 Seafile Ltd.
import seahub.settings as settings
# OfficeOnlineServer, OnlyOffice, CollaboraOffice
OFFICE_SERVER_TYPE = getattr(settings, 'OFFICE_SERVER_TYPE', '')
@ -26,6 +25,26 @@ OFFICE_WEB_APP_CLIENT_PEM = getattr(settings, 'OFFICE_WEB_APP_CLIENT_PEM', '')
## Server certificates ##
# 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

View File

@ -11,4 +11,4 @@ class UtilsTest(BaseTestCase):
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())) == 23
assert len(list(get_enabled_role_permissions_by_role(DEFAULT_USER).keys())) == 24