Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nextcloud/spreed.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoas Schilling <213943+nickvergessen@users.noreply.github.com>2019-09-27 12:04:50 +0300
committerGitHub <noreply@github.com>2019-09-27 12:04:50 +0300
commit26338673df843555acda91108c8ec4c9bfe7953f (patch)
tree9ab3796ca97e7f1c4779c1593c8509aac1d48fde
parent6ce9e643d62251aa99df82d0c270c631e2dd88ad (diff)
parent68797f3c5c10b805fe2489600b8cb0911a1a21e0 (diff)
Merge pull request #2238 from nextcloud/backport/2107/stable17-add-support-for-talk-sidebar-in-public-share-pages
[stable17] Add support for Talk sidebar in public share pages
-rw-r--r--.drone.yml42
-rw-r--r--appinfo/routes.php10
-rw-r--r--css/merged-public-share.scss4
-rw-r--r--css/publicshare.scss413
-rw-r--r--css/video.scss10
-rw-r--r--js/app.js8
-rw-r--r--js/currentuser.js65
-rw-r--r--js/embedded.js2
-rw-r--r--js/filesplugin.js4
-rw-r--r--js/merged-files.json1
-rw-r--r--js/merged-guest.json1
-rw-r--r--js/merged-public-share.json32
-rw-r--r--js/merged-share-auth.json1
-rw-r--r--js/merged.json1
-rw-r--r--js/publicshare.js277
-rw-r--r--js/signaling.js4
-rw-r--r--js/views/chatview.js28
-rw-r--r--js/views/emptycontentview.js4
-rw-r--r--js/views/localvideoview.js2
-rw-r--r--js/views/participantlistview.js10
-rw-r--r--js/views/videoview.js6
-rw-r--r--js/webrtc.js6
-rw-r--r--lib/AppInfo/Application.php2
-rw-r--r--lib/Chat/AutoComplete/SearchPlugin.php9
-rw-r--r--lib/Chat/SystemMessage/Listener.php4
-rw-r--r--lib/Controller/FilesController.php23
-rw-r--r--lib/Controller/PublicShareController.php148
-rw-r--r--lib/Files/Listener.php57
-rw-r--r--lib/Files/Util.php63
-rw-r--r--lib/PublicShare/TemplateLoader.php57
-rw-r--r--lib/TalkSession.php12
-rw-r--r--tests/acceptance/config/behat.yml2
-rw-r--r--tests/acceptance/features/app-files.feature91
-rw-r--r--tests/acceptance/features/bootstrap/ChatContext.php29
-rw-r--r--tests/acceptance/features/bootstrap/TalkPublicShareContext.php77
-rw-r--r--tests/acceptance/features/public-share.feature289
-rw-r--r--tests/integration/features/bootstrap/FeatureContext.php82
-rw-r--r--tests/integration/features/bootstrap/SharingContext.php12
-rw-r--r--tests/integration/features/chat/mentions.feature196
-rw-r--r--tests/integration/features/chat/system-messages.feature62
-rw-r--r--tests/integration/features/conversation/files.feature236
-rw-r--r--tests/php/Files/UtilTest.php5
42 files changed, 2311 insertions, 76 deletions
diff --git a/.drone.yml b/.drone.yml
index 454dc6fe5..8a8b6c068 100644
--- a/.drone.yml
+++ b/.drone.yml
@@ -1169,6 +1169,48 @@ trigger:
---
kind: pipeline
+name: acc-sqlite-public-share
+
+steps:
+ - name: acceptance-public-share
+ image: nextcloudci/acceptance-php7.1:acceptance-php7.1-2
+ environment:
+ APP_NAME: spreed
+ CORE_BRANCH: master
+ SELENIUM_HOST: selenium
+ TESTS_ACCEPTANCE: public-share
+ commands:
+ - bash tests/drone-run-acceptance-tests.sh || exit 0
+ # Pre-setup steps
+ - git clone --depth 1 -b $CORE_BRANCH https://github.com/nextcloud/server ../server
+ - cp -R . ../server/apps/$APP_NAME
+ - cd ../server
+ - git submodule update --init
+ - ln --symbolic `pwd` /var/www/html
+
+ # Run acceptance tests
+ - tests/acceptance/run-local.sh --acceptance-tests-dir apps/spreed/tests/acceptance --timeout-multiplier 10 --nextcloud-server-domain acceptance-$TESTS_ACCEPTANCE --selenium-server $SELENIUM_HOST:4444 allow-git-repository-modifications features/$TESTS_ACCEPTANCE.feature
+
+services:
+ - name: cache
+ image: redis
+ - name: selenium
+ image: selenium/standalone-firefox:2.53.1-beryllium
+ environment:
+ # Reduce default log level for Selenium server (INFO) as it is too
+ # verbose.
+ JAVA_OPTS: -Dselenium.LOGGER.level=WARNING
+
+trigger:
+ branch:
+ - master
+ - stable*
+ event:
+ - pull_request
+ - push
+
+---
+kind: pipeline
name: acc-sqlite-public-share-auth
steps:
diff --git a/appinfo/routes.php b/appinfo/routes.php
index b88b28112..0339ffad4 100644
--- a/appinfo/routes.php
+++ b/appinfo/routes.php
@@ -351,6 +351,16 @@ return [
],
/**
+ * PublicShare
+ */
+ [
+ 'name' => 'PublicShare#getRoom',
+ 'url' => '/api/{apiVersion}/publicshare/{shareToken}',
+ 'verb' => 'GET',
+ 'requirements' => ['apiVersion' => 'v1'],
+ ],
+
+ /**
* Guest
*/
[
diff --git a/css/merged-public-share.scss b/css/merged-public-share.scss
new file mode 100644
index 000000000..4b1d62a9b
--- /dev/null
+++ b/css/merged-public-share.scss
@@ -0,0 +1,4 @@
+@import 'chatview.scss';
+@import 'autocomplete.scss';
+@import 'video.scss';
+@import 'publicshare.scss';
diff --git a/css/publicshare.scss b/css/publicshare.scss
new file mode 100644
index 000000000..3c925ea51
--- /dev/null
+++ b/css/publicshare.scss
@@ -0,0 +1,413 @@
+/* Special layout to include the Talk sidebar */
+
+/* The standard layout defined in the server includes a fixed header with a
+ * sticky sidebar. This causes the scroll bar for the main area to appear to the
+ * right of the sidebar, which looks confusing for the chat. Thus that layout is
+ * overridden with a full set of flex containers to cascade parent element
+ * height to the main view to limit the vertical scroll bar only to it (same
+ * thing done for the sidebar and the chat view). */
+#body-user,
+#body-public {
+ display: flex;
+ flex-direction: column;
+}
+
+#body-user #header,
+#body-public #header {
+ /* Override fixed position from server to include it in the flex layout */
+ position: static;
+ flex-shrink: 0;
+}
+
+#content {
+ display: flex;
+ flex-direction: row;
+ overflow: hidden;
+
+ flex-grow: 1;
+
+ /* Override "min-height: 100%" and "padding-top: 50px" set in server, as the
+ * header is part of the flex layout and thus the whole body is not
+ * available for the content. */
+ min-height: 0;
+ padding-top: 0;
+
+ /* Does not change anything in normal mode, but ensures that the element
+ * will stretch to the full width in full screen mode. */
+ width: 100%;
+
+ /* Override margin used in server, as the header is part of the flex layout
+ * and thus the content does not need to be pushed down. */
+ margin-top: 0;
+}
+
+#app-content {
+ display: flex;
+ flex-direction: column;
+ overflow-y: auto;
+ overflow-x: hidden;
+
+ flex-grow: 1;
+
+ margin-right: 0;
+}
+
+#files-public-content {
+ flex-grow: 1;
+}
+
+
+
+/* Properties based on the app-sidebar */
+#talk-sidebar {
+ position: relative;
+ flex-shrink: 0;
+ width: 27vw;
+ min-width: 300px;
+
+ background: var(--color-main-background);
+ border-left: 1px solid var(--color-border);
+
+ overflow-x: hidden;
+ overflow-y: auto;
+ z-index: 500;
+
+ transition: 300ms width ease-in-out,
+ 300ms min-width ease-in-out;
+}
+
+#talk-sidebar.disappear {
+ width: 0;
+ min-width: 0;
+ border-left-width: 0;
+}
+
+
+
+/* Talk sidebar */
+#talk-sidebar {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+}
+
+#talk-sidebar #emptycontent {
+ position: relative;
+
+ margin-top: 10px;
+}
+
+#talk-sidebar #call-container-wrapper {
+ /* Overlap the padding from the sidebar itself to maximize the area of the
+ * video as much as possible */
+ margin-left: -15px;
+ margin-right: -15px;
+}
+
+#talk-sidebar #call-container-wrapper #emptycontent {
+ /* Compensate for the removed margins above. */
+ margin-left: 30px;
+ margin-right: 30px;
+
+ /* Override "width: 100%" set in server so margins are respected. */
+ width: auto;
+}
+
+#talk-sidebar #call-container-wrapper #emptycontent-icon {
+ width: 128px;
+ margin: 0 auto;
+ padding-bottom: 20px;
+}
+
+#talk-sidebar #call-container-wrapper #call-container.incall ~ #emptycontent {
+ display: none;
+}
+
+#talk-sidebar #call-container-wrapper #call-container:not(.incall) {
+ display: none;
+}
+
+#talk-sidebar #call-container-wrapper #call-container {
+ position: relative;
+
+ flex-grow: 1;
+
+ /* Prevent shadows of videos from leaking on other elements. */
+ overflow: hidden;
+
+ /* Show the call container in a 16/9 proportion based on the sidebar
+ * width. This is the same proportion used for previews of images by the
+ * SidebarPreviewManager. */
+ padding-bottom: 56.25%;
+ max-height: 56.25%;
+
+ /* Ensure that the background will be black also in voice only calls. */
+ background-color: #000;
+}
+
+/* Video in Talk sidebar */
+#talk-sidebar #call-container-wrapper #videos {
+ flex-grow: 1;
+}
+
+#talk-sidebar #call-container-wrapper .videoContainer.promoted video {
+ /* Base the size of the video on its width instead of on its height;
+ * otherwise the video could appear in full height but cropped on the sides
+ * due to the space available in the sidebar being typically larger in
+ * vertical than in horizontal. */
+ width: 100%;
+ height: auto;
+}
+
+/* Screensharing in Talk sidebar */
+#talk-sidebar #call-container-wrapper #screens {
+ display: none;
+}
+
+
+
+#talk-sidebar #videos .videoContainer:not(.promoted) video {
+ /* Make the unpromoted videos smaller to not overlap too much the promoted
+ * video */
+ max-height: 100px;
+}
+
+/* The avatars are requested with a size of 128px, so reduce it to not overlap
+ * too much the promoted video */
+#talk-sidebar #videos .videoContainer:not(.promoted) .avatar,
+#talk-sidebar #videos .videoContainer:not(.promoted) .avatar img {
+ width: 64px !important;
+ height: 64px !important;
+}
+
+#talk-sidebar .participants-1 .videoView,
+#talk-sidebar .participants-2 .videoView {
+ /* Do not force the width to 200px, as otherwise the video is too tall and
+ * overlaps too much with the promoted video. */
+ min-width: initial;
+ /* z-index of 10 would put the video on top of the close button. */
+ z-index: 1;
+}
+
+#talk-sidebar .nameIndicator {
+ /* Reduce padding to bring the name closer to the bottom */
+ padding: 3px;
+ /* Use default font size, as it takes too much space otherwise */
+ font-size: initial;
+}
+
+#talk-sidebar .participants-2 .videoContainer.promoted + .videoContainer-dummy .nameIndicator {
+ /* Reduce padding to bring the name closer to the bottom */
+ padding: 3px 35%;
+}
+
+#talk-sidebar .mediaIndicator {
+ /* Move the media indicator closer to the bottom */
+ bottom: 16px;
+}
+
+
+
+#talk-sidebar .call-button {
+ text-align: center;
+ margin-top: 20px;
+ margin-bottom: 20px;
+
+ button {
+ padding-left: 26px;
+ padding-right: 26px;
+ }
+
+ .icon-loading-small {
+ /* Prevent the text from being moved when the icon is shown. */
+ position: absolute;
+
+ margin-left: 5px;
+ margin-top: 1px;
+
+ /* Unset the background image set by the server for loading icons inside
+ * buttons, as in this case the pure CSS icon can be used instead of the
+ * image. */
+ background-image: unset;
+
+ &.hidden {
+ display: none;
+ }
+ }
+}
+
+
+
+/**
+ * Cascade parent element height to the chat view in the sidebar to limit the
+ * vertical scroll bar only to the list of messages. Otherwise, the vertical
+ * scroll bar would be shown for the whole sidebar and everything would be
+ * moved when scrolling to see overflown messages.
+ *
+ * The list of messages should stretch to fill the available space at the bottom
+ * of the right sidebar, so the height is cascaded using flex boxes.
+ */
+#talk-sidebar #chatView {
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+
+ flex-grow: 1;
+
+ /* Distribute available height between call container and chat view. */
+ height: 50%;
+}
+
+#talk-sidebar .comments {
+ overflow-y: auto;
+
+ /* Needed for proper calculation of comment positions in the scrolling
+ container (as otherwise the comment position is calculated with respect
+ to the closest ancestor with a relative position) */
+ position: relative;
+}
+
+#talk-sidebar #chatView .newCommentRow,
+#talk-sidebar #chatView .comments {
+ padding-left: 15px;
+ padding-right: 15px;
+}
+
+#talk-sidebar #chatView .comments .wrapper-background,
+#talk-sidebar #chatView .comments .wrapper {
+ /* Padding is not respected in the comment wrapper due to its absolute
+ * positioning, so it must be set through its position. */
+ left: 15px;
+ right: 15px;
+}
+
+#talk-sidebar #chatView .newCommentForm.with-add-button {
+ /* Make room to show the "Add" button if needed. */
+ margin-right: 44px;
+}
+
+
+
+/**
+ * Confirm icon inside input field.
+ *
+ * The input and the icon should be direct children of a wrapper with a relative
+ * position. The input is expected to be as wide as its wrapper.
+ *
+ * It is assumed that the icon will have the standard width for buttons in
+ * inputs of 34px. However, further adjustments may be needed for the input and
+ * the padding depending on the context where they are used.
+ *
+ * The confirm icon can have a sibling loading icon to switch to (by hiding one
+ * icon and showing the other) while the operation is in progress.
+ */
+input[type="text"],
+input[type="password"] {
+ padding-right: 34px;
+
+ /* When the input is focused it is expected that pressing enter will confirm
+ * the input just like clicking on the icon would do. To hint this behaviour
+ * the opacity of the confirm icon is slightly increased in this case.
+ */
+ &:focus + .icon-confirm:not(:disabled) {
+ opacity: .6;
+ }
+
+ & + .icon-confirm {
+ position: absolute;
+ top: 0;
+ /* Compensate for right margin of inputs set in the server. */
+ right: 3px;
+
+ /* Border and background color are removed to show only the icon inside
+ * the input. */
+ border: none;
+ background-color: transparent;
+
+ opacity: .3;
+
+ &:hover:not(:disabled),
+ &:focus:not(:disabled),
+ &:active:not(:disabled) {
+ opacity: 1;
+ }
+
+ + .icon-loading-small {
+ /* Mimic size set in server for confirm button. */
+ width: 34px;
+ height: 34px;
+ padding: 7px 6px;
+ margin-top: 3px;
+ margin-bottom: 3px;
+
+ position: absolute;
+ top: 0;
+ right: 3px;
+ }
+ }
+}
+
+
+
+.authorRow {
+ .editable-text-label {
+ .label-wrapper {
+ display: flex;
+ align-items: center;
+ .edit-button .icon {
+ background-color: transparent;
+ border: none;
+ padding: 13px 22px;
+ margin: 0;
+
+ opacity: .3;
+
+ &:hover,
+ &:focus,
+ &:active {
+ opacity: 1;
+ }
+ }
+ }
+ .input-wrapper {
+ position: relative;
+
+ .icon-confirm {
+ /* Needed to override an important rule set in the
+ * server. */
+ background-color: transparent !important;
+ }
+ }
+ .label {
+ margin-left: 5px;
+ }
+ }
+
+ .guest-name p {
+ display: inline-block;
+ padding: 9px 0;
+ }
+
+ /* The specific locator is needed to have higher priority than other
+ * important rules set in the server. */
+ input.checkbox + label.link-checkbox-label,
+ input.radio + label {
+ /* If ".icon-loading-small" is set hide the checkbox and show a
+ * loading icon instead. */
+ &.icon-loading-small:before {
+ background-image: none !important;
+ background-color: transparent !important;
+ border-color: transparent !important;
+ }
+ &.icon-loading-small:after {
+ top: 22px;
+ left: 21px;
+ }
+ }
+}
+
+
+
+.hidden-important {
+ display: none !important;
+}
diff --git a/css/video.scss b/css/video.scss
index fa3bab9f8..bd06645b9 100644
--- a/css/video.scss
+++ b/css/video.scss
@@ -85,11 +85,11 @@ video {
}
.videoContainer .avatar-container {
- position: absolute;
- text-align: center;
- bottom: 44px;
- left: 0;
- width: 100%;
+ display: flex;
+ margin-bottom: 50px;
+}
+.videoContainer .avatar-container.hidden {
+ display: none;
}
.videoContainer .avatar-container .avatar {
display: inline-block;
diff --git a/js/app.js b/js/app.js
index 5df4d176e..dbb9ba24a 100644
--- a/js/app.js
+++ b/js/app.js
@@ -337,7 +337,7 @@
* @param {string} token
*/
_setRoomActive: function(token) {
- if (OC.getCurrentUser().uid) {
+ if (OCA.Talk.getCurrentUser().uid) {
this._rooms.forEach(function(room) {
room.set('active', room.get('token') === token);
});
@@ -365,7 +365,7 @@
self.stopListening(self.activeRoom, 'change:lobbyState', self.updateContentsLayout);
self.stopListening(self.activeRoom, 'change:lobbyState', self._updateSidebar);
- if (OC.getCurrentUser().uid) {
+ if (OCA.Talk.getCurrentUser().uid) {
roomChannel.trigger('active', token);
self._rooms.forEach(function(room) {
@@ -504,7 +504,7 @@
this._sidebarView = new OCA.SpreedMe.Views.SidebarView();
$('#content').append(this._sidebarView.$el);
- if (OC.getCurrentUser().uid) {
+ if (OCA.Talk.getCurrentUser().uid) {
this._rooms = new OCA.SpreedMe.Models.RoomCollection();
this.listenTo(roomChannel, 'active', this._setRoomActive);
} else {
@@ -725,7 +725,7 @@
this.signaling.disconnect();
}.bind(this));
- if (OC.getCurrentUser().uid) {
+ if (OCA.Talk.getCurrentUser().uid) {
this._showRoomList();
this.signaling.setRoomCollection(this._rooms)
.then(function(data) {
diff --git a/js/currentuser.js b/js/currentuser.js
new file mode 100644
index 000000000..65904ec47
--- /dev/null
+++ b/js/currentuser.js
@@ -0,0 +1,65 @@
+/* global OC, OCA */
+
+/**
+ *
+ * @copyright Copyright (c) 2019, Daniel Calviño Sánchez (danxuliu@gmail.com)
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+(function(OC, OCA) {
+
+ 'use strict';
+
+ OCA.Talk = OCA.Talk || {};
+
+ /**
+ * Current user as seen by Talk.
+ *
+ * This may differ from the current user returned by OC.getCurrentUser().
+ */
+ var currentUser = undefined;
+
+ /**
+ * Returns the current user set in Talk or, if none, the current user as
+ * returned by OC.getCurrentUser().
+ */
+ function getCurrentUser() {
+ if (currentUser) {
+ return currentUser;
+ }
+
+ return OC.getCurrentUser();
+ }
+
+ /**
+ * Sets the current user returned by getCurrentUser().
+ *
+ * @param string uid
+ * @param string displayName
+ */
+ function setCurrentUser(uid, displayName) {
+ currentUser = {
+ uid: uid,
+ displayName: displayName
+ };
+ }
+
+ OCA.Talk.getCurrentUser = getCurrentUser;
+ OCA.Talk.setCurrentUser = setCurrentUser;
+
+})(OC, OCA);
diff --git a/js/embedded.js b/js/embedded.js
index 7d8f1655d..17f426acd 100644
--- a/js/embedded.js
+++ b/js/embedded.js
@@ -65,7 +65,7 @@
},
initialize: function() {
- if (!OC.getCurrentUser().uid) {
+ if (!OCA.Talk.getCurrentUser().uid) {
this.initGuestName();
}
diff --git a/js/filesplugin.js b/js/filesplugin.js
index 01f3fd5f2..e6224bd9c 100644
--- a/js/filesplugin.js
+++ b/js/filesplugin.js
@@ -591,7 +591,9 @@
return shareType === OC.Share.SHARE_TYPE_USER ||
shareType === OC.Share.SHARE_TYPE_GROUP ||
shareType === OC.Share.SHARE_TYPE_CIRCLE ||
- shareType === OC.Share.SHARE_TYPE_ROOM;
+ shareType === OC.Share.SHARE_TYPE_ROOM ||
+ shareType === OC.Share.SHARE_TYPE_LINK ||
+ shareType === OC.Share.SHARE_TYPE_EMAIL;
});
if (shareTypes.length === 0) {
diff --git a/js/merged-files.json b/js/merged-files.json
index 8066ec142..0121faf7e 100644
--- a/js/merged-files.json
+++ b/js/merged-files.json
@@ -25,6 +25,7 @@
"webrtc.js",
"signaling.js",
"connection.js",
+ "currentuser.js",
"embedded.js",
"filesplugin.js"
]
diff --git a/js/merged-guest.json b/js/merged-guest.json
index 97fa53527..00a2a6d80 100644
--- a/js/merged-guest.json
+++ b/js/merged-guest.json
@@ -32,6 +32,7 @@
"webrtc.js",
"signaling.js",
"connection.js",
+ "currentuser.js",
"app.js",
"init.js"
]
diff --git a/js/merged-public-share.json b/js/merged-public-share.json
new file mode 100644
index 000000000..46db74c65
--- /dev/null
+++ b/js/merged-public-share.json
@@ -0,0 +1,32 @@
+[
+ "vendor/backbone/backbone-min.js",
+ "vendor/backbone.radio/build/backbone.radio.min.js",
+ "vendor/backbone.marionette/lib/backbone.marionette.min.js",
+ "vendor/jshashes/hashes.min.js",
+ "vendor/Caret.js/dist/jquery.caret.min.js",
+ "vendor/At.js/dist/js/jquery.atwho.min.js",
+ "simplewebrtc/bundled.js",
+ "models/chatmessage.js",
+ "models/chatmessagecollection.js",
+ "models/localstoragemodel.js",
+ "models/room.js",
+ "models/roomcollection.js",
+ "views/callbutton.js",
+ "views/chatview.js",
+ "views/editabletextlabel.js",
+ "views/emptycontentview.js",
+ "views/localvideoview.js",
+ "views/mediacontrolsview.js",
+ "views/richobjectstringparser.js",
+ "views/screenview.js",
+ "views/speakingwhilemutedwarner.js",
+ "views/templates.js",
+ "views/videoview.js",
+ "views/virtuallist.js",
+ "webrtc.js",
+ "signaling.js",
+ "connection.js",
+ "currentuser.js",
+ "embedded.js",
+ "publicshare.js"
+]
diff --git a/js/merged-share-auth.json b/js/merged-share-auth.json
index aa669a19c..2c8551326 100644
--- a/js/merged-share-auth.json
+++ b/js/merged-share-auth.json
@@ -26,6 +26,7 @@
"webrtc.js",
"signaling.js",
"connection.js",
+ "currentuser.js",
"embedded.js",
"publicshareauth.js"
]
diff --git a/js/merged.json b/js/merged.json
index f160e5017..d0de1a284 100644
--- a/js/merged.json
+++ b/js/merged.json
@@ -35,6 +35,7 @@
"webrtc.js",
"signaling.js",
"connection.js",
+ "currentuser.js",
"app.js",
"init.js"
]
diff --git a/js/publicshare.js b/js/publicshare.js
new file mode 100644
index 000000000..2778dc7f8
--- /dev/null
+++ b/js/publicshare.js
@@ -0,0 +1,277 @@
+/**
+ *
+ * @copyright Copyright (c) 2018, Daniel Calviño Sánchez (danxuliu@gmail.com)
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+(function(OCA) {
+ 'use strict';
+
+ OCA.Talk = OCA.Talk || {};
+ OCA.Talk.PublicShare = {
+
+ init: function() {
+ this._boundHideCallUi = this._hideCallUi.bind(this);
+
+ // Match the size set in the CSS.
+ OCA.Talk.Views.VideoView.prototype.participantAvatarSize = 64;
+
+ this.setupLayoutForTalkSidebar();
+
+ this.setupSignalingEventHandlers();
+
+ this.enableTalkSidebar();
+ },
+
+ setupLayoutForTalkSidebar: function() {
+ $('#app-content').append($('footer'));
+
+ this._$callContainerWrapper = $('<div id="call-container-wrapper" class="hidden"></div>');
+
+ $('#content').append('<div id="talk-sidebar" class="disappear"></div>');
+ $('#talk-sidebar').append(this._$callContainerWrapper);
+ $('#call-container-wrapper').append('<div id="call-container"></div>');
+ $('#call-container-wrapper').append('<div id="emptycontent"><div id="emptycontent-icon" class="icon-loading"></div><h2></h2><p class="emptycontent-additional"></p></div>');
+ $('#call-container').append('<div id="videos"></div>');
+ $('#call-container').append('<div id="screens"></div>');
+
+ OCA.SpreedMe.app._emptyContentView = new OCA.SpreedMe.Views.EmptyContentView({
+ el: '#call-container-wrapper > #emptycontent'
+ });
+
+ OCA.SpreedMe.app._localVideoView.render();
+ $('#videos').append(OCA.SpreedMe.app._localVideoView.$el);
+ },
+
+ enableTalkSidebar: function() {
+ var self = this;
+
+ var shareToken = $('#sharingToken').val();
+
+ if (this.hideTalkSidebarTimeout) {
+ clearTimeout(this.hideTalkSidebarTimeout);
+ delete this.hideTalkSidebarTimeout;
+ }
+
+ $.ajax({
+ url: OC.linkToOCS('apps/spreed/api/v1', 2) + 'publicshare/' + shareToken,
+ type: 'GET',
+ beforeSend: function(request) {
+ request.setRequestHeader('Accept', 'application/json');
+ },
+ success: function(ocsResponse) {
+ if (ocsResponse.ocs.data.userId) {
+ // Override "OC.getCurrentUser()" with the user returned
+ // by the controller (as the public share page uses the
+ // incognito mode, and thus it always returns an
+ // anonymous user).
+ //
+ // When the external signaling server is used it should
+ // wait until the current user is set before trying to
+ // connect, as otherwise the connection would fail due
+ // to a mismatch between the user ID given when
+ // connecting to the backend (an anonymous user) and the
+ // user that fetched the signaling settings (the actual
+ // user). However, if that happens the signaling server
+ // will retry the connection again and again, so at some
+ // point the anonymous user will have been overriden
+ // with the current user and the connection will
+ // succeed.
+ OCA.Talk.setCurrentUser(ocsResponse.ocs.data.userId, ocsResponse.ocs.data.userDisplayName);
+ }
+
+ self.setupRoom(ocsResponse.ocs.data.token);
+ },
+ error: function() {
+ // Just keep sidebar hidden
+ }
+ });
+ },
+
+ setupSignalingEventHandlers: function() {
+ var self = this;
+
+ OCA.SpreedMe.app.signaling.on('joinRoom', function(joinedRoomToken) {
+ if (OCA.SpreedMe.app.token !== joinedRoomToken) {
+ return;
+ }
+
+ function setPageTitle(title) {
+ if (title) {
+ title += ' - ';
+ } else {
+ title = '';
+ }
+ title += t('spreed', 'Talk');
+ title += ' - ' + oc_defaults.title;
+ window.document.title = title;
+ }
+
+ OCA.SpreedMe.app.signaling.syncRooms().then(function() {
+ OCA.SpreedMe.app._chatView.$el.appendTo('#talk-sidebar');
+ OCA.SpreedMe.app._chatView.setTooltipContainer($('body'));
+
+ // "joinRoom" will be called again in a forced reconnection
+ // during a call with the MCU, so the previous button needs
+ // to be removed before adding a new one.
+ if (self._callButton) {
+ self._callButton.remove();
+ }
+
+ self._callButton = new OCA.SpreedMe.Views.CallButton({
+ model: OCA.SpreedMe.app.activeRoom,
+ connection: OCA.SpreedMe.app.connection,
+ });
+ // Force initial rendering; changes in the room state will
+ // automatically render the button again from now on.
+ self._callButton.render();
+ self._callButton.$el.insertBefore(OCA.SpreedMe.app._chatView.$el);
+
+ self.stopListening(OCA.SpreedMe.app.activeRoom, 'change:participantFlags', self._updateCallContainer);
+ // Signaling uses its own event system, so Backbone methods can not
+ // be used.
+ OCA.SpreedMe.app.signaling.off('leaveCall', self._boundHideCallUi);
+
+ if (OCA.SpreedMe.app.activeRoom) {
+ self.listenTo(OCA.SpreedMe.app.activeRoom, 'change:participantFlags', self._updateCallContainer);
+ // Signaling uses its own event system, so Backbone methods can
+ // not be used.
+ OCA.SpreedMe.app.signaling.on('leaveCall', self._boundHideCallUi);
+ }
+
+ OCA.SpreedMe.app._emptyContentView.setActiveRoom(OCA.SpreedMe.app.activeRoom);
+
+ setPageTitle(OCA.SpreedMe.app.activeRoom.get('displayName'));
+
+ OCA.SpreedMe.app._chatView.setRoom(OCA.SpreedMe.app.activeRoom);
+ OCA.SpreedMe.app._messageCollection.setRoomToken(OCA.SpreedMe.app.activeRoom.get('token'));
+ OCA.SpreedMe.app._messageCollection.receiveMessages();
+
+ self.showTalkSidebar().then(function() {
+ // Once the sidebar is shown its size has changed, so
+ // the chat view needs to handle a size change.
+ OCA.SpreedMe.app._chatView.handleSizeChanged();
+ });
+ });
+ });
+ },
+
+ setupRoom: function(token) {
+ OCA.SpreedMe.app.activeRoom = new OCA.SpreedMe.Models.Room({token: token});
+ OCA.SpreedMe.app.signaling.setRoom(OCA.SpreedMe.app.activeRoom);
+
+ OCA.SpreedMe.app.token = token;
+ OCA.SpreedMe.app.signaling.joinRoom(token);
+ },
+
+ _updateCallContainer: function() {
+ var flags = OCA.SpreedMe.app.activeRoom.get('participantFlags') || 0;
+ var inCall = flags & OCA.SpreedMe.app.FLAG_IN_CALL !== 0;
+ if (inCall) {
+ this._showCallUi();
+ } else {
+ this._hideCallUi();
+ }
+ },
+
+ _showCallUi: function() {
+ if (!this._$callContainerWrapper || !this._$callContainerWrapper.hasClass('hidden')) {
+ return;
+ }
+
+ this._$callContainerWrapper.removeClass('hidden');
+ },
+
+ _hideCallUi: function() {
+ if (!this._$callContainerWrapper || this._$callContainerWrapper.hasClass('hidden')) {
+ return;
+ }
+
+ this._$callContainerWrapper.addClass('hidden');
+ },
+
+ leaveRoom: function() {
+ this.hideTalkSidebarTimeout = setTimeout(this.hideTalkSidebar, 5000);
+ },
+
+ /**
+ * Shows the Talk sidebar.
+ *
+ * The sidebar is shown with an animation; this method returns a promise
+ * that is resolved once the sidebar has been fully shown.
+ */
+ showTalkSidebar: function() {
+ var deferred = $.Deferred();
+
+ if (!$('#talk-sidebar').hasClass('disappear')) {
+ deferred.resolve();
+
+ return deferred.promise();
+ }
+
+ if ('ontransitionend' in $('#talk-sidebar').get(0)) {
+ var resolveOnceSidebarIsOpen = function(event) {
+ if (event.propertyName !== 'min-width' && event.propertyName !== 'width') {
+ return;
+ }
+
+ $('#talk-sidebar').get(0).removeEventListener('transitionend', resolveOnceSidebarIsOpen);
+
+ deferred.resolve();
+ };
+
+ $('#talk-sidebar').get(0).addEventListener('transitionend', resolveOnceSidebarIsOpen);
+ } else {
+ // The browser does not support the "ontransitionend" event, so
+ // just wait a few milliseconds more than the duration of the
+ // transition (300ms).
+ setTimeout(function() {
+ console.log('ontransitionend is not supported; the sidebar should have been fully shown by now');
+
+ deferred.resolve();
+ }, 500);
+ }
+
+ $('#talk-sidebar').removeClass('disappear');
+
+ return deferred.promise();
+ },
+
+ hideTalkSidebar: function() {
+ $('#talk-sidebar').addClass('disappear');
+
+ delete this.hideTalkSidebarTimeout;
+ },
+ };
+
+ _.extend(OCA.Talk.PublicShare, Backbone.Events);
+
+ OCA.SpreedMe.app = new OCA.Talk.Embedded();
+
+ OCA.SpreedMe.app.on('start', function() {
+ OCA.Talk.PublicShare.init();
+ });
+
+ // Unlike in the regular Talk app when Talk is embedded the signaling
+ // settings are not initially included in the HTML, so they need to be
+ // explicitly loaded before starting the app.
+ OCA.Talk.Signaling.loadSettings().then(function() {
+ OCA.SpreedMe.app.start();
+ });
+
+})(OCA);
diff --git a/js/signaling.js b/js/signaling.js
index 3fe2f2e52..c689145d4 100644
--- a/js/signaling.js
+++ b/js/signaling.js
@@ -205,7 +205,7 @@
OCA.Talk.Signaling.Base.prototype.syncRooms = function() {
var defer = $.Deferred();
- if (this.roomCollection && OC.getCurrentUser().uid) {
+ if (this.roomCollection && OCA.Talk.getCurrentUser().uid) {
this.roomCollection.fetch({
success: function(roomCollection) {
defer.resolve(roomCollection);
@@ -956,7 +956,7 @@
} else {
// Already reconnected with a new session.
this._forceReconnect = false;
- var user = OC.getCurrentUser();
+ var user = OCA.Talk.getCurrentUser();
var url = OC.linkToOCS('apps/spreed/api/v1/signaling', 2) + 'backend';
msg = {
"type": "hello",
diff --git a/js/views/chatview.js b/js/views/chatview.js
index 511a43787..7c87ee22a 100644
--- a/js/views/chatview.js
+++ b/js/views/chatview.js
@@ -206,14 +206,14 @@
}
return this._addCommentTemplate(_.extend({
- isGuest: !OC.getCurrentUser().uid,
- actorId: OC.getCurrentUser().uid || 'guest/' + this.model.get('hashedSessionId'),
- actorDisplayName: OC.getCurrentUser().displayName || '',
+ isGuest: !OCA.Talk.getCurrentUser().uid,
+ actorId: OCA.Talk.getCurrentUser().uid || 'guest/' + this.model.get('hashedSessionId'),
+ actorDisplayName: OCA.Talk.getCurrentUser().displayName || '',
newMessagePlaceholder: newMessagePlaceholder,
submitText: submitText,
shareText: t('spreed', 'Share'),
isReadOnly: isReadOnly,
- canShare: !isReadOnly && OC.getCurrentUser().uid,
+ canShare: !isReadOnly && OCA.Talk.getCurrentUser().uid,
}, params));
},
@@ -224,7 +224,7 @@
params = _.extend({
// TODO isUserAuthor is not properly set for guests
- isUserAuthor: OC.getCurrentUser().uid === params.actorId,
+ isUserAuthor: OCA.Talk.getCurrentUser().uid === params.actorId,
isGuest: params.actorType === 'guests',
}, params);
@@ -256,8 +256,8 @@
this._virtualList = new OCA.SpreedMe.Views.VirtualList(this.$container);
var avatarSize = 32;
- if (OC.getCurrentUser().uid) {
- this.$el.find('.avatar').avatar(OC.getCurrentUser().uid, avatarSize, undefined, false, undefined, OC.getCurrentUser().displayName);
+ if (OCA.Talk.getCurrentUser().uid) {
+ this.$el.find('.avatar').avatar(OCA.Talk.getCurrentUser().uid, avatarSize, undefined, false, undefined, OCA.Talk.getCurrentUser().displayName);
} else {
var displayName = this.getOption('guestNameModel').get('nick');
var customName = displayName !== t('spreed', 'Guest') ? displayName : '';
@@ -500,7 +500,7 @@
formattedMessage = formattedMessage.replace(/\n/g, '<br/>');
formattedMessage = OCA.SpreedMe.Views.RichObjectStringParser.parseMessage(
formattedMessage, commentModel.get('messageParameters'), {
- userId: OC.getCurrentUser().uid,
+ userId: OCA.Talk.getCurrentUser().uid,
sessionHash: this.model.get('hashedSessionId'),
});
@@ -807,10 +807,10 @@
setAvatar($(this), inlineAvatarSize);
});
- if (OC.getCurrentUser().uid &&
+ if (OCA.Talk.getCurrentUser().uid &&
model &&
model.get('actorType') === 'users' &&
- model.get('actorId') !== OC.getCurrentUser().uid) {
+ model.get('actorId') !== OCA.Talk.getCurrentUser().uid) {
$el.find('.authorRow .avatar, .authorRow .author').contactsMenu(
model.get('actorId'), 0, $el.find('.authorRow'));
}
@@ -827,7 +827,7 @@
});
// Contacts menu is not shown in public view.
- if (!OC.getCurrentUser().uid) {
+ if (!OCA.Talk.getCurrentUser().uid) {
return;
}
@@ -836,7 +836,7 @@
var $avatar = $this.find('.avatar');
var user = $avatar.data('user-id');
- if (user !== OC.getCurrentUser().uid) {
+ if (user !== OCA.Talk.getCurrentUser().uid) {
$this.contactsMenu(user, 0, $this);
}
});
@@ -884,7 +884,7 @@
img.width = previewSize;
img.height = previewSize;
- if (OC.getCurrentUser().uid) {
+ if (OCA.Talk.getCurrentUser().uid) {
img.onerror = handlePreviewLoadError;
img.src = previewUrl;
} else {
@@ -1018,7 +1018,7 @@
message: message
};
- if (!OC.getCurrentUser().uid) {
+ if (!OCA.Talk.getCurrentUser().uid) {
var guestNick = OCA.SpreedMe.app._localStorageModel.get('nick');
if (guestNick) {
data.actorDisplayName = guestNick;
diff --git a/js/views/emptycontentview.js b/js/views/emptycontentview.js
index bd39238f2..b4774c648 100644
--- a/js/views/emptycontentview.js
+++ b/js/views/emptycontentview.js
@@ -211,7 +211,7 @@
var messageAdditional = '';
var url = '';
- var isGuest = (OC.getCurrentUser().uid === null);
+ var isGuest = (OCA.Talk.getCurrentUser().uid === null);
var participants = this._activeRoom.get('participants');
var numberOfParticipants = Object.keys(participants).length;
@@ -230,7 +230,7 @@
participantName = '';
_.each(participants, function(data, userId) {
- if (OC.getCurrentUser().uid !== userId) {
+ if (OCA.Talk.getCurrentUser().uid !== userId) {
participantId = userId;
participantName = data.name;
}
diff --git a/js/views/localvideoview.js b/js/views/localvideoview.js
index d12f7a508..5240cb383 100644
--- a/js/views/localvideoview.js
+++ b/js/views/localvideoview.js
@@ -103,7 +103,7 @@
return;
}
- var userId = OC.getCurrentUser().uid;
+ var userId = OCA.Talk.getCurrentUser().uid;
var guestName = localStorage.getItem("nick");
this.setAvatar(userId, guestName);
diff --git a/js/views/participantlistview.js b/js/views/participantlistview.js
index 4d66e7d2c..8fbbd2efa 100644
--- a/js/views/participantlistview.js
+++ b/js/views/participantlistview.js
@@ -81,8 +81,8 @@
templateContext: function() {
var isSelf = false,
isModerator = false;
- if (OC.getCurrentUser().uid) {
- isSelf = this.model.get('userId') === OC.getCurrentUser().uid;
+ if (OCA.Talk.getCurrentUser().uid) {
+ isSelf = this.model.get('userId') === OCA.Talk.getCurrentUser().uid;
isModerator = this.room.get('participantType') === OCA.SpreedMe.app.OWNER ||
this.room.get('participantType') === OCA.SpreedMe.app.MODERATOR;
} else {
@@ -102,7 +102,7 @@
}
var isGuestOrGuestModerator = this.model.get('participantType') === OCA.SpreedMe.app.GUEST || this.model.get('participantType') === OCA.SpreedMe.app.GUEST_MODERATOR;
- var hasContactsMenu = OC.getCurrentUser().uid && !isSelf && !isGuestOrGuestModerator;
+ var hasContactsMenu = OCA.Talk.getCurrentUser().uid && !isSelf && !isGuestOrGuestModerator;
return {
canModerate: canModerate,
@@ -139,8 +139,8 @@
}
});
- if (OC.getCurrentUser().uid && model.get('userId') &&
- model.get('userId') !== OC.getCurrentUser().uid) {
+ if (OCA.Talk.getCurrentUser().uid && model.get('userId') &&
+ model.get('userId') !== OCA.Talk.getCurrentUser().uid) {
this.$el.find('.participant-entry .avatar').contactsMenu(
model.get('userId'), 0, this.$el.find('.participant-entry'));
diff --git a/js/views/videoview.js b/js/views/videoview.js
index 9b7411ee6..2e62f502a 100644
--- a/js/views/videoview.js
+++ b/js/views/videoview.js
@@ -53,6 +53,8 @@
template: OCA.Talk.Views.Templates['videoview'],
+ participantAvatarSize: 128,
+
ui: {
'audio': 'audio',
'video': 'video',
@@ -129,9 +131,9 @@
this._participantName = participantName;
if (userId && userId.length) {
- this.getUI('avatar').avatar(userId, 128);
+ this.getUI('avatar').avatar(userId, this.participantAvatarSize);
} else {
- this.getUI('avatar').imageplaceholder('?', rawParticipantName, 128);
+ this.getUI('avatar').imageplaceholder('?', rawParticipantName, this.participantAvatarSize);
this.getUI('avatar').css('background-color', '#b9b9b9');
}
diff --git a/js/webrtc.js b/js/webrtc.js
index 39bcd1ef2..225fcd27f 100644
--- a/js/webrtc.js
+++ b/js/webrtc.js
@@ -381,7 +381,7 @@ var spreedPeerConnectionTable = [];
detectSpeakingEvents: true,
connection: signaling,
enableDataChannels: true,
- nick: OC.getCurrentUser().displayName
+ nick: OCA.Talk.getCurrentUser().displayName
});
if (signaling.hasFeature('mcu')) {
// Force "Plan-B" semantics if the MCU is used, which doesn't support
@@ -536,7 +536,7 @@ var spreedPeerConnectionTable = [];
} else {
OCA.SpreedMe.webrtc.emit('audioOn');
}
- if (!OC.getCurrentUser()['uid']) {
+ if (!OCA.Talk.getCurrentUser()['uid']) {
var currentGuestNick = localStorage.getItem("nick");
sendDataChannelToAll('status', 'nickChanged', currentGuestNick);
}
@@ -624,7 +624,7 @@ var spreedPeerConnectionTable = [];
OCA.SpreedMe.videos.stopSendingNick(peer);
peer.nickInterval = setInterval(function() {
var payload;
- var user = OC.getCurrentUser();
+ var user = OCA.Talk.getCurrentUser();
if (!user.uid) {
payload = localStorage.getItem("nick");
} else {
diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php
index 1a0e13db8..79d969729 100644
--- a/lib/AppInfo/Application.php
+++ b/lib/AppInfo/Application.php
@@ -43,6 +43,7 @@ use OCA\Spreed\Notification\Listener as NotificationListener;
use OCA\Spreed\Notification\Notifier;
use OCA\Spreed\PublicShareAuth\Listener as PublicShareAuthListener;
use OCA\Spreed\PublicShareAuth\TemplateLoader as PublicShareAuthTemplateLoader;
+use OCA\Spreed\PublicShare\TemplateLoader as PublicShareTemplateLoader;
use OCA\Spreed\Room;
use OCA\Spreed\Settings\Personal;
use OCA\Spreed\Share\RoomShareProvider;
@@ -110,6 +111,7 @@ class Application extends App {
ParserListener::register($dispatcher);
PublicShareAuthListener::register($dispatcher);
PublicShareAuthTemplateLoader::register($dispatcher);
+ PublicShareTemplateLoader::register($dispatcher);
FilesListener::register($dispatcher);
FilesTemplateLoader::register($dispatcher);
RestrictStartingCallsListener::register($dispatcher);
diff --git a/lib/Chat/AutoComplete/SearchPlugin.php b/lib/Chat/AutoComplete/SearchPlugin.php
index 8658dbd99..13c1f35e9 100644
--- a/lib/Chat/AutoComplete/SearchPlugin.php
+++ b/lib/Chat/AutoComplete/SearchPlugin.php
@@ -84,8 +84,6 @@ class SearchPlugin implements ISearchPlugin {
if (!empty($usersWithFileAccess)) {
$this->searchUsers($search, $usersWithFileAccess, $searchResult);
}
-
- return false;
}
$userIds = $guestSessionHashes = [];
@@ -113,6 +111,8 @@ class SearchPlugin implements ISearchPlugin {
protected function searchUsers(string $search, array $userIds, ISearchResult $searchResult): void {
$search = strtolower($search);
+ $type = new SearchResultType('users');
+
$matches = $exactMatches = [];
foreach ($userIds as $userId) {
if ($this->userId !== '' && $this->userId === $userId) {
@@ -120,6 +120,10 @@ class SearchPlugin implements ISearchPlugin {
continue;
}
+ if ($searchResult->hasResult($type, $userId)) {
+ continue;
+ }
+
if ($search === '') {
$matches[] = $this->createResult('user', $userId, '');
continue;
@@ -151,7 +155,6 @@ class SearchPlugin implements ISearchPlugin {
}
}
- $type = new SearchResultType('users');
$searchResult->addResultSet($type, $matches, $exactMatches);
}
diff --git a/lib/Chat/SystemMessage/Listener.php b/lib/Chat/SystemMessage/Listener.php
index c18cb016f..5b53b48e9 100644
--- a/lib/Chat/SystemMessage/Listener.php
+++ b/lib/Chat/SystemMessage/Listener.php
@@ -195,7 +195,9 @@ class Listener {
/** @var self $listener */
$listener = \OC::$server->query(self::class);
foreach ($participants as $participant) {
- if ($room->getObjectType() === 'file' || $userId !== $participant['userId']) {
+ $userJoinedFileRoom = $room->getObjectType() === 'file' &&
+ (!array_key_exists('participantType', $participant) || $participant['participantType'] !== Participant::USER_SELF_JOINED);
+ if ($userJoinedFileRoom || $userId !== $participant['userId']) {
$listener->sendSystemMessage($room, 'user_added', ['user' => $participant['userId']]);
}
}
diff --git a/lib/Controller/FilesController.php b/lib/Controller/FilesController.php
index be16d07dc..6c4b86a45 100644
--- a/lib/Controller/FilesController.php
+++ b/lib/Controller/FilesController.php
@@ -67,20 +67,25 @@ class FilesController extends OCSController {
*
* Returns the token of the room associated to the given file id.
*
+ * This is the counterpart of PublicShareController::getRoom() for file ids
+ * instead of share tokens, although both return the same room token if the
+ * given file id and share token refer to the same file.
+ *
* If there is no room associated to the given file id a new room is
* created; the new room is a public room associated with a "file" object
* with the given file id. Unlike normal rooms in which the owner is the
* user that created the room these are special rooms without owner
- * (although self joined users become persistent participants automatically
- * when they join until they explicitly leave or no longer have access to
- * the file).
+ * (although self joined users with direct access to the file become
+ * persistent participants automatically when they join until they
+ * explicitly leave or no longer have access to the file).
*
* In any case, to create or even get the token of the room, the file must
- * be shared and the user must have direct access to that file; an error
- * is returned otherwise. A user has direct access to a file if she has
- * access to it (or to an ancestor) through a user, group, circle or room
- * share (but not through a link share, for example), or if she is the owner
- * of such a file.
+ * be shared and the user must be the owner of a public share of the file
+ * (like a link share, for example) or have direct access to that file; an
+ * error is returned otherwise. A user has direct access to a file if she
+ * has access to it (or to an ancestor) through a user, group, circle or
+ * room share (but not through a link share, for example), or if she is the
+ * owner of such a file.
*
* @param string $fileId
* @return DataResponse the status code is "200 OK" if a room is returned,
@@ -88,7 +93,7 @@ class FilesController extends OCSController {
* @throws OCSNotFoundException
*/
public function getRoom(string $fileId): DataResponse {
- $share = $this->util->getAnyDirectShareOfFileAccessibleByUser($fileId, $this->currentUser);
+ $share = $this->util->getAnyPublicShareOfFileOwnedByUserOrAnyDirectShareOfFileAccessibleByUser($fileId, $this->currentUser);
$groupFolder = null;
if (!$share) {
$groupFolder = $this->util->getGroupFolderNode($fileId, $this->currentUser);
diff --git a/lib/Controller/PublicShareController.php b/lib/Controller/PublicShareController.php
new file mode 100644
index 000000000..b2e4c9a40
--- /dev/null
+++ b/lib/Controller/PublicShareController.php
@@ -0,0 +1,148 @@
+<?php
+declare(strict_types=1);
+
+/**
+ *
+ * @copyright Copyright (c) 2019, Daniel Calviño Sánchez (danxuliu@gmail.com)
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\Spreed\Controller;
+
+use OCA\Spreed\Exceptions\RoomNotFoundException;
+use OCA\Spreed\Manager;
+use OCA\Spreed\TalkSession;
+use OCP\AppFramework\Http;
+use OCP\AppFramework\Http\DataResponse;
+use OCP\AppFramework\OCSController;
+use OCP\Files\FileInfo;
+use OCP\Files\NotFoundException;
+use OCP\IRequest;
+use OCP\IUser;
+use OCP\IUserManager;
+use OCP\ISession;
+use OCP\Share\Exceptions\ShareNotFound;
+use OCP\Share\IManager as ShareManager;
+use OCP\Share\IShare;
+
+class PublicShareController extends OCSController {
+
+ /** @var string|null */
+ private $userId;
+ /** @var IUserManager */
+ private $userManager;
+ /** @var ShareManager */
+ private $shareManager;
+ /** @var ISession */
+ private $session;
+ /** @var TalkSession */
+ private $talkSession;
+ /** @var Manager */
+ private $manager;
+
+ public function __construct(
+ $appName,
+ ?string $UserId,
+ IRequest $request,
+ IUserManager $userManager,
+ ShareManager $shareManager,
+ ISession $session,
+ TalkSession $talkSession,
+ Manager $manager
+ ) {
+ parent::__construct($appName, $request);
+ $this->userId = $UserId;
+ $this->userManager = $userManager;
+ $this->shareManager = $shareManager;
+ $this->session = $session;
+ $this->talkSession = $talkSession;
+ $this->manager = $manager;
+ }
+
+ /**
+ * @PublicPage
+ * @UseSession
+ *
+ * Returns the token of the room associated to the file id of the given
+ * share token.
+ *
+ * This is the counterpart of FilesController::getRoom() for share tokens
+ * instead of file ids, although both return the same room token if the
+ * given file id and share token refer to the same file.
+ *
+ * If there is no room associated to the file id of the given share token a
+ * new room is created; the new room is a public room associated with a
+ * "file" object with the file id of the given share token. Unlike normal
+ * rooms in which the owner is the user that created the room these are
+ * special rooms without owner (although self joined users with direct
+ * access to the file become persistent participants automatically when they
+ * join until they explicitly leave or no longer have access to the file).
+ *
+ * In any case, to create or even get the token of the room, the file must
+ * be publicly shared (like a link share, for example); an error is returned
+ * otherwise.
+ *
+ * Besides the token of the room this also returns the current user ID and
+ * display name, if any; this is needed by the Talk sidebar to know the
+ * actual current user, as the public share page uses the incognito mode and
+ * thus logged in users as seen as guests.
+ *
+ * @param string $shareToken
+ * @return DataResponse the status code is "200 OK" if a room is returned,
+ * or "404 Not found" if the given share token was invalid.
+ */
+ public function getRoom(string $shareToken) {
+ try {
+ $share = $this->shareManager->getShareByToken($shareToken);
+ if ($share->getPassword() !== null) {
+ $shareId = $this->session->get('public_link_authenticated');
+ if ($share->getId() !== $shareId) {
+ throw new ShareNotFound();
+ }
+ }
+ } catch (ShareNotFound $e) {
+ return new DataResponse([], Http::STATUS_NOT_FOUND);
+ }
+
+ if ($share->getNodeType() !== FileInfo::TYPE_FILE) {
+ return new DataResponse([], Http::STATUS_NOT_FOUND);
+ }
+
+ $fileId = (string)$share->getNodeId();
+
+ try {
+ $room = $this->manager->getRoomByObject('file', $fileId);
+ } catch (RoomNotFoundException $e) {
+ $name = $share->getNode()->getName();
+ $room = $this->manager->createPublicRoom($name, 'file', $fileId);
+ }
+
+ $this->talkSession->setFileShareTokenForRoom($room->getToken(), $shareToken);
+
+ $currentUser = $this->userManager->get($this->userId);
+ $currentUserId = $currentUser instanceof IUser ? $currentUser->getUID() : '';
+ $currentUserDisplayName = $currentUser instanceof IUser ? $currentUser->getDisplayName() : '';
+
+ return new DataResponse([
+ 'token' => $room->getToken(),
+ 'userId' => $currentUserId,
+ 'userDisplayName' => $currentUserDisplayName,
+ ]);
+ }
+
+}
diff --git a/lib/Files/Listener.php b/lib/Files/Listener.php
index caad44a87..105dff0bc 100644
--- a/lib/Files/Listener.php
+++ b/lib/Files/Listener.php
@@ -26,6 +26,7 @@ namespace OCA\Spreed\Files;
use OCA\Spreed\Exceptions\ParticipantNotFoundException;
use OCA\Spreed\Exceptions\UnauthorizedException;
use OCA\Spreed\Room;
+use OCA\Spreed\TalkSession;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\GenericEvent;
@@ -34,11 +35,12 @@ use Symfony\Component\EventDispatcher\GenericEvent;
*
* The rooms for files are intended to give the users a way to talk about a
* specific shared file, for example, when collaboratively editing it. The room
- * is persistent and can be accessed simultaneously by any user with direct
+ * is persistent and can be accessed simultaneously by any user or guest if the
+ * file is publicly shared (link share, for example), or by any user with direct
* access (user, group, circle and room share, but not link share, for example)
* to that file (or to an ancestor). The room has no owner, although self joined
- * users become persistent participants automatically when they join until they
- * explicitly leave or no longer have access to the file.
+ * users with direct access become persistent participants automatically when
+ * they join until they explicitly leave or no longer have access to the file.
*
* These rooms are associated to a "file" object, and their custom behaviour is
* provided by calling the methods of this class as a response to different room
@@ -48,9 +50,13 @@ class Listener {
/** @var Util */
protected $util;
+ /** @var TalkSession */
+ protected $talkSession;
- public function __construct(Util $util) {
+ public function __construct(Util $util,
+ TalkSession $talkSession) {
$this->util = $util;
+ $this->talkSession = $talkSession;
}
public static function register(EventDispatcherInterface $dispatcher): void {
@@ -61,7 +67,7 @@ class Listener {
$listener = \OC::$server->query(self::class);
try {
- $listener->preventUsersWithoutDirectAccessToTheFileFromJoining($room, $event->getArgument('userId'));
+ $listener->preventUsersWithoutAccessToTheFileFromJoining($room, $event->getArgument('userId'));
$listener->addUserAsPersistentParticipant($room, $event->getArgument('userId'));
} catch (UnauthorizedException $e) {
$event->setArgument('cancel', true);
@@ -72,8 +78,11 @@ class Listener {
$listener = function(GenericEvent $event) {
/** @var Room $room */
$room = $event->getSubject();
+ /** @var self $listener */
+ $listener = \OC::$server->query(self::class);
+
try {
- self::preventGuestsFromJoining($room);
+ $listener->preventGuestsFromJoiningIfNotPubliclyAccessible($room);
} catch (UnauthorizedException $e) {
$event->setArgument('cancel', true);
}
@@ -82,8 +91,10 @@ class Listener {
}
/**
- * Prevents users from joining if they do not have direct access to the
- * file.
+ * Prevents users from joining if they do not have access to the file.
+ *
+ * A user has access to the file if the file is publicly accessible (through
+ * a link share, for example) or if the user has direct access to it.
*
* A user has direct access to a file if she received the file (or an
* ancestor) through a user, group, circle or room share (but not through a
@@ -95,16 +106,22 @@ class Listener {
* @param string $userId
* @throws UnauthorizedException
*/
- public function preventUsersWithoutDirectAccessToTheFileFromJoining(Room $room, string $userId): void {
+ public function preventUsersWithoutAccessToTheFileFromJoining(Room $room, string $userId): void {
if ($room->getObjectType() !== 'file') {
return;
}
- $share = $this->util->getAnyDirectShareOfFileAccessibleByUser($room->getObjectId(), $userId);
+ // If a guest can access the file then any user can too.
+ $shareToken = $this->talkSession->getFileShareTokenForRoom($room->getToken());
+ if ($shareToken && $this->util->canGuestAccessFile($shareToken)) {
+ return;
+ }
+
+ $share = $this->util->getAnyPublicShareOfFileOwnedByUserOrAnyDirectShareOfFileAccessibleByUser($room->getObjectId(), $userId);
if (!$share) {
$groupFolder = $this->util->getGroupFolderNode($room->getObjectId(), $userId);
if (!$groupFolder) {
- throw new UnauthorizedException('User does not have direct access to the file');
+ throw new UnauthorizedException('User does not have access to the file');
}
}
}
@@ -112,6 +129,9 @@ class Listener {
/**
* Add user as a persistent participant of a file room.
*
+ * Only users with direct access to the file are added as persistent
+ * participants of the room.
+ *
* This method should be called before a user joins a room, but only if the
* user should be able to join the room.
*
@@ -123,6 +143,10 @@ class Listener {
return;
}
+ if (!$this->util->getAnyPublicShareOfFileOwnedByUserOrAnyDirectShareOfFileAccessibleByUser($room->getObjectId(), $userId)) {
+ return;
+ }
+
try {
$room->getParticipant($userId);
} catch (ParticipantNotFoundException $e) {
@@ -131,19 +155,24 @@ class Listener {
}
/**
- * Prevents guests from joining the room.
+ * Prevents guests from joining the room if it is not publicly accessible.
*
* This method should be called before a guest joins a room.
*
* @param Room $room
* @throws UnauthorizedException
*/
- protected static function preventGuestsFromJoining(Room $room): void {
+ protected function preventGuestsFromJoiningIfNotPubliclyAccessible(Room $room): void {
if ($room->getObjectType() !== 'file') {
return;
}
- throw new UnauthorizedException('Guests are not allowed in rooms for files');
+ $shareToken = $this->talkSession->getFileShareTokenForRoom($room->getToken());
+ if ($shareToken && $this->util->canGuestAccessFile($shareToken)) {
+ return;
+ }
+
+ throw new UnauthorizedException('Guests are not allowed in this room');
}
}
diff --git a/lib/Files/Util.php b/lib/Files/Util.php
index 331f6d946..0f00b36ce 100644
--- a/lib/Files/Util.php
+++ b/lib/Files/Util.php
@@ -28,6 +28,8 @@ use OCP\Files\FileInfo;
use OCP\Files\IRootFolder;
use OCP\Files\Node;
use OCP\Files\NotFoundException;
+use OCP\ISession;
+use OCP\Share\Exceptions\ShareNotFound;
use OCP\Share\IManager as IShareManager;
use OCP\Share\IShare;
@@ -35,14 +37,18 @@ class Util {
/** @var IRootFolder */
private $rootFolder;
+ /** @var ISession */
+ private $session;
/** @var IShareManager */
private $shareManager;
/** @var array[] */
private $accessLists = [];
public function __construct(IRootFolder $rootFolder,
+ ISession $session,
IShareManager $shareManager) {
$this->rootFolder = $rootFolder;
+ $this->session = $session;
$this->shareManager = $shareManager;
}
@@ -67,8 +73,29 @@ class Util {
return \in_array($userId, $this->getUsersWithAccessFile($fileId), true);
}
+ public function canGuestAccessFile(string $shareToken): bool {
+ try {
+ $share = $this->shareManager->getShareByToken($shareToken);
+ if ($share->getPassword() !== null) {
+ $shareId = $this->session->get('public_link_authenticated');
+ if ($share->getId() !== $shareId) {
+ throw new ShareNotFound();
+ }
+ }
+ return true;
+ } catch (ShareNotFound $e) {
+ return false;
+ }
+ }
+
/**
- * Returns any share of the file that the user has direct access to.
+ * Returns any share of the file that is public and owned by the user, or
+ * that the user has direct access to.
+ *
+ * A public share is one accessible by any user, including guests, like a
+ * share by link. Note that only a share of the file itself is taken into
+ * account; if an ancestor folder is shared publicly that share will not be
+ * returned.
*
* A user has direct access to a share and, thus, to a file, if she received
* the file through a user, group, circle or room share (but not through a
@@ -82,7 +109,7 @@ class Util {
* @param string $userId
* @return IShare|null
*/
- public function getAnyDirectShareOfFileAccessibleByUser(string $fileId, string $userId): ?IShare {
+ public function getAnyPublicShareOfFileOwnedByUserOrAnyDirectShareOfFileAccessibleByUser(string $fileId, string $userId): ?IShare {
$userFolder = $this->rootFolder->getUserFolder($userId);
$nodes = $userFolder->getById($fileId);
if (empty($nodes)) {
@@ -93,6 +120,13 @@ class Util {
return $node->getType() === FileInfo::TYPE_FILE;
});
+ if (!empty($nodes)) {
+ $share = $this->getAnyPublicShareOfNodeOwnedByUser($nodes[0], $userId);
+ if ($share) {
+ return $share;
+ }
+ }
+
while (!empty($nodes)) {
$node = array_pop($nodes);
@@ -111,6 +145,31 @@ class Util {
}
/**
+ * Returns any public share of the node (like a link share) created by the
+ * user.
+ *
+ * @param Node $node
+ * @param string $userId
+ * @return IShare|null
+ */
+ private function getAnyPublicShareOfNodeOwnedByUser(Node $node, string $userId): ?IShare {
+ $reshares = false;
+ $limit = 1;
+
+ $shares = $this->shareManager->getSharesBy($userId, \OCP\Share::SHARE_TYPE_LINK, $node, $reshares, $limit);
+ if (\count($shares) > 0) {
+ return $shares[0];
+ }
+
+ $shares = $this->shareManager->getSharesBy($userId, \OCP\Share::SHARE_TYPE_EMAIL, $node, $reshares, $limit);
+ if (\count($shares) > 0) {
+ return $shares[0];
+ }
+
+ return null;
+ }
+
+ /**
* Returns any share of the node that the user has direct access to.
*
* @param Node $node
diff --git a/lib/PublicShare/TemplateLoader.php b/lib/PublicShare/TemplateLoader.php
new file mode 100644
index 000000000..32c5d9389
--- /dev/null
+++ b/lib/PublicShare/TemplateLoader.php
@@ -0,0 +1,57 @@
+<?php
+declare(strict_types=1);
+
+/**
+ *
+ * @copyright Copyright (c) 2018, Daniel Calviño Sánchez (danxuliu@gmail.com)
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\Spreed\PublicShare;
+
+use OCP\Util;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+
+/**
+ * Helper class to extend the "publicshare" template from the server.
+ *
+ * The "loadTalkSidebarUi" method loads additional scripts that, when run on the
+ * browser, adjust the page generated by the server to inject the Talk UI as
+ * needed.
+ */
+class TemplateLoader {
+
+ public static function register(EventDispatcherInterface $dispatcher): void {
+ $listener = function() {
+ self::loadTalkSidebarUi();
+ };
+ $dispatcher->addListener('OCA\Files_Sharing::loadAdditionalScripts', $listener);
+ }
+
+ /**
+ * Load the "Talk sidebar" UI in the public share page for the given share.
+ *
+ * This method should be called when loading additional scripts for the
+ * public share page of the server.
+ */
+ public static function loadTalkSidebarUi() {
+ Util::addStyle('spreed', 'merged-public-share');
+ Util::addScript('spreed', 'merged-public-share');
+ }
+
+}
diff --git a/lib/TalkSession.php b/lib/TalkSession.php
index 97c8107a3..cee112d03 100644
--- a/lib/TalkSession.php
+++ b/lib/TalkSession.php
@@ -51,6 +51,18 @@ class TalkSession {
$this->removeValue('spreed-session', $token);
}
+ public function getFileShareTokenForRoom(string $roomToken): ?string {
+ return $this->getValue('spreed-file-share-token', $roomToken);
+ }
+
+ public function setFileShareTokenForRoom(string $roomToken, string $shareToken): void {
+ $this->setValue('spreed-file-share-token', $roomToken, $shareToken);
+ }
+
+ public function removeFileShareTokenForRoom(string $roomToken): void {
+ $this->removeValue('spreed-file-share-token', $roomToken);
+ }
+
public function getPasswordForRoom(string $token): ?string {
return $this->getValue('spreed-password', $token);
}
diff --git a/tests/acceptance/config/behat.yml b/tests/acceptance/config/behat.yml
index 7f663333d..fa792b327 100644
--- a/tests/acceptance/config/behat.yml
+++ b/tests/acceptance/config/behat.yml
@@ -18,7 +18,6 @@ default:
- FilesAppSharingContext
- LoginPageContext
- NotificationContext
- - PublicShareContext
# Talk app contexts
- ChatContext
@@ -30,6 +29,7 @@ default:
- PublicConversationContext
- PublicSharePasswordRequestContext
- TalkAppContext
+ - TalkPublicShareContext
extensions:
Behat\MinkExtension:
diff --git a/tests/acceptance/features/app-files.feature b/tests/acceptance/features/app-files.feature
index 7cd385236..b22ba557a 100644
--- a/tests/acceptance/features/app-files.feature
+++ b/tests/acceptance/features/app-files.feature
@@ -38,7 +38,7 @@ Feature: app-files
# share is ready before continuing.
And I write down the shared link
And I open the Chat tab in the details view of the Files app
- Then I see that the "Start a conversation Share this file with others to discuss Share" empty content message is shown in the chat tab
+ Then I see that the chat is shown in the Chat tab
Scenario: chat tab header is not shown in a folder even if shared
Given I am logged in as the admin
@@ -84,6 +84,51 @@ Feature: app-files
+ Scenario: mention a user that has not joined yet but has access to a file room
+ Given I act as John
+ And I am logged in as the admin
+ And I share "welcome.txt" with "user0"
+ And I see that the file is shared with "user0"
+ And I open the Chat tab in the details view of the Files app
+ And I see that the chat is shown in the Chat tab
+ And I act as Jane
+ And I am logged in
+ And I have opened the Talk app
+ # Wait until the "Talk updates" conversation is shown to ensure that the
+ # list is loaded before checking that there is no "welcome.txt" conversation
+ And I see that the "Talk updates ✅" conversation is shown in the list
+ And I see that the "welcome.txt" conversation is not shown in the list
+ When I act as John
+ And I type a new chat message with the text "Hello @"
+ And I choose the candidate mention for "user0"
+ And I send the current chat message
+ Then I see that the message 1 was sent by "admin" with the text "Hello user0"
+ And I see that the message 1 contains a formatted mention of "user0"
+ And I act as Jane
+ And I see that the "welcome.txt" conversation is shown in the list
+
+ Scenario: mention all users when a user has not joined yet the file room
+ Given I act as John
+ And I am logged in as the admin
+ And I share "welcome.txt" with "user0"
+ And I see that the file is shared with "user0"
+ And I open the Chat tab in the details view of the Files app
+ And I see that the chat is shown in the Chat tab
+ When I type a new chat message with the text "Hello @"
+ And I choose the candidate mention for "welcome.txt"
+ And I send the current chat message
+ Then I see that the message 1 was sent by "admin" with the text "Hello welcome.txt"
+ And I see that the message 1 contains a formatted mention of all participants of "welcome.txt"
+ And I act as Jane
+ And I am logged in
+ And I have opened the Talk app
+ # Wait until the "Talk updates" conversation is shown to ensure that the
+ # list is loaded before checking that there is no "welcome.txt" conversation
+ And I see that the "Talk updates ✅" conversation is shown in the list
+ And I see that the "welcome.txt" conversation is not shown in the list
+
+
+
Scenario: chat in a shared file
Given I act as John
And I am logged in as the admin
@@ -156,3 +201,47 @@ Feature: app-files
# And I see that the message 1 was sent by "admin" with the text "Hello"
# And I see that the message 2 was sent by "user0" with the text "Hi!"
# And I see that the message 3 was sent by "user1" with the text "Hey!"
+
+ Scenario: chat in a file shared by link
+ Given I act as John
+ And I am logged in
+ And I share the link for "welcome.txt"
+ And I write down the shared link
+ And I open the Chat tab in the details view of the Files app
+ And I act as Jane
+ And I visit the shared link I wrote down
+ And I see that the current page is the shared link I wrote down
+ When I act as John
+ And I send a new chat message with the text "Hello"
+ And I act as Jane
+ And I see that the message 1 was sent by "user0" with the text "Hello"
+ And I send a new chat message with the text "Hi!"
+ Then I see that the message 1 was sent by "user0" with the text "Hello"
+ And I see that the message 2 was sent by "Guest" with the text "Hi!"
+ And I act as John
+ And I see that the message 1 was sent by "user0" with the text "Hello"
+ And I see that the message 2 was sent by "Guest" with the text "Hi!"
+
+
+
+ Scenario: chat in a file shared by link with a password
+ Given I act as John
+ And I am logged in
+ And I share the link for "welcome.txt" protected by the password "abcdef"
+ And I write down the shared link
+ And I open the Chat tab in the details view of the Files app
+ And I act as Jane
+ And I visit the shared link I wrote down
+ And I see that the current page is the Authenticate page for the shared link I wrote down
+ And I authenticate with password "abcdef"
+ And I see that the current page is the shared link I wrote down
+ When I act as John
+ And I send a new chat message with the text "Hello"
+ And I act as Jane
+ And I see that the message 1 was sent by "user0" with the text "Hello"
+ And I send a new chat message with the text "Hi!"
+ Then I see that the message 1 was sent by "user0" with the text "Hello"
+ And I see that the message 2 was sent by "Guest" with the text "Hi!"
+ And I act as John
+ And I see that the message 1 was sent by "user0" with the text "Hello"
+ And I see that the message 2 was sent by "Guest" with the text "Hi!"
diff --git a/tests/acceptance/features/bootstrap/ChatContext.php b/tests/acceptance/features/bootstrap/ChatContext.php
index 19f0cbf22..0f377c95b 100644
--- a/tests/acceptance/features/bootstrap/ChatContext.php
+++ b/tests/acceptance/features/bootstrap/ChatContext.php
@@ -204,9 +204,27 @@ class ChatContext implements Context, ActorAwareInterface {
/**
* @return Locator
*/
+ public static function newChatMessageRow($chatAncestor) {
+ return Locator::forThe()->css(".newCommentRow")->
+ descendantOf(self::chatView($chatAncestor))->
+ describedAs("New chat message row");
+ }
+
+ /**
+ * @return Locator
+ */
+ public static function userNameLabel($chatAncestor) {
+ return Locator::forThe()->css(".author")->
+ descendantOf(self::newChatMessageRow($chatAncestor))->
+ describedAs("User name label");
+ }
+
+ /**
+ * @return Locator
+ */
public static function guestNameEditableTextLabel($chatAncestor) {
return Locator::forThe()->css(".guest-name.editable-text-label")->
- descendantOf(self::chatView($chatAncestor))->
+ descendantOf(self::newChatMessageRow($chatAncestor))->
describedAs("Guest name editable text label");
}
@@ -233,7 +251,7 @@ class ChatContext implements Context, ActorAwareInterface {
*/
public static function newChatMessageForm($chatAncestor) {
return Locator::forThe()->css(".newCommentForm")->
- descendantOf(self::chatView($chatAncestor))->
+ descendantOf(self::newChatMessageRow($chatAncestor))->
describedAs("New chat message form");
}
@@ -365,6 +383,13 @@ class ChatContext implements Context, ActorAwareInterface {
}
/**
+ * @Then I see that the current participant is the user :user
+ */
+ public function iSeeThatTheCurrentParticipantIsTheUser($user) {
+ PHPUnit_Framework_Assert::assertEquals($user, $this->actor->find(self::userNameLabel($this->chatAncestor), 10)->getText());
+ }
+
+ /**
* @Then I see that the message :number was sent by :author with the text :message
*/
public function iSeeThatTheMessageWasSentByWithTheText($number, $author, $message) {
diff --git a/tests/acceptance/features/bootstrap/TalkPublicShareContext.php b/tests/acceptance/features/bootstrap/TalkPublicShareContext.php
new file mode 100644
index 000000000..a25f33786
--- /dev/null
+++ b/tests/acceptance/features/bootstrap/TalkPublicShareContext.php
@@ -0,0 +1,77 @@
+<?php
+
+/**
+ *
+ * @copyright Copyright (c) 2019, Daniel Calviño Sánchez (danxuliu@gmail.com)
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+class TalkPublicShareContext extends PublicShareContext {
+
+ use ChatAncestorSetter;
+
+ /**
+ * @return Locator
+ */
+ public static function talkSidebar() {
+ return Locator::forThe()->css("#talk-sidebar")->
+ describedAs("Talk sidebar in the public share page");
+ }
+
+ /**
+ * @override I see that the current page is the shared link I wrote down
+ */
+ public function iSeeThatTheCurrentPageIsTheSharedLinkIWroteDown() {
+ parent::iSeeThatTheCurrentPageIsTheSharedLinkIWroteDown();
+
+ $this->setChatAncestorForActor(self::talkSidebar(), $this->actor);
+ }
+
+ /**
+ * @Then I see that the Talk sidebar is shown in the public share page
+ */
+ public function iSeeThatTheTalkSidebarIsShownInThePublicSharePage() {
+ if (!WaitFor::elementToBeEventuallyShown(
+ $this->actor,
+ self::talkSidebar(),
+ $timeout = 10 * $this->actor->getFindTimeoutMultiplier())) {
+ PHPUnit_Framework_Assert::fail("The Talk sidebar is not shown yet after $timeout seconds");
+ }
+ }
+
+ /**
+ * @Then I see that the Talk sidebar is not shown in the public share page
+ */
+ public function iSeeThatTheTalkSidebarIsNotShownInThePublicSharePage() {
+ try {
+ // Wait a little before deciding that the sidebar is not shown, as
+ // the sidebar would be loaded after the page has loaded.
+ $this->actor->find(self::talkSidebar(), 5);
+
+ // Once the sidebar has loaded it is not immediately shown; it will
+ // be shown once it has joined the room, so wait a little more to
+ // "ensure" that it is not shown.
+ sleep(2 * $this->actor->getFindTimeoutMultiplier());
+
+ PHPUnit_Framework_Assert::assertFalse(
+ $this->actor->find(self::talkSidebar())->isVisible());
+ } catch (NoSuchElementException $exception) {
+ }
+ }
+
+}
diff --git a/tests/acceptance/features/public-share.feature b/tests/acceptance/features/public-share.feature
new file mode 100644
index 000000000..0dcaa3d91
--- /dev/null
+++ b/tests/acceptance/features/public-share.feature
@@ -0,0 +1,289 @@
+Feature: public share
+
+ Scenario: open the public shared link of a file
+ Given I act as John
+ And I am logged in
+ And I share the link for "welcome.txt"
+ And I write down the shared link
+ When I act as Jane
+ And I visit the shared link I wrote down
+ And I see that the current page is the shared link I wrote down
+ Then I see that the Talk sidebar is shown in the public share page
+
+ Scenario: open the public shared link of a folder
+ Given I act as John
+ And I am logged in
+ And I create a new folder named "Shared folder"
+ # To share the link the "Share" inline action has to be clicked but, as the
+ # details view is opened automatically when the folder is created, clicking
+ # on the inline action could fail if it is covered by the details view due
+ # to its opening animation. Instead of ensuring that the animations of the
+ # contents and the details view have both finished it is easier to close the
+ # details view and wait until it is closed before continuing.
+ And I close the details view
+ And I see that the details view is closed
+ And I share the link for "Shared folder"
+ And I write down the shared link
+ When I act as Jane
+ And I visit the shared link I wrote down
+ And I see that the current page is the shared link I wrote down
+ Then I see that the Talk sidebar is not shown in the public share page
+
+
+
+ Scenario: open Talk after opening the public shared link of a file as a user with direct access to the file
+ Given I act as John
+ And I am logged in as the admin
+ And I share "welcome.txt" with "user0"
+ And I see that the file is shared with "user0"
+ And I share the link for "welcome.txt"
+ And I write down the shared link
+ And I act as Jane
+ And I am logged in
+ And I have opened the Talk app
+ # Wait until the "Talk updates" conversation is shown to ensure that the
+ # list is loaded before checking that there is no "welcome.txt" conversation
+ And I see that the "Talk updates ✅" conversation is shown in the list
+ And I see that the "welcome.txt" conversation is not shown in the list
+ And I visit the shared link I wrote down
+ And I see that the current page is the shared link I wrote down
+ And I see that the Talk sidebar is shown in the public share page
+ And I see that the current participant is the user "user0"
+ # Visit the Home page so the header shows again the list of apps
+ When I visit the Home page
+ And I have opened the Talk app
+ Then I see that the "welcome.txt" conversation is shown in the list
+
+ Scenario: open Talk after opening the public shared link of a file as a user without direct access to the file
+ Given I act as John
+ And I am logged in
+ And I share the link for "welcome.txt"
+ And I write down the shared link
+ And I act as Jane
+ And I am logged in as the admin
+ And I have opened the Talk app
+ # Wait until the "Talk updates" conversation is shown to ensure that the
+ # list is loaded before checking that there is no "welcome.txt" conversation
+ And I see that the "Talk updates ✅" conversation is shown in the list
+ And I see that the "welcome.txt" conversation is not shown in the list
+ And I visit the shared link I wrote down
+ And I see that the current page is the shared link I wrote down
+ And I see that the Talk sidebar is shown in the public share page
+ And I see that the current participant is the user "admin"
+ # Log in with the same user from a different window to check Talk while the
+ # original window is in the public share page
+ And I act as Jim
+ And I am logged in as the admin
+ And I have opened the Talk app
+ And I see that the "welcome.txt" conversation is shown in the list
+ # Leave the public share page from the original window by going to Talk and
+ # checking that the conversation is no longer shown
+ When I act as Jane
+ # Visit the Home page so the header shows again the list of apps
+ And I visit the Home page
+ And I have opened the Talk app
+ # Wait until the "Talk updates" conversation is shown to ensure that the
+ # list is loaded before checking that there is no "welcome.txt" conversation
+ And I see that the "Talk updates ✅" conversation is shown in the list
+ Then I see that the "welcome.txt" conversation is not shown in the list
+
+
+
+ Scenario: mention a user that has direct access to a file shared by link
+ Given I act as John
+ And I am logged in as the admin
+ And I share "welcome.txt" with "user0"
+ And I see that the file is shared with "user0"
+ And I share the link for "welcome.txt"
+ And I write down the shared link
+ And I visit the shared link I wrote down
+ And I see that the current page is the shared link I wrote down
+ And I see that the Talk sidebar is shown in the public share page
+ When I type a new chat message with the text "Hello @"
+ And I choose the candidate mention for "user0"
+ And I send the current chat message
+ Then I see that the message 1 was sent by "admin" with the text "Hello user0"
+ And I see that the message 1 contains a formatted mention of "user0"
+ And I act as Jane
+ And I am logged in
+ And I have opened the Talk app
+ And I see that the "welcome.txt" conversation is shown in the list
+
+ Scenario: mention a user that has no direct access to a file shared by link
+ Given I act as John
+ And I am logged in as the admin
+ And I share the link for "welcome.txt"
+ And I write down the shared link
+ And I visit the shared link I wrote down
+ And I see that the current page is the shared link I wrote down
+ And I see that the Talk sidebar is shown in the public share page
+ When I send a new chat message with the text "Hello @user0"
+ Then I see that the message 1 was sent by "admin" with the text "Hello user0"
+ And I see that the message 1 contains a formatted mention of "user0"
+ And I act as Jane
+ And I am logged in
+ And I have opened the Talk app
+ # Wait until the "Talk updates" conversation is shown to ensure that the
+ # list is loaded before checking that there is no "welcome.txt" conversation
+ And I see that the "Talk updates ✅" conversation is shown in the list
+ And I see that the "welcome.txt" conversation is not shown in the list
+
+ Scenario: mention a user that has no direct access to a file shared by link while the user is in the public share page
+ Given I act as John
+ And I am logged in
+ And I share the link for "welcome.txt"
+ And I write down the shared link
+ And I act as Jane
+ And I visit the shared link I wrote down
+ And I see that the current page is the shared link I wrote down
+ And I see that the Talk sidebar is shown in the public share page
+ And I act as Jim
+ And I am logged in as the admin
+ And I visit the shared link I wrote down
+ And I see that the current page is the shared link I wrote down
+ And I see that the Talk sidebar is shown in the public share page
+ When I act as Jane
+ And I type a new chat message with the text "Hello @"
+ And I choose the candidate mention for "admin"
+ And I send the current chat message
+ Then I see that the message 1 was sent by "Guest" with the text "Hello admin"
+ And I see that the message 1 contains a formatted mention of "admin"
+ And I act as Jim
+ And I see that the message 1 was sent by "Guest" with the text "Hello admin"
+ And I see that the message 1 contains a formatted mention of "admin" as current user
+ # Leave the public share page from the original window by going to Talk and
+ # checking that the conversation is no longer shown
+ # Visit the Home page so the header shows again the list of apps
+ And I visit the Home page
+ And I have opened the Talk app
+ # Wait until the "Talk updates" conversation is shown to ensure that the
+ # list is loaded before checking that there is no "welcome.txt" conversation
+ And I see that the "Talk updates ✅" conversation is shown in the list
+ And I see that the "welcome.txt" conversation is not shown in the list
+
+ Scenario: mention another guest in the public share page
+ Given I act as John
+ And I am logged in
+ And I share the link for "welcome.txt"
+ And I write down the shared link
+ And I act as Jane
+ And I visit the shared link I wrote down
+ And I see that the current page is the shared link I wrote down
+ And I see that the Talk sidebar is shown in the public share page
+ And I set my guest name to "Cat"
+ And I act as Jim
+ And I visit the shared link I wrote down
+ And I see that the current page is the shared link I wrote down
+ And I see that the Talk sidebar is shown in the public share page
+ When I type a new chat message with the text "Hello @"
+ And I choose the candidate mention for "Cat"
+ And I send the current chat message
+ Then I see that the message 1 was sent by "Guest" with the text "Hello CCat"
+ And I see that the message 1 contains a formatted mention of "Cat"
+ And I act as Jane
+ And I see that the message 1 was sent by "Guest" with the text "Hello CCat"
+ And I see that the message 1 contains a formatted mention of "Cat" as current user
+
+ Scenario: mention all users when a user with direct access has not joined yet the file room
+ Given I act as John
+ And I am logged in as the admin
+ And I share "welcome.txt" with "user0"
+ And I see that the file is shared with "user0"
+ And I share the link for "welcome.txt"
+ And I write down the shared link
+ And I act as Jane
+ And I visit the shared link I wrote down
+ And I see that the current page is the shared link I wrote down
+ And I see that the Talk sidebar is shown in the public share page
+ When I type a new chat message with the text "Hello @"
+ And I choose the candidate mention for "welcome.txt"
+ And I send the current chat message
+ Then I see that the message 1 was sent by "Guest" with the text "Hello welcome.txt"
+ And I see that the message 1 contains a formatted mention of all participants of "welcome.txt"
+ And I act as Jim
+ And I am logged in
+ And I have opened the Talk app
+ # Wait until the "Talk updates" conversation is shown to ensure that the
+ # list is loaded before checking that there is no "welcome.txt" conversation
+ And I see that the "Talk updates ✅" conversation is shown in the list
+ And I see that the "welcome.txt" conversation is not shown in the list
+
+
+
+ Scenario: chat in the public share page of a link share
+ Given I act as John
+ And I am logged in
+ And I share the link for "welcome.txt"
+ And I write down the shared link
+ And I visit the shared link I wrote down
+ And I see that the current page is the shared link I wrote down
+ And I act as Jane
+ And I am logged in as the admin
+ And I visit the shared link I wrote down
+ And I see that the current page is the shared link I wrote down
+ And I act as Jim
+ And I visit the shared link I wrote down
+ And I see that the current page is the shared link I wrote down
+ And I set my guest name to "Rob"
+ When I act as John
+ And I send a new chat message with the text "Hello"
+ And I act as Jane
+ And I see that the message 1 was sent by "user0" with the text "Hello"
+ And I send a new chat message with the text "Hi!"
+ And I act as Jim
+ And I see that the message 2 was sent by "admin" with the text "Hi!"
+ And I send a new chat message with the text "Hey!"
+ Then I see that the message 1 was sent by "user0" with the text "Hello"
+ And I see that the message 2 was sent by "admin" with the text "Hi!"
+ And I see that the message 3 was sent by "Rob" with the text "Hey!"
+ And I act as Jane
+ And I see that the message 1 was sent by "user0" with the text "Hello"
+ And I see that the message 2 was sent by "admin" with the text "Hi!"
+ And I see that the message 3 was sent by "Rob" with the text "Hey!"
+ And I act as John
+ And I see that the message 1 was sent by "user0" with the text "Hello"
+ And I see that the message 2 was sent by "admin" with the text "Hi!"
+ And I see that the message 3 was sent by "Rob" with the text "Hey!"
+
+
+
+ Scenario: chat in the public share page of a link share with a password
+ Given I act as John
+ And I am logged in
+ And I share the link for "welcome.txt" protected by the password "abcdef"
+ And I write down the shared link
+ And I visit the shared link I wrote down
+ # Even the owner needs to authenticate when accessing the shared link
+ And I see that the current page is the Authenticate page for the shared link I wrote down
+ And I authenticate with password "abcdef"
+ And I see that the current page is the shared link I wrote down
+ And I act as Jane
+ And I am logged in as the admin
+ And I visit the shared link I wrote down
+ And I see that the current page is the Authenticate page for the shared link I wrote down
+ And I authenticate with password "abcdef"
+ And I see that the current page is the shared link I wrote down
+ And I act as Jim
+ And I visit the shared link I wrote down
+ And I see that the current page is the Authenticate page for the shared link I wrote down
+ And I authenticate with password "abcdef"
+ And I see that the current page is the shared link I wrote down
+ When I act as John
+ And I send a new chat message with the text "Hello"
+ And I act as Jane
+ And I see that the message 1 was sent by "user0" with the text "Hello"
+ And I send a new chat message with the text "Hi!"
+ And I act as Jim
+ And I see that the message 2 was sent by "admin" with the text "Hi!"
+ And I send a new chat message with the text "Hey!"
+ Then I see that the message 1 was sent by "user0" with the text "Hello"
+ And I see that the message 2 was sent by "admin" with the text "Hi!"
+ And I see that the message 3 was sent by "Guest" with the text "Hey!"
+ And I act as Jane
+ And I see that the message 1 was sent by "user0" with the text "Hello"
+ And I see that the message 2 was sent by "admin" with the text "Hi!"
+ And I see that the message 3 was sent by "Guest" with the text "Hey!"
+ And I act as John
+ And I see that the message 1 was sent by "user0" with the text "Hello"
+ And I see that the message 2 was sent by "admin" with the text "Hi!"
+ And I see that the message 3 was sent by "Guest" with the text "Hey!"
diff --git a/tests/integration/features/bootstrap/FeatureContext.php b/tests/integration/features/bootstrap/FeatureContext.php
index 1c8ce26c9..1ce98a883 100644
--- a/tests/integration/features/bootstrap/FeatureContext.php
+++ b/tests/integration/features/bootstrap/FeatureContext.php
@@ -23,6 +23,7 @@ require __DIR__ . '/../../vendor/autoload.php';
use Behat\Behat\Context\Context;
use Behat\Behat\Context\SnippetAcceptingContext;
+use Behat\Behat\Hook\Scope\BeforeScenarioScope;
use Behat\Gherkin\Node\TableNode;
use GuzzleHttp\Client;
use GuzzleHttp\Cookie\CookieJar;
@@ -65,6 +66,9 @@ class FeatureContext implements Context, SnippetAcceptingContext {
/** @var array */
protected $createdGroups = [];
+ /** @var SharingContext */
+ private $sharingContext;
+
public static function getTokenForIdentifier(string $identifier) {
return self::$identifierToToken[$identifier];
}
@@ -92,6 +96,15 @@ class FeatureContext implements Context, SnippetAcceptingContext {
}
/**
+ * @BeforeScenario
+ */
+ public function getOtherRequiredSiblingContexts(BeforeScenarioScope $scope) {
+ $environment = $scope->getEnvironment();
+
+ $this->sharingContext = $environment->getContext("SharingContext");
+ }
+
+ /**
* @AfterScenario
*/
public function tearDown() {
@@ -363,6 +376,30 @@ class FeatureContext implements Context, SnippetAcceptingContext {
}
/**
+ * @Then /^user "([^"]*)" gets the room for last share with (\d+)$/
+ *
+ * @param string $user
+ * @param int $statusCode
+ */
+ public function userGetsTheRoomForLastShare($user, $statusCode) {
+ $shareToken = $this->sharingContext->getLastShareToken();
+
+ $this->setCurrentUser($user);
+ $this->sendRequest('GET', '/apps/spreed/api/v1/publicshare/' . $shareToken);
+ $this->assertStatusCode($this->response, $statusCode);
+
+ if ($statusCode !== '200') {
+ return;
+ }
+
+ $response = $this->getDataFromResponse($this->response);
+
+ $identifier = 'file last share room';
+ self::$identifierToToken[$identifier] = $response['token'];
+ self::$tokenToIdentifier[$response['token']] = $identifier;
+ }
+
+ /**
* @Then /^user "([^"]*)" joins room "([^"]*)" with (\d+)$/
*
* @param string $user
@@ -1071,6 +1108,51 @@ class FeatureContext implements Context, SnippetAcceptingContext {
*/
/**
+ * @Given /^user "([^"]*)" logs in$/
+ */
+ public function userLogsIn(string $user) {
+ $loginUrl = $this->baseUrl . '/login';
+
+ $cookieJar = $this->getUserCookieJar($user);
+
+ // Request a new session and extract CSRF token
+ $client = new Client();
+ $this->response = $client->get(
+ $loginUrl,
+ [
+ 'cookies' => $cookieJar,
+ ]
+ );
+
+ $requestToken = $this->extractRequestTokenFromResponse($this->response);
+
+ // Login and extract new token
+ $password = ($user === 'admin') ? 'admin' : '123456';
+ $client = new Client();
+ $this->response = $client->post(
+ $loginUrl,
+ [
+ 'body' => [
+ 'user' => $user,
+ 'password' => $password,
+ 'requesttoken' => $requestToken,
+ ],
+ 'cookies' => $cookieJar,
+ ]
+ );
+
+ $this->assertStatusCode($this->response, 200);
+ }
+
+ /**
+ * @param ResponseInterface $response
+ * @return string
+ */
+ private function extractRequestTokenFromResponse(ResponseInterface $response): string {
+ return substr(preg_replace('/(.*)data-requesttoken="(.*)">(.*)/sm', '\2', $response->getBody()->getContents()), 0, 89);
+ }
+
+ /**
* @When /^sending "([^"]*)" to "([^"]*)" with$/
* @param string $verb
* @param string $url
diff --git a/tests/integration/features/bootstrap/SharingContext.php b/tests/integration/features/bootstrap/SharingContext.php
index deabef7d0..987ceee8f 100644
--- a/tests/integration/features/bootstrap/SharingContext.php
+++ b/tests/integration/features/bootstrap/SharingContext.php
@@ -211,9 +211,10 @@ class SharingContext implements Context {
* @param string $user
* @param string $path
* @param int $statusCode
+ * @param TableNode|null $body
*/
- public function userSharesByLinkWithOcs(string $user, string $path, int $statusCode) {
- $this->userSharesByLink($user, $path);
+ public function userSharesByLinkWithOcs(string $user, string $path, int $statusCode, TableNode $body = null) {
+ $this->userSharesByLink($user, $path, $body);
$this->theOCSStatusCodeShouldBe($statusCode);
}
@@ -886,6 +887,13 @@ class SharingContext implements Context {
/**
* @return string
*/
+ public function getLastShareToken(): string {
+ return (string)$this->lastCreatedShareData->data[0]->token;
+ }
+
+ /**
+ * @return string
+ */
private function getLastShareId(): string {
return (string)$this->lastCreatedShareData->data[0]->id;
}
diff --git a/tests/integration/features/chat/mentions.feature b/tests/integration/features/chat/mentions.feature
index 504e78f54..e1df90e4e 100644
--- a/tests/integration/features/chat/mentions.feature
+++ b/tests/integration/features/chat/mentions.feature
@@ -321,3 +321,199 @@ Feature: chat/mentions
And user "participant2" is not participant of room "file welcome.txt room"
When user "participant1" sends message "hi @participant2" to room "file welcome.txt room" with 201
Then user "participant2" is participant of room "file welcome.txt room"
+
+
+
+ Scenario: get mentions in a room for a file shared by link with no other joined participant
+ Given user "participant1" shares "welcome.txt" with user "participant2" with OCS 100
+ And user "participant1" shares "welcome.txt" by link with OCS 100
+ When user "participant1" gets the room for last share with 200
+ And user "participant1" joins room "file last share room" with 200
+ And user "participant1" is participant of room "file last share room"
+ And user "participant2" is not participant of room "file last share room"
+ Then user "participant1" gets the following candidate mentions in room "file last share room" for "" with 200
+ | id | label | source |
+ | all | welcome.txt | calls |
+ | participant2 | participant2-displayname | users |
+ And user "participant2" gets the following candidate mentions in room "file last share room" for "" with 404
+ And user "participant3" gets the following candidate mentions in room "file last share room" for "" with 404
+ And user "guest" gets the following candidate mentions in room "file last share room" for "" with 404
+
+ Scenario: get mentions in a room for a file shared by link
+ Given user "participant1" shares "welcome.txt" with user "participant2" with OCS 100
+ And user "participant1" shares "welcome.txt" with user "participant4" with OCS 100
+ And user "participant1" shares "welcome.txt" by link with OCS 100
+ When user "participant1" gets the room for last share with 200
+ And user "participant1" joins room "file last share room" with 200
+ And user "participant2" joins room "file last share room" with 200
+ # Users without access to a file shared by link need to log in (so further
+ # requests keep the same session) and get the room (so the share token is
+ # stored in the session) to be able to join it.
+ And user "participant3" logs in
+ And user "participant3" gets the room for last share with 200
+ And user "participant3" joins room "file last share room" with 200
+ # Guests need to get the room (so the share token is stored in the session)
+ # to be able to join it.
+ And user "guest" gets the room for last share with 200
+ And user "guest" joins room "file last share room" with 200
+ And user "participant1" is participant of room "file last share room"
+ And user "participant2" is participant of room "file last share room"
+ And user "participant3" is participant of room "file last share room"
+ And user "guest" is participant of room "file last share room"
+ Then user "participant1" gets the following candidate mentions in room "file last share room" for "" with 200
+ | id | label | source |
+ | all | welcome.txt | calls |
+ | participant2 | participant2-displayname | users |
+ | participant4 | participant4-displayname | users |
+ | participant3 | participant3-displayname | users |
+ | GUEST_ID | Guest | guests |
+ And user "participant2" gets the following candidate mentions in room "file last share room" for "" with 200
+ | id | label | source |
+ | all | welcome.txt | calls |
+ | participant1 | participant1-displayname | users |
+ | participant4 | participant4-displayname | users |
+ | participant3 | participant3-displayname | users |
+ | GUEST_ID | Guest | guests |
+ # Self-joined users can not mention users with access to the file that have
+ # not joined the room.
+ And user "participant3" gets the following candidate mentions in room "file last share room" for "" with 200
+ | id | label | source |
+ | all | welcome.txt | calls |
+ | participant1 | participant1-displayname | users |
+ | participant2 | participant2-displayname | users |
+ | GUEST_ID | Guest | guests |
+ # Guests can not mention users with access to the file that have not joined
+ # the room.
+ And user "guest" gets the following candidate mentions in room "file last share room" for "" with 200
+ | id | label | source |
+ | all | welcome.txt | calls |
+ | participant1 | participant1-displayname | users |
+ | participant2 | participant2-displayname | users |
+ | participant3 | participant3-displayname | users |
+
+ Scenario: get matched mentions in a room for a file shared by link
+ Given user "participant1" shares "welcome.txt" with user "participant2" with OCS 100
+ And user "participant1" shares "welcome.txt" with user "participant4" with OCS 100
+ And user "participant1" shares "welcome.txt" by link with OCS 100
+ When user "participant2" gets the room for last share with 200
+ And user "participant1" joins room "file last share room" with 200
+ And user "participant2" joins room "file last share room" with 200
+ # Users without access to a file shared by link need to log in (so further
+ # requests keep the same session) and get the room (so the share token is
+ # stored in the session) to be able to join it.
+ And user "participant3" logs in
+ And user "participant3" gets the room for last share with 200
+ And user "participant3" joins room "file last share room" with 200
+ # Guests need to get the room (so the share token is stored in the session)
+ # to be able to join it.
+ And user "guest" gets the room for last share with 200
+ And user "guest" joins room "file last share room" with 200
+ And user "participant1" is participant of room "file last share room"
+ And user "participant2" is participant of room "file last share room"
+ And user "participant3" is participant of room "file last share room"
+ And user "guest" is participant of room "file last share room"
+ Then user "participant1" gets the following candidate mentions in room "file last share room" for "part" with 200
+ | id | label | source |
+ | participant2 | participant2-displayname | users |
+ | participant4 | participant4-displayname | users |
+ | participant3 | participant3-displayname | users |
+ And user "participant2" gets the following candidate mentions in room "file last share room" for "part" with 200
+ | id | label | source |
+ | participant1 | participant1-displayname | users |
+ | participant4 | participant4-displayname | users |
+ | participant3 | participant3-displayname | users |
+ # Self-joined users can not mention users with access to the file that have
+ # not joined the room.
+ And user "participant3" gets the following candidate mentions in room "file last share room" for "part" with 200
+ | id | label | source |
+ | participant1 | participant1-displayname | users |
+ | participant2 | participant2-displayname | users |
+ # Guests can not mention users with access to the file that have not joined
+ # the room.
+ And user "guest" gets the following candidate mentions in room "file last share room" for "part" with 200
+ | id | label | source |
+ | participant1 | participant1-displayname | users |
+ | participant2 | participant2-displayname | users |
+ | participant3 | participant3-displayname | users |
+
+ Scenario: get unmatched mentions in a room for a file shared by link
+ Given user "participant1" shares "welcome.txt" with user "participant2" with OCS 100
+ And user "participant1" shares "welcome.txt" with user "participant4" with OCS 100
+ And user "participant1" shares "welcome.txt" by link with OCS 100
+ When user "participant2" gets the room for last share with 200
+ And user "participant1" joins room "file last share room" with 200
+ And user "participant2" joins room "file last share room" with 200
+ # Users without access to a file shared by link need to log in (so further
+ # requests keep the same session) and get the room (so the share token is
+ # stored in the session) to be able to join it.
+ And user "participant3" logs in
+ And user "participant3" gets the room for last share with 200
+ And user "participant3" joins room "file last share room" with 200
+ # Guests need to get the room (so the share token is stored in the session)
+ # to be able to join it.
+ And user "guest" gets the room for last share with 200
+ And user "guest" joins room "file last share room" with 200
+ And user "participant1" is participant of room "file last share room"
+ And user "participant2" is participant of room "file last share room"
+ And user "participant3" is participant of room "file last share room"
+ And user "guest" is participant of room "file last share room"
+ Then user "participant1" gets the following candidate mentions in room "file last share room" for "unknown" with 200
+ And user "participant2" gets the following candidate mentions in room "file last share room" for "unknown" with 200
+ And user "participant3" gets the following candidate mentions in room "file last share room" for "unknown" with 200
+ And user "guest" gets the following candidate mentions in room "file last share room" for "unknown" with 200
+
+ Scenario: get mentions in a room for a file shared by link with a participant without access to the file and not joined
+ Given user "participant1" shares "welcome.txt" with user "participant2" with OCS 100
+ And user "participant1" shares "welcome.txt" by link with OCS 100
+ When user "participant2" gets the room for last share with 200
+ And user "participant1" joins room "file last share room" with 200
+ And user "participant2" joins room "file last share room" with 200
+ And user "participant1" is participant of room "file last share room"
+ And user "participant2" is participant of room "file last share room"
+ Then user "participant3" gets the following candidate mentions in room "file last share room" for "" with 404
+ And user "guest" gets the following candidate mentions in room "file last share room" for "" with 404
+
+ Scenario: mention a participant with access to the file but not joined in a room for a file shared by link
+ Given user "participant1" shares "welcome.txt" with user "participant2" with OCS 100
+ And user "participant1" shares "welcome.txt" by link with OCS 100
+ And user "participant1" gets the room for last share with 200
+ And user "participant1" joins room "file last share room" with 200
+ And user "participant1" is participant of room "file last share room"
+ And user "participant2" is not participant of room "file last share room"
+ When user "participant1" sends message "hi @participant2" to room "file last share room" with 201
+ Then user "participant2" is participant of room "file last share room"
+
+ Scenario: mention a participant with access to the file but not joined by self-joined user and guest in a room for a file shared by link
+ Given user "participant1" shares "welcome.txt" with user "participant2" with OCS 100
+ And user "participant1" shares "welcome.txt" by link with OCS 100
+ # Users without access to a file shared by link need to log in (so further
+ # requests keep the same session) and get the room (so the share token is
+ # stored in the session) to be able to join it.
+ And user "participant3" logs in
+ And user "participant3" gets the room for last share with 200
+ And user "participant3" joins room "file last share room" with 200
+ # Guests need to get the room (so the share token is stored in the session)
+ # to be able to join it.
+ And user "guest" gets the room for last share with 200
+ And user "guest" joins room "file last share room" with 200
+ And user "participant2" is not participant of room "file last share room"
+ When user "participant3" sends message "hi @participant2" to room "file last share room" with 201
+ And user "guest" sends message "hello @participant2" to room "file last share room" with 201
+ Then user "participant2" is not participant of room "file last share room"
+
+ Scenario: mention a participant without access to the file but joined in a room for a file shared by link
+ Given user "participant1" shares "welcome.txt" with user "participant2" with OCS 100
+ And user "participant1" shares "welcome.txt" by link with OCS 100
+ And user "participant1" gets the room for last share with 200
+ And user "participant1" joins room "file last share room" with 200
+ # Users without access to a file shared by link need to log in (so further
+ # requests keep the same session) and get the room (so the share token is
+ # stored in the session) to be able to join it.
+ And user "participant3" logs in
+ And user "participant3" gets the room for last share with 200
+ And user "participant3" joins room "file last share room" with 200
+ And user "participant1" is participant of room "file last share room"
+ And user "participant3" is participant of room "file last share room"
+ When user "participant1" sends message "hi @participant3" to room "file last share room" with 201
+ And user "participant3" leaves room "file last share room" with 200
+ Then user "participant3" is not participant of room "file last share room"
diff --git a/tests/integration/features/chat/system-messages.feature b/tests/integration/features/chat/system-messages.feature
index 5b2cb9420..e74abb2a2 100644
--- a/tests/integration/features/chat/system-messages.feature
+++ b/tests/integration/features/chat/system-messages.feature
@@ -75,6 +75,68 @@ Feature: System messages
| room | actorType | actorId | actorDisplayName | systemMessage |
| room | users | participant1 | participant1-displayname | conversation_created |
+ Scenario: Adding participant to room
+ Given user "participant1" creates room "room"
+ | roomType | 2 |
+ | roomName | room |
+ When user "participant1" adds "participant2" to room "room" with 200
+ Then user "participant1" sees the following system messages in room "room" with 200
+ | room | actorType | actorId | actorDisplayName | systemMessage |
+ | room | users | participant1 | participant1-displayname | user_added |
+ | room | users | participant1 | participant1-displayname | conversation_created |
+ And user "participant2" sees the following system messages in room "room" with 200
+ | room | actorType | actorId | actorDisplayName | systemMessage |
+ | room | users | participant1 | participant1-displayname | user_added |
+ | room | users | participant1 | participant1-displayname | conversation_created |
+
+ Scenario: Joining public room
+ Given user "participant1" creates room "room"
+ | roomType | 3 |
+ | roomName | room |
+ When user "participant1" joins room "room" with 200
+ And user "participant2" joins room "room" with 200
+ Then user "participant1" sees the following system messages in room "room" with 200
+ | room | actorType | actorId | actorDisplayName | systemMessage |
+ | room | users | participant1 | participant1-displayname | conversation_created |
+ And user "participant2" sees the following system messages in room "room" with 200
+ | room | actorType | actorId | actorDisplayName | systemMessage |
+ | room | users | participant1 | participant1-displayname | conversation_created |
+
+ Scenario: Joining room for file
+ Given user "participant1" shares "welcome.txt" with user "participant2" with OCS 100
+ And user "participant1" gets the room for path "welcome.txt" with 200
+ When user "participant1" joins room "file welcome.txt room" with 200
+ And user "participant2" joins room "file welcome.txt room" with 200
+ Then user "participant1" sees the following system messages in room "file welcome.txt room" with 200
+ | room | actorType | actorId | actorDisplayName | systemMessage |
+ | file welcome.txt room | users | participant2 | participant2-displayname | user_added |
+ | file welcome.txt room | users | participant1 | participant1-displayname | user_added |
+ | file welcome.txt room | users | participant1 | participant1-displayname | conversation_created |
+ And user "participant2" sees the following system messages in room "file welcome.txt room" with 200
+ | room | actorType | actorId | actorDisplayName | systemMessage |
+ | file welcome.txt room | users | participant2 | participant2-displayname | user_added |
+ | file welcome.txt room | users | participant1 | participant1-displayname | user_added |
+ | file welcome.txt room | users | participant1 | participant1-displayname | conversation_created |
+
+ Scenario: Joining room for link share
+ Given user "participant1" shares "welcome.txt" by link with OCS 100
+ And user "participant1" gets the room for last share with 200
+ # Users without access to a file shared by link need to log in (so further
+ # requests keep the same session) and get the room (so the share token is
+ # stored in the session) to be able to join it.
+ And user "participant2" logs in
+ And user "participant2" gets the room for last share with 200
+ When user "participant1" joins room "file last share room" with 200
+ And user "participant2" joins room "file last share room" with 200
+ Then user "participant1" sees the following system messages in room "file last share room" with 200
+ | room | actorType | actorId | actorDisplayName | systemMessage |
+ | file last share room | users | participant1 | participant1-displayname | user_added |
+ | file last share room | users | participant1 | participant1-displayname | conversation_created |
+ And user "participant2" sees the following system messages in room "file last share room" with 200
+ | room | actorType | actorId | actorDisplayName | systemMessage |
+ | file last share room | users | participant1 | participant1-displayname | user_added |
+ | file last share room | users | participant1 | participant1-displayname | conversation_created |
+
Scenario: Participant escalation
Given user "participant1" creates room "room"
| roomType | 2 |
diff --git a/tests/integration/features/conversation/files.feature b/tests/integration/features/conversation/files.feature
index a5aef3177..69fce170a 100644
--- a/tests/integration/features/conversation/files.feature
+++ b/tests/integration/features/conversation/files.feature
@@ -75,9 +75,74 @@ Feature: conversation/files
+ Scenario: get room for link share
+ Given user "participant1" shares "welcome.txt" with user "participant2" with OCS 100
+ And user "participant1" shares "welcome.txt" by link with OCS 100
+ When user "participant1" gets the room for last share with 200
+ And user "participant2" gets the room for last share with 200
+ And user "participant3" gets the room for last share with 200
+ And user "guest" gets the room for last share with 200
+ Then user "participant1" is not participant of room "file last share room"
+ And user "participant2" is not participant of room "file last share room"
+ And user "participant3" is not participant of room "file last share room"
+ And user "guest" is not participant of room "file last share room"
+
+ Scenario: get room for link share protected by password
+ Given user "participant1" shares "welcome.txt" with user "participant2" with OCS 100
+ And user "participant1" shares "welcome.txt" by link with OCS 100
+ | password | 123456 |
+ When user "participant1" gets the room for last share with 404
+ And user "participant2" gets the room for last share with 404
+ And user "participant3" gets the room for last share with 404
+ And user "guest" gets the room for last share with 404
+
+ Scenario: get room for link share of a folder
+ Given user "participant1" creates folder "/test"
+ And user "participant1" shares "test" by link with OCS 100
+ When user "participant1" gets the room for last share with 404
+ And user "participant2" gets the room for last share with 404
+ And user "guest" gets the room for last share with 404
+
+ Scenario: get room for link no longer shared
+ Given user "participant1" shares "welcome.txt" with user "participant2" with OCS 100
+ And user "participant1" shares "welcome.txt" by link with OCS 100
+ And user "participant1" deletes last share
+ When user "participant1" gets the room for last share with 404
+ And user "participant2" gets the room for last share with 404
+ And user "participant3" gets the room for last share with 404
+ And user "guest" gets the room for last share with 404
+
Scenario: get room for file shared by link
Given user "participant1" shares "welcome.txt" by link with OCS 100
- When user "participant1" gets the room for path "welcome.txt" with 404
+ When user "participant1" gets the room for path "welcome.txt" with 200
+ Then user "participant1" is not participant of room "file welcome.txt room"
+
+ Scenario: get room for file shared by link and protected by password
+ Given user "participant1" shares "welcome.txt" by link with OCS 100
+ | password | 123456 |
+ When user "participant1" gets the room for path "welcome.txt" with 200
+ Then user "participant1" is not participant of room "file welcome.txt room"
+
+ Scenario: get room for folder shared by link
+ Given user "participant1" creates folder "/test"
+ And user "participant1" shares "test" by link with OCS 100
+ When user "participant1" gets the room for path "test" with 404
+
+ Scenario: get room for file in folder shared by link
+ Given user "participant1" creates folder "/test"
+ And user "participant1" moves file "/welcome.txt" to "/test/renamed.txt" with 201
+ And user "participant1" shares "test" by link with OCS 100
+ When user "participant1" gets the room for path "test/renamed.txt" with 404
+
+ Scenario: get room for file in folder shared by link and reshared with user
+ Given user "participant1" creates folder "/test"
+ And user "participant1" moves file "/welcome.txt" to "/test/renamed.txt" with 201
+ And user "participant1" shares "test" by link with OCS 100
+ And user "participant1" shares "test" with user "participant2" with OCS 100
+ When user "participant1" gets the room for path "test/renamed.txt" with 200
+ And user "participant2" gets the room for path "test/renamed.txt" with 200
+ Then user "participant1" is not participant of room "file test/renamed.txt room"
+ And user "participant2" is not participant of room "file test/renamed.txt room"
Scenario: get room for file shared with user and by link
Given user "participant1" shares "welcome.txt" by link with OCS 100
@@ -87,6 +152,14 @@ Feature: conversation/files
Then user "participant1" is not participant of room "file welcome (2).txt room"
And user "participant2" is not participant of room "file welcome (2).txt room"
+ Scenario: get room for last link share also shared with user
+ Given user "participant1" shares "welcome.txt" with user "participant2" with OCS 100
+ And user "participant1" shares "welcome.txt" by link with OCS 100
+ When user "participant1" gets the room for last share with 200
+ And user "participant2" gets the room for last share with 200
+ Then user "participant1" is not participant of room "file welcome (2).txt room"
+ And user "participant2" is not participant of room "file welcome (2).txt room"
+
Scenario: owner of a shared file can join its room
@@ -178,6 +251,37 @@ Feature: conversation/files
+ Scenario: owner of a file shared by link can join its room
+ Given user "participant1" shares "welcome.txt" by link with OCS 100
+ And user "participant1" gets the room for last share with 200
+ When user "participant1" joins room "file last share room" with 200
+ Then user "participant1" is participant of room "file last share room"
+
+ Scenario: user with access to a file shared by link can join its room
+ Given user "participant1" shares "welcome.txt" with user "participant2" with OCS 100
+ And user "participant1" shares "welcome.txt" by link with OCS 100
+ And user "participant2" gets the room for last share with 200
+ When user "participant2" joins room "file last share room" with 200
+ Then user "participant2" is participant of room "file last share room"
+
+ Scenario: user without access to a file shared by link can join its room
+ Given user "participant1" shares "welcome.txt" by link with OCS 100
+ # Users without access to a file shared by link need to log in (so further
+ # requests keep the same session) and get the room (so the share token is
+ # stored in the session) to be able to join it.
+ And user "participant2" logs in
+ And user "participant2" gets the room for last share with 200
+ When user "participant2" joins room "file last share room" with 200
+ Then user "participant2" is participant of room "file last share room"
+
+ Scenario: guest can join the room of a file shared by link
+ Given user "participant1" shares "welcome.txt" by link with OCS 100
+ And user "guest" gets the room for last share with 200
+ When user "guest" joins room "file last share room" with 200
+ And user "guest" is participant of room "file last share room"
+
+
+
Scenario: owner of a shared file is not removed from its room after leaving it
Given user "participant1" shares "welcome.txt" with user "participant2" with OCS 100
# Note that the room token is got by a different user than the one that
@@ -200,6 +304,45 @@ Feature: conversation/files
+ Scenario: owner of a file shared by link is not removed from its room after leaving it
+ Given user "participant1" shares "welcome.txt" by link with OCS 100
+ And user "participant1" gets the room for last share with 200
+ And user "participant1" joins room "file last share room" with 200
+ And user "participant1" is participant of room "file last share room"
+ When user "participant1" leaves room "file last share room" with 200
+ Then user "participant1" is participant of room "file last share room"
+
+ Scenario: user with access to a file shared by link is not removed from its room after leaving it
+ Given user "participant1" shares "welcome.txt" with user "participant2" with OCS 100
+ And user "participant1" shares "welcome.txt" by link with OCS 100
+ And user "participant2" gets the room for last share with 200
+ And user "participant2" joins room "file last share room" with 200
+ And user "participant2" is participant of room "file last share room"
+ When user "participant2" leaves room "file last share room" with 200
+ Then user "participant2" is participant of room "file last share room"
+
+ Scenario: user without access to a file shared by link is removed from its room after leaving it
+ Given user "participant1" shares "welcome.txt" by link with OCS 100
+ # Users without access to a file shared by link need to log in (so further
+ # requests keep the same session) and get the room (so the share token is
+ # stored in the session) to be able to join it.
+ And user "participant2" logs in
+ And user "participant2" gets the room for last share with 200
+ And user "participant2" joins room "file last share room" with 200
+ And user "participant2" is participant of room "file last share room"
+ When user "participant2" leaves room "file last share room" with 200
+ Then user "participant2" is not participant of room "file last share room"
+
+ Scenario: guest is removed from the room of a file shared by link after leaving it
+ Given user "participant1" shares "welcome.txt" by link with OCS 100
+ And user "guest" gets the room for last share with 200
+ And user "guest" joins room "file last share room" with 200
+ And user "guest" is participant of room "file last share room"
+ When user "guest" leaves room "file last share room" with 200
+ And user "guest" is not participant of room "file last share room"
+
+
+
Scenario: owner of a shared file can join its room again after removing self from it
Given user "participant1" shares "welcome.txt" with user "participant2" with OCS 100
# Note that the room token is got by a different user than the one that
@@ -226,6 +369,45 @@ Feature: conversation/files
+ Scenario: owner of a file shared by link can join its room again after removing self from it
+ Given user "participant1" shares "welcome.txt" by link with OCS 100
+ And user "participant1" gets the room for last share with 200
+ And user "participant1" joins room "file last share room" with 200
+ And user "participant1" is participant of room "file last share room"
+ When user "participant1" removes themselves from room "file last share room" with 200
+ And user "participant1" is not participant of room "file last share room"
+ And user "participant1" joins room "file last share room" with 200
+ Then user "participant1" is participant of room "file last share room"
+
+ Scenario: user with access to a file shared by link can join its room again after removing self from it
+ Given user "participant1" shares "welcome.txt" with user "participant2" with OCS 100
+ And user "participant1" shares "welcome.txt" by link with OCS 100
+ And user "participant2" gets the room for last share with 200
+ And user "participant2" joins room "file last share room" with 200
+ And user "participant2" is participant of room "file last share room"
+ When user "participant2" removes themselves from room "file last share room" with 200
+ And user "participant2" is not participant of room "file last share room"
+ And user "participant2" joins room "file last share room" with 200
+ Then user "participant2" is participant of room "file last share room"
+
+ Scenario: user without access to a file shared by link can join its room again after removing self from it
+ Given user "participant1" shares "welcome.txt" by link with OCS 100
+ # Users without access to a file shared by link need to log in (so further
+ # requests keep the same session) and get the room (so the share token is
+ # stored in the session) to be able to join it.
+ And user "participant2" logs in
+ And user "participant2" gets the room for last share with 200
+ And user "participant2" joins room "file last share room" with 200
+ And user "participant2" is participant of room "file last share room"
+ When user "participant2" removes themselves from room "file last share room" with 200
+ And user "participant2" is not participant of room "file last share room"
+ And user "participant2" joins room "file last share room" with 200
+ Then user "participant2" is participant of room "file last share room"
+
+ # Guests can not remove themselves from a room.
+
+
+
# Participants are removed from the room for a no longer shared file once they
# try to join the room again, but not when the file is unshared.
@@ -250,3 +432,55 @@ Feature: conversation/files
Then user "participant2" is participant of room "file welcome (2).txt room"
And user "participant2" joins room "file welcome (2).txt room" with 404
And user "participant2" is not participant of room "file welcome (2).txt room"
+
+
+
+ Scenario: owner is not participant of room for file no longer shared by link
+ Given user "participant1" shares "welcome.txt" by link with OCS 100
+ And user "participant1" gets the room for last share with 200
+ And user "participant1" joins room "file last share room" with 200
+ And user "participant1" leaves room "file last share room" with 200
+ And user "participant1" is participant of room "file last share room"
+ When user "participant1" deletes last share
+ Then user "participant1" is participant of room "file last share room"
+ And user "participant1" joins room "file last share room" with 404
+ And user "participant1" is not participant of room "file last share room"
+
+ Scenario: user is participant of room for file no longer shared by link but with access to it
+ Given user "participant1" shares "welcome.txt" with user "participant2" with OCS 100
+ And user "participant1" shares "welcome.txt" by link with OCS 100
+ And user "participant2" gets the room for last share with 200
+ And user "participant2" joins room "file last share room" with 200
+ And user "participant2" leaves room "file last share room" with 200
+ And user "participant2" is participant of room "file last share room"
+ When user "participant1" deletes last share
+ Then user "participant2" is participant of room "file last share room"
+ # Although the room was created for the shared link it will still be
+ # available to other types of shares after the shared link is deleted.
+ And user "participant2" joins room "file last share room" with 200
+ And user "participant2" is participant of room "file last share room"
+
+ Scenario: user is not participant of room for file no longer shared by link and without access to it
+ Given user "participant1" shares "welcome.txt" by link with OCS 100
+ # Users without access to a file shared by link need to log in (so further
+ # requests keep the same session) and get the room (so the share token is
+ # stored in the session) to be able to join it.
+ And user "participant2" logs in
+ And user "participant2" gets the room for last share with 200
+ And user "participant2" joins room "file last share room" with 200
+ And user "participant2" leaves room "file last share room" with 200
+ And user "participant2" is not participant of room "file last share room"
+ When user "participant1" deletes last share
+ Then user "participant2" is not participant of room "file last share room"
+ And user "participant2" joins room "file last share room" with 404
+ And user "participant2" is not participant of room "file last share room"
+
+ Scenario: guest is not participant of room for file no longer shared by link
+ Given user "participant1" shares "welcome.txt" by link with OCS 100
+ And user "guest" gets the room for last share with 200
+ And user "guest" joins room "file last share room" with 200
+ And user "guest" leaves room "file last share room" with 200
+ When user "participant1" deletes last share
+ Then user "guest" is not participant of room "file last share room"
+ And user "guest" joins room "file last share room" with 404
+ And user "guest" is not participant of room "file last share room"
diff --git a/tests/php/Files/UtilTest.php b/tests/php/Files/UtilTest.php
index 942a6dc49..b7a9c4f3f 100644
--- a/tests/php/Files/UtilTest.php
+++ b/tests/php/Files/UtilTest.php
@@ -29,6 +29,7 @@ use OCP\Files\Folder;
use OCP\Files\Node;
use OCP\Files\IRootFolder;
use OCP\Files\Storage\IStorage;
+use OCP\ISession;
use OCP\Share\IManager;
use PHPUnit\Framework\MockObject\MockObject;
use Test\TestCase;
@@ -94,11 +95,15 @@ class UtilTest extends TestCase {
->with($userId)
->willReturn($userFolder);
+ /** @var ISession|MockObject $session */
+ $session = $this->createMock(ISession::class);
+
/** @var IManager|MockObject $shareManager */
$shareManager = $this->createMock(IManager::class);
$util = new Util(
$rootFolder,
+ $session,
$shareManager
);
$result = $util->getGroupFolderNode($fileId, $userId);