From 1332822301246ac767ae7d32bd39ba7ccc7a018d Mon Sep 17 00:00:00 2001
From: uhensler <urs.hensler@frentix.com>
Date: Fri, 27 Mar 2020 08:41:36 +0100
Subject: [PATCH] OO-4598: List of documents open in an external editor

---
 .../ui/OnlyOfficeEditorController.java        |  13 +-
 .../ui/AdminDocEditorController.java          |  28 +++-
 .../doceditor/ui/DocumentsInUseDataModel.java | 136 ++++++++++++++++++
 .../ui/DocumentsInUseListController.java      | 135 +++++++++++++++++
 .../doceditor/ui/DocumentsInUseRow.java       |  60 ++++++++
 .../ui/_i18n/LocalStrings_de.properties       |   9 ++
 .../ui/_i18n/LocalStrings_en.properties       |   9 ++
 .../services/doceditor/wopi/Access.java       |   2 +
 .../services/doceditor/wopi/WopiService.java  |   3 +
 .../doceditor/wopi/manager/AccessDAO.java     |  20 +++
 .../wopi/manager/WopiServiceImpl.java         |   5 +
 .../doceditor/wopi/model/AccessImpl.java      |   1 +
 .../_spring/userPropertiesContext.xml         |  22 +++
 .../doceditor/wopi/manager/AccessDAOTest.java |  21 +++
 14 files changed, 460 insertions(+), 4 deletions(-)
 create mode 100644 src/main/java/org/olat/core/commons/services/doceditor/ui/DocumentsInUseDataModel.java
 create mode 100644 src/main/java/org/olat/core/commons/services/doceditor/ui/DocumentsInUseListController.java
 create mode 100644 src/main/java/org/olat/core/commons/services/doceditor/ui/DocumentsInUseRow.java

diff --git a/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/ui/OnlyOfficeEditorController.java b/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/ui/OnlyOfficeEditorController.java
index 8118d08d2ba..a5a4a123d55 100644
--- a/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/ui/OnlyOfficeEditorController.java
+++ b/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/ui/OnlyOfficeEditorController.java
@@ -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);
+		}
 	}
 
 }
diff --git a/src/main/java/org/olat/core/commons/services/doceditor/ui/AdminDocEditorController.java b/src/main/java/org/olat/core/commons/services/doceditor/ui/AdminDocEditorController.java
index 6d61d588a7f..a5f3fb71c40 100644
--- a/src/main/java/org/olat/core/commons/services/doceditor/ui/AdminDocEditorController.java
+++ b/src/main/java/org/olat/core/commons/services/doceditor/ui/AdminDocEditorController.java
@@ -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() {
diff --git a/src/main/java/org/olat/core/commons/services/doceditor/ui/DocumentsInUseDataModel.java b/src/main/java/org/olat/core/commons/services/doceditor/ui/DocumentsInUseDataModel.java
new file mode 100644
index 00000000000..6720a6a266e
--- /dev/null
+++ b/src/main/java/org/olat/core/commons/services/doceditor/ui/DocumentsInUseDataModel.java
@@ -0,0 +1,136 @@
+/**
+ * <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();
+		}
+	}
+}
diff --git a/src/main/java/org/olat/core/commons/services/doceditor/ui/DocumentsInUseListController.java b/src/main/java/org/olat/core/commons/services/doceditor/ui/DocumentsInUseListController.java
new file mode 100644
index 00000000000..d6afef98bfa
--- /dev/null
+++ b/src/main/java/org/olat/core/commons/services/doceditor/ui/DocumentsInUseListController.java
@@ -0,0 +1,135 @@
+/**
+ * <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() {
+		//
+	}
+
+}
diff --git a/src/main/java/org/olat/core/commons/services/doceditor/ui/DocumentsInUseRow.java b/src/main/java/org/olat/core/commons/services/doceditor/ui/DocumentsInUseRow.java
new file mode 100644
index 00000000000..c1d4367d661
--- /dev/null
+++ b/src/main/java/org/olat/core/commons/services/doceditor/ui/DocumentsInUseRow.java
@@ -0,0 +1,60 @@
+/**
+ * <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();
+	}
+
+}
diff --git a/src/main/java/org/olat/core/commons/services/doceditor/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/core/commons/services/doceditor/ui/_i18n/LocalStrings_de.properties
index 6266a1c4b6b..0c9988683dc 100644
--- a/src/main/java/org/olat/core/commons/services/doceditor/ui/_i18n/LocalStrings_de.properties
+++ b/src/main/java/org/olat/core/commons/services/doceditor/ui/_i18n/LocalStrings_de.properties
@@ -1,5 +1,6 @@
 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
diff --git a/src/main/java/org/olat/core/commons/services/doceditor/ui/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/core/commons/services/doceditor/ui/_i18n/LocalStrings_en.properties
index b18ca53a4bf..c99c7962302 100644
--- a/src/main/java/org/olat/core/commons/services/doceditor/ui/_i18n/LocalStrings_en.properties
+++ b/src/main/java/org/olat/core/commons/services/doceditor/ui/_i18n/LocalStrings_en.properties
@@ -1,5 +1,6 @@
 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
diff --git a/src/main/java/org/olat/core/commons/services/doceditor/wopi/Access.java b/src/main/java/org/olat/core/commons/services/doceditor/wopi/Access.java
index edbddfd13a3..61665ad6c06 100644
--- a/src/main/java/org/olat/core/commons/services/doceditor/wopi/Access.java
+++ b/src/main/java/org/olat/core/commons/services/doceditor/wopi/Access.java
@@ -36,6 +36,8 @@ public interface Access extends ModifiedInfo, CreateInfo {
 
 	String getToken();
 
+	String getApp();
+	
 	Date getExpiresAt();
 	
 	boolean isCanEdit();
diff --git a/src/main/java/org/olat/core/commons/services/doceditor/wopi/WopiService.java b/src/main/java/org/olat/core/commons/services/doceditor/wopi/WopiService.java
index 4526e7f9af5..55c3ac27719 100644
--- a/src/main/java/org/olat/core/commons/services/doceditor/wopi/WopiService.java
+++ b/src/main/java/org/olat/core/commons/services/doceditor/wopi/WopiService.java
@@ -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);
diff --git a/src/main/java/org/olat/core/commons/services/doceditor/wopi/manager/AccessDAO.java b/src/main/java/org/olat/core/commons/services/doceditor/wopi/manager/AccessDAO.java
index 64b339daf07..b03de18f9a9 100644
--- a/src/main/java/org/olat/core/commons/services/doceditor/wopi/manager/AccessDAO.java
+++ b/src/main/java/org/olat/core/commons/services/doceditor/wopi/manager/AccessDAO.java
@@ -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(*)");
diff --git a/src/main/java/org/olat/core/commons/services/doceditor/wopi/manager/WopiServiceImpl.java b/src/main/java/org/olat/core/commons/services/doceditor/wopi/manager/WopiServiceImpl.java
index 0f368d29063..5deb17ce9db 100644
--- a/src/main/java/org/olat/core/commons/services/doceditor/wopi/manager/WopiServiceImpl.java
+++ b/src/main/java/org/olat/core/commons/services/doceditor/wopi/manager/WopiServiceImpl.java
@@ -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);
diff --git a/src/main/java/org/olat/core/commons/services/doceditor/wopi/model/AccessImpl.java b/src/main/java/org/olat/core/commons/services/doceditor/wopi/model/AccessImpl.java
index cb508ad7d94..7eceb0f8677 100644
--- a/src/main/java/org/olat/core/commons/services/doceditor/wopi/model/AccessImpl.java
+++ b/src/main/java/org/olat/core/commons/services/doceditor/wopi/model/AccessImpl.java
@@ -121,6 +121,7 @@ public class AccessImpl implements Access, Persistable {
 		this.token = token;
 	}
 
+	@Override
 	public String getApp() {
 		return app;
 	}
diff --git a/src/main/java/org/olat/user/propertyhandlers/_spring/userPropertiesContext.xml b/src/main/java/org/olat/user/propertyhandlers/_spring/userPropertiesContext.xml
index ed0568f5905..8831f335b58 100644
--- a/src/main/java/org/olat/user/propertyhandlers/_spring/userPropertiesContext.xml
+++ b/src/main/java/org/olat/user/propertyhandlers/_spring/userPropertiesContext.xml
@@ -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">
diff --git a/src/test/java/org/olat/core/commons/services/doceditor/wopi/manager/AccessDAOTest.java b/src/test/java/org/olat/core/commons/services/doceditor/wopi/manager/AccessDAOTest.java
index 59fbc0c778e..6fa0762e10e 100644
--- a/src/test/java/org/olat/core/commons/services/doceditor/wopi/manager/AccessDAOTest.java
+++ b/src/test/java/org/olat/core/commons/services/doceditor/wopi/manager/AccessDAOTest.java
@@ -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");
-- 
GitLab