001    /**
002     * Copyright (c) 2000-present Liferay, Inc. All rights reserved.
003     *
004     * This library is free software; you can redistribute it and/or modify it under
005     * the terms of the GNU Lesser General Public License as published by the Free
006     * Software Foundation; either version 2.1 of the License, or (at your option)
007     * any later version.
008     *
009     * This library is distributed in the hope that it will be useful, but WITHOUT
010     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
011     * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
012     * details.
013     */
014    
015    package com.liferay.portlet.blogs.util;
016    
017    import com.liferay.portal.kernel.comment.CommentManager;
018    import com.liferay.portal.kernel.comment.CommentManagerUtil;
019    import com.liferay.portal.kernel.comment.DuplicateCommentException;
020    import com.liferay.portal.kernel.language.LanguageUtil;
021    import com.liferay.portal.kernel.log.Log;
022    import com.liferay.portal.kernel.log.LogFactoryUtil;
023    import com.liferay.portal.kernel.portlet.FriendlyURLMapper;
024    import com.liferay.portal.kernel.portlet.FriendlyURLMapperThreadLocal;
025    import com.liferay.portal.kernel.util.ArrayUtil;
026    import com.liferay.portal.kernel.util.Function;
027    import com.liferay.portal.kernel.util.GetterUtil;
028    import com.liferay.portal.kernel.util.HttpUtil;
029    import com.liferay.portal.kernel.util.LocaleUtil;
030    import com.liferay.portal.kernel.util.StringBundler;
031    import com.liferay.portal.kernel.util.StringPool;
032    import com.liferay.portal.kernel.util.StringUtil;
033    import com.liferay.portal.kernel.util.Validator;
034    import com.liferay.portal.kernel.xmlrpc.Method;
035    import com.liferay.portal.kernel.xmlrpc.Response;
036    import com.liferay.portal.kernel.xmlrpc.XmlRpcConstants;
037    import com.liferay.portal.kernel.xmlrpc.XmlRpcUtil;
038    import com.liferay.portal.model.Portlet;
039    import com.liferay.portal.service.PortletLocalServiceUtil;
040    import com.liferay.portal.service.ServiceContext;
041    import com.liferay.portal.service.UserLocalServiceUtil;
042    import com.liferay.portal.util.Portal;
043    import com.liferay.portal.util.PortalUtil;
044    import com.liferay.portal.util.PortletKeys;
045    import com.liferay.portal.util.PropsValues;
046    import com.liferay.portlet.blogs.model.BlogsEntry;
047    import com.liferay.portlet.blogs.pingback.DisabledPingbackException;
048    import com.liferay.portlet.blogs.pingback.InvalidSourceURIException;
049    import com.liferay.portlet.blogs.pingback.UnavailableSourceURIException;
050    import com.liferay.portlet.blogs.service.BlogsEntryLocalServiceUtil;
051    
052    import java.io.IOException;
053    
054    import java.net.URL;
055    
056    import java.util.HashMap;
057    import java.util.List;
058    import java.util.Map;
059    
060    import net.htmlparser.jericho.Element;
061    import net.htmlparser.jericho.Source;
062    import net.htmlparser.jericho.StartTag;
063    import net.htmlparser.jericho.TextExtractor;
064    
065    /**
066     * @author Alexander Chow
067     */
068    public class PingbackMethodImpl implements Method {
069    
070            public static final int ACCESS_DENIED = 49;
071    
072            public static final int GENERIC_FAULT = 0;
073    
074            public static final int PINGBACK_ALREADY_REGISTERED = 48;
075    
076            public static final int SERVER_ERROR = 50;
077    
078            public static final int SOURCE_URI_DOES_NOT_EXIST = 16;
079    
080            public static final int SOURCE_URI_INVALID = 17;
081    
082            public static final int TARGET_URI_DOES_NOT_EXIST = 32;
083    
084            public static final int TARGET_URI_INVALID = 33;
085    
086            @Override
087            public Response execute(long companyId) {
088                    try {
089                            addPingback(companyId);
090    
091                            return XmlRpcUtil.createSuccess("Pingback accepted");
092                    }
093                    catch (DuplicateCommentException dce) {
094                            return XmlRpcUtil.createFault(
095                                    PINGBACK_ALREADY_REGISTERED, "Pingback is already registered");
096                    }
097                    catch (InvalidSourceURIException isue) {
098                            return XmlRpcUtil.createFault(
099                                    SOURCE_URI_INVALID, isue.getMessage());
100                    }
101                    catch (DisabledPingbackException pde) {
102                            return XmlRpcUtil.createFault(
103                                    XmlRpcConstants.REQUESTED_METHOD_NOT_FOUND, pde.getMessage());
104                    }
105                    catch (UnavailableSourceURIException usue) {
106                            return XmlRpcUtil.createFault(
107                                    SOURCE_URI_DOES_NOT_EXIST, usue.getMessage());
108                    }
109                    catch (Exception e) {
110                            if (_log.isDebugEnabled()) {
111                                    _log.debug(e, e);
112                            }
113    
114                            return XmlRpcUtil.createFault(
115                                    TARGET_URI_INVALID, "Unable to parse target URI");
116                    }
117            }
118    
119            @Override
120            public String getMethodName() {
121                    return "pingback.ping";
122            }
123    
124            @Override
125            public String getToken() {
126                    return "pingback";
127            }
128    
129            @Override
130            public boolean setArguments(Object[] arguments) {
131                    try {
132                            _sourceURI = (String)arguments[0];
133                            _targetURI = (String)arguments[1];
134    
135                            return true;
136                    }
137                    catch (Exception e) {
138                            if (_log.isDebugEnabled()) {
139                                    _log.debug(e, e);
140                            }
141    
142                            return false;
143                    }
144            }
145    
146            public void setCommentManager(CommentManager commentManager) {
147                    _commentManager = commentManager;
148            }
149    
150            protected void addPingback(long companyId) throws Exception {
151                    if (!PropsValues.BLOGS_PINGBACK_ENABLED) {
152                            throw new DisabledPingbackException("Pingbacks are disabled");
153                    }
154    
155                    validateSource();
156    
157                    BlogsEntry entry = getBlogsEntry(companyId);
158    
159                    if (!entry.isAllowPingbacks()) {
160                            throw new DisabledPingbackException("Pingbacks are disabled");
161                    }
162    
163                    long userId = UserLocalServiceUtil.getDefaultUserId(companyId);
164                    long groupId = entry.getGroupId();
165                    String className = BlogsEntry.class.getName();
166                    long classPK = entry.getEntryId();
167    
168                    String body =
169                            "[...] " + getExcerpt() + " [...] [url=" + _sourceURI + "]" +
170                                    LanguageUtil.get(LocaleUtil.getSiteDefault(), "read-more") +
171                                            "[/url]";
172    
173                    ServiceContext serviceContext = buildServiceContext(
174                            companyId, groupId, entry.getUrlTitle());
175    
176                    _commentManager.addComment(
177                            userId, groupId, className, classPK, body,
178                            new IdentityServiceContextFunction(serviceContext));
179            }
180    
181            protected ServiceContext buildServiceContext(
182                            long companyId, long groupId, String urlTitle)
183                    throws Exception {
184    
185                    ServiceContext serviceContext = new ServiceContext();
186    
187                    String pingbackUserName = LanguageUtil.get(
188                            LocaleUtil.getSiteDefault(), "pingback");
189    
190                    serviceContext.setAttribute("pingbackUserName", pingbackUserName);
191    
192                    StringBundler sb = new StringBundler(5);
193    
194                    String layoutFullURL = PortalUtil.getLayoutFullURL(
195                            groupId, PortletKeys.BLOGS);
196    
197                    sb.append(layoutFullURL);
198    
199                    sb.append(Portal.FRIENDLY_URL_SEPARATOR);
200    
201                    Portlet portlet = PortletLocalServiceUtil.getPortletById(
202                            companyId, PortletKeys.BLOGS);
203    
204                    sb.append(portlet.getFriendlyURLMapping());
205                    sb.append(StringPool.SLASH);
206                    sb.append(urlTitle);
207    
208                    serviceContext.setAttribute("redirect", sb.toString());
209    
210                    serviceContext.setLayoutFullURL(layoutFullURL);
211    
212                    return serviceContext;
213            }
214    
215            protected BlogsEntry getBlogsEntry(long companyId) throws Exception {
216                    BlogsEntry entry = null;
217    
218                    URL url = new URL(_targetURI);
219    
220                    String friendlyURL = url.getPath();
221    
222                    int end = friendlyURL.indexOf(Portal.FRIENDLY_URL_SEPARATOR);
223    
224                    if (end != -1) {
225                            friendlyURL = friendlyURL.substring(0, end);
226                    }
227    
228                    long plid = PortalUtil.getPlidFromFriendlyURL(companyId, friendlyURL);
229                    long groupId = PortalUtil.getScopeGroupId(plid);
230    
231                    Map<String, String[]> params = new HashMap<>();
232    
233                    FriendlyURLMapperThreadLocal.setPRPIdentifiers(
234                            new HashMap<String, String>());
235    
236                    Portlet portlet = PortletLocalServiceUtil.getPortletById(
237                            PortletKeys.BLOGS);
238    
239                    FriendlyURLMapper friendlyURLMapper =
240                            portlet.getFriendlyURLMapperInstance();
241    
242                    friendlyURL = url.getPath();
243    
244                    end = friendlyURL.indexOf(Portal.FRIENDLY_URL_SEPARATOR);
245    
246                    if (end != -1) {
247                            friendlyURL = friendlyURL.substring(
248                                    end + Portal.FRIENDLY_URL_SEPARATOR.length() - 1);
249                    }
250    
251                    Map<String, Object> requestContext = new HashMap<>();
252    
253                    friendlyURLMapper.populateParams(friendlyURL, params, requestContext);
254    
255                    String param = getParam(params, "entryId");
256    
257                    if (Validator.isNotNull(param)) {
258                            long entryId = GetterUtil.getLong(param);
259    
260                            entry = BlogsEntryLocalServiceUtil.getEntry(entryId);
261                    }
262                    else {
263                            String urlTitle = getParam(params, "urlTitle");
264    
265                            entry = BlogsEntryLocalServiceUtil.getEntry(groupId, urlTitle);
266                    }
267    
268                    return entry;
269            }
270    
271            protected String getExcerpt() throws IOException {
272                    String html = HttpUtil.URLtoString(_sourceURI);
273    
274                    Source source = new Source(html);
275    
276                    source.fullSequentialParse();
277    
278                    List<Element> elements = source.getAllElements("a");
279    
280                    for (Element element : elements) {
281                            String href = GetterUtil.getString(
282                                    element.getAttributeValue("href"));
283    
284                            if (href.equals(_targetURI)) {
285                                    element = element.getParentElement();
286    
287                                    TextExtractor textExtractor = new TextExtractor(element);
288    
289                                    String body = textExtractor.toString();
290    
291                                    if (body.length() < PropsValues.BLOGS_LINKBACK_EXCERPT_LENGTH) {
292                                            element = element.getParentElement();
293    
294                                            if (element != null) {
295                                                    textExtractor = new TextExtractor(element);
296    
297                                                    body = textExtractor.toString();
298                                            }
299                                    }
300    
301                                    return StringUtil.shorten(
302                                            body, PropsValues.BLOGS_LINKBACK_EXCERPT_LENGTH);
303                            }
304                    }
305    
306                    return StringPool.BLANK;
307            }
308    
309            protected String getParam(Map<String, String[]> params, String name) {
310                    String[] paramArray = params.get(name);
311    
312                    if (paramArray == null) {
313                            String namespace = PortalUtil.getPortletNamespace(
314                                    PortletKeys.BLOGS);
315    
316                            paramArray = params.get(namespace + name);
317                    }
318    
319                    if (ArrayUtil.isNotEmpty(paramArray)) {
320                            return paramArray[0];
321                    }
322                    else {
323                            return null;
324                    }
325            }
326    
327            protected void validateSource() throws Exception {
328                    Source source = null;
329    
330                    try {
331                            String html = HttpUtil.URLtoString(_sourceURI);
332    
333                            source = new Source(html);
334                    }
335                    catch (Exception e) {
336                            if (_log.isDebugEnabled()) {
337                                    _log.debug(e, e);
338                            }
339    
340                            throw new UnavailableSourceURIException(
341                                    "Error accessing source URI", e);
342                    }
343    
344                    List<StartTag> startTags = source.getAllStartTags("a");
345    
346                    for (StartTag startTag : startTags) {
347                            String href = GetterUtil.getString(
348                                    startTag.getAttributeValue("href"));
349    
350                            if (href.equals(_targetURI)) {
351                                    return;
352                            }
353                    }
354    
355                    throw new InvalidSourceURIException(
356                            "Unable to find target URI in source");
357            }
358    
359            private static final Log _log = LogFactoryUtil.getLog(
360                    PingbackMethodImpl.class);
361    
362            private CommentManager _commentManager =
363                    CommentManagerUtil.getCommentManager();
364            private String _sourceURI;
365            private String _targetURI;
366    
367            private static class IdentityServiceContextFunction
368                    implements Function<String, ServiceContext> {
369    
370                    public IdentityServiceContextFunction(ServiceContext serviceContext) {
371                            _serviceContext = serviceContext;
372                    }
373    
374                    @Override
375                    public ServiceContext apply(String s) {
376                            return _serviceContext;
377                    }
378    
379                    private final ServiceContext _serviceContext;
380    
381            }
382    
383    }