Skip to content
Snippets Groups Projects
Commit 13328223 authored by uhensler's avatar uhensler
Browse files

OO-4598: List of documents open in an external editor

parent 186a4afb
No related branches found
No related tags found
No related merge requests found
Showing
with 460 additions and 4 deletions
......@@ -50,6 +50,8 @@ public class OnlyOfficeEditorController extends BasicController {
private static final Logger log = Tracing.createLoggerFor(OnlyOfficeEditorController.class);
private Access access;
private Mode openMode;
private Long openVfsMetadataKey;
@Autowired
private OnlyOfficeModule onlyOfficeModule;
......@@ -96,6 +98,11 @@ public class OnlyOfficeEditorController extends BasicController {
mainVC.contextPut("id", "o_" + CodeHelper.getRAMUniqueID());
mainVC.contextPut("apiUrl", onlyOfficeModule.getApiUrl());
mainVC.contextPut("apiConfig", apiConfigJson);
openVfsMetadataKey = vfsMetadata.getKey();
openMode = secCallback.getMode();
log.info("Document (key={}) opened with ONLYOFFICE ({}) by {}", openVfsMetadataKey, openMode,
getIdentity());
}
}
......@@ -109,7 +116,11 @@ public class OnlyOfficeEditorController extends BasicController {
@Override
protected void doDispose() {
onlyOfficeService.deleteAccess(access);
if (access != null) {
log.info("Document (key={}) opened with ONLYOFFICE ({}) by {}", openVfsMetadataKey, openMode,
getIdentity());
onlyOfficeService.deleteAccess(access);
}
}
}
......@@ -32,6 +32,7 @@ import org.olat.core.gui.components.segmentedview.SegmentViewComponent;
import org.olat.core.gui.components.segmentedview.SegmentViewEvent;
import org.olat.core.gui.components.segmentedview.SegmentViewFactory;
import org.olat.core.gui.components.velocity.VelocityContainer;
import org.olat.core.gui.control.Controller;
import org.olat.core.gui.control.Event;
import org.olat.core.gui.control.WindowControl;
import org.olat.core.gui.control.controller.BasicController;
......@@ -51,16 +52,19 @@ public class AdminDocEditorController extends BasicController implements Activat
private static final String COLLABORA_RES_TYPE = "Collabora";
private static final String ONLY_OFFICE_RES_TYPE = "OnlyOffice";
private static final String OFFICE365_RES_TYPE = "Office365";
private static final String DOCUMENTS_IN_USE_RES_TYPE = "OpenDocuments";
private VelocityContainer mainVC;
private final Link collaboraLink;
private final Link onlyOfficeLink;
private final Link office365Link;
private final Link documentsInUseLink;
private final SegmentViewComponent segmentView;
private CollaboraAdminController collaboraCtrl;
private OnlyOfficeAdminController onlyOfficeCtrl;
private Office365AdminController office365Ctrl;
private Controller collaboraCtrl;
private Controller onlyOfficeCtrl;
private Controller office365Ctrl;
private DocumentsInUseListController documentsInUseCtrl;
public AdminDocEditorController(UserRequest ureq, WindowControl wControl) {
super(ureq, wControl);
......@@ -74,6 +78,8 @@ public class AdminDocEditorController extends BasicController implements Activat
segmentView.addSegment(onlyOfficeLink, false);
office365Link = LinkFactory.createLink("admin.office365", mainVC, this);
segmentView.addSegment(office365Link, false);
documentsInUseLink = LinkFactory.createLink("admin.documents.in.use", mainVC, this);
segmentView.addSegment(documentsInUseLink, false);
doOpenCollabora(ureq);
putInitialPanel(mainVC);
......@@ -93,6 +99,9 @@ public class AdminDocEditorController extends BasicController implements Activat
} else if(OFFICE365_RES_TYPE.equalsIgnoreCase(type)) {
doOpenOffice365(ureq);
segmentView.select(office365Link);
} else if(DOCUMENTS_IN_USE_RES_TYPE.equalsIgnoreCase(type)) {
doOpenDocumentsInUse(ureq);
segmentView.select(documentsInUseLink);
}
}
......@@ -109,6 +118,8 @@ public class AdminDocEditorController extends BasicController implements Activat
doOpenOnlyOffice(ureq);
} else if (clickedLink == office365Link) {
doOpenOffice365(ureq);
} else if (clickedLink == documentsInUseLink) {
doOpenDocumentsInUse(ureq);
}
}
}
......@@ -147,6 +158,17 @@ public class AdminDocEditorController extends BasicController implements Activat
mainVC.put("segmentCmp", office365Ctrl.getInitialComponent());
}
private void doOpenDocumentsInUse(UserRequest ureq) {
if(documentsInUseCtrl == null) {
WindowControl swControl = addToHistory(ureq, OresHelper.createOLATResourceableType(DOCUMENTS_IN_USE_RES_TYPE), null);
documentsInUseCtrl = new DocumentsInUseListController(ureq, swControl);
listenTo(documentsInUseCtrl);
} else {
documentsInUseCtrl.loadModel();
addToHistory(ureq, documentsInUseCtrl);
}
mainVC.put("segmentCmp", documentsInUseCtrl.getInitialComponent());
}
@Override
protected void doDispose() {
......
/**
* <a href="http://www.openolat.org">
* OpenOLAT - Online Learning and Training</a><br>
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); <br>
* you may not use this file except in compliance with the License.<br>
* You may obtain a copy of the License at the
* <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
* <p>
* Unless required by applicable law or agreed to in writing,<br>
* software distributed under the License is distributed on an "AS IS" BASIS, <br>
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
* See the License for the specific language governing permissions and <br>
* limitations under the License.
* <p>
* Initial code contributed and copyrighted by<br>
* frentix GmbH, http://www.frentix.com
* <p>
*/
package org.olat.core.commons.services.doceditor.ui;
import java.util.List;
import java.util.Locale;
import java.util.stream.Collectors;
import org.olat.core.commons.persistence.SortKey;
import org.olat.core.commons.services.doceditor.DocEditor.Mode;
import org.olat.core.gui.components.form.flexible.elements.FlexiTableFilter;
import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFlexiTableDataModel;
import org.olat.core.gui.components.form.flexible.impl.elements.table.FilterableFlexiTableModel;
import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiSortableColumnDef;
import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableColumnModel;
import org.olat.core.gui.components.form.flexible.impl.elements.table.SortableFlexiTableDataModel;
import org.olat.core.gui.components.form.flexible.impl.elements.table.SortableFlexiTableModelDelegate;
import org.olat.modules.fo.ui.ForumUserDataModel.UserCols;
/**
*
* Initial date: 26 Mar 2020<br>
* @author uhensler, urs.hensler@frentix.com, http://www.frentix.com
*
*/
public class DocumentsInUseDataModel extends DefaultFlexiTableDataModel<DocumentsInUseRow>
implements SortableFlexiTableDataModel<DocumentsInUseRow>, FilterableFlexiTableModel {
private final Locale locale;
private List<DocumentsInUseRow> backups;
public DocumentsInUseDataModel(FlexiTableColumnModel columnsModel, Locale locale) {
super(columnsModel);
this.locale = locale;
}
@Override
public void setObjects(List<DocumentsInUseRow> objects) {
super.setObjects(objects);
backups = objects;
}
@Override
public void filter(String searchString, List<FlexiTableFilter> filters) {
if(filters != null && !filters.isEmpty() && filters.get(0) != null && !filters.get(0).isShowAll()) {
String filterKey = filters.get(0).getFilter();
boolean canEdit = Mode.EDIT == Mode.valueOf(filterKey);
List<DocumentsInUseRow> filteredRows = backups.stream()
.filter(row -> canEdit == row.isCanEdit())
.collect(Collectors.toList());
super.setObjects(filteredRows);
} else {
super.setObjects(backups);
}
}
@Override
public void sort(SortKey orderBy) {
List<DocumentsInUseRow> rows = new SortableFlexiTableModelDelegate<>(orderBy, this, locale).sort();
super.setObjects(rows);
}
@Override
public Object getValueAt(int row, int col) {
DocumentsInUseRow reason = getObject(row);
return getValueAt(reason, col);
}
@Override
public Object getValueAt(DocumentsInUseRow row, int col) {
if(col >= 0 && col < UserCols.values().length) {
switch(DocumentsInUseCols.values()[col]) {
case username: return row.getIdentityName();
case fileName: return row.getFilename();
case app: return row.getApp();
case edit: return row.isCanEdit();
case opened: return row.getOpened();
default: return null;
}
}
int propPos = col - DocumentsInUseListController.USER_PROPS_OFFSET;
return row.getIdentityProp(propPos);
}
@Override
public DefaultFlexiTableDataModel<DocumentsInUseRow> createCopyWithEmptyList() {
return new DocumentsInUseDataModel(getTableColumnModel(), locale);
}
public enum DocumentsInUseCols implements FlexiSortableColumnDef {
username("table.header.username"),
fileName("table.header.file.name"),
app("table.header.app"),
edit("table.header.edit"),
opened("table.header.opened");
private final String i18nKey;
private DocumentsInUseCols(String i18nKey) {
this.i18nKey = i18nKey;
}
@Override
public String i18nHeaderKey() {
return i18nKey;
}
@Override
public boolean sortable() {
return true;
}
@Override
public String sortKey() {
return name();
}
}
}
/**
* <a href="http://www.openolat.org">
* OpenOLAT - Online Learning and Training</a><br>
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); <br>
* you may not use this file except in compliance with the License.<br>
* You may obtain a copy of the License at the
* <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
* <p>
* Unless required by applicable law or agreed to in writing,<br>
* software distributed under the License is distributed on an "AS IS" BASIS, <br>
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
* See the License for the specific language governing permissions and <br>
* limitations under the License.
* <p>
* Initial code contributed and copyrighted by<br>
* frentix GmbH, http://www.frentix.com
* <p>
*/
package org.olat.core.commons.services.doceditor.ui;
import java.util.ArrayList;
import java.util.List;
import org.olat.basesecurity.BaseSecurityModule;
import org.olat.core.commons.services.doceditor.DocEditor.Mode;
import org.olat.core.commons.services.doceditor.ui.DocumentsInUseDataModel.DocumentsInUseCols;
import org.olat.core.commons.services.doceditor.wopi.Access;
import org.olat.core.commons.services.doceditor.wopi.WopiService;
import org.olat.core.gui.UserRequest;
import org.olat.core.gui.components.form.flexible.FormItemContainer;
import org.olat.core.gui.components.form.flexible.elements.FlexiTableElement;
import org.olat.core.gui.components.form.flexible.elements.FlexiTableFilter;
import org.olat.core.gui.components.form.flexible.impl.FormBasicController;
import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFlexiColumnModel;
import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableColumnModel;
import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableDataModelFactory;
import org.olat.core.gui.control.Controller;
import org.olat.core.gui.control.WindowControl;
import org.olat.user.UserManager;
import org.olat.user.propertyhandlers.UserPropertyHandler;
import org.springframework.beans.factory.annotation.Autowired;
/**
*
* Initial date: 26 Mar 2020<br>
* @author uhensler, urs.hensler@frentix.com, http://www.frentix.com
*
*/
public class DocumentsInUseListController extends FormBasicController {
private static final String USER_PROPS_ID = DocumentsInUseListController.class.getCanonicalName();
public static final int USER_PROPS_OFFSET = 500;
private FlexiTableElement tableEl;
private DocumentsInUseDataModel dataModel;
private final boolean isAdministrativeUser;
private final List<UserPropertyHandler> userPropertyHandlers;
@Autowired
private WopiService wopiService;
@Autowired
private UserManager userManager;
@Autowired
private BaseSecurityModule securityModule;
public DocumentsInUseListController(UserRequest ureq, WindowControl wControl) {
super(ureq, wControl, LAYOUT_BAREBONE);
setTranslator(userManager.getPropertyHandlerTranslator(getTranslator()));
isAdministrativeUser = securityModule.isUserAllowedAdminProps(ureq.getUserSession().getRoles());
userPropertyHandlers = userManager.getUserPropertyHandlersFor(USER_PROPS_ID, isAdministrativeUser);
initForm(ureq);
loadModel();
}
@Override
protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
FlexiTableColumnModel columnsModel = FlexiTableDataModelFactory.createFlexiTableColumnModel();
if(isAdministrativeUser) {
columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(DocumentsInUseCols.username));
}
int colIndex = USER_PROPS_OFFSET;
for (int i = 0; i < userPropertyHandlers.size(); i++) {
UserPropertyHandler userPropertyHandler = userPropertyHandlers.get(i);
boolean visible = userManager.isMandatoryUserProperty(USER_PROPS_ID , userPropertyHandler);
columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(visible, userPropertyHandler.i18nColumnDescriptorLabelKey(), colIndex, null, true, "userProp-" + colIndex));
colIndex++;
}
columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(DocumentsInUseCols.fileName));
columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(DocumentsInUseCols.app));
columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(DocumentsInUseCols.edit));
columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(DocumentsInUseCols.opened));
dataModel = new DocumentsInUseDataModel(columnsModel, getLocale());
tableEl = uifactory.addTableElement(getWindowControl(), "table", dataModel, 20, false, getTranslator(), formLayout);
tableEl.setAndLoadPersistedPreferences(ureq, "doc-editor-files-in-use");
List<FlexiTableFilter> filters = new ArrayList<>();
filters.add(new FlexiTableFilter(translate("table.filter.edit"), Mode.EDIT.name()));
filters.add(new FlexiTableFilter(translate("table.filter.read.only"), Mode.VIEW.name()));
filters.add(FlexiTableFilter.SPACER);
filters.add(new FlexiTableFilter(translate("table.filter.show.all"), "showAll", true));
tableEl.setFilters("", filters, false);
tableEl.setSelectedFilterKey(Mode.EDIT.name());
}
void loadModel() {
List<Access> accesses = wopiService.getAccesses(null);
List<DocumentsInUseRow> rows = new ArrayList<>(accesses.size());
for (Access access : accesses) {
rows.add(new DocumentsInUseRow(access, userPropertyHandlers, getLocale()));
}
dataModel.setObjects(rows);
tableEl.reset(false, false, true);
}
@Override
protected void formOK(UserRequest ureq) {
//
}
@Override
protected void doDispose() {
//
}
}
/**
* <a href="http://www.openolat.org">
* OpenOLAT - Online Learning and Training</a><br>
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); <br>
* you may not use this file except in compliance with the License.<br>
* You may obtain a copy of the License at the
* <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
* <p>
* Unless required by applicable law or agreed to in writing,<br>
* software distributed under the License is distributed on an "AS IS" BASIS, <br>
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
* See the License for the specific language governing permissions and <br>
* limitations under the License.
* <p>
* Initial code contributed and copyrighted by<br>
* frentix GmbH, http://www.frentix.com
* <p>
*/
package org.olat.core.commons.services.doceditor.ui;
import java.util.List;
import java.util.Locale;
import org.olat.core.commons.services.doceditor.wopi.Access;
import org.olat.user.UserPropertiesRow;
import org.olat.user.propertyhandlers.UserPropertyHandler;
/**
*
* Initial date: 26 Mar 2020<br>
* @author uhensler, urs.hensler@frentix.com, http://www.frentix.com
*
*/
public class DocumentsInUseRow extends UserPropertiesRow {
private final Access access;
public DocumentsInUseRow(Access access, List<UserPropertyHandler> userPropertyHandlers, Locale locale) {
super(access.getIdentity(), userPropertyHandlers, locale);
this.access = access;
}
public String getFilename() {
return access.getMetadata() != null? access.getMetadata().getFilename(): null;
}
public String getApp() {
return access.getApp();
}
public Boolean isCanEdit() {
return Boolean.valueOf(access.isCanEdit());
}
public Object getOpened() {
return access.getCreationDate();
}
}
admin.collabora=$org.olat.core.commons.services.doceditor.collabora.ui\:editor.display.name
admin.data.transfer.confirmation.enabled=Best\u00E4tigung Datentransfer
admin.documents.in.use=Ge\u00F6ffnete Dokumente
admin.menu.title.alt=Dokumenteneditoren
admin.menu.title=Dokumenteneditoren
admin.office365=$org.olat.core.commons.services.doceditor.office365.ui\:editor.display.name
......@@ -25,3 +26,11 @@ doc.type.txt=Text
doc.type.xlsx=Excel
doc.type.xml=Extensible Markup Language
error.no.editor=Die Datei kann nicht angezeigt werden.
table.filter.edit=Bearbeiten
table.filter.read.only=Anzeige
table.filter.show.all=Alle anzeigen
table.header.app=Editor
table.header.edit=Bearbeiten
table.header.file.name=Dateiname
table.header.opened=Ge\u00F6ffnet
table.header.username=Benutzername
admin.collabora=$org.olat.core.commons.services.doceditor.collabora.ui\:editor.display.name
admin.data.transfer.confirmation.enabled=Confirmation of data transfer
admin.documents.in.use=Open documents
admin.menu.title.alt=Document editors
admin.menu.title=Document editors
admin.usage.roles=Restrict usage to
......@@ -25,3 +26,11 @@ doc.type.txt=Text
doc.type.xlsx=Excel
doc.type.xml=Extensible Markup Language
error.no.editor=The content of the document cannot be displayed.
table.filter.edit=Edit
table.filter.read.only=Read only
table.filter.show.all=Show all
table.header.app=Editor
table.header.edit=Edit
table.header.file.name=File name
table.header.opened=Opened
table.header.username=Username
......@@ -36,6 +36,8 @@ public interface Access extends ModifiedInfo, CreateInfo {
String getToken();
String getApp();
Date getExpiresAt();
boolean isCanEdit();
......
......@@ -21,6 +21,7 @@ package org.olat.core.commons.services.doceditor.wopi;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import org.olat.core.commons.services.doceditor.DocEditor.Mode;
import org.olat.core.commons.services.doceditor.DocEditorSecurityCallback;
......@@ -73,6 +74,8 @@ public interface WopiService {
VFSLeaf getVfsLeaf(Access access);
List<Access> getAccesses(Mode mode);
Long getAccessCount(String app, Mode mode);
void deleteAccess(String accessToken);
......
......@@ -23,6 +23,7 @@ import java.util.Date;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.persistence.TypedQuery;
import org.olat.core.commons.persistence.DB;
import org.olat.core.commons.persistence.QueryBuilder;
......@@ -120,6 +121,25 @@ class AccessDAO {
return accesses.isEmpty() ? null : accesses.get(0);
}
public List<Access> getAccesses(Mode mode) {
QueryBuilder sb = new QueryBuilder();
sb.append("select access");
sb.append(" from wopiaccess access");
sb.append(" join fetch access.metadata metadata");
sb.append(" join fetch access.identity identity");
sb.append(" join fetch identity.user user");
if (mode != null) {
sb.and().append("access.canEdit = :canEdit");
}
TypedQuery<Access> query = dbInstance.getCurrentEntityManager()
.createQuery(sb.toString(), Access.class);
if (mode != null) {
query.setParameter("canEdit", Mode.EDIT == mode);
}
return query.getResultList();
}
Long getAccessCount(String app, Mode mode) {
QueryBuilder sb = new QueryBuilder();
sb.append("select count(*)");
......
......@@ -171,6 +171,11 @@ public class WopiServiceImpl implements WopiService {
return null;
}
@Override
public List<Access> getAccesses(Mode mode) {
return accessDao.getAccesses(mode);
}
@Override
public Long getAccessCount(String app, Mode mode) {
return accessDao.getAccessCount(app, mode);
......
......@@ -121,6 +121,7 @@ public class AccessImpl implements Access, Persistable {
this.token = token;
}
@Override
public String getApp() {
return app;
}
......
......@@ -514,6 +514,28 @@
</property>
</bean>
</entry>
<entry key="org.olat.core.commons.services.doceditor.ui.DocumentsInUseListController">
<bean class="org.olat.user.propertyhandlers.UserPropertyUsageContext">
<property name="propertyHandlers">
<list>
<ref bean="userPropertyFirstName" />
<ref bean="userPropertyLastName" />
<ref bean="userPropertyEmail" />
</list>
</property>
<property name="adminViewOnlyProperties">
<set></set>
</property>
<property name="mandatoryProperties">
<set>
<ref bean="userPropertyFirstName" />
<ref bean="userPropertyLastName" />
<ref bean="userPropertyEmail" />
</set>
</property>
</bean>
</entry>
<entry key="org.olat.commons.memberlist.ui.MembersPrintController">
<bean class="org.olat.user.propertyhandlers.UserPropertyUsageContext">
......
......@@ -25,6 +25,7 @@ import static org.olat.test.JunitTestHelper.random;
import java.time.Duration;
import java.time.Instant;
import java.util.Date;
import java.util.List;
import org.assertj.core.api.SoftAssertions;
import org.junit.Before;
......@@ -138,6 +139,26 @@ public class AccessDAOTest extends OlatTestCase {
assertThat(reloadedAccess).isEqualTo(access);
}
@Test
public void shouldGetAccessesBayMode() {
Identity identity1 = JunitTestHelper.createAndPersistIdentityAsRndUser("wopi");
Identity identity2 = JunitTestHelper.createAndPersistIdentityAsRndUser("wopi2");
Identity identity3 = JunitTestHelper.createAndPersistIdentityAsRndUser("wopi3");
VFSMetadata vfsMetadata = vfsMetadataDAO.createMetadata(random(), "relPath", "file.name", new Date(), 1000l, false, "file://" + random(), "file", null);
boolean canEdit = true;
Access accessEdit1 = sut.createAccess(vfsMetadata, identity1, "app1", random(), canEdit, true, true, null);
Access accessEdit2 = sut.createAccess(vfsMetadata, identity2, "app1", random(), canEdit, true, true, null);
Access accessEdit3 = sut.createAccess(vfsMetadata, identity1, "app2", random(), canEdit, true, true, null);
Access accessView = sut.createAccess(vfsMetadata, identity3, "app1", random(), false, true, true, null);
dbInstance.commitAndCloseSession();
List<Access> accesses = sut.getAccesses(Mode.EDIT);
assertThat(accesses)
.containsExactlyInAnyOrder(accessEdit1, accessEdit2, accessEdit3)
.doesNotContain(accessView);
}
@Test
public void shouldGetAccessCount() {
Identity identity1 = JunitTestHelper.createAndPersistIdentityAsRndUser("wopi");
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment