Merge pull request #133 from owncloud/member-management
Member management
This commit is contained in:
commit
5bdad7be17
@ -50,50 +50,6 @@ try{
|
|||||||
|
|
||||||
$command = $request->getParam('command');
|
$command = $request->getParam('command');
|
||||||
switch ($command){
|
switch ($command){
|
||||||
case 'query_memberdata_list':
|
|
||||||
$ids = $request->getParam('args/member_ids');
|
|
||||||
|
|
||||||
$member = new Db_Member();
|
|
||||||
$members = $member->getCollectionBy('member_id', $ids);
|
|
||||||
|
|
||||||
$response["memberdata_list"] = array_map(
|
|
||||||
function($x){
|
|
||||||
$x['display_name'] = \OCP\User::getDisplayName($x['uid']);
|
|
||||||
|
|
||||||
// Do we have OC_Avatar in out disposal?
|
|
||||||
if (!class_exists('\OC_Avatar') || \OC_Config::getValue('enable_avatars', true) !== true){
|
|
||||||
//$x['avatar_url'] = \OCP\Util::linkToRoute('documents_user_avatar');
|
|
||||||
$x['avatar_url'] = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAAAAACH5BAAAAAAALAAAAAABAAEAAAICTAEAOw==';
|
|
||||||
} else {
|
|
||||||
// https://github.com/owncloud/documents/issues/51
|
|
||||||
// Temporary stub
|
|
||||||
|
|
||||||
$x['avatar_url'] = $x['uid'];
|
|
||||||
|
|
||||||
/*
|
|
||||||
$avatar = new \OC_Avatar($x['uid']);
|
|
||||||
$image = $avatar->get(64);
|
|
||||||
// User has an avatar
|
|
||||||
if ($image instanceof \OC_Image) {
|
|
||||||
$x['avatar_url'] = \OC_Helper::linkToRoute(
|
|
||||||
'core_avatar_get',
|
|
||||||
array( 'user' => $x['uid'], 'size' => 64)
|
|
||||||
) . '?requesttoken=' . \OC::$session->get('requesttoken');
|
|
||||||
} else {
|
|
||||||
//shortcircuit if it's not an image
|
|
||||||
$x['avatar_url'] = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAAAAACH5BAAAAAAALAAAAAABAAEAAAICTAEAOw==';
|
|
||||||
}
|
|
||||||
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return $x;
|
|
||||||
},
|
|
||||||
$members
|
|
||||||
);
|
|
||||||
|
|
||||||
break;
|
|
||||||
case 'sync_ops':
|
case 'sync_ops':
|
||||||
$seqHead = (string) $request->getParam('args/seq_head');
|
$seqHead = (string) $request->getParam('args/seq_head');
|
||||||
if (!is_null($seqHead)){
|
if (!is_null($seqHead)){
|
||||||
|
@ -32,6 +32,8 @@ class UserController extends Controller{
|
|||||||
$memberData = $member->getData();
|
$memberData = $member->getData();
|
||||||
if ($memberData['es_id']===$esId){
|
if ($memberData['es_id']===$esId){
|
||||||
$member->deactivate(array($args['member_id']));
|
$member->deactivate(array($args['member_id']));
|
||||||
|
$op = new Db_Op();
|
||||||
|
$op->removeMember($esId, $args['member_id']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
\OCP\JSON::success();
|
\OCP\JSON::success();
|
||||||
|
31
js/3rdparty/webodf/editor/Editor.js
vendored
31
js/3rdparty/webodf/editor/Editor.js
vendored
@ -124,7 +124,20 @@ define("webodf/editor/Editor", [
|
|||||||
* @return {undefined}
|
* @return {undefined}
|
||||||
*/
|
*/
|
||||||
this.openDocument = function (docUrl, memberId, editorReadyCallback) {
|
this.openDocument = function (docUrl, memberId, editorReadyCallback) {
|
||||||
initDocLoading(docUrl, memberId, editorReadyCallback);
|
initDocLoading(docUrl, memberId, function () {
|
||||||
|
runtime.loadClass("ops.OpAddMember");
|
||||||
|
var op = new ops.OpAddMember();
|
||||||
|
op.init({
|
||||||
|
memberid: memberId,
|
||||||
|
setProperties: {
|
||||||
|
fullName: runtime.tr("Unknown Author"),
|
||||||
|
color: "black",
|
||||||
|
imageUrl: "avatar-joe.png"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
session.enqueue([op]);
|
||||||
|
editorReadyCallback();
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -134,6 +147,14 @@ define("webodf/editor/Editor", [
|
|||||||
*/
|
*/
|
||||||
this.closeDocument = function (callback) {
|
this.closeDocument = function (callback) {
|
||||||
runtime.assert(session, "session should exist here.");
|
runtime.assert(session, "session should exist here.");
|
||||||
|
runtime.loadClass("ops.OpRemoveMember");
|
||||||
|
|
||||||
|
var op = new ops.OpRemoveMember();
|
||||||
|
op.init({
|
||||||
|
memberid: editorSession.sessionController.getInputMemberId()
|
||||||
|
});
|
||||||
|
session.enqueue([op]);
|
||||||
|
|
||||||
session.close(function (err) {
|
session.close(function (err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
callback(err);
|
callback(err);
|
||||||
@ -195,16 +216,12 @@ define("webodf/editor/Editor", [
|
|||||||
*/
|
*/
|
||||||
this.openSession = function (sessionId, memberId, editorReadyCallback) {
|
this.openSession = function (sessionId, memberId, editorReadyCallback) {
|
||||||
initDocLoading(server.getGenesisUrl(sessionId), memberId, function () {
|
initDocLoading(server.getGenesisUrl(sessionId), memberId, function () {
|
||||||
var opRouter, memberModel;
|
// overwrite router
|
||||||
// overwrite router and member model
|
|
||||||
// TODO: serverFactory should be a backendFactory,
|
// TODO: serverFactory should be a backendFactory,
|
||||||
// and there should be a backendFactory for local editing
|
// and there should be a backendFactory for local editing
|
||||||
opRouter = serverFactory.createOperationRouter(sessionId, memberId, server, odfCanvas.odfContainer());
|
var opRouter = serverFactory.createOperationRouter(sessionId, memberId, server, odfCanvas.odfContainer());
|
||||||
session.setOperationRouter(opRouter);
|
session.setOperationRouter(opRouter);
|
||||||
|
|
||||||
memberModel = serverFactory.createMemberModel(sessionId, server);
|
|
||||||
session.setMemberModel(memberModel);
|
|
||||||
|
|
||||||
opRouter.requestReplay(function done() {
|
opRouter.requestReplay(function done() {
|
||||||
editorReadyCallback();
|
editorReadyCallback();
|
||||||
});
|
});
|
||||||
|
44
js/3rdparty/webodf/editor/EditorSession.js
vendored
44
js/3rdparty/webodf/editor/EditorSession.js
vendored
@ -82,8 +82,11 @@ define("webodf/editor/EditorSession", [
|
|||||||
domUtils = new core.DomUtils(),
|
domUtils = new core.DomUtils(),
|
||||||
eventNotifier = new core.EventNotifier([
|
eventNotifier = new core.EventNotifier([
|
||||||
EditorSession.signalMemberAdded,
|
EditorSession.signalMemberAdded,
|
||||||
|
EditorSession.signalMemberUpdated,
|
||||||
EditorSession.signalMemberRemoved,
|
EditorSession.signalMemberRemoved,
|
||||||
|
EditorSession.signalCursorAdded,
|
||||||
EditorSession.signalCursorMoved,
|
EditorSession.signalCursorMoved,
|
||||||
|
EditorSession.signalCursorRemoved,
|
||||||
EditorSession.signalParagraphChanged,
|
EditorSession.signalParagraphChanged,
|
||||||
EditorSession.signalCommonStyleCreated,
|
EditorSession.signalCommonStyleCreated,
|
||||||
EditorSession.signalCommonStyleDeleted,
|
EditorSession.signalCommonStyleDeleted,
|
||||||
@ -211,13 +214,25 @@ define("webodf/editor/EditorSession", [
|
|||||||
paragraphRange.detach();
|
paragraphRange.detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onMemberAdded(member) {
|
||||||
|
self.emit(EditorSession.signalMemberAdded, member.getMemberId());
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMemberUpdated(member) {
|
||||||
|
self.emit(EditorSession.signalMemberUpdated, member.getMemberId());
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMemberRemoved(memberId) {
|
||||||
|
self.emit(EditorSession.signalMemberRemoved, memberId);
|
||||||
|
}
|
||||||
|
|
||||||
function onCursorAdded(cursor) {
|
function onCursorAdded(cursor) {
|
||||||
self.emit(EditorSession.signalMemberAdded, cursor.getMemberId());
|
self.emit(EditorSession.signalCursorAdded, cursor.getMemberId());
|
||||||
trackCursor(cursor);
|
trackCursor(cursor);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onCursorRemoved(memberId) {
|
function onCursorRemoved(memberId) {
|
||||||
self.emit(EditorSession.signalMemberRemoved, memberId);
|
self.emit(EditorSession.signalCursorRemoved, memberId);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onCursorMoved(cursor) {
|
function onCursorMoved(cursor) {
|
||||||
@ -267,14 +282,6 @@ define("webodf/editor/EditorSession", [
|
|||||||
eventNotifier.unsubscribe(eventid, cb);
|
eventNotifier.unsubscribe(eventid, cb);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.getMemberDetailsAndUpdates = function (memberId, subscriber) {
|
|
||||||
return session.getMemberModel().getMemberDetailsAndUpdates(memberId, subscriber);
|
|
||||||
};
|
|
||||||
|
|
||||||
this.unsubscribeMemberDetailsUpdates = function (memberId, subscriber) {
|
|
||||||
return session.getMemberModel().unsubscribeMemberDetailsUpdates(memberId, subscriber);
|
|
||||||
};
|
|
||||||
|
|
||||||
this.getCursorPosition = function () {
|
this.getCursorPosition = function () {
|
||||||
return odtDocument.getCursorPosition(localMemberId);
|
return odtDocument.getCursorPosition(localMemberId);
|
||||||
};
|
};
|
||||||
@ -513,6 +520,14 @@ define("webodf/editor/EditorSession", [
|
|||||||
self.sessionController.getImageManager().insertImage(mimetype, content, width, height);
|
self.sessionController.getImageManager().insertImage(mimetype, content, width, height);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {!string} memberId
|
||||||
|
* @return {?ops.Member}
|
||||||
|
*/
|
||||||
|
this.getMember = function (memberId) {
|
||||||
|
return odtDocument.getMember(memberId);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {!function(!Object=)} callback, passing an error object in case of error
|
* @param {!function(!Object=)} callback, passing an error object in case of error
|
||||||
* @return {undefined}
|
* @return {undefined}
|
||||||
@ -522,6 +537,9 @@ define("webodf/editor/EditorSession", [
|
|||||||
|
|
||||||
head.removeChild(fontStyles);
|
head.removeChild(fontStyles);
|
||||||
|
|
||||||
|
odtDocument.unsubscribe(ops.OdtDocument.signalMemberAdded, onMemberAdded);
|
||||||
|
odtDocument.unsubscribe(ops.OdtDocument.signalMemberUpdated, onMemberUpdated);
|
||||||
|
odtDocument.unsubscribe(ops.OdtDocument.signalMemberRemoved, onMemberRemoved);
|
||||||
odtDocument.unsubscribe(ops.OdtDocument.signalCursorAdded, onCursorAdded);
|
odtDocument.unsubscribe(ops.OdtDocument.signalCursorAdded, onCursorAdded);
|
||||||
odtDocument.unsubscribe(ops.OdtDocument.signalCursorRemoved, onCursorRemoved);
|
odtDocument.unsubscribe(ops.OdtDocument.signalCursorRemoved, onCursorRemoved);
|
||||||
odtDocument.unsubscribe(ops.OdtDocument.signalCursorMoved, onCursorMoved);
|
odtDocument.unsubscribe(ops.OdtDocument.signalCursorMoved, onCursorMoved);
|
||||||
@ -578,6 +596,9 @@ define("webodf/editor/EditorSession", [
|
|||||||
self.availableFonts = getAvailableFonts();
|
self.availableFonts = getAvailableFonts();
|
||||||
selectionViewManager.registerCursor(shadowCursor, true);
|
selectionViewManager.registerCursor(shadowCursor, true);
|
||||||
// Custom signals, that make sense in the Editor context. We do not want to expose webodf's ops signals to random bits of the editor UI.
|
// Custom signals, that make sense in the Editor context. We do not want to expose webodf's ops signals to random bits of the editor UI.
|
||||||
|
odtDocument.subscribe(ops.OdtDocument.signalMemberAdded, onMemberAdded);
|
||||||
|
odtDocument.subscribe(ops.OdtDocument.signalMemberUpdated, onMemberUpdated);
|
||||||
|
odtDocument.subscribe(ops.OdtDocument.signalMemberRemoved, onMemberRemoved);
|
||||||
odtDocument.subscribe(ops.OdtDocument.signalCursorAdded, onCursorAdded);
|
odtDocument.subscribe(ops.OdtDocument.signalCursorAdded, onCursorAdded);
|
||||||
odtDocument.subscribe(ops.OdtDocument.signalCursorRemoved, onCursorRemoved);
|
odtDocument.subscribe(ops.OdtDocument.signalCursorRemoved, onCursorRemoved);
|
||||||
odtDocument.subscribe(ops.OdtDocument.signalCursorMoved, onCursorMoved);
|
odtDocument.subscribe(ops.OdtDocument.signalCursorMoved, onCursorMoved);
|
||||||
@ -592,7 +613,10 @@ define("webodf/editor/EditorSession", [
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**@const*/EditorSession.signalMemberAdded = "memberAdded";
|
/**@const*/EditorSession.signalMemberAdded = "memberAdded";
|
||||||
|
/**@const*/EditorSession.signalMemberUpdated = "memberUpdated";
|
||||||
/**@const*/EditorSession.signalMemberRemoved = "memberRemoved";
|
/**@const*/EditorSession.signalMemberRemoved = "memberRemoved";
|
||||||
|
/**@const*/EditorSession.signalCursorAdded = "cursorAdded";
|
||||||
|
/**@const*/EditorSession.signalCursorRemoved = "cursorRemoved";
|
||||||
/**@const*/EditorSession.signalCursorMoved = "cursorMoved";
|
/**@const*/EditorSession.signalCursorMoved = "cursorMoved";
|
||||||
/**@const*/EditorSession.signalParagraphChanged = "paragraphChanged";
|
/**@const*/EditorSession.signalParagraphChanged = "paragraphChanged";
|
||||||
/**@const*/EditorSession.signalCommonStyleCreated = "styleCreated";
|
/**@const*/EditorSession.signalCommonStyleCreated = "styleCreated";
|
||||||
|
29
js/3rdparty/webodf/editor/MemberListView.js
vendored
29
js/3rdparty/webodf/editor/MemberListView.js
vendored
@ -73,14 +73,14 @@ define("webodf/editor/MemberListView",
|
|||||||
while (node) {
|
while (node) {
|
||||||
if (node.localName === "img") {
|
if (node.localName === "img") {
|
||||||
// update avatar image
|
// update avatar image
|
||||||
node.src = memberDetails.imageurl;
|
node.src = memberDetails.imageUrl;
|
||||||
// update border color
|
// update border color
|
||||||
node.style.borderColor = memberDetails.color;
|
node.style.borderColor = memberDetails.color;
|
||||||
} else if (node.localName === "span" && memberDetails.imageurl){
|
} else if (node.localName === "span" && memberDetails.imageUrl){
|
||||||
$(node).avatar(memberDetails.imageurl, 60);
|
$(node).avatar(memberDetails.imageUrl, 60);
|
||||||
node.style.borderColor = memberDetails.color;
|
node.style.borderColor = memberDetails.color;
|
||||||
} else if (node.localName === "div") {
|
} else if (node.localName === "div") {
|
||||||
node.setAttribute('fullname', memberDetails.fullname);
|
node.setAttribute('fullname', memberDetails.fullName);
|
||||||
}
|
}
|
||||||
node = node.nextSibling;
|
node = node.nextSibling;
|
||||||
}
|
}
|
||||||
@ -142,8 +142,21 @@ define("webodf/editor/MemberListView",
|
|||||||
* @return {undefined}
|
* @return {undefined}
|
||||||
*/
|
*/
|
||||||
function addMember(memberId) {
|
function addMember(memberId) {
|
||||||
|
var member = editorSession.getMember(memberId),
|
||||||
|
properties = member.getProperties();
|
||||||
createAvatarButton(memberId);
|
createAvatarButton(memberId);
|
||||||
editorSession.getMemberDetailsAndUpdates(memberId, updateAvatarButton);
|
updateAvatarButton(memberId, properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {!string} memberId
|
||||||
|
* @return {undefined}
|
||||||
|
*/
|
||||||
|
function updateMember(memberId) {
|
||||||
|
var member = editorSession.getMember(memberId),
|
||||||
|
properties = member.getProperties();
|
||||||
|
|
||||||
|
updateAvatarButton(memberId, properties);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -151,7 +164,6 @@ define("webodf/editor/MemberListView",
|
|||||||
* @return {undefined}
|
* @return {undefined}
|
||||||
*/
|
*/
|
||||||
function removeMember(memberId) {
|
function removeMember(memberId) {
|
||||||
editorSession.unsubscribeMemberDetailsUpdates(memberId, updateAvatarButton);
|
|
||||||
removeAvatarButton(memberId);
|
removeAvatarButton(memberId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -161,14 +173,12 @@ define("webodf/editor/MemberListView",
|
|||||||
if (editorSession) {
|
if (editorSession) {
|
||||||
// unsubscribe from editorSession
|
// unsubscribe from editorSession
|
||||||
editorSession.unsubscribe(EditorSession.signalMemberAdded, addMember);
|
editorSession.unsubscribe(EditorSession.signalMemberAdded, addMember);
|
||||||
|
editorSession.unsubscribe(EditorSession.signalMemberUpdated, updateMember);
|
||||||
editorSession.unsubscribe(EditorSession.signalMemberRemoved, removeMember);
|
editorSession.unsubscribe(EditorSession.signalMemberRemoved, removeMember);
|
||||||
// remove all current avatars
|
// remove all current avatars
|
||||||
node = memberListDiv.firstChild;
|
node = memberListDiv.firstChild;
|
||||||
while (node) {
|
while (node) {
|
||||||
nextNode = node.nextSibling;
|
nextNode = node.nextSibling;
|
||||||
if (node.memberId) {
|
|
||||||
editorSession.unsubscribeMemberDetailsUpdates(node.memberId, updateAvatarButton);
|
|
||||||
}
|
|
||||||
memberListDiv.removeChild(node);
|
memberListDiv.removeChild(node);
|
||||||
node = nextNode;
|
node = nextNode;
|
||||||
}
|
}
|
||||||
@ -185,6 +195,7 @@ define("webodf/editor/MemberListView",
|
|||||||
editorSession = session;
|
editorSession = session;
|
||||||
if (editorSession) {
|
if (editorSession) {
|
||||||
editorSession.subscribe(EditorSession.signalMemberAdded, addMember);
|
editorSession.subscribe(EditorSession.signalMemberAdded, addMember);
|
||||||
|
editorSession.subscribe(EditorSession.signalMemberUpdated, updateMember);
|
||||||
editorSession.subscribe(EditorSession.signalMemberRemoved, removeMember);
|
editorSession.subscribe(EditorSession.signalMemberRemoved, removeMember);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -57,13 +57,6 @@ ServerFactory.prototype.createServer = function () {"use strict"; };
|
|||||||
*/
|
*/
|
||||||
ServerFactory.prototype.createOperationRouter = function (sessionId, memberId, server, odfContainer) {"use strict"; };
|
ServerFactory.prototype.createOperationRouter = function (sessionId, memberId, server, odfContainer) {"use strict"; };
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {!string} sessionId
|
|
||||||
* @param {!ops.Server} server
|
|
||||||
* @return {!ops.MemberModel}
|
|
||||||
*/
|
|
||||||
ServerFactory.prototype.createMemberModel = function (sessionId, server) {"use strict"; };
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {!ops.Server} server
|
* @param {!ops.Server} server
|
||||||
* @return {!SessionList}
|
* @return {!SessionList}
|
||||||
|
@ -27,10 +27,9 @@
|
|||||||
|
|
||||||
define("webodf/editor/server/owncloud/ServerFactory", [
|
define("webodf/editor/server/owncloud/ServerFactory", [
|
||||||
"webodf/editor/server/pullbox/Server",
|
"webodf/editor/server/pullbox/Server",
|
||||||
"webodf/editor/server/pullbox/MemberModel",
|
|
||||||
"webodf/editor/server/pullbox/OperationRouter",
|
"webodf/editor/server/pullbox/OperationRouter",
|
||||||
"webodf/editor/server/pullbox/SessionList"],
|
"webodf/editor/server/pullbox/SessionList"],
|
||||||
function (PullBoxServer, PullBoxMemberModel, PullBoxOperationRouter, PullBoxSessionList) {
|
function (PullBoxServer, PullBoxOperationRouter, PullBoxSessionList) {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -54,9 +53,6 @@ define("webodf/editor/server/owncloud/ServerFactory", [
|
|||||||
this.createOperationRouter = function (sid, mid, server, odfContainer) {
|
this.createOperationRouter = function (sid, mid, server, odfContainer) {
|
||||||
return new PullBoxOperationRouter(sid, mid, server, odfContainer);
|
return new PullBoxOperationRouter(sid, mid, server, odfContainer);
|
||||||
};
|
};
|
||||||
this.createMemberModel = function (sid, server) {
|
|
||||||
return new PullBoxMemberModel(sid, server);
|
|
||||||
};
|
|
||||||
this.createSessionList = function (server) {
|
this.createSessionList = function (server) {
|
||||||
return new PullBoxSessionList(server);
|
return new PullBoxSessionList(server);
|
||||||
};
|
};
|
||||||
|
@ -1,279 +0,0 @@
|
|||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright (C) 2013 KO GmbH <copyright@kogmbh.com>
|
|
||||||
*
|
|
||||||
* @licstart
|
|
||||||
* This file is part of WebODF.
|
|
||||||
*
|
|
||||||
* WebODF is free software: you can redistribute it and/or modify it
|
|
||||||
* under the terms of the GNU Affero General Public License (GNU AGPL)
|
|
||||||
* as published by the Free Software Foundation, either version 3 of
|
|
||||||
* the License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* WebODF 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 WebODF. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
* @licend
|
|
||||||
*
|
|
||||||
* @source: http://www.webodf.org/
|
|
||||||
* @source: https://github.com/kogmbh/WebODF/
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*global runtime, ops*/
|
|
||||||
|
|
||||||
define("webodf/editor/server/pullbox/MemberModel", [], function () {
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @constructor
|
|
||||||
* @implements ops.MemberModel
|
|
||||||
*/
|
|
||||||
return function PullBoxMemberModel(sessionId, server) {
|
|
||||||
|
|
||||||
var cachedMemberData = {},
|
|
||||||
memberDataSubscribers = {},
|
|
||||||
isServerPullingActivated = false,
|
|
||||||
isServerPullingOpen = true,
|
|
||||||
serverPullingTimeoutId = null,
|
|
||||||
isInstantPullingRequested = false,
|
|
||||||
isPulling = false,
|
|
||||||
/**@const*/pullingIntervall = 20000;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {!Object} memberData
|
|
||||||
*/
|
|
||||||
function cacheMemberDatum(memberData) {
|
|
||||||
var subscribers,
|
|
||||||
i;
|
|
||||||
|
|
||||||
// notify all subscribers who are interested in this data
|
|
||||||
subscribers = memberDataSubscribers[memberData.memberid];
|
|
||||||
if (subscribers) {
|
|
||||||
// cache
|
|
||||||
cachedMemberData[memberData.memberid] = memberData;
|
|
||||||
|
|
||||||
for (i = 0; i < subscribers.length; i += 1) {
|
|
||||||
subscribers[i](memberData.memberid, memberData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function pullMemberData() {
|
|
||||||
var i,
|
|
||||||
memberIds = Object.keys(memberDataSubscribers);
|
|
||||||
|
|
||||||
if (!isServerPullingOpen || isPulling) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// no more timeout or instant pull request in any case
|
|
||||||
serverPullingTimeoutId = null;
|
|
||||||
isInstantPullingRequested = false;
|
|
||||||
// set lock
|
|
||||||
isPulling = true;
|
|
||||||
|
|
||||||
runtime.log("member-list request for : " + memberIds.join(","));
|
|
||||||
|
|
||||||
server.call({
|
|
||||||
command: 'query_memberdata_list',
|
|
||||||
args: {
|
|
||||||
es_id: sessionId,
|
|
||||||
member_ids: memberIds
|
|
||||||
}
|
|
||||||
}, function(responseData) {
|
|
||||||
var response = /**@type {{memberdata_list:Array.<{uid,member_id,display_name,avatar_url,color}>}}*/(runtime.fromJson(responseData)),
|
|
||||||
memberDataList,
|
|
||||||
newMemberData, oldMemberData;
|
|
||||||
|
|
||||||
// unlock
|
|
||||||
isPulling = false;
|
|
||||||
|
|
||||||
// meanwhile closed/disactivated?
|
|
||||||
if (!isServerPullingOpen || !isServerPullingActivated) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
runtime.log("member-list reply: " + responseData);
|
|
||||||
|
|
||||||
if (response.hasOwnProperty("memberdata_list")) {
|
|
||||||
|
|
||||||
// add/update with all delivered memberdata
|
|
||||||
memberDataList = response.memberdata_list;
|
|
||||||
for (i = 0; i < memberDataList.length; i+=1) {
|
|
||||||
newMemberData = {
|
|
||||||
memberid: memberDataList[i].member_id,
|
|
||||||
fullname: memberDataList[i].display_name,
|
|
||||||
imageurl: memberDataList[i].avatar_url,
|
|
||||||
color: memberDataList[i].color
|
|
||||||
};
|
|
||||||
|
|
||||||
oldMemberData = cachedMemberData.hasOwnProperty(newMemberData.memberid) ? cachedMemberData[newMemberData.memberid] : null;
|
|
||||||
if (!oldMemberData ||
|
|
||||||
oldMemberData.fullname !== newMemberData.fullname ||
|
|
||||||
oldMemberData.imageurl !== newMemberData.imageurl ||
|
|
||||||
oldMemberData.color !== newMemberData.color) {
|
|
||||||
cacheMemberDatum(newMemberData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
runtime.log("Meh, memberdata list broken: " + responseData);
|
|
||||||
}
|
|
||||||
|
|
||||||
// trigger the next pulling
|
|
||||||
if (isInstantPullingRequested) {
|
|
||||||
pullMemberData();
|
|
||||||
} else {
|
|
||||||
serverPullingTimeoutId = runtime.setTimeout(pullMemberData, pullingIntervall);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Activates the pulling
|
|
||||||
* @return {undefined}
|
|
||||||
*/
|
|
||||||
function pullNewMemberData() {
|
|
||||||
// cancel any running pulling timeout
|
|
||||||
if (serverPullingTimeoutId !== null) {
|
|
||||||
runtime.clearTimeout(serverPullingTimeoutId);
|
|
||||||
}
|
|
||||||
|
|
||||||
isInstantPullingRequested = true;
|
|
||||||
isServerPullingActivated = true;
|
|
||||||
|
|
||||||
pullMemberData();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deactivates the pulling if there are no more subscribers
|
|
||||||
* @return {undefined}
|
|
||||||
*/
|
|
||||||
function deactivatePeriodicMemberDataPulling() {
|
|
||||||
var key;
|
|
||||||
|
|
||||||
if (!isServerPullingActivated) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if there is no more subscription
|
|
||||||
for(key in memberDataSubscribers) {
|
|
||||||
if (memberDataSubscribers.hasOwnProperty(key)) {
|
|
||||||
// still subscribers, cannot deactivate yet
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
isServerPullingActivated = false;
|
|
||||||
// cancel any running pulling timeout
|
|
||||||
if (serverPullingTimeoutId !== null) {
|
|
||||||
runtime.clearTimeout(serverPullingTimeoutId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* callback is called as soon as the memberdata is available and after that
|
|
||||||
* on every memberdata update.
|
|
||||||
* a parameter `null` passed to the callback means that the member is finally
|
|
||||||
* not known.
|
|
||||||
*
|
|
||||||
* @param {!string} memberId
|
|
||||||
* @param {!function(!string, ?Object)} subscriber
|
|
||||||
* @return {undefined}
|
|
||||||
*/
|
|
||||||
this.getMemberDetailsAndUpdates = function (memberId, subscriber) {
|
|
||||||
var /**@type{Object}*/
|
|
||||||
memberData = cachedMemberData[memberId],
|
|
||||||
subscribers = memberDataSubscribers[memberId] || [],
|
|
||||||
i;
|
|
||||||
memberDataSubscribers[memberId] = subscribers;
|
|
||||||
|
|
||||||
runtime.assert(subscriber !== undefined, "missing callback");
|
|
||||||
|
|
||||||
// detect double subscription
|
|
||||||
for (i=0; i<subscribers.length; i+=1) {
|
|
||||||
if (subscribers[i] === subscriber) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (i < subscribers.length) {
|
|
||||||
// already subscribed
|
|
||||||
runtime.log("double subscription request for "+memberId+" in PullBoxMemberModel::getMemberDetailsAndUpdates");
|
|
||||||
} else {
|
|
||||||
// subscribe
|
|
||||||
subscribers.push(subscriber);
|
|
||||||
// query data from server, if not done yet
|
|
||||||
if (subscribers.length === 1) {
|
|
||||||
// TODO: only fetch data for memberId here
|
|
||||||
pullNewMemberData();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (memberData) {
|
|
||||||
// data available from cache
|
|
||||||
subscriber(memberId, memberData);
|
|
||||||
} else {
|
|
||||||
// pass temporary data
|
|
||||||
subscriber(memberId, {
|
|
||||||
memberid: memberId,
|
|
||||||
fullname: "Unknown",
|
|
||||||
color: "black",
|
|
||||||
imageurl: ""
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* getMemberDetailsAndUpdates subscribes a callback for updates on member details.
|
|
||||||
* this function undoes this subscription.
|
|
||||||
*
|
|
||||||
* @param {!string} memberId
|
|
||||||
* @param {!function(!string, ?Object)} subscriber
|
|
||||||
* @return {undefined}
|
|
||||||
*/
|
|
||||||
this.unsubscribeMemberDetailsUpdates = function (memberId, subscriber) {
|
|
||||||
var i,
|
|
||||||
subscribers = memberDataSubscribers[memberId];
|
|
||||||
|
|
||||||
runtime.assert(subscriber!==undefined, "missing subscriber parameter or null");
|
|
||||||
runtime.assert(subscribers,
|
|
||||||
"tried to unsubscribe when no one is subscribed ('" + memberId + "')");
|
|
||||||
if (subscribers) {
|
|
||||||
for (i=0; i<subscribers.length; i+=1) {
|
|
||||||
if (subscribers[i] === subscriber) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
runtime.assert((i < subscribers.length),
|
|
||||||
"tried to unsubscribe when not subscribed for memberId '" + memberId + "'");
|
|
||||||
|
|
||||||
subscribers.splice(i,1);
|
|
||||||
|
|
||||||
// clean up
|
|
||||||
if (subscribers.length === 0) {
|
|
||||||
runtime.log("no more subscribers for: "+memberId);
|
|
||||||
delete memberDataSubscribers[memberId];
|
|
||||||
delete cachedMemberData[memberId];
|
|
||||||
deactivatePeriodicMemberDataPulling();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Requests a gracefull shutdown of the Member Model
|
|
||||||
* No more network activity is necessary.
|
|
||||||
*/
|
|
||||||
this.close = function (cb) {
|
|
||||||
isServerPullingOpen = false;
|
|
||||||
cb();
|
|
||||||
};
|
|
||||||
|
|
||||||
runtime.assert(server.networkStatus() === "ready", "network not ready");
|
|
||||||
};
|
|
||||||
});
|
|
@ -410,8 +410,8 @@ runtime.log("OperationRouter: instant opsSync requested");
|
|||||||
var timedOp,
|
var timedOp,
|
||||||
opspec = op.spec();
|
opspec = op.spec();
|
||||||
|
|
||||||
// note if any local ops modified TODO: find less fragile way, perhaps have the operationFactory check it?
|
// note if any local ops modified
|
||||||
hasPushedModificationOps = hasPushedModificationOps || !/^(AddCursor|MoveCursor|RemoveCursor)$/.test(opspec.optype);
|
hasPushedModificationOps = hasPushedModificationOps || op.isEdit;
|
||||||
|
|
||||||
// apply locally
|
// apply locally
|
||||||
opspec.timestamp = (new Date()).getTime();
|
opspec.timestamp = (new Date()).getTime();
|
||||||
|
@ -57,9 +57,7 @@ define("webodf/editor/server/pullbox/Server", [], function () {
|
|||||||
*/
|
*/
|
||||||
function call(message, cb) {
|
function call(message, cb) {
|
||||||
var xhr = new XMLHttpRequest(),
|
var xhr = new XMLHttpRequest(),
|
||||||
byteArrayWriter = new core.ByteArrayWriter("utf8"),
|
messageString = JSON.stringify(message);
|
||||||
messageString = JSON.stringify(message),
|
|
||||||
data;
|
|
||||||
|
|
||||||
function handleResult() {
|
function handleResult() {
|
||||||
if (xhr.readyState === 4) {
|
if (xhr.readyState === 4) {
|
||||||
@ -73,9 +71,6 @@ define("webodf/editor/server/pullbox/Server", [], function () {
|
|||||||
}
|
}
|
||||||
runtime.log("Sending message to server: "+messageString);
|
runtime.log("Sending message to server: "+messageString);
|
||||||
// create body data for request from metadata and payload
|
// create body data for request from metadata and payload
|
||||||
byteArrayWriter.appendString(messageString);
|
|
||||||
// byteArrayWriter.appendByteArray(zipData);
|
|
||||||
data = byteArrayWriter.getByteArray();
|
|
||||||
|
|
||||||
// do the request
|
// do the request
|
||||||
xhr.open('POST', args.url, true);
|
xhr.open('POST', args.url, true);
|
||||||
@ -83,20 +78,8 @@ runtime.log("Sending message to server: "+messageString);
|
|||||||
xhr.setRequestHeader("requesttoken", token);
|
xhr.setRequestHeader("requesttoken", token);
|
||||||
}
|
}
|
||||||
xhr.onreadystatechange = handleResult;
|
xhr.onreadystatechange = handleResult;
|
||||||
// ArrayBufferView will have an ArrayBuffer property, in WebKit, XHR can send()
|
|
||||||
// an ArrayBuffer, In Firefox, one must use sendAsBinary with a string
|
|
||||||
if (data.buffer && !xhr.sendAsBinary) {
|
|
||||||
data = data.buffer; // webkit supports sending an ArrayBuffer
|
|
||||||
} else {
|
|
||||||
// encode into a string, this works in FireFox >= 3
|
|
||||||
data = runtime.byteArrayToString(data, "binary");
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
if (xhr.sendAsBinary) {
|
xhr.send(messageString);
|
||||||
xhr.sendAsBinary(data);
|
|
||||||
} else {
|
|
||||||
xhr.send(data);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
runtime.log("Problem with calling server: " + e + " " + data);
|
runtime.log("Problem with calling server: " + e + " " + data);
|
||||||
cb(e.message);
|
cb(e.message);
|
||||||
|
@ -27,10 +27,9 @@
|
|||||||
|
|
||||||
define("webodf/editor/server/pullbox/ServerFactory", [
|
define("webodf/editor/server/pullbox/ServerFactory", [
|
||||||
"webodf/editor/server/pullbox/Server",
|
"webodf/editor/server/pullbox/Server",
|
||||||
"webodf/editor/server/pullbox/MemberModel",
|
|
||||||
"webodf/editor/server/pullbox/OperationRouter",
|
"webodf/editor/server/pullbox/OperationRouter",
|
||||||
"webodf/editor/server/pullbox/SessionList"],
|
"webodf/editor/server/pullbox/SessionList"],
|
||||||
function (PullBoxServer, PullBoxMemberModel, PullBoxOperationRouter, PullBoxSessionList) {
|
function (PullBoxServer, PullBoxOperationRouter, PullBoxSessionList) {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -44,9 +43,6 @@ define("webodf/editor/server/pullbox/ServerFactory", [
|
|||||||
this.createOperationRouter = function (sid, mid, server, odfContainer) {
|
this.createOperationRouter = function (sid, mid, server, odfContainer) {
|
||||||
return new PullBoxOperationRouter(sid, mid, server, odfContainer);
|
return new PullBoxOperationRouter(sid, mid, server, odfContainer);
|
||||||
};
|
};
|
||||||
this.createMemberModel = function (sid, server) {
|
|
||||||
return new PullBoxMemberModel(sid, server);
|
|
||||||
};
|
|
||||||
this.createSessionList = function (server) {
|
this.createSessionList = function (server) {
|
||||||
return new PullBoxSessionList(server);
|
return new PullBoxSessionList(server);
|
||||||
};
|
};
|
||||||
|
18
js/3rdparty/webodf/editor/widgets/annotation.js
vendored
18
js/3rdparty/webodf/editor/widgets/annotation.js
vendored
@ -48,7 +48,7 @@ define("webodf/editor/widgets/annotation", [
|
|||||||
var self = this,
|
var self = this,
|
||||||
widget = {},
|
widget = {},
|
||||||
addAnnotationButton,
|
addAnnotationButton,
|
||||||
annotationManager;
|
annotationController;
|
||||||
|
|
||||||
|
|
||||||
addAnnotationButton = new Button({
|
addAnnotationButton = new Button({
|
||||||
@ -57,8 +57,8 @@ define("webodf/editor/widgets/annotation", [
|
|||||||
showLabel: false,
|
showLabel: false,
|
||||||
iconClass: 'dijitIconBookmark',
|
iconClass: 'dijitIconBookmark',
|
||||||
onClick: function () {
|
onClick: function () {
|
||||||
if (annotationManager) {
|
if (annotationController) {
|
||||||
annotationManager.addAnnotation();
|
annotationController.addAnnotation();
|
||||||
self.onToolDone();
|
self.onToolDone();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -83,14 +83,14 @@ define("webodf/editor/widgets/annotation", [
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.setEditorSession = function (session) {
|
this.setEditorSession = function (session) {
|
||||||
if (annotationManager) {
|
if (annotationController) {
|
||||||
annotationManager.unsubscribe(gui.AnnotationManager.annotatableChanged, onAnnotatableChanged);
|
annotationController.unsubscribe(gui.AnnotationController.annotatableChanged, onAnnotatableChanged);
|
||||||
}
|
}
|
||||||
annotationManager = session && session.sessionController.getAnnotationManager();
|
annotationController = session && session.sessionController.getAnnotationController();
|
||||||
if (annotationManager) {
|
if (annotationController) {
|
||||||
annotationManager.subscribe(gui.AnnotationManager.annotatableChanged, onAnnotatableChanged);
|
annotationController.subscribe(gui.AnnotationController.annotatableChanged, onAnnotatableChanged);
|
||||||
}
|
}
|
||||||
onAnnotatableChanged(annotationManager && annotationManager.isAnnotatable());
|
onAnnotatableChanged(annotationController && annotationController.isAnnotatable());
|
||||||
};
|
};
|
||||||
|
|
||||||
this.onToolDone = function () {};
|
this.onToolDone = function () {};
|
||||||
|
1867
js/3rdparty/webodf/webodf-debug.js
vendored
1867
js/3rdparty/webodf/webodf-debug.js
vendored
File diff suppressed because one or more lines are too long
2148
js/3rdparty/webodf/webodf.js
vendored
2148
js/3rdparty/webodf/webodf.js
vendored
File diff suppressed because one or more lines are too long
@ -84,11 +84,34 @@ class Db_Op extends Db {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function removeCursor($esId, $memberId){
|
public function removeCursor($esId, $memberId){
|
||||||
|
$op = '{"optype":"RemoveCursor","memberid":"'. $memberId .'","reason":"server-idle","timestamp":'. time() .'}';
|
||||||
|
$this->insertOp($esId, $op);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addMember($esId, $memberId, $fullName, $color, $imageUrl){
|
||||||
|
$op = '{"optype":"AddMember","memberid":"'. $memberId .'","timestamp":"'. time() .'", "setProperties":{"fullName":"'. $fullName .'","color":"'. $color .'","imageUrl":"'. $imageUrl .'"}}';
|
||||||
|
$this->insertOp($esId, $op);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function removeMember($esId, $memberId){
|
||||||
|
$op ='{"optype":"RemoveMember","memberid":"'. $memberId .'","timestamp":'. time() .'}';
|
||||||
|
$this->insertOp($esId, $op);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateMember($esId, $memberId, $fullName, $color, $imageUrl){
|
||||||
|
//TODO: Follow the spec https://github.com/kogmbh/WebODF/blob/master/webodf/lib/ops/OpUpdateMember.js#L95
|
||||||
|
$op = '{"optype":"UpdateMember","memberid":"'. $memberId .'","fullName":"'. $fullName .'","color":"'. $color .'","imageUrl":"'. $imageUrl .'","timestamp":'. time() .'}'
|
||||||
|
;
|
||||||
|
$this->insertOp($esId, $op);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function insertOp($esId, $op){
|
||||||
$op = new Db_Op(array(
|
$op = new Db_Op(array(
|
||||||
$esId,
|
$esId,
|
||||||
0,
|
0,
|
||||||
'{"optype":"RemoveCursor","memberid":"'. $memberId .'","reason":"server-idle","timestamp":'. time() .'}'
|
$op
|
||||||
));
|
));
|
||||||
$op->insert();
|
$op->insert();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -64,14 +64,52 @@ class Db_Session extends \OCA\Documents\Db {
|
|||||||
->getData()
|
->getData()
|
||||||
;
|
;
|
||||||
|
|
||||||
|
$memberColor = Helper::getRandomColor();
|
||||||
|
|
||||||
$member = new Db_Member(array(
|
$member = new Db_Member(array(
|
||||||
$session['es_id'],
|
$session['es_id'],
|
||||||
$uid,
|
$uid,
|
||||||
Helper::getRandomColor(),
|
$memberColor,
|
||||||
time()
|
time()
|
||||||
));
|
));
|
||||||
|
|
||||||
if ($member->insert()){
|
if ($member->insert()){
|
||||||
|
// Do we have OC_Avatar in out disposal?
|
||||||
|
if (!class_exists('\OC_Avatar') || \OC_Config::getValue('enable_avatars', true) !== true){
|
||||||
|
//$x['avatar_url'] = \OCP\Util::linkToRoute('documents_user_avatar');
|
||||||
|
$imageUrl = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAAAAACH5BAAAAAAALAAAAAABAAEAAAICTAEAOw==';
|
||||||
|
} else {
|
||||||
|
// https://github.com/owncloud/documents/issues/51
|
||||||
|
// Temporary stub
|
||||||
|
$imageUrl = $uid;
|
||||||
|
|
||||||
|
/*
|
||||||
|
$avatar = new \OC_Avatar($uid);
|
||||||
|
$image = $avatar->get(64);
|
||||||
|
// User has an avatar
|
||||||
|
if ($image instanceof \OC_Image) {
|
||||||
|
$imageUrl = \OC_Helper::linkToRoute(
|
||||||
|
'core_avatar_get',
|
||||||
|
array( 'user' => $uid, 'size' => 64)
|
||||||
|
) . '?requesttoken=' . \OC::$session->get('requesttoken');
|
||||||
|
} else {
|
||||||
|
//shortcircuit if it's not an image
|
||||||
|
$imageUrl = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAAAAACH5BAAAAAAALAAAAAABAAEAAAICTAEAOw==';
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
$session['member_id'] = (string) $member->getLastInsertId();
|
$session['member_id'] = (string) $member->getLastInsertId();
|
||||||
|
$op = new Db_Op();
|
||||||
|
$op->addMember(
|
||||||
|
$session['es_id'],
|
||||||
|
$session['member_id'],
|
||||||
|
\OCP\User::getDisplayName($uid),
|
||||||
|
$memberColor,
|
||||||
|
$imageUrl
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
throw new \Exception('Failed to add member into database');
|
throw new \Exception('Failed to add member into database');
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user