mirror of
https://bitbucket.org/wisemapping/wisemapping-open-source.git
synced 2024-09-12 07:10:07 +02:00
- Finish exclusive locking support.
This commit is contained in:
parent
4f1bb45fc2
commit
3672d2a8e2
@ -18,28 +18,28 @@
|
|||||||
|
|
||||||
mindplot.LocalStorageManager = new Class({
|
mindplot.LocalStorageManager = new Class({
|
||||||
Extends:mindplot.PersistenceManager,
|
Extends:mindplot.PersistenceManager,
|
||||||
initialize: function() {
|
initialize:function () {
|
||||||
this.parent();
|
this.parent();
|
||||||
},
|
},
|
||||||
|
|
||||||
saveMapXml : function(mapId, mapXml, pref, saveHistory, events) {
|
saveMapXml:function (mapId, mapXml, pref, saveHistory, events) {
|
||||||
localStorage.setItem(mapId + "-xml", mapXml);
|
localStorage.setItem(mapId + "-xml", mapXml);
|
||||||
events.onSuccess();
|
events.onSuccess();
|
||||||
},
|
},
|
||||||
|
|
||||||
discardChanges : function(mapId) {
|
discardChanges:function (mapId) {
|
||||||
localStorage.removeItem(mapId + "-xml");
|
localStorage.removeItem(mapId + "-xml");
|
||||||
},
|
},
|
||||||
|
|
||||||
loadMapDom : function(mapId) {
|
loadMapDom:function (mapId) {
|
||||||
var xml = localStorage.getItem(mapId + "-xml");
|
var xml = localStorage.getItem(mapId + "-xml");
|
||||||
if (xml == null) {
|
if (xml == null) {
|
||||||
// Let's try to open one from the local directory ...
|
// Let's try to open one from the local directory ...
|
||||||
var xmlRequest = new Request({
|
var xmlRequest = new Request({
|
||||||
url: 'samples/' + mapId + '.xml',
|
url:'samples/' + mapId + '.xml',
|
||||||
method: 'get',
|
method:'get',
|
||||||
async: false,
|
async:false,
|
||||||
onSuccess: function(responseText) {
|
onSuccess:function (responseText) {
|
||||||
xml = responseText;
|
xml = responseText;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -54,6 +54,10 @@ mindplot.LocalStorageManager = new Class({
|
|||||||
|
|
||||||
var parser = new DOMParser();
|
var parser = new DOMParser();
|
||||||
return parser.parseFromString(xml, "text/xml");
|
return parser.parseFromString(xml, "text/xml");
|
||||||
|
},
|
||||||
|
|
||||||
|
unlockMap:function (mindmap) {
|
||||||
|
// Ignore, no implementation required ...
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -31,7 +31,7 @@ mindplot.PersistenceManager = new Class({
|
|||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
save:function (mindmap, editorProperties, saveHistory, events) {
|
save:function (mindmap, editorProperties, saveHistory, events, sync) {
|
||||||
$assert(mindmap, "mindmap can not be null");
|
$assert(mindmap, "mindmap can not be null");
|
||||||
$assert(editorProperties, "editorProperties can not be null");
|
$assert(editorProperties, "editorProperties can not be null");
|
||||||
|
|
||||||
@ -44,7 +44,7 @@ mindplot.PersistenceManager = new Class({
|
|||||||
|
|
||||||
var pref = JSON.encode(editorProperties);
|
var pref = JSON.encode(editorProperties);
|
||||||
try {
|
try {
|
||||||
this.saveMapXml(mapId, mapXml, pref, saveHistory, events);
|
this.saveMapXml(mapId, mapXml, pref, saveHistory, events,sync);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
events.onError();
|
events.onError();
|
||||||
@ -58,15 +58,19 @@ mindplot.PersistenceManager = new Class({
|
|||||||
},
|
},
|
||||||
|
|
||||||
discardChanges:function (mapId) {
|
discardChanges:function (mapId) {
|
||||||
throw "Method must be implemented";
|
throw new Error("Method must be implemented");
|
||||||
},
|
},
|
||||||
|
|
||||||
loadMapDom:function (mapId) {
|
loadMapDom:function (mapId) {
|
||||||
throw "Method must be implemented";
|
throw new Error("Method must be implemented");
|
||||||
},
|
},
|
||||||
|
|
||||||
saveMapXml:function (mapId, mapXml, pref, saveHistory, events) {
|
saveMapXml:function (mapId, mapXml, pref, saveHistory, events,sync) {
|
||||||
throw "Method must be implemented";
|
throw new Error("Method must be implemented");
|
||||||
|
},
|
||||||
|
|
||||||
|
unlockMap:function (mindmap) {
|
||||||
|
throw new Error("Method must be implemented");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -18,15 +18,17 @@
|
|||||||
|
|
||||||
mindplot.RESTPersistenceManager = new Class({
|
mindplot.RESTPersistenceManager = new Class({
|
||||||
Extends:mindplot.PersistenceManager,
|
Extends:mindplot.PersistenceManager,
|
||||||
initialize:function (saveUrl, revertUrl) {
|
initialize:function (saveUrl, revertUrl, lockUrl) {
|
||||||
this.parent();
|
this.parent();
|
||||||
$assert(saveUrl, "saveUrl can not be null");
|
$assert(saveUrl, "saveUrl can not be null");
|
||||||
$assert(revertUrl, "revertUrl can not be null");
|
$assert(revertUrl, "revertUrl can not be null");
|
||||||
this.saveUrl = saveUrl;
|
this.saveUrl = saveUrl;
|
||||||
this.revertUrl = revertUrl;
|
this.revertUrl = revertUrl;
|
||||||
|
this.lockUrl = lockUrl;
|
||||||
|
this.timestamp = null;
|
||||||
},
|
},
|
||||||
|
|
||||||
saveMapXml:function (mapId, mapXml, pref, saveHistory, events) {
|
saveMapXml:function (mapId, mapXml, pref, saveHistory, events, sync) {
|
||||||
|
|
||||||
var data = {
|
var data = {
|
||||||
id:mapId,
|
id:mapId,
|
||||||
@ -34,12 +36,17 @@ mindplot.RESTPersistenceManager = new Class({
|
|||||||
properties:pref
|
properties:pref
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var persistence = this;
|
||||||
|
var query = "minor=" + !saveHistory;
|
||||||
|
query = query + (this.timestamp ? "×tamp=" + this.timestamp : "");
|
||||||
|
|
||||||
var request = new Request({
|
var request = new Request({
|
||||||
url:this.saveUrl.replace("{id}", mapId) + "?minor=" + !saveHistory,
|
url:this.saveUrl.replace("{id}", mapId) + "?" + query,
|
||||||
method:'put',
|
method:'put',
|
||||||
|
async:!sync,
|
||||||
onSuccess:function (responseText, responseXML) {
|
onSuccess:function (responseText, responseXML) {
|
||||||
events.onSuccess();
|
events.onSuccess();
|
||||||
|
persistence.timestamp = responseText;
|
||||||
},
|
},
|
||||||
onException:function (headerName, value) {
|
onException:function (headerName, value) {
|
||||||
events.onError();
|
events.onError();
|
||||||
@ -81,8 +88,27 @@ mindplot.RESTPersistenceManager = new Class({
|
|||||||
urlEncoded:false
|
urlEncoded:false
|
||||||
});
|
});
|
||||||
request.post();
|
request.post();
|
||||||
}
|
},
|
||||||
|
|
||||||
|
unlockMap:function (mindmap) {
|
||||||
|
var mapId = mindmap.getId();
|
||||||
|
var request = new Request({
|
||||||
|
url:this.lockUrl.replace("{id}", mapId),
|
||||||
|
async:false,
|
||||||
|
method:'put',
|
||||||
|
onSuccess:function () {
|
||||||
|
|
||||||
|
},
|
||||||
|
onException:function () {
|
||||||
|
},
|
||||||
|
onFailure:function () {
|
||||||
|
},
|
||||||
|
headers:{"Content-Type":"text/plain"},
|
||||||
|
emulation:false,
|
||||||
|
urlEncoded:false
|
||||||
|
});
|
||||||
|
request.put("false");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ mindplot.widget.IMenu = new Class({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
discardChanges:function () {
|
discardChanges:function (designer) {
|
||||||
// Avoid autosave before leaving the page ....
|
// Avoid autosave before leaving the page ....
|
||||||
this.setRequireChange(false);
|
this.setRequireChange(false);
|
||||||
|
|
||||||
@ -49,12 +49,21 @@ mindplot.widget.IMenu = new Class({
|
|||||||
var mindmap = designer.getMindmap();
|
var mindmap = designer.getMindmap();
|
||||||
persistenceManager.discardChanges(mindmap.getId());
|
persistenceManager.discardChanges(mindmap.getId());
|
||||||
|
|
||||||
|
// Unlock map ...
|
||||||
|
this.unlockMap(designer);
|
||||||
|
|
||||||
// Reload the page ...
|
// Reload the page ...
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
save:function (saveElem, designer, saveHistory) {
|
unlockMap:function (designer) {
|
||||||
|
var mindmap = designer.getMindmap();
|
||||||
|
var persistenceManager = mindplot.PersistenceManager.getInstance();
|
||||||
|
persistenceManager.unlockMap(mindmap);
|
||||||
|
},
|
||||||
|
|
||||||
|
save:function (saveElem, designer, saveHistory, sync) {
|
||||||
// Load map content ...
|
// Load map content ...
|
||||||
var mindmap = designer.getMindmap();
|
var mindmap = designer.getMindmap();
|
||||||
var mindmapProp = designer.getMindmapProperties();
|
var mindmapProp = designer.getMindmapProperties();
|
||||||
@ -88,7 +97,8 @@ mindplot.widget.IMenu = new Class({
|
|||||||
$notify(msg);
|
$notify(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}, sync);
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
isSaveRequired:function () {
|
isSaveRequired:function () {
|
||||||
|
@ -325,10 +325,12 @@ mindplot.widget.Menu = new Class({
|
|||||||
|
|
||||||
if (!readOnly) {
|
if (!readOnly) {
|
||||||
// To prevent the user from leaving the page with changes ...
|
// To prevent the user from leaving the page with changes ...
|
||||||
$(window).addEvent('beforeunload', function () {
|
Element.NativeEvents.unload = 2;
|
||||||
|
$(window).addEvent('unload', function () {
|
||||||
if (this.isSaveRequired()) {
|
if (this.isSaveRequired()) {
|
||||||
this.save(saveElem, designer, false);
|
this.save(saveElem, designer, false, true);
|
||||||
}
|
}
|
||||||
|
this.unlockMap(designer);
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
|
|
||||||
// Autosave on a fixed period of time ...
|
// Autosave on a fixed period of time ...
|
||||||
@ -343,29 +345,11 @@ mindplot.widget.Menu = new Class({
|
|||||||
var discardElem = $('discard');
|
var discardElem = $('discard');
|
||||||
if (discardElem) {
|
if (discardElem) {
|
||||||
this._addButton('discard', false, false, function () {
|
this._addButton('discard', false, false, function () {
|
||||||
this.discardChanges();
|
this.discardChanges(designer);
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
this._registerTooltip('discard', $msg('DISCARD_CHANGES'));
|
this._registerTooltip('discard', $msg('DISCARD_CHANGES'));
|
||||||
}
|
}
|
||||||
|
|
||||||
var tagElem = $('tagIt');
|
|
||||||
if (tagElem) {
|
|
||||||
this._addButton('tagIt', false, false, function () {
|
|
||||||
var reqDialog = new MooDialog.Request('c/tags?mapId=' + mapId, null,
|
|
||||||
{'class':'modalDialog tagItModalDialog',
|
|
||||||
closeButton:true,
|
|
||||||
destroyOnClose:true,
|
|
||||||
title:'Tags'
|
|
||||||
});
|
|
||||||
reqDialog.setRequestOptions({
|
|
||||||
onRequest:function () {
|
|
||||||
reqDialog.setContent($msg('LOADING'));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
this._registerTooltip('tagIt', "Tag");
|
|
||||||
}
|
|
||||||
|
|
||||||
var shareElem = $('shareIt');
|
var shareElem = $('shareIt');
|
||||||
if (shareElem) {
|
if (shareElem) {
|
||||||
this._addButton('shareIt', false, false, function () {
|
this._addButton('shareIt', false, false, function () {
|
||||||
|
35
wise-webapp/src/main/java/com/wisemapping/exceptions/LockException.java
Executable file
35
wise-webapp/src/main/java/com/wisemapping/exceptions/LockException.java
Executable file
@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* Copyright [2011] [wisemapping]
|
||||||
|
*
|
||||||
|
* Licensed under WiseMapping Public License, Version 1.0 (the "License").
|
||||||
|
* It is basically the Apache License, Version 2.0 (the "License") plus the
|
||||||
|
* "powered by wisemapping" text requirement on every single page;
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the license at
|
||||||
|
*
|
||||||
|
* http://www.wisemapping.org/license
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.wisemapping.exceptions;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
public class LockException
|
||||||
|
extends ClientException
|
||||||
|
{
|
||||||
|
public LockException(@NotNull String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
protected String getMsgBundleKey() {
|
||||||
|
return null; //To change body of implemented methods use File | Settings | File Templates.
|
||||||
|
}
|
||||||
|
}
|
@ -1,29 +1,38 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright [2011] [wisemapping]
|
* Copyright [2011] [wisemapping]
|
||||||
*
|
*
|
||||||
* Licensed under WiseMapping Public License, Version 1.0 (the "License").
|
* Licensed under WiseMapping Public License, Version 1.0 (the "License").
|
||||||
* It is basically the Apache License, Version 2.0 (the "License") plus the
|
* It is basically the Apache License, Version 2.0 (the "License") plus the
|
||||||
* "powered by wisemapping" text requirement on every single page;
|
* "powered by wisemapping" text requirement on every single page;
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the license at
|
* You may obtain a copy of the license at
|
||||||
*
|
*
|
||||||
* http://www.wisemapping.org/license
|
* http://www.wisemapping.org/license
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.wisemapping.exceptions;
|
package com.wisemapping.exceptions;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
public class UnexpectedArgumentException
|
|
||||||
extends Exception
|
public class MindmapOutdatedException
|
||||||
{
|
extends ClientException
|
||||||
public UnexpectedArgumentException(String msg)
|
{
|
||||||
{
|
public static final String MSG_KEY = "MINDMAP_TIMESTAMP_OUTDATED";
|
||||||
super(msg);
|
|
||||||
}
|
public MindmapOutdatedException(@NotNull String msg)
|
||||||
}
|
{
|
||||||
|
super(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
protected String getMsgBundleKey() {
|
||||||
|
return MSG_KEY;
|
||||||
|
}
|
||||||
|
}
|
@ -138,10 +138,6 @@ public class Mindmap {
|
|||||||
return lastModificationTime;
|
return lastModificationTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Date getLastModificationDate() {
|
|
||||||
return new Date();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLastModificationTime(Calendar lastModificationTime) {
|
public void setLastModificationTime(Calendar lastModificationTime) {
|
||||||
this.lastModificationTime = lastModificationTime;
|
this.lastModificationTime = lastModificationTime;
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,8 @@ package com.wisemapping.model;
|
|||||||
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpSessionBindingEvent;
|
||||||
|
import javax.servlet.http.HttpSessionBindingListener;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
|
@ -19,12 +19,15 @@
|
|||||||
package com.wisemapping.ncontroller;
|
package com.wisemapping.ncontroller;
|
||||||
|
|
||||||
|
|
||||||
|
import com.wisemapping.exceptions.AccessDeniedSecurityException;
|
||||||
|
import com.wisemapping.exceptions.LockException;
|
||||||
import com.wisemapping.exceptions.WiseMappingException;
|
import com.wisemapping.exceptions.WiseMappingException;
|
||||||
import com.wisemapping.model.CollaborationRole;
|
import com.wisemapping.model.CollaborationRole;
|
||||||
import com.wisemapping.model.Mindmap;
|
import com.wisemapping.model.Mindmap;
|
||||||
import com.wisemapping.model.MindMapHistory;
|
import com.wisemapping.model.MindMapHistory;
|
||||||
import com.wisemapping.model.User;
|
import com.wisemapping.model.User;
|
||||||
import com.wisemapping.security.Utils;
|
import com.wisemapping.security.Utils;
|
||||||
|
import com.wisemapping.service.LockManager;
|
||||||
import com.wisemapping.service.MindmapService;
|
import com.wisemapping.service.MindmapService;
|
||||||
import com.wisemapping.view.MindMapBean;
|
import com.wisemapping.view.MindMapBean;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
@ -140,33 +143,47 @@ public class MindmapController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@RequestMapping(value = "maps/{id}/edit", method = RequestMethod.GET)
|
@RequestMapping(value = "maps/{id}/edit", method = RequestMethod.GET)
|
||||||
public String showMindmapEditorPage(@PathVariable int id, @NotNull Model model) {
|
public String showMindmapEditorPage(@PathVariable int id, @NotNull Model model) throws WiseMappingException {
|
||||||
|
return showEditorPage(id, model, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String showEditorPage(int id, @NotNull final Model model, boolean requiresLock) throws AccessDeniedSecurityException, LockException {
|
||||||
final MindMapBean mindmapBean = findMindmapBean(id);
|
final MindMapBean mindmapBean = findMindmapBean(id);
|
||||||
final Mindmap mindmap = mindmapBean.getDelegated();
|
final Mindmap mindmap = mindmapBean.getDelegated();
|
||||||
|
final User collaborator = Utils.getUser();
|
||||||
|
final Locale locale = LocaleContextHolder.getLocale();
|
||||||
|
|
||||||
|
// Is the mindmap locked ?.
|
||||||
|
boolean readOnlyMode = !requiresLock || !mindmap.hasPermissions(collaborator, CollaborationRole.EDITOR);
|
||||||
|
if (!readOnlyMode) {
|
||||||
|
final LockManager lockManager = this.mindmapService.getLockManager();
|
||||||
|
if (lockManager.isLocked(mindmap) && !lockManager.isLockedBy(mindmap, collaborator)) {
|
||||||
|
readOnlyMode = true;
|
||||||
|
model.addAttribute("lockedBy", lockManager.getLockInfo(mindmap));
|
||||||
|
} else {
|
||||||
|
lockManager.lock(mindmap, collaborator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set render attributes ...
|
||||||
model.addAttribute("mindmap", mindmapBean);
|
model.addAttribute("mindmap", mindmapBean);
|
||||||
|
|
||||||
// Configure default locale for the editor ...
|
// Configure default locale for the editor ...
|
||||||
final Locale locale = LocaleContextHolder.getLocale();
|
|
||||||
model.addAttribute("locale", locale.toString().toLowerCase());
|
model.addAttribute("locale", locale.toString().toLowerCase());
|
||||||
final User collaborator = Utils.getUser();
|
|
||||||
model.addAttribute("principal", collaborator);
|
model.addAttribute("principal", collaborator);
|
||||||
model.addAttribute("readOnlyMode", !mindmap.hasPermissions(collaborator, CollaborationRole.EDITOR));
|
model.addAttribute("readOnlyMode", readOnlyMode);
|
||||||
return "mindmapEditor";
|
return "mindmapEditor";
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequestMapping(value = "maps/{id}/view", method = RequestMethod.GET)
|
@RequestMapping(value = "maps/{id}/view", method = RequestMethod.GET)
|
||||||
public String showMindmapViewerPage(@PathVariable int id, @NotNull Model model) {
|
public String showMindmapViewerPage(@PathVariable int id, @NotNull Model model) throws LockException, AccessDeniedSecurityException {
|
||||||
final String result = showMindmapEditorPage(id, model);
|
return showEditorPage(id, model, false);
|
||||||
model.addAttribute("readOnlyMode", true);
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequestMapping(value = "maps/{id}/try", method = RequestMethod.GET)
|
@RequestMapping(value = "maps/{id}/try", method = RequestMethod.GET)
|
||||||
public String showMindmapTryPage(@PathVariable int id, @NotNull Model model) {
|
public String showMindmapTryPage(@PathVariable int id, @NotNull Model model) throws LockException, AccessDeniedSecurityException {
|
||||||
final String result = showMindmapEditorPage(id, model);
|
final String result = showEditorPage(id, model, false);
|
||||||
model.addAttribute("memoryPersistence", true);
|
model.addAttribute("memoryPersistence", true);
|
||||||
model.addAttribute("readOnlyMode", false);
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -213,11 +230,7 @@ public class MindmapController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Mindmap findMindmap(long mapId) {
|
private Mindmap findMindmap(long mapId) {
|
||||||
final Mindmap mindmap = mindmapService.findMindmapById((int) mapId);
|
return mindmapService.findMindmapById((int) mapId);
|
||||||
if (mindmap == null) {
|
|
||||||
throw new IllegalArgumentException("Mindmap could not be found");
|
|
||||||
}
|
|
||||||
return mindmap;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private MindMapBean findMindmapBean(long mapId) {
|
private MindMapBean findMindmapBean(long mapId) {
|
||||||
|
@ -24,6 +24,7 @@ import com.wisemapping.mail.NotificationService;
|
|||||||
import com.wisemapping.model.User;
|
import com.wisemapping.model.User;
|
||||||
import com.wisemapping.rest.model.RestErrors;
|
import com.wisemapping.rest.model.RestErrors;
|
||||||
import com.wisemapping.security.Utils;
|
import com.wisemapping.security.Utils;
|
||||||
|
import org.apache.log4j.Logger;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
@ -41,6 +42,8 @@ import java.util.Locale;
|
|||||||
|
|
||||||
public class BaseController {
|
public class BaseController {
|
||||||
|
|
||||||
|
final protected static Logger logger = Logger.getLogger("com.wisemapping.rest");
|
||||||
|
|
||||||
@Qualifier("messageSource")
|
@Qualifier("messageSource")
|
||||||
@Autowired
|
@Autowired
|
||||||
private ResourceBundleMessageSource messageSource;
|
private ResourceBundleMessageSource messageSource;
|
||||||
|
@ -20,6 +20,7 @@ package com.wisemapping.rest;
|
|||||||
|
|
||||||
|
|
||||||
import com.wisemapping.exceptions.ImportUnexpectedException;
|
import com.wisemapping.exceptions.ImportUnexpectedException;
|
||||||
|
import com.wisemapping.exceptions.MindmapOutdatedException;
|
||||||
import com.wisemapping.exceptions.WiseMappingException;
|
import com.wisemapping.exceptions.WiseMappingException;
|
||||||
import com.wisemapping.importer.ImportFormat;
|
import com.wisemapping.importer.ImportFormat;
|
||||||
import com.wisemapping.importer.Importer;
|
import com.wisemapping.importer.Importer;
|
||||||
@ -29,8 +30,10 @@ import com.wisemapping.model.*;
|
|||||||
import com.wisemapping.rest.model.*;
|
import com.wisemapping.rest.model.*;
|
||||||
import com.wisemapping.security.Utils;
|
import com.wisemapping.security.Utils;
|
||||||
import com.wisemapping.service.CollaborationException;
|
import com.wisemapping.service.CollaborationException;
|
||||||
|
import com.wisemapping.service.LockManager;
|
||||||
import com.wisemapping.service.MindmapService;
|
import com.wisemapping.service.MindmapService;
|
||||||
import com.wisemapping.validator.MapInfoValidator;
|
import com.wisemapping.validator.MapInfoValidator;
|
||||||
|
import org.apache.log4j.Logger;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
@ -49,6 +52,7 @@ import java.util.*;
|
|||||||
|
|
||||||
@Controller
|
@Controller
|
||||||
public class MindmapController extends BaseController {
|
public class MindmapController extends BaseController {
|
||||||
|
|
||||||
public static final String LATEST_HISTORY_REVISION = "latest";
|
public static final String LATEST_HISTORY_REVISION = "latest";
|
||||||
@Qualifier("mindmapService")
|
@Qualifier("mindmapService")
|
||||||
@Autowired
|
@Autowired
|
||||||
@ -136,8 +140,8 @@ public class MindmapController extends BaseController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@RequestMapping(method = RequestMethod.PUT, value = "/maps/{id}/document", consumes = {"application/xml", "application/json"}, produces = {"application/json", "text/html", "application/xml"})
|
@RequestMapping(method = RequestMethod.PUT, value = "/maps/{id}/document", consumes = {"application/xml", "application/json"}, produces = {"application/json", "text/html", "application/xml"})
|
||||||
@ResponseStatus(value = HttpStatus.NO_CONTENT)
|
@ResponseBody
|
||||||
public void updateDocument(@RequestBody RestMindmap restMindmap, @PathVariable int id, @RequestParam(required = false) boolean minor) throws WiseMappingException, IOException {
|
public long updateDocument(@RequestBody RestMindmap restMindmap, @PathVariable int id, @RequestParam(required = false) boolean minor, @RequestParam(required = false) Long timestamp) throws WiseMappingException, IOException {
|
||||||
|
|
||||||
final Mindmap mindmap = mindmapService.findMindmapById(id);
|
final Mindmap mindmap = mindmapService.findMindmapById(id);
|
||||||
final User user = Utils.getUser();
|
final User user = Utils.getUser();
|
||||||
@ -148,6 +152,11 @@ public class MindmapController extends BaseController {
|
|||||||
throw new IllegalArgumentException("Map properties can not be null");
|
throw new IllegalArgumentException("Map properties can not be null");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check that there we are not overwriting an already existing map ...
|
||||||
|
if (timestamp != null && mindmap.getLastModificationTime().getTimeInMillis() > timestamp) {
|
||||||
|
throw new MindmapOutdatedException("Mindmap timestamp out of sync. Client timestamp: " + timestamp + ", DB Timestamp:" + timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
// Update collaboration properties ...
|
// Update collaboration properties ...
|
||||||
final CollaborationProperties collaborationProperties = mindmap.findCollaborationProperties(user);
|
final CollaborationProperties collaborationProperties = mindmap.findCollaborationProperties(user);
|
||||||
collaborationProperties.setMindmapProperties(properties);
|
collaborationProperties.setMindmapProperties(properties);
|
||||||
@ -160,7 +169,11 @@ public class MindmapController extends BaseController {
|
|||||||
mindmap.setXmlStr(xml);
|
mindmap.setXmlStr(xml);
|
||||||
|
|
||||||
// Update map ...
|
// Update map ...
|
||||||
|
logger.debug("Mindmap save completed:" + restMindmap.getXml());
|
||||||
saveMindmap(minor, mindmap, user);
|
saveMindmap(minor, mindmap, user);
|
||||||
|
|
||||||
|
// Return last update timestamp ...
|
||||||
|
return mindmap.getLastModificationTime().getTimeInMillis();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -317,6 +330,14 @@ public class MindmapController extends BaseController {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@RequestMapping(method = RequestMethod.DELETE, value = "/maps/{id}")
|
||||||
|
@ResponseStatus(value = HttpStatus.NO_CONTENT)
|
||||||
|
public void updateMap(@PathVariable int id) throws IOException, WiseMappingException {
|
||||||
|
final User user = Utils.getUser();
|
||||||
|
final Mindmap mindmap = mindmapService.findMindmapById(id);
|
||||||
|
mindmapService.removeMindmap(mindmap, user);
|
||||||
|
}
|
||||||
|
|
||||||
@RequestMapping(method = RequestMethod.PUT, value = "/maps/{id}/starred", consumes = {"text/plain"}, produces = {"application/json", "text/html", "application/xml"})
|
@RequestMapping(method = RequestMethod.PUT, value = "/maps/{id}/starred", consumes = {"text/plain"}, produces = {"application/json", "text/html", "application/xml"})
|
||||||
@ResponseStatus(value = HttpStatus.NO_CONTENT)
|
@ResponseStatus(value = HttpStatus.NO_CONTENT)
|
||||||
public void updateStarredState(@RequestBody String value, @PathVariable int id) throws WiseMappingException {
|
public void updateStarredState(@RequestBody String value, @PathVariable int id) throws WiseMappingException {
|
||||||
@ -334,12 +355,13 @@ public class MindmapController extends BaseController {
|
|||||||
mindmapService.updateCollaboration(user, collaboration);
|
mindmapService.updateCollaboration(user, collaboration);
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequestMapping(method = RequestMethod.DELETE, value = "/maps/{id}")
|
@RequestMapping(method = RequestMethod.PUT, value = "/maps/{id}/lock", consumes = {"text/plain"}, produces = {"application/json", "text/html", "application/xml"})
|
||||||
@ResponseStatus(value = HttpStatus.NO_CONTENT)
|
@ResponseStatus(value = HttpStatus.NO_CONTENT)
|
||||||
public void updateMap(@PathVariable int id) throws IOException, WiseMappingException {
|
public void updateMapLock(@RequestBody String value, @PathVariable int id) throws IOException, WiseMappingException {
|
||||||
final User user = Utils.getUser();
|
final User user = Utils.getUser();
|
||||||
|
final LockManager lockManager = mindmapService.getLockManager();
|
||||||
final Mindmap mindmap = mindmapService.findMindmapById(id);
|
final Mindmap mindmap = mindmapService.findMindmapById(id);
|
||||||
mindmapService.removeMindmap(mindmap, user);
|
lockManager.updateLock(Boolean.parseBoolean(value), mindmap, user);
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequestMapping(method = RequestMethod.DELETE, value = "/maps/batch")
|
@RequestMapping(method = RequestMethod.DELETE, value = "/maps/batch")
|
||||||
|
@ -0,0 +1,55 @@
|
|||||||
|
package com.wisemapping.rest.model;
|
||||||
|
|
||||||
|
|
||||||
|
import com.wisemapping.model.Collaborator;
|
||||||
|
import com.wisemapping.model.User;
|
||||||
|
import com.wisemapping.service.LockInfo;
|
||||||
|
import org.codehaus.jackson.annotate.JsonAutoDetect;
|
||||||
|
import org.codehaus.jackson.annotate.JsonIgnore;
|
||||||
|
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import javax.xml.bind.annotation.XmlAccessType;
|
||||||
|
import javax.xml.bind.annotation.XmlAccessorType;
|
||||||
|
import javax.xml.bind.annotation.XmlRootElement;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
@XmlRootElement(name = "lock")
|
||||||
|
@XmlAccessorType(XmlAccessType.PROPERTY)
|
||||||
|
@JsonAutoDetect(
|
||||||
|
fieldVisibility = JsonAutoDetect.Visibility.NONE,
|
||||||
|
getterVisibility = JsonAutoDetect.Visibility.PUBLIC_ONLY,
|
||||||
|
isGetterVisibility = JsonAutoDetect.Visibility.PUBLIC_ONLY)
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public class RestMindmapLock {
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private Collaborator user;
|
||||||
|
@Nullable
|
||||||
|
private LockInfo lockInfo;
|
||||||
|
|
||||||
|
public RestMindmapLock(@Nullable LockInfo lockInfo, @NotNull Collaborator collaborator) {
|
||||||
|
|
||||||
|
this.lockInfo = lockInfo;
|
||||||
|
this.user = collaborator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isLocked() {
|
||||||
|
return lockInfo != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLocked(boolean locked) {
|
||||||
|
// Ignore ...
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isLockedByMe() {
|
||||||
|
return isLocked() && lockInfo != null && lockInfo.getCollaborator().equals(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLockedByMe(boolean lockedForMe) {
|
||||||
|
// Ignore ...
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* Copyright [2011] [wisemapping]
|
||||||
|
*
|
||||||
|
* Licensed under WiseMapping Public License, Version 1.0 (the "License").
|
||||||
|
* It is basically the Apache License, Version 2.0 (the "License") plus the
|
||||||
|
* "powered by wisemapping" text requirement on every single page;
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the license at
|
||||||
|
*
|
||||||
|
* http://www.wisemapping.org/license
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.wisemapping.security;
|
package com.wisemapping.security;
|
||||||
|
|
||||||
|
|
||||||
|
@ -22,7 +22,6 @@ import com.wisemapping.model.Collaborator;
|
|||||||
import com.wisemapping.model.Mindmap;
|
import com.wisemapping.model.Mindmap;
|
||||||
import com.wisemapping.model.User;
|
import com.wisemapping.model.User;
|
||||||
import com.wisemapping.exceptions.AccessDeniedSecurityException;
|
import com.wisemapping.exceptions.AccessDeniedSecurityException;
|
||||||
import com.wisemapping.exceptions.UnexpectedArgumentException;
|
|
||||||
import com.wisemapping.security.Utils;
|
import com.wisemapping.security.Utils;
|
||||||
import com.wisemapping.service.MindmapService;
|
import com.wisemapping.service.MindmapService;
|
||||||
import org.aopalliance.intercept.MethodInvocation;
|
import org.aopalliance.intercept.MethodInvocation;
|
||||||
@ -31,7 +30,7 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
public abstract class BaseSecurityAdvice {
|
public abstract class BaseSecurityAdvice {
|
||||||
private MindmapService mindmapService = null;
|
private MindmapService mindmapService = null;
|
||||||
|
|
||||||
public void checkRole(MethodInvocation methodInvocation) throws UnexpectedArgumentException, AccessDeniedSecurityException {
|
public void checkRole(MethodInvocation methodInvocation) throws AccessDeniedSecurityException {
|
||||||
final User user = Utils.getUser();
|
final User user = Utils.getUser();
|
||||||
final Object argument = methodInvocation.getArguments()[0];
|
final Object argument = methodInvocation.getArguments()[0];
|
||||||
boolean isAllowed;
|
boolean isAllowed;
|
||||||
@ -44,7 +43,7 @@ public abstract class BaseSecurityAdvice {
|
|||||||
// Read operation find on the user are allowed ...
|
// Read operation find on the user are allowed ...
|
||||||
isAllowed = user.equals(argument);
|
isAllowed = user.equals(argument);
|
||||||
} else {
|
} else {
|
||||||
throw new UnexpectedArgumentException("Argument " + argument);
|
throw new IllegalArgumentException("Argument " + argument);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isAllowed) {
|
if (!isAllowed) {
|
||||||
|
@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* Copyright [2011] [wisemapping]
|
||||||
|
*
|
||||||
|
* Licensed under WiseMapping Public License, Version 1.0 (the "License").
|
||||||
|
* It is basically the Apache License, Version 2.0 (the "License") plus the
|
||||||
|
* "powered by wisemapping" text requirement on every single page;
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the license at
|
||||||
|
*
|
||||||
|
* http://www.wisemapping.org/license
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.wisemapping.service;
|
||||||
|
|
||||||
|
import com.wisemapping.model.Collaborator;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.Calendar;
|
||||||
|
|
||||||
|
public class LockInfo {
|
||||||
|
final private Collaborator collaborator;
|
||||||
|
private Calendar timeout;
|
||||||
|
private static int EXPIRATION_MIN = 25;
|
||||||
|
|
||||||
|
public LockInfo(@NotNull Collaborator collaborator) {
|
||||||
|
this.collaborator = collaborator;
|
||||||
|
this.updateTimeout();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collaborator getCollaborator() {
|
||||||
|
return collaborator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isExpired() {
|
||||||
|
return timeout.before(Calendar.getInstance());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateTimeout() {
|
||||||
|
final Calendar calendar = Calendar.getInstance();
|
||||||
|
calendar.add(Calendar.MINUTE, EXPIRATION_MIN);
|
||||||
|
this.timeout = calendar;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* Copyright [2011] [wisemapping]
|
||||||
|
*
|
||||||
|
* Licensed under WiseMapping Public License, Version 1.0 (the "License").
|
||||||
|
* It is basically the Apache License, Version 2.0 (the "License") plus the
|
||||||
|
* "powered by wisemapping" text requirement on every single page;
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the license at
|
||||||
|
*
|
||||||
|
* http://www.wisemapping.org/license
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.wisemapping.service;
|
||||||
|
|
||||||
|
import com.wisemapping.exceptions.AccessDeniedSecurityException;
|
||||||
|
import com.wisemapping.exceptions.LockException;
|
||||||
|
import com.wisemapping.exceptions.WiseMappingException;
|
||||||
|
import com.wisemapping.model.Collaborator;
|
||||||
|
import com.wisemapping.model.Mindmap;
|
||||||
|
import com.wisemapping.model.User;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
public interface LockManager {
|
||||||
|
boolean isLocked(@NotNull Mindmap mindmap);
|
||||||
|
|
||||||
|
LockInfo getLockInfo(@NotNull Mindmap mindmap);
|
||||||
|
|
||||||
|
void updateExpirationTimeout(@NotNull Mindmap mindmap, @NotNull Collaborator user);
|
||||||
|
|
||||||
|
void unlock(@NotNull Mindmap mindmap, @NotNull Collaborator user) throws LockException, AccessDeniedSecurityException;
|
||||||
|
|
||||||
|
boolean isLockedBy(@NotNull Mindmap mindmap, @NotNull Collaborator collaborator);
|
||||||
|
|
||||||
|
void lock(@NotNull Mindmap mindmap, @NotNull Collaborator user) throws AccessDeniedSecurityException, LockException;
|
||||||
|
|
||||||
|
void updateLock(boolean value, Mindmap mindmap, User user) throws WiseMappingException;
|
||||||
|
}
|
@ -0,0 +1,153 @@
|
|||||||
|
/*
|
||||||
|
* Copyright [2011] [wisemapping]
|
||||||
|
*
|
||||||
|
* Licensed under WiseMapping Public License, Version 1.0 (the "License").
|
||||||
|
* It is basically the Apache License, Version 2.0 (the "License") plus the
|
||||||
|
* "powered by wisemapping" text requirement on every single page;
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the license at
|
||||||
|
*
|
||||||
|
* http://www.wisemapping.org/license
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.wisemapping.service;
|
||||||
|
|
||||||
|
import com.wisemapping.exceptions.AccessDeniedSecurityException;
|
||||||
|
import com.wisemapping.exceptions.LockException;
|
||||||
|
import com.wisemapping.exceptions.WiseMappingException;
|
||||||
|
import com.wisemapping.model.CollaborationRole;
|
||||||
|
import com.wisemapping.model.Collaborator;
|
||||||
|
import com.wisemapping.model.Mindmap;
|
||||||
|
import com.wisemapping.model.User;
|
||||||
|
import org.apache.log4j.Logger;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Refresh page should not lost the lock.
|
||||||
|
* En caso que no sea posible grabar por que se perdio el lock, usar mensaje de error para explicar el por que...
|
||||||
|
* Mensaje modal explicando que el mapa esta siendo editado, por eso no es posible edilarlo....
|
||||||
|
*/
|
||||||
|
|
||||||
|
class LockManagerImpl implements LockManager {
|
||||||
|
public static final int ONE_MINUTE_MILLISECONDS = 1000 * 60;
|
||||||
|
final Map<Integer, LockInfo> lockInfoByMapId;
|
||||||
|
final static Timer expirationTimer = new Timer();
|
||||||
|
final private static Logger logger = Logger.getLogger("com.wisemapping.service.LockManager");
|
||||||
|
|
||||||
|
public LockManagerImpl() {
|
||||||
|
lockInfoByMapId = new ConcurrentHashMap<Integer, LockInfo>();
|
||||||
|
expirationTimer.schedule(new TimerTask() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
|
||||||
|
logger.debug("Lock expiration scheduler started. Current locks:" + lockInfoByMapId.keySet());
|
||||||
|
|
||||||
|
final List<Integer> toRemove = new ArrayList<Integer>();
|
||||||
|
final Set<Integer> mapIds = lockInfoByMapId.keySet();
|
||||||
|
for (Integer mapId : mapIds) {
|
||||||
|
final LockInfo lockInfo = lockInfoByMapId.get(mapId);
|
||||||
|
if (lockInfo.isExpired()) {
|
||||||
|
toRemove.add(mapId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Integer mapId : toRemove) {
|
||||||
|
unlock(mapId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, ONE_MINUTE_MILLISECONDS, ONE_MINUTE_MILLISECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isLocked(@NotNull Mindmap mindmap) {
|
||||||
|
return this.getLockInfo(mindmap) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LockInfo getLockInfo(@NotNull Mindmap mindmap) {
|
||||||
|
return lockInfoByMapId.get(mindmap.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateExpirationTimeout(@NotNull Mindmap mindmap, @NotNull Collaborator user) {
|
||||||
|
if (this.isLocked(mindmap)) {
|
||||||
|
final LockInfo lockInfo = this.getLockInfo(mindmap);
|
||||||
|
if (!lockInfo.getCollaborator().equals(user)) {
|
||||||
|
throw new IllegalStateException("Could not update map lock timeout if you are not the locking user. User:" + lockInfo.getCollaborator() + ", " + user);
|
||||||
|
}
|
||||||
|
lockInfo.updateTimeout();
|
||||||
|
logger.debug("Timeout updated for:" + mindmap.getId());
|
||||||
|
|
||||||
|
}else {
|
||||||
|
throw new IllegalStateException("Lock lost for map. No update possible.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void unlock(@NotNull Mindmap mindmap, @NotNull Collaborator user) throws LockException, AccessDeniedSecurityException {
|
||||||
|
if (isLocked(mindmap) && !isLockedBy(mindmap, user)) {
|
||||||
|
throw new LockException("Lock can be only revoked by the locker.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mindmap.hasPermissions(user, CollaborationRole.EDITOR)) {
|
||||||
|
throw new AccessDeniedSecurityException("Invalid lock, this should not happen");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.unlock(mindmap.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void unlock(int mapId) {
|
||||||
|
logger.debug("Unlock map id:" + mapId);
|
||||||
|
lockInfoByMapId.remove(mapId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isLockedBy(@NotNull Mindmap mindmap, @NotNull Collaborator collaborator) {
|
||||||
|
boolean result = false;
|
||||||
|
final LockInfo lockInfo = this.getLockInfo(mindmap);
|
||||||
|
if (lockInfo != null && lockInfo.getCollaborator().equals(collaborator)) {
|
||||||
|
result = true;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void lock(@NotNull Mindmap mindmap, @NotNull Collaborator user) throws AccessDeniedSecurityException, LockException {
|
||||||
|
if (isLocked(mindmap) && !isLockedBy(mindmap, user)) {
|
||||||
|
throw new LockException("Invalid lock, this should not happen");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mindmap.hasPermissions(user, CollaborationRole.EDITOR)) {
|
||||||
|
throw new AccessDeniedSecurityException("Invalid lock, this should not happen");
|
||||||
|
}
|
||||||
|
|
||||||
|
final LockInfo lockInfo = lockInfoByMapId.get(mindmap.getId());
|
||||||
|
if (lockInfo != null) {
|
||||||
|
// Update timeout only...
|
||||||
|
logger.debug("Update timestamp:" + mindmap.getId());
|
||||||
|
updateExpirationTimeout(mindmap, user);
|
||||||
|
} else {
|
||||||
|
logger.debug("Lock map id:" + mindmap.getId());
|
||||||
|
lockInfoByMapId.put(mindmap.getId(), new LockInfo(user));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateLock(boolean lock, @NotNull Mindmap mindmap, @NotNull User user) throws WiseMappingException {
|
||||||
|
if (lock) {
|
||||||
|
this.lock(mindmap, user);
|
||||||
|
} else {
|
||||||
|
this.unlock(mindmap, user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -62,4 +62,6 @@ public interface MindmapService {
|
|||||||
MindMapHistory findMindmapHistory(int id, int hid) throws WiseMappingException;
|
MindMapHistory findMindmapHistory(int id, int hid) throws WiseMappingException;
|
||||||
|
|
||||||
void updateCollaboration(@NotNull Collaborator collaborator, @NotNull Collaboration collaboration) throws WiseMappingException;
|
void updateCollaboration(@NotNull Collaborator collaborator, @NotNull Collaboration collaboration) throws WiseMappingException;
|
||||||
|
|
||||||
|
LockManager getLockManager();
|
||||||
}
|
}
|
||||||
|
@ -45,6 +45,11 @@ public class MindmapServiceImpl
|
|||||||
private NotificationService notificationService;
|
private NotificationService notificationService;
|
||||||
|
|
||||||
private String adminUser;
|
private String adminUser;
|
||||||
|
final private LockManager lockManager;
|
||||||
|
|
||||||
|
public MindmapServiceImpl() {
|
||||||
|
this.lockManager = new LockManagerImpl();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasPermissions(@Nullable User user, int mapId, @NotNull CollaborationRole grantedRole) {
|
public boolean hasPermissions(@Nullable User user, int mapId, @NotNull CollaborationRole grantedRole) {
|
||||||
@ -94,6 +99,11 @@ public class MindmapServiceImpl
|
|||||||
if (mindMap.getTitle() == null || mindMap.getTitle().length() == 0) {
|
if (mindMap.getTitle() == null || mindMap.getTitle().length() == 0) {
|
||||||
throw new WiseMappingException("The tile can not be empty");
|
throw new WiseMappingException("The tile can not be empty");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update edition timeout ...
|
||||||
|
final LockManager lockManager = this.getLockManager();
|
||||||
|
lockManager.updateExpirationTimeout(mindMap, Utils.getUser());
|
||||||
|
|
||||||
mindmapManager.updateMindmap(mindMap, saveHistory);
|
mindmapManager.updateMindmap(mindMap, saveHistory);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -264,6 +274,12 @@ public class MindmapServiceImpl
|
|||||||
mindmapManager.updateCollaboration(collaboration);
|
mindmapManager.updateCollaboration(collaboration);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NotNull
|
||||||
|
public LockManager getLockManager() {
|
||||||
|
return this.lockManager;
|
||||||
|
}
|
||||||
|
|
||||||
private Collaboration getCollaborationBy(String email, Set<Collaboration> collaborations) {
|
private Collaboration getCollaborationBy(String email, Set<Collaboration> collaborations) {
|
||||||
Collaboration collaboration = null;
|
Collaboration collaboration = null;
|
||||||
|
|
||||||
|
@ -249,7 +249,7 @@ DIRECT_LINK_EXPLANATION=Copy and paste the link below to share your map with col
|
|||||||
TEMPORAL_PASSWORD_SENT=Your temporal password has been sent
|
TEMPORAL_PASSWORD_SENT=Your temporal password has been sent
|
||||||
TEMPORAL_PASSWORD_SENT_DETAILS=We've sent you an email that will allow you to reset your password. Please check your email now.
|
TEMPORAL_PASSWORD_SENT_DETAILS=We've sent you an email that will allow you to reset your password. Please check your email now.
|
||||||
TEMPORAL_PASSWORD_SENT_SUPPORT=If you have any problem receiving the email, contact us to <a href\="mailto\:support@wisemapping.com">support@wisemapping.com </a>
|
TEMPORAL_PASSWORD_SENT_SUPPORT=If you have any problem receiving the email, contact us to <a href\="mailto\:support@wisemapping.com">support@wisemapping.com </a>
|
||||||
|
MINDMAP_TIMESTAMP_OUTDATED=It's not possible to save your changes because your mindmap is out of date. Refresh the page and try again.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
log4j.rootLogger=WARN, stdout, R
|
log4j.rootLogger=WARN, stdout, R
|
||||||
|
log4j.logger.com.wisemapping.service.LockManager=DEBUG,stdout,R
|
||||||
log4j.logger.com.wisemapping=WARN,stdout,R
|
log4j.logger.com.wisemapping=WARN,stdout,R
|
||||||
log4j.logger.org.springframework=WARN,stdout,R
|
log4j.logger.org.springframework=WARN,stdout,R
|
||||||
log4j.logger.org.codehaus.jackson=WARN,stdout,R
|
log4j.logger.org.codehaus.jackson=WARN,stdout,R
|
||||||
log4j.logger.org.hibernate=DEBUG,stdout,R
|
log4j.logger.org.hibernate=WARN,stdout,R
|
||||||
log4j.logger.org.hibernate.SQL=true
|
log4j.logger.org.hibernate.SQL=true
|
||||||
|
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@
|
|||||||
// Configure designer options ...
|
// Configure designer options ...
|
||||||
var options = loadDesignerOptions();
|
var options = loadDesignerOptions();
|
||||||
<c:if test="${!memoryPersistence}">
|
<c:if test="${!memoryPersistence}">
|
||||||
options.persistenceManager = new mindplot.RESTPersistenceManager("service/maps/{id}/document", "service/maps/{id}/history/latest");
|
options.persistenceManager = new mindplot.RESTPersistenceManager("service/maps/{id}/document", "service/maps/{id}/history/latest","service/maps/{id}/lock");
|
||||||
</c:if>
|
</c:if>
|
||||||
var userOptions = ${mindmap.properties};
|
var userOptions = ${mindmap.properties};
|
||||||
options.zoom = userOptions.zoom;
|
options.zoom = userOptions.zoom;
|
||||||
|
@ -15,31 +15,31 @@
|
|||||||
<edge COLOR="#808080"/>
|
<edge COLOR="#808080"/>
|
||||||
</node>
|
</node>
|
||||||
</node>
|
</node>
|
||||||
<node TEXT="Node Background Color" POSITION="left" ID="ID_6" BACKGROUND_COLOR="#f20707">
|
<node TEXT="Node Background Color" STYLE="rectagle" POSITION="left" ID="ID_6" BACKGROUND_COLOR="#f20707">
|
||||||
<node TEXT="Fork" POSITION="left" ID="ID_7" BACKGROUND_COLOR="#0000cc">
|
<node TEXT="Fork" STYLE="rectagle" POSITION="left" ID="ID_7" BACKGROUND_COLOR="#0000cc">
|
||||||
<edge COLOR="#808080"/>
|
<edge COLOR="#808080"/>
|
||||||
</node>
|
</node>
|
||||||
<node TEXT="Bubble" STYLE="bubble" POSITION="left" ID="ID_8" BACKGROUND_COLOR="#ccffcc">
|
<node TEXT="Bubble" STYLE="bubble" POSITION="left" ID="ID_8" BACKGROUND_COLOR="#ccffcc">
|
||||||
<edge COLOR="#808080"/>
|
<edge COLOR="#808080"/>
|
||||||
</node>
|
</node>
|
||||||
<node TEXT="As parent" POSITION="left" ID="ID_9" BACKGROUND_COLOR="#00ffff">
|
<node TEXT="As parent" STYLE="rectagle" POSITION="left" ID="ID_9" BACKGROUND_COLOR="#00ffff">
|
||||||
<edge COLOR="#808080"/>
|
<edge COLOR="#808080"/>
|
||||||
</node>
|
</node>
|
||||||
<node TEXT="Combined" POSITION="left" ID="ID_10" BACKGROUND_COLOR="#990099">
|
<node TEXT="Combined" STYLE="rectagle" POSITION="left" ID="ID_10" BACKGROUND_COLOR="#990099">
|
||||||
<edge COLOR="#808080"/>
|
<edge COLOR="#808080"/>
|
||||||
</node>
|
</node>
|
||||||
</node>
|
</node>
|
||||||
<node TEXT="Node Text Color" POSITION="left" ID="ID_11" BACKGROUND_COLOR="#f20707">
|
<node TEXT="Node Text Color" STYLE="rectagle" POSITION="left" ID="ID_11" BACKGROUND_COLOR="#f20707">
|
||||||
<node TEXT="Fork" POSITION="left" ID="ID_12" COLOR="#ffff00" BACKGROUND_COLOR="#0000cc">
|
<node TEXT="Fork" STYLE="rectagle" POSITION="left" ID="ID_12" COLOR="#ffff00" BACKGROUND_COLOR="#0000cc">
|
||||||
<edge COLOR="#808080"/>
|
<edge COLOR="#808080"/>
|
||||||
</node>
|
</node>
|
||||||
<node TEXT="Bubble" STYLE="bubble" POSITION="left" ID="ID_13" COLOR="#ff6666" BACKGROUND_COLOR="#ccffcc">
|
<node TEXT="Bubble" STYLE="bubble" POSITION="left" ID="ID_13" COLOR="#ff6666" BACKGROUND_COLOR="#ccffcc">
|
||||||
<edge COLOR="#808080"/>
|
<edge COLOR="#808080"/>
|
||||||
</node>
|
</node>
|
||||||
<node TEXT="As parent" POSITION="left" ID="ID_14" COLOR="#009999" BACKGROUND_COLOR="#00ffff">
|
<node TEXT="As parent" STYLE="rectagle" POSITION="left" ID="ID_14" COLOR="#009999" BACKGROUND_COLOR="#00ffff">
|
||||||
<edge COLOR="#808080"/>
|
<edge COLOR="#808080"/>
|
||||||
</node>
|
</node>
|
||||||
<node TEXT="Combined" POSITION="left" ID="ID_15" COLOR="#009999" BACKGROUND_COLOR="#990099">
|
<node TEXT="Combined" STYLE="rectagle" POSITION="left" ID="ID_15" COLOR="#009999" BACKGROUND_COLOR="#990099">
|
||||||
<edge COLOR="#808080"/>
|
<edge COLOR="#808080"/>
|
||||||
</node>
|
</node>
|
||||||
</node>
|
</node>
|
||||||
|
@ -17,10 +17,10 @@
|
|||||||
<text><![CDATA[Combined]]></text>
|
<text><![CDATA[Combined]]></text>
|
||||||
</topic>
|
</topic>
|
||||||
</topic>
|
</topic>
|
||||||
<topic id="6" position="-200,0" order="0" bgColor="#f20707" shape="line">
|
<topic id="6" position="-200,0" order="0" bgColor="#f20707" shape="rectagle">
|
||||||
<text><![CDATA[Node Background Color]]></text>
|
<text><![CDATA[Node Background Color]]></text>
|
||||||
<topic id="7" position="-290,-50" order="0"
|
<topic id="7" position="-290,-50" order="0"
|
||||||
brColor="#808080" bgColor="#0000cc" shape="line">
|
brColor="#808080" bgColor="#0000cc" shape="rectagle">
|
||||||
<text><![CDATA[Fork]]></text>
|
<text><![CDATA[Fork]]></text>
|
||||||
</topic>
|
</topic>
|
||||||
<topic id="8" position="-290,-25" order="1"
|
<topic id="8" position="-290,-25" order="1"
|
||||||
@ -28,19 +28,19 @@
|
|||||||
<text><![CDATA[Bubble]]></text>
|
<text><![CDATA[Bubble]]></text>
|
||||||
</topic>
|
</topic>
|
||||||
<topic id="9" position="-290,0" order="2" brColor="#808080"
|
<topic id="9" position="-290,0" order="2" brColor="#808080"
|
||||||
bgColor="#00ffff" shape="line">
|
bgColor="#00ffff" shape="rectagle">
|
||||||
<text><![CDATA[As parent]]></text>
|
<text><![CDATA[As parent]]></text>
|
||||||
</topic>
|
</topic>
|
||||||
<topic id="10" position="-290,25" order="3"
|
<topic id="10" position="-290,25" order="3"
|
||||||
brColor="#808080" bgColor="#990099" shape="line">
|
brColor="#808080" bgColor="#990099" shape="rectagle">
|
||||||
<text><![CDATA[Combined]]></text>
|
<text><![CDATA[Combined]]></text>
|
||||||
</topic>
|
</topic>
|
||||||
</topic>
|
</topic>
|
||||||
<topic id="11" position="-200,100" order="4" bgColor="#f20707" shape="line">
|
<topic id="11" position="-200,100" order="4" bgColor="#f20707" shape="rectagle">
|
||||||
<text><![CDATA[Node Text Color]]></text>
|
<text><![CDATA[Node Text Color]]></text>
|
||||||
<topic id="12" position="-290,50" order="0"
|
<topic id="12" position="-290,50" order="0"
|
||||||
brColor="#808080" bgColor="#0000cc"
|
brColor="#808080" bgColor="#0000cc"
|
||||||
fontStyle=";;#ffff00;;;" shape="line">
|
fontStyle=";;#ffff00;;;" shape="rectagle">
|
||||||
<text><![CDATA[Fork]]></text>
|
<text><![CDATA[Fork]]></text>
|
||||||
</topic>
|
</topic>
|
||||||
<topic id="13" position="-290,75" order="1"
|
<topic id="13" position="-290,75" order="1"
|
||||||
@ -50,12 +50,12 @@
|
|||||||
</topic>
|
</topic>
|
||||||
<topic id="14" position="-290,100" order="2"
|
<topic id="14" position="-290,100" order="2"
|
||||||
brColor="#808080" bgColor="#00ffff"
|
brColor="#808080" bgColor="#00ffff"
|
||||||
fontStyle=";;#009999;;;" shape="line">
|
fontStyle=";;#009999;;;" shape="rectagle">
|
||||||
<text><![CDATA[As parent]]></text>
|
<text><![CDATA[As parent]]></text>
|
||||||
</topic>
|
</topic>
|
||||||
<topic id="15" position="-290,125" order="3"
|
<topic id="15" position="-290,125" order="3"
|
||||||
brColor="#808080" bgColor="#990099"
|
brColor="#808080" bgColor="#990099"
|
||||||
fontStyle=";;#009999;;;" shape="line">
|
fontStyle=";;#009999;;;" shape="rectagle">
|
||||||
<text><![CDATA[Combined]]></text>
|
<text><![CDATA[Combined]]></text>
|
||||||
</topic>
|
</topic>
|
||||||
</topic>
|
</topic>
|
||||||
|
@ -24,36 +24,36 @@
|
|||||||
<node TEXT="1) Adapter et améliorer 
 notre offre de services 
 selon l'évolution des besoins" STYLE="bubble" POSITION="right" ID="ID_7" COLOR="#0000cc" BACKGROUND_COLOR="#99ffff">
|
<node TEXT="1) Adapter et améliorer 
 notre offre de services 
 selon l'évolution des besoins" STYLE="bubble" POSITION="right" ID="ID_7" COLOR="#0000cc" BACKGROUND_COLOR="#99ffff">
|
||||||
<font SIZE="8" NAME="Arial" BOLD="true"/>
|
<font SIZE="8" NAME="Arial" BOLD="true"/>
|
||||||
<edge COLOR="#808080"/>
|
<edge COLOR="#808080"/>
|
||||||
<node TEXT="couleurs = état d'avancement en septembre 2011, 
à mettre à jour selon avancement 2012" POSITION="right" ID="ID_8" BACKGROUND_COLOR="#99ffff">
|
<node TEXT="couleurs = état d'avancement en septembre 2011, 
à mettre à jour selon avancement 2012" STYLE="rectagle" POSITION="right" ID="ID_8" BACKGROUND_COLOR="#99ffff">
|
||||||
<node TEXT="Bleu 0%" POSITION="right" ID="ID_9" BACKGROUND_COLOR="#0099ff"/>
|
<node TEXT="Bleu 0%" STYLE="rectagle" POSITION="right" ID="ID_9" BACKGROUND_COLOR="#0099ff"/>
|
||||||
<node TEXT="Rouge 25%" POSITION="right" ID="ID_10" BACKGROUND_COLOR="#ff9999"/>
|
<node TEXT="Rouge 25%" STYLE="rectagle" POSITION="right" ID="ID_10" BACKGROUND_COLOR="#ff9999"/>
|
||||||
<node TEXT="Orange 50%" POSITION="right" ID="ID_11" BACKGROUND_COLOR="#ffcc00"/>
|
<node TEXT="Orange 50%" STYLE="rectagle" POSITION="right" ID="ID_11" BACKGROUND_COLOR="#ffcc00"/>
|
||||||
<node TEXT="Jaune 75%" POSITION="right" ID="ID_12" BACKGROUND_COLOR="#ffff00"/>
|
<node TEXT="Jaune 75%" STYLE="rectagle" POSITION="right" ID="ID_12" BACKGROUND_COLOR="#ffff00"/>
|
||||||
<node TEXT="Vert" POSITION="right" ID="ID_13" BACKGROUND_COLOR="#66ff00">
|
<node TEXT="Vert" STYLE="rectagle" POSITION="right" ID="ID_13" BACKGROUND_COLOR="#66ff00">
|
||||||
<node TEXT="100 %" POSITION="right" ID="ID_14"/>
|
<node TEXT="100 %" POSITION="right" ID="ID_14"/>
|
||||||
</node>
|
</node>
|
||||||
</node>
|
</node>
|
||||||
<node TEXT="1.1. .Renforcer et structurer 
 le service "accueil et orientation"" POSITION="right" ID="ID_15" FOLDED="true" COLOR="#006600" BACKGROUND_COLOR="#66ff00">
|
<node TEXT="1.1. .Renforcer et structurer 
 le service "accueil et orientation"" STYLE="rectagle" POSITION="right" ID="ID_15" FOLDED="true" COLOR="#006600" BACKGROUND_COLOR="#66ff00">
|
||||||
<font SIZE="8" NAME="Arial" BOLD="true"/>
|
<font SIZE="8" NAME="Arial" BOLD="true"/>
|
||||||
<node TEXT="Sophie" POSITION="right" ID="ID_16"/>
|
<node TEXT="Sophie" POSITION="right" ID="ID_16"/>
|
||||||
</node>
|
</node>
|
||||||
<node TEXT="1.2. Adapter l'offre Halte-Garderie 
 aux besoins des familles" POSITION="right" ID="ID_17" COLOR="#006600" BACKGROUND_COLOR="#ffff00">
|
<node TEXT="1.2. Adapter l'offre Halte-Garderie 
 aux besoins des familles" STYLE="rectagle" POSITION="right" ID="ID_17" COLOR="#006600" BACKGROUND_COLOR="#ffff00">
|
||||||
<font SIZE="8" NAME="Arial" BOLD="true"/>
|
<font SIZE="8" NAME="Arial" BOLD="true"/>
|
||||||
<node TEXT="Martine D et Christine" POSITION="right" ID="ID_18"/>
|
<node TEXT="Martine D et Christine" POSITION="right" ID="ID_18"/>
|
||||||
</node>
|
</node>
|
||||||
<node TEXT="1.3. Renforcer et structurer les services 
 de soutien à la parentalité" POSITION="right" ID="ID_19" COLOR="#006600" BACKGROUND_COLOR="#66ff00">
|
<node TEXT="1.3. Renforcer et structurer les services 
 de soutien à la parentalité" STYLE="rectagle" POSITION="right" ID="ID_19" COLOR="#006600" BACKGROUND_COLOR="#66ff00">
|
||||||
<font SIZE="8" NAME="Arial" BOLD="true"/>
|
<font SIZE="8" NAME="Arial" BOLD="true"/>
|
||||||
<node TEXT="Nathalie" POSITION="right" ID="ID_20"/>
|
<node TEXT="Nathalie" POSITION="right" ID="ID_20"/>
|
||||||
</node>
|
</node>
|
||||||
<node TEXT="1.4 Améliorer la qualité des 
 projets pédagogiques des ALSH" POSITION="right" ID="ID_21" COLOR="#006600" BACKGROUND_COLOR="#ffff00">
|
<node TEXT="1.4 Améliorer la qualité des 
 projets pédagogiques des ALSH" STYLE="rectagle" POSITION="right" ID="ID_21" COLOR="#006600" BACKGROUND_COLOR="#ffff00">
|
||||||
<font SIZE="8" NAME="Arial" BOLD="true"/>
|
<font SIZE="8" NAME="Arial" BOLD="true"/>
|
||||||
<node TEXT="Josiane" POSITION="right" ID="ID_22"/>
|
<node TEXT="Josiane" POSITION="right" ID="ID_22"/>
|
||||||
</node>
|
</node>
|
||||||
<node TEXT="1.5 Renforcer et améliorer les 
 activités de loisirs existantes" POSITION="right" ID="ID_23" COLOR="#006600" BACKGROUND_COLOR="#ffcc00">
|
<node TEXT="1.5 Renforcer et améliorer les 
 activités de loisirs existantes" STYLE="rectagle" POSITION="right" ID="ID_23" COLOR="#006600" BACKGROUND_COLOR="#ffcc00">
|
||||||
<font SIZE="8" NAME="Arial" BOLD="true"/>
|
<font SIZE="8" NAME="Arial" BOLD="true"/>
|
||||||
<node TEXT="Agnès" POSITION="right" ID="ID_24"/>
|
<node TEXT="Agnès" POSITION="right" ID="ID_24"/>
|
||||||
</node>
|
</node>
|
||||||
<node TEXT="1.6 Renforcer et améliorer les ateliers 
 visant à rompre l'isolement lié à la langue, 
 la pauvreté, la pression de la consommation" POSITION="right" ID="ID_25" COLOR="#006600" BACKGROUND_COLOR="#66ff00">
|
<node TEXT="1.6 Renforcer et améliorer les ateliers 
 visant à rompre l'isolement lié à la langue, 
 la pauvreté, la pression de la consommation" STYLE="rectagle" POSITION="right" ID="ID_25" COLOR="#006600" BACKGROUND_COLOR="#66ff00">
|
||||||
<font SIZE="8" NAME="Arial" BOLD="true"/>
|
<font SIZE="8" NAME="Arial" BOLD="true"/>
|
||||||
<node TEXT="Nathalie" POSITION="right" ID="ID_26"/>
|
<node TEXT="Nathalie" POSITION="right" ID="ID_26"/>
|
||||||
</node>
|
</node>
|
||||||
@ -61,15 +61,15 @@
|
|||||||
<node TEXT="2) Consolider nos moyens d'action : 
 locaux, organisation, communication interne" STYLE="bubble" POSITION="right" ID="ID_27" COLOR="#0000cc" BACKGROUND_COLOR="#99ffff">
|
<node TEXT="2) Consolider nos moyens d'action : 
 locaux, organisation, communication interne" STYLE="bubble" POSITION="right" ID="ID_27" COLOR="#0000cc" BACKGROUND_COLOR="#99ffff">
|
||||||
<font SIZE="8" NAME="Arial" BOLD="true"/>
|
<font SIZE="8" NAME="Arial" BOLD="true"/>
|
||||||
<edge COLOR="#808080"/>
|
<edge COLOR="#808080"/>
|
||||||
<node TEXT="2.1 Formaliser notre offre de services 
 et les modèles économiques correspondants" POSITION="right" ID="ID_28" COLOR="#006600" BACKGROUND_COLOR="#ffff00">
|
<node TEXT="2.1 Formaliser notre offre de services 
 et les modèles économiques correspondants" STYLE="rectagle" POSITION="right" ID="ID_28" COLOR="#006600" BACKGROUND_COLOR="#ffff00">
|
||||||
<font SIZE="8" NAME="Arial" BOLD="true"/>
|
<font SIZE="8" NAME="Arial" BOLD="true"/>
|
||||||
<node TEXT="Agnès" POSITION="right" ID="ID_29"/>
|
<node TEXT="Agnès" POSITION="right" ID="ID_29"/>
|
||||||
</node>
|
</node>
|
||||||
<node TEXT="2.2 Formaliser le "qui fait quoi" 
 en lien avec notre offre de service" POSITION="right" ID="ID_30" COLOR="#006600" BACKGROUND_COLOR="#ffcc00">
|
<node TEXT="2.2 Formaliser le "qui fait quoi" 
 en lien avec notre offre de service" STYLE="rectagle" POSITION="right" ID="ID_30" COLOR="#006600" BACKGROUND_COLOR="#ffcc00">
|
||||||
<font SIZE="8" NAME="Arial" BOLD="true"/>
|
<font SIZE="8" NAME="Arial" BOLD="true"/>
|
||||||
<node TEXT="Martine G" POSITION="right" ID="ID_31"/>
|
<node TEXT="Martine G" POSITION="right" ID="ID_31"/>
|
||||||
</node>
|
</node>
|
||||||
<node TEXT="2.3. Elaborer un projet immobilier de proximité 
 réalisable au centre ville" POSITION="right" ID="ID_32" COLOR="#006600" BACKGROUND_COLOR="#66ff00">
|
<node TEXT="2.3. Elaborer un projet immobilier de proximité 
 réalisable au centre ville" STYLE="rectagle" POSITION="right" ID="ID_32" COLOR="#006600" BACKGROUND_COLOR="#66ff00">
|
||||||
<font SIZE="8" NAME="Arial" BOLD="true"/>
|
<font SIZE="8" NAME="Arial" BOLD="true"/>
|
||||||
<node TEXT="JMDW" POSITION="right" ID="ID_33"/>
|
<node TEXT="JMDW" POSITION="right" ID="ID_33"/>
|
||||||
</node>
|
</node>
|
||||||
@ -77,21 +77,21 @@
|
|||||||
<node TEXT="3) Renforcer nos 
 partenariats et 
 notre notoriété" STYLE="bubble" POSITION="right" ID="ID_34" COLOR="#0000cc" BACKGROUND_COLOR="#99ffff">
|
<node TEXT="3) Renforcer nos 
 partenariats et 
 notre notoriété" STYLE="bubble" POSITION="right" ID="ID_34" COLOR="#0000cc" BACKGROUND_COLOR="#99ffff">
|
||||||
<font SIZE="8" NAME="Arial" BOLD="true"/>
|
<font SIZE="8" NAME="Arial" BOLD="true"/>
|
||||||
<edge COLOR="#808080"/>
|
<edge COLOR="#808080"/>
|
||||||
<node TEXT="3.1 Organiser pour chaque segment de notre offre 
 l'identification et la rencontre des partenaires concernés" POSITION="right" ID="ID_35" COLOR="#006600" BACKGROUND_COLOR="#ffff00">
|
<node TEXT="3.1 Organiser pour chaque segment de notre offre 
 l'identification et la rencontre des partenaires concernés" STYLE="rectagle" POSITION="right" ID="ID_35" COLOR="#006600" BACKGROUND_COLOR="#ffff00">
|
||||||
<font SIZE="8" NAME="Arial" BOLD="true"/>
|
<font SIZE="8" NAME="Arial" BOLD="true"/>
|
||||||
<edge COLOR="#808080"/>
|
<edge COLOR="#808080"/>
|
||||||
<node TEXT="Martine P" POSITION="right" ID="ID_36"/>
|
<node TEXT="Martine P" POSITION="right" ID="ID_36"/>
|
||||||
</node>
|
</node>
|
||||||
<node TEXT="3.2 Formaliser et contractualiser les partenariats 
 émergeants autour d'actions communes" POSITION="right" ID="ID_37" COLOR="#006600" BACKGROUND_COLOR="#ffcc00">
|
<node TEXT="3.2 Formaliser et contractualiser les partenariats 
 émergeants autour d'actions communes" STYLE="rectagle" POSITION="right" ID="ID_37" COLOR="#006600" BACKGROUND_COLOR="#ffcc00">
|
||||||
<font SIZE="8" NAME="Arial" BOLD="true"/>
|
<font SIZE="8" NAME="Arial" BOLD="true"/>
|
||||||
<edge COLOR="#808080"/>
|
<edge COLOR="#808080"/>
|
||||||
<node TEXT="Pdt + Agnès" POSITION="right" ID="ID_38"/>
|
<node TEXT="Pdt + Agnès" POSITION="right" ID="ID_38"/>
|
||||||
</node>
|
</node>
|
||||||
<node TEXT="3.3 développer la vie associative" POSITION="right" ID="ID_39" COLOR="#006600" BACKGROUND_COLOR="#ff6666">
|
<node TEXT="3.3 développer la vie associative" STYLE="rectagle" POSITION="right" ID="ID_39" COLOR="#006600" BACKGROUND_COLOR="#ff6666">
|
||||||
<font SIZE="8" NAME="Arial" BOLD="true"/>
|
<font SIZE="8" NAME="Arial" BOLD="true"/>
|
||||||
<node TEXT="Pdt + Agnès" POSITION="right" ID="ID_40"/>
|
<node TEXT="Pdt + Agnès" POSITION="right" ID="ID_40"/>
|
||||||
</node>
|
</node>
|
||||||
<node TEXT="3.4 définir et faire vivre un plan de communication 
 à partir de notre segmentation de l'offre" POSITION="right" ID="ID_41" COLOR="#006600" BACKGROUND_COLOR="#ff6666">
|
<node TEXT="3.4 définir et faire vivre un plan de communication 
 à partir de notre segmentation de l'offre" STYLE="rectagle" POSITION="right" ID="ID_41" COLOR="#006600" BACKGROUND_COLOR="#ff6666">
|
||||||
<font SIZE="8" NAME="Arial" BOLD="true"/>
|
<font SIZE="8" NAME="Arial" BOLD="true"/>
|
||||||
<edge COLOR="#808080"/>
|
<edge COLOR="#808080"/>
|
||||||
<node TEXT="Pdt + Agnès" POSITION="right" ID="ID_42"/>
|
<node TEXT="Pdt + Agnès" POSITION="right" ID="ID_42"/>
|
||||||
|
@ -35,27 +35,27 @@ du plan d'actions]]></text>
|
|||||||
notre offre de services
|
notre offre de services
|
||||||
selon l'évolution des besoins]]></text>
|
selon l'évolution des besoins]]></text>
|
||||||
<topic id="8" position="380,-175" order="0"
|
<topic id="8" position="380,-175" order="0"
|
||||||
bgColor="#99ffff" shape="line">
|
bgColor="#99ffff" shape="rectagle">
|
||||||
<text><![CDATA[couleurs = état d'avancement en septembre 2011,
|
<text><![CDATA[couleurs = état d'avancement en septembre 2011,
|
||||||
à mettre à jour selon avancement 2012]]></text>
|
à mettre à jour selon avancement 2012]]></text>
|
||||||
<topic id="9" position="470,-225" order="0"
|
<topic id="9" position="470,-225" order="0"
|
||||||
bgColor="#0099ff" shape="line">
|
bgColor="#0099ff" shape="rectagle">
|
||||||
<text><![CDATA[Bleu 0%]]></text>
|
<text><![CDATA[Bleu 0%]]></text>
|
||||||
</topic>
|
</topic>
|
||||||
<topic id="10" position="470,-200" order="1"
|
<topic id="10" position="470,-200" order="1"
|
||||||
bgColor="#ff9999" shape="line">
|
bgColor="#ff9999" shape="rectagle">
|
||||||
<text><![CDATA[Rouge 25%]]></text>
|
<text><![CDATA[Rouge 25%]]></text>
|
||||||
</topic>
|
</topic>
|
||||||
<topic id="11" position="470,-175" order="2"
|
<topic id="11" position="470,-175" order="2"
|
||||||
bgColor="#ffcc00" shape="line">
|
bgColor="#ffcc00" shape="rectagle">
|
||||||
<text><![CDATA[Orange 50%]]></text>
|
<text><![CDATA[Orange 50%]]></text>
|
||||||
</topic>
|
</topic>
|
||||||
<topic id="12" position="470,-150" order="3"
|
<topic id="12" position="470,-150" order="3"
|
||||||
bgColor="#ffff00" shape="line">
|
bgColor="#ffff00" shape="rectagle">
|
||||||
<text><![CDATA[Jaune 75%]]></text>
|
<text><![CDATA[Jaune 75%]]></text>
|
||||||
</topic>
|
</topic>
|
||||||
<topic id="13" position="470,-125" order="4"
|
<topic id="13" position="470,-125" order="4"
|
||||||
bgColor="#66ff00" shape="line">
|
bgColor="#66ff00" shape="rectagle">
|
||||||
<text><![CDATA[Vert]]></text>
|
<text><![CDATA[Vert]]></text>
|
||||||
<topic id="14" position="560,-125" order="0" shape="line">
|
<topic id="14" position="560,-125" order="0" shape="line">
|
||||||
<text><![CDATA[100 %]]></text>
|
<text><![CDATA[100 %]]></text>
|
||||||
@ -64,7 +64,7 @@ du plan d'actions]]></text>
|
|||||||
</topic>
|
</topic>
|
||||||
<topic shrink="true" id="15" position="380,-150"
|
<topic shrink="true" id="15" position="380,-150"
|
||||||
order="1" bgColor="#66ff00"
|
order="1" bgColor="#66ff00"
|
||||||
fontStyle="Arial;8;#006600;bold;;" shape="line">
|
fontStyle="Arial;8;#006600;bold;;" shape="rectagle">
|
||||||
<text><![CDATA[1.1. .Renforcer et structurer
|
<text><![CDATA[1.1. .Renforcer et structurer
|
||||||
le service "accueil et orientation"]]></text>
|
le service "accueil et orientation"]]></text>
|
||||||
<topic id="16" position="470,-175" order="0" shape="line">
|
<topic id="16" position="470,-175" order="0" shape="line">
|
||||||
@ -72,7 +72,7 @@ du plan d'actions]]></text>
|
|||||||
</topic>
|
</topic>
|
||||||
</topic>
|
</topic>
|
||||||
<topic id="17" position="380,-125" order="2"
|
<topic id="17" position="380,-125" order="2"
|
||||||
bgColor="#ffff00" fontStyle="Arial;8;#006600;bold;;" shape="line">
|
bgColor="#ffff00" fontStyle="Arial;8;#006600;bold;;" shape="rectagle">
|
||||||
<text><![CDATA[1.2. Adapter l'offre Halte-Garderie
|
<text><![CDATA[1.2. Adapter l'offre Halte-Garderie
|
||||||
aux besoins des familles]]></text>
|
aux besoins des familles]]></text>
|
||||||
<topic id="18" position="470,-150" order="0" shape="line">
|
<topic id="18" position="470,-150" order="0" shape="line">
|
||||||
@ -80,7 +80,7 @@ du plan d'actions]]></text>
|
|||||||
</topic>
|
</topic>
|
||||||
</topic>
|
</topic>
|
||||||
<topic id="19" position="380,-100" order="3"
|
<topic id="19" position="380,-100" order="3"
|
||||||
bgColor="#66ff00" fontStyle="Arial;8;#006600;bold;;" shape="line">
|
bgColor="#66ff00" fontStyle="Arial;8;#006600;bold;;" shape="rectagle">
|
||||||
<text><![CDATA[1.3. Renforcer et structurer les services
|
<text><![CDATA[1.3. Renforcer et structurer les services
|
||||||
de soutien à la parentalité]]></text>
|
de soutien à la parentalité]]></text>
|
||||||
<topic id="20" position="470,-125" order="0" shape="line">
|
<topic id="20" position="470,-125" order="0" shape="line">
|
||||||
@ -88,7 +88,7 @@ du plan d'actions]]></text>
|
|||||||
</topic>
|
</topic>
|
||||||
</topic>
|
</topic>
|
||||||
<topic id="21" position="380,-75" order="4"
|
<topic id="21" position="380,-75" order="4"
|
||||||
bgColor="#ffff00" fontStyle="Arial;8;#006600;bold;;" shape="line">
|
bgColor="#ffff00" fontStyle="Arial;8;#006600;bold;;" shape="rectagle">
|
||||||
<text><![CDATA[1.4 Améliorer la qualité des
|
<text><![CDATA[1.4 Améliorer la qualité des
|
||||||
projets pédagogiques des ALSH]]></text>
|
projets pédagogiques des ALSH]]></text>
|
||||||
<topic id="22" position="470,-100" order="0" shape="line">
|
<topic id="22" position="470,-100" order="0" shape="line">
|
||||||
@ -96,7 +96,7 @@ du plan d'actions]]></text>
|
|||||||
</topic>
|
</topic>
|
||||||
</topic>
|
</topic>
|
||||||
<topic id="23" position="380,-50" order="5"
|
<topic id="23" position="380,-50" order="5"
|
||||||
bgColor="#ffcc00" fontStyle="Arial;8;#006600;bold;;" shape="line">
|
bgColor="#ffcc00" fontStyle="Arial;8;#006600;bold;;" shape="rectagle">
|
||||||
<text><![CDATA[1.5 Renforcer et améliorer les
|
<text><![CDATA[1.5 Renforcer et améliorer les
|
||||||
activités de loisirs existantes]]></text>
|
activités de loisirs existantes]]></text>
|
||||||
<topic id="24" position="470,-75" order="0" shape="line">
|
<topic id="24" position="470,-75" order="0" shape="line">
|
||||||
@ -104,7 +104,7 @@ du plan d'actions]]></text>
|
|||||||
</topic>
|
</topic>
|
||||||
</topic>
|
</topic>
|
||||||
<topic id="25" position="380,-25" order="6"
|
<topic id="25" position="380,-25" order="6"
|
||||||
bgColor="#66ff00" fontStyle="Arial;8;#006600;bold;;" shape="line">
|
bgColor="#66ff00" fontStyle="Arial;8;#006600;bold;;" shape="rectagle">
|
||||||
<text><![CDATA[1.6 Renforcer et améliorer les ateliers
|
<text><![CDATA[1.6 Renforcer et améliorer les ateliers
|
||||||
visant à rompre l'isolement lié à la langue,
|
visant à rompre l'isolement lié à la langue,
|
||||||
la pauvreté, la pression de la consommation]]></text>
|
la pauvreté, la pression de la consommation]]></text>
|
||||||
@ -119,7 +119,7 @@ du plan d'actions]]></text>
|
|||||||
<text><![CDATA[2) Consolider nos moyens d'action :
|
<text><![CDATA[2) Consolider nos moyens d'action :
|
||||||
locaux, organisation, communication interne]]></text>
|
locaux, organisation, communication interne]]></text>
|
||||||
<topic id="28" position="380,-100" order="0"
|
<topic id="28" position="380,-100" order="0"
|
||||||
bgColor="#ffff00" fontStyle="Arial;8;#006600;bold;;" shape="line">
|
bgColor="#ffff00" fontStyle="Arial;8;#006600;bold;;" shape="rectagle">
|
||||||
<text><![CDATA[2.1 Formaliser notre offre de services
|
<text><![CDATA[2.1 Formaliser notre offre de services
|
||||||
et les modèles économiques correspondants]]></text>
|
et les modèles économiques correspondants]]></text>
|
||||||
<topic id="29" position="470,-150" order="0" shape="line">
|
<topic id="29" position="470,-150" order="0" shape="line">
|
||||||
@ -127,7 +127,7 @@ du plan d'actions]]></text>
|
|||||||
</topic>
|
</topic>
|
||||||
</topic>
|
</topic>
|
||||||
<topic id="30" position="380,-75" order="1"
|
<topic id="30" position="380,-75" order="1"
|
||||||
bgColor="#ffcc00" fontStyle="Arial;8;#006600;bold;;" shape="line">
|
bgColor="#ffcc00" fontStyle="Arial;8;#006600;bold;;" shape="rectagle">
|
||||||
<text><![CDATA[2.2 Formaliser le "qui fait quoi"
|
<text><![CDATA[2.2 Formaliser le "qui fait quoi"
|
||||||
en lien avec notre offre de service]]></text>
|
en lien avec notre offre de service]]></text>
|
||||||
<topic id="31" position="470,-125" order="0" shape="line">
|
<topic id="31" position="470,-125" order="0" shape="line">
|
||||||
@ -135,7 +135,7 @@ du plan d'actions]]></text>
|
|||||||
</topic>
|
</topic>
|
||||||
</topic>
|
</topic>
|
||||||
<topic id="32" position="380,-50" order="2"
|
<topic id="32" position="380,-50" order="2"
|
||||||
bgColor="#66ff00" fontStyle="Arial;8;#006600;bold;;" shape="line">
|
bgColor="#66ff00" fontStyle="Arial;8;#006600;bold;;" shape="rectagle">
|
||||||
<text><![CDATA[2.3. Elaborer un projet immobilier de proximité
|
<text><![CDATA[2.3. Elaborer un projet immobilier de proximité
|
||||||
réalisable au centre ville]]></text>
|
réalisable au centre ville]]></text>
|
||||||
<topic id="33" position="470,-100" order="0" shape="line">
|
<topic id="33" position="470,-100" order="0" shape="line">
|
||||||
@ -150,7 +150,7 @@ du plan d'actions]]></text>
|
|||||||
notre notoriété]]></text>
|
notre notoriété]]></text>
|
||||||
<topic id="35" position="380,-75" order="0"
|
<topic id="35" position="380,-75" order="0"
|
||||||
brColor="#808080" bgColor="#ffff00"
|
brColor="#808080" bgColor="#ffff00"
|
||||||
fontStyle="Arial;8;#006600;bold;;" shape="line">
|
fontStyle="Arial;8;#006600;bold;;" shape="rectagle">
|
||||||
<text><![CDATA[3.1 Organiser pour chaque segment de notre offre
|
<text><![CDATA[3.1 Organiser pour chaque segment de notre offre
|
||||||
l'identification et la rencontre des partenaires concernés]]></text>
|
l'identification et la rencontre des partenaires concernés]]></text>
|
||||||
<topic id="36" position="470,-125" order="0" shape="line">
|
<topic id="36" position="470,-125" order="0" shape="line">
|
||||||
@ -159,7 +159,7 @@ du plan d'actions]]></text>
|
|||||||
</topic>
|
</topic>
|
||||||
<topic id="37" position="380,-50" order="1"
|
<topic id="37" position="380,-50" order="1"
|
||||||
brColor="#808080" bgColor="#ffcc00"
|
brColor="#808080" bgColor="#ffcc00"
|
||||||
fontStyle="Arial;8;#006600;bold;;" shape="line">
|
fontStyle="Arial;8;#006600;bold;;" shape="rectagle">
|
||||||
<text><![CDATA[3.2 Formaliser et contractualiser les partenariats
|
<text><![CDATA[3.2 Formaliser et contractualiser les partenariats
|
||||||
émergeants autour d'actions communes]]></text>
|
émergeants autour d'actions communes]]></text>
|
||||||
<topic id="38" position="470,-100" order="0" shape="line">
|
<topic id="38" position="470,-100" order="0" shape="line">
|
||||||
@ -167,7 +167,7 @@ du plan d'actions]]></text>
|
|||||||
</topic>
|
</topic>
|
||||||
</topic>
|
</topic>
|
||||||
<topic id="39" position="380,-25" order="2"
|
<topic id="39" position="380,-25" order="2"
|
||||||
bgColor="#ff6666" fontStyle="Arial;8;#006600;bold;;" shape="line">
|
bgColor="#ff6666" fontStyle="Arial;8;#006600;bold;;" shape="rectagle">
|
||||||
<text><![CDATA[3.3 développer la vie associative]]></text>
|
<text><![CDATA[3.3 développer la vie associative]]></text>
|
||||||
<topic id="40" position="470,-50" order="0" shape="line">
|
<topic id="40" position="470,-50" order="0" shape="line">
|
||||||
<text><![CDATA[Pdt + Agnès]]></text>
|
<text><![CDATA[Pdt + Agnès]]></text>
|
||||||
@ -175,7 +175,7 @@ du plan d'actions]]></text>
|
|||||||
</topic>
|
</topic>
|
||||||
<topic id="41" position="380,0" order="3"
|
<topic id="41" position="380,0" order="3"
|
||||||
brColor="#808080" bgColor="#ff6666"
|
brColor="#808080" bgColor="#ff6666"
|
||||||
fontStyle="Arial;8;#006600;bold;;" shape="line">
|
fontStyle="Arial;8;#006600;bold;;" shape="rectagle">
|
||||||
<text><![CDATA[3.4 définir et faire vivre un plan de communication
|
<text><![CDATA[3.4 définir et faire vivre un plan de communication
|
||||||
à partir de notre segmentation de l'offre]]></text>
|
à partir de notre segmentation de l'offre]]></text>
|
||||||
<topic id="42" position="470,-50" order="0" shape="line">
|
<topic id="42" position="470,-50" order="0" shape="line">
|
||||||
|
Loading…
Reference in New Issue
Block a user