Commit b5ea441f authored by  JosephK's avatar JosephK

Mindmaps

parent e88d4419
/*
* Copyright [2015] [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.exporter;
import com.wisemapping.importer.VersionNumber;
import org.apache.batik.parser.AWTTransformProducer;
import org.apache.batik.parser.ParseException;
import org.apache.batik.parser.TransformListParser;
import org.apache.batik.transcoder.Transcoder;
import org.apache.batik.transcoder.TranscoderException;
import org.apache.batik.transcoder.TranscoderInput;
import org.apache.batik.transcoder.TranscoderOutput;
import org.apache.batik.transcoder.image.ImageTranscoder;
import org.apache.batik.transcoder.image.JPEGTranscoder;
import org.apache.batik.transcoder.image.PNGTranscoder;
import org.apache.fop.svg.PDFTranscoder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.w3c.dom.*;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import sun.misc.BASE64Encoder;
import javax.servlet.ServletContext;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.*;
import java.awt.geom.AffineTransform;
import java.io.*;
import java.util.regex.Pattern;
public class ExporterFactory {
private static final String GROUP_NODE_NAME = "g";
private static final String IMAGE_NODE_NAME = "image";
public static final int MANGING = 50;
public static final String UTF_8_CHARSET_NAME = "UTF-8";
private File baseImgDir;
public ExporterFactory(@NotNull final ServletContext servletContext) throws ParserConfigurationException {
this.baseImgDir = new File(servletContext.getRealPath("/"));
}
public ExporterFactory(@NotNull final File baseImgDir) throws ParserConfigurationException {
this.baseImgDir = baseImgDir;
}
public void export(@NotNull ExportProperties properties, @Nullable String xml, @NotNull OutputStream output, @Nullable String mapSvg) throws ExportException, IOException, TranscoderException {
final ExportFormat format = properties.getFormat();
switch (format) {
case PNG: {
// Create a JPEG transcoder
final Transcoder transcoder = new PNGTranscoder();
final ExportProperties.ImageProperties imageProperties =
(ExportProperties.ImageProperties) properties;
final ExportProperties.ImageProperties.Size size = imageProperties.getSize();
transcoder.addTranscodingHint(ImageTranscoder.KEY_WIDTH, size.getWidth());
// Create the transcoder input.
final String svgString = normalizeSvg(mapSvg);
final TranscoderInput input = new TranscoderInput(new CharArrayReader(svgString.toCharArray()));
TranscoderOutput trascoderOutput = new TranscoderOutput(output);
// Save the image.
transcoder.transcode(input, trascoderOutput);
break;
}
case JPG: {
// Create a JPEG transcoder
final Transcoder transcoder = new JPEGTranscoder();
transcoder.addTranscodingHint(JPEGTranscoder.KEY_QUALITY, new Float(.99));
final ExportProperties.ImageProperties imageProperties =
(ExportProperties.ImageProperties) properties;
final ExportProperties.ImageProperties.Size size = imageProperties.getSize();
transcoder.addTranscodingHint(ImageTranscoder.KEY_WIDTH, size.getWidth());
// Create the transcoder input.
final String svgString = normalizeSvg(mapSvg);
final TranscoderInput input = new TranscoderInput(new CharArrayReader(svgString.toCharArray()));
TranscoderOutput trascoderOutput = new TranscoderOutput(output);
// Save the image.
transcoder.transcode(input, trascoderOutput);
break;
}
case PDF: {
// Create a JPEG transcoder
final Transcoder transcoder = new PDFTranscoder();
// Create the transcoder input.
final String svgString = normalizeSvg(mapSvg);
final TranscoderInput input = new TranscoderInput(new CharArrayReader(svgString.toCharArray()));
TranscoderOutput trascoderOutput = new TranscoderOutput(output);
// Save the image.
transcoder.transcode(input, trascoderOutput);
break;
}
case SVG: {
final String svgString = normalizeSvg(mapSvg);
output.write(svgString.getBytes(UTF_8_CHARSET_NAME));
break;
}
case TEXT: {
final Exporter exporter = XSLTExporter.create(XSLTExporter.Type.TEXT);
exporter.export(xml.getBytes(UTF_8_CHARSET_NAME), output);
break;
}
case OPEN_OFFICE_WRITER: {
final Exporter exporter = XSLTExporter.create(XSLTExporter.Type.OPEN_OFFICE);
exporter.export(xml.getBytes(UTF_8_CHARSET_NAME), output);
break;
}
case MICROSOFT_EXCEL: {
final Exporter exporter = XSLTExporter.create(XSLTExporter.Type.MICROSOFT_EXCEL);
exporter.export(xml.getBytes(UTF_8_CHARSET_NAME), output);
break;
}
case FREEMIND: {
final FreemindExporter exporter = new FreemindExporter();
exporter.setVersion(new VersionNumber(properties.getVersion()));
exporter.export(xml.getBytes(UTF_8_CHARSET_NAME), output);
break;
}
case MINDJET: {
final Exporter exporter = XSLTExporter.create(XSLTExporter.Type.MINDJET);
exporter.export(xml.getBytes(UTF_8_CHARSET_NAME), output);
break;
}
default:
throw new UnsupportedOperationException("Export method not supported.");
}
}
private String normalizeSvg(@NotNull String svgXml) throws ExportException {
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
final DocumentBuilder documentBuilder = factory.newDocumentBuilder();
if (!svgXml.contains("xmlns:xlink=\"http://www.w3.org/1999/xlink\"")) {
svgXml = svgXml.replaceFirst("<svg ", "<svg xmlns = \"http://www.w3.org/2000/svg\" xmlns:xlink = \"http://www.w3.org/1999/xlink\" ");
}
// Hacks for some legacy cases ....
svgXml = svgXml.replaceAll("NaN,", "0");
svgXml = svgXml.replaceAll(",NaN", "0");
// Bratik do not manage nbsp properly.
svgXml = svgXml.replaceAll(Pattern.quote("&nbsp;"), " ");
Document document;
try {
final Reader in = new CharArrayReader(svgXml.toCharArray());
final InputSource is = new InputSource(in);
document = documentBuilder.parse(is);
} catch (SAXException e) {
// It must be a corrupted SVG format. Try to hack it and try again ...
svgXml = svgXml.replaceAll("<image([^>]+)>", "<image$1/>");
svgXml = svgXml.replaceAll("\u001B", "");
final Reader in = new CharArrayReader(svgXml.toCharArray());
final InputSource is = new InputSource(in);
document = documentBuilder.parse(is);
}
resizeSVG(document);
final Node child = document.getFirstChild();
inlineImages(document, (Element) child);
return domToString(document);
} catch (ParserConfigurationException | TransformerException | SAXException | IOException e) {
throw new ExportException(e);
}
}
private static String domToString(@NotNull Document document) throws TransformerException {
DOMSource domSource = new DOMSource(document);
// Create a string writer
final CharArrayWriter result = new CharArrayWriter();
// Create the stream stream for the transform
StreamResult stream = new StreamResult(result);
// Create a Transformer to serialize the document
TransformerFactory tFactory = TransformerFactory.newInstance();
Transformer transformer = tFactory.newTransformer();
transformer.setOutputProperty("indent", "yes");
// Transform the document to the stream stream
transformer.transform(domSource, stream);
return result.toString();
}
private void inlineImages(@NotNull Document document, @NotNull Element element) {
final NodeList list = element.getChildNodes();
for (int i = 0; i < list.getLength(); i++) {
final Node node = list.item(i);
// find all groups
if (GROUP_NODE_NAME.equals(node.getNodeName())) {
// Must continue looking ....
inlineImages(document, (Element) node);
} else if (IMAGE_NODE_NAME.equals(node.getNodeName())) {
Element elem = (Element) node;
final String imgUrl = fixHref(elem);
if (!imgUrl.isEmpty() && (!imgUrl.startsWith("image/png;base64") || !imgUrl.startsWith("data:image/png;base64"))) {
elem.removeAttribute("href");
InputStream fis = null;
// Obtains file name ...
try {
final File iconFile = iconFile(imgUrl);
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
fis = new FileInputStream(iconFile);
BASE64Encoder encoder = new BASE64Encoder();
encoder.encode(fis, bos);
elem.setAttribute("xlink:href", "data:image/png;base64," + bos.toString("8859_1"));
elem.appendChild(document.createTextNode(" "));
} catch (IOException e) {
e.printStackTrace();
} finally {
close(fis);
}
}
}
}
}
@NotNull
private String fixHref(@NotNull Element elem) {
// Fix href attribute ...
// Hack for IE: If the image is a external URL, embeed it...
String result = elem.getAttribute("href");
if (result.isEmpty()) {
// Bug WISE-422: This seems to be a bug in Safari. For some reason, img add prefixed with NS1
// <image NS1:href="icons/sign_help.png"
// Also: Remove replace "xlink:href" to href to uniform ...
final NamedNodeMap attributes = elem.getAttributes();
for (int i = 0; i < attributes.getLength(); i++) {
final Node node = attributes.item(i);
String nodeName = node.getNodeName();
if(nodeName.contains(":href")){
elem.removeAttribute(nodeName);
result = node.getNodeValue();
}
}
elem.setAttribute("href", result);
}
return result;
}
private File iconFile(@NotNull final String imgUrl) throws IOException {
int index = imgUrl.lastIndexOf("/");
final String iconName = imgUrl.substring(index + 1);
final File iconsDir = new File(baseImgDir, "icons");
File result = new File(iconsDir, iconName);
if (!result.exists()) {
// It's not a icon, must be a note, attach image ...
final File legacyIconsDir = new File(baseImgDir, "images");
result = new File(legacyIconsDir, iconName);
}
if (!result.exists()) {
final File legacyIconsDir = new File(iconsDir, "legacy");
result = new File(legacyIconsDir, iconName);
}
if (!result.exists() || result.isDirectory()) {
throw new IOException("Icon could not be found:" + imgUrl);
}
return result;
}
private void close(Closeable fis) {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
// Ignore ...
}
}
}
private static void resizeSVG(@NotNull Document document) throws ExportException {
try {
XPathFactory xPathfactory = XPathFactory.newInstance();
XPath xpath = xPathfactory.newXPath();
XPathExpression expr = xpath.compile("/svg/g/rect");
NodeList nl = (NodeList) expr.evaluate(document, XPathConstants.NODESET);
final int length = nl.getLength();
double maxX = 0, minX = 0, minY = 0, maxY = 0;
for (int i = 0; i < length; i++) {
final Element rectElem = (Element) nl.item(i);
final Element gElem = (Element) rectElem.getParentNode();
final TransformListParser p = new TransformListParser();
final AWTTransformProducer tp = new AWTTransformProducer();
p.setTransformListHandler(tp);
p.parse(gElem.getAttribute("transform"));
final AffineTransform transform = tp.getAffineTransform();
double yPos = transform.getTranslateY();
if (yPos > 0) {
yPos += Double.parseDouble(rectElem.getAttribute("height"));
}
maxY = maxY < yPos ? yPos : maxY;
minY = minY > yPos ? yPos : minY;
double xPos = transform.getTranslateX();
if (xPos > 0) {
xPos += Double.parseDouble(rectElem.getAttribute("width"));
}
maxX = maxX < xPos ? xPos : maxX;
minX = minX > xPos ? xPos : minX;
}
// Add some extra margin ...
maxX += MANGING;
minX += -MANGING;
maxY += MANGING;
minY += -MANGING;
// Calculate dimentions ...
final double width = maxX + Math.abs(minX);
final double height = maxY + Math.abs(minY);
// Finally, update centers ...
final Element svgNode = (Element) document.getFirstChild();
svgNode.setAttribute("viewBox", minX + " " + minY + " " + width + " " + height);
svgNode.setAttribute("width", Double.toString(width));
svgNode.setAttribute("height", Double.toString(height));
svgNode.setAttribute("preserveAspectRatio", "xMinYMin");
} catch (XPathExpressionException | ParseException | NumberFormatException | DOMException e) {
throw new ExportException(e);
}
}
private static String[] getTransformUnit(NamedNodeMap groupAttributes) {
final String value = groupAttributes.getNamedItem("transform").getNodeValue();
final int pos = value.indexOf("translate");
final int initTranslate = value.indexOf("(", pos);
final int endTranslate = value.indexOf(")", pos);
final String transate = value.substring(initTranslate + 1, endTranslate);
return transate.contains(",") ? transate.split(",") : transate.split(" ");
}
}
/*
* Copyright [2015] [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.mail;
import com.wisemapping.filter.SupportedUserAgent;
import com.wisemapping.model.Collaboration;
import com.wisemapping.model.Mindmap;
import com.wisemapping.model.User;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringEscapeUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.springframework.beans.factory.annotation.Autowired;
import javax.servlet.http.HttpServletRequest;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.Map;
final public class NotificationService {
public static final String DEFAULT_WISE_URL = "http://localhost:8080/wisemapping";
@Autowired
private Mailer mailer;
private NotifierFilter notificationFilter;
private String baseUrl;
public NotificationService() {
this.notificationFilter = new NotifierFilter();
}
public void newCollaboration(@NotNull Collaboration collaboration, @NotNull Mindmap mindmap, @NotNull User user, @Nullable String message) {
try {
// Sent collaboration email ...
final String formMail = mailer.getServerSenderEmail();
// Is the user already registered user ?.
final String collabEmail = collaboration.getCollaborator().getEmail();
// Build the subject ...
final String subject = "[Framindmap] " + user.getFullName() + " a partagé une carte mentale avec vous";
// Fill template properties ...
final Map<String, Object> model = new HashMap<String, Object>();
model.put("mindmap", mindmap);
model.put("message", "message");
model.put("ownerName", user.getFirstname());
model.put("mapEditUrl", getBaseUrl() + "/c/maps/" + mindmap.getId() + "/edit");
model.put("baseUrl", getBaseUrl());
model.put("senderMail", user.getEmail());
model.put("message", message);
model.put("supportEmail", mailer.getSupportEmail());
mailer.sendEmail(formMail, collabEmail, subject, model, "newCollaboration.vm");
} catch (Exception e) {
handleException(e);
}
}
public void resetPassword(@NotNull User user, @NotNull String temporalPassword) {
final String mailSubject = "[Framindmap] Votre nouveau mot de passe";
final String messageTitle = "Votre nouveau mot de passe a &eacute;t&eacute; cr&eacute;&eacute;";
final String messageBody =
"<p>Quelqu'un, probablement vous, a demand&eacute; &agrave; r&eacuteinitialiser le mot de passe de votre compte Framindmap. </p>\n" +
"<p><strong>Voici votre nouveau mot de passe : " + temporalPassword + "</strong></p>\n" +
"<p>Vous pouvez vous connecter en cliquant <a href=\"" + getBaseUrl() + "/c/login\">sur ce lien</a>. Nous vous encourageons fortement &agrave; changer ce mot de passe le plus rapidement possible.</p>";
sendTemplateMail(user, mailSubject, messageTitle, messageBody);
}
public void passwordChanged(@NotNull User user) {
final String mailSubject = "[Framindmap] Votre mot de passe a été modifié";
final String messageTitle = "Votre nouveau mot de passe a bien &eacute;t&eacute; modifi&eacute; correctement";
final String messageBody =
"<p>Ceci est juste un mail de confirmation que votre mot de passe a bien &eacute;t&eacute; chang&eacute;. Aucune action suppl&eacute;mentaire n'est requise.</p>";
sendTemplateMail(user, mailSubject, messageTitle, messageBody);
}
public void newAccountCreated(@NotNull User user) {
final String mailSubject = "Bienvenue dans Framindmap !";
final String messageTitle = "Votre compte a &eacute;t&eacute; cr&eacute;&eacute; avec succ&eagrave;s";
final String messageBody =
"<p>Merci de l'int&eacute;r&ecirc;t que vous portez &agrave; Framindmap. Si vous avez des remarques ou id&eacute;es d'am&eacute;lioration, envoyez nous un email sur <a href=\"https://contact.framasoft.org/#framindmap\">contact.framasoft.org</a>.</p>";
sendTemplateMail(user, mailSubject, messageTitle, messageBody);
}
private void sendTemplateMail(@NotNull User user, @NotNull String mailSubject, @NotNull String messageTitle, @NotNull String messageBody) {
try {
final Map<String, Object> model = new HashMap<String, Object>();
model.put("firstName", user.getFirstname());
model.put("messageTitle", messageTitle);
model.put("messageBody", messageBody);
model.put("baseUrl", getBaseUrl());
model.put("supportEmail", mailer.getSupportEmail());
mailer.sendEmail(mailer.getServerSenderEmail(), user.getEmail(), mailSubject, model, "baseLayout.vm");
} catch (Exception e) {
handleException(e);
}
}
private void handleException(Exception e) {
System.err.println("An expected error has occurred trying to send an email notification. Usually, the main reason for this is that the SMTP server properties has not been configured properly. Edit the WEB-INF/app.properties file and verify the SMTP server configuration properties.");
System.err.println("Cause:" + e.getMessage());
}
public void setMailer(Mailer mailer) {
this.mailer = mailer;
}
public void activateAccount(@NotNull User user) {
final Map<String, User> model = new HashMap<String, User>();
model.put("user", user);
mailer.sendEmail(mailer.getServerSenderEmail(), user.getEmail(), "[WiseMapping] Active account", model, "activationAccountMail.vm");
}
public void sendRegistrationEmail(@NotNull User user) {
// throw new UnsupportedOperationException("Not implemented yet");
// try {
// final Map<String, String> model = new HashMap<String, String>();
// model.put("email", user.getEmail());
//// final String activationUrl = "http://wisemapping.com/c/activation?code=" + user.getActivationCode();
//// model.put("emailcheck", activationUrl);
//// mailer.sendEmail(mailer.getServerSenderEmail(), user.getEmail(), "Welcome to Wisemapping!", model,
//// "confirmationMail.vm");
// } catch (Exception e) {
// handleException(e);
// }
}
public void reportJavascriptException(@Nullable Mindmap mindmap, @Nullable User user, @Nullable String jsErrorMsg, @NotNull HttpServletRequest request) {
final Map<String, String> model = new HashMap<String, String>();
model.put("errorMsg", jsErrorMsg);