001/* 002 * Copyright 2017 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.plugins.linkdirectory.link; 017 018import java.io.IOException; 019import java.io.InputStream; 020import java.net.URL; 021import java.util.ArrayList; 022import java.util.HashMap; 023import java.util.List; 024import java.util.Map; 025 026import org.apache.avalon.framework.parameters.Parameters; 027import org.apache.avalon.framework.service.ServiceException; 028import org.apache.avalon.framework.service.ServiceManager; 029import org.apache.cocoon.acting.ServiceableAction; 030import org.apache.cocoon.environment.ObjectModelHelper; 031import org.apache.cocoon.environment.Redirector; 032import org.apache.cocoon.environment.Request; 033import org.apache.cocoon.environment.Response; 034import org.apache.cocoon.environment.SourceResolver; 035import org.apache.commons.lang3.StringUtils; 036import org.apache.http.HttpEntity; 037import org.apache.http.client.config.RequestConfig; 038import org.apache.http.client.methods.CloseableHttpResponse; 039import org.apache.http.client.methods.HttpGet; 040import org.apache.http.entity.ContentType; 041import org.apache.http.impl.client.CloseableHttpClient; 042import org.apache.http.impl.client.HttpClientBuilder; 043 044import org.ametys.core.cocoon.JSonReader; 045import org.ametys.core.upload.Upload; 046import org.ametys.core.upload.UploadManager; 047import org.ametys.core.user.CurrentUserProvider; 048import org.ametys.core.user.UserIdentity; 049import org.ametys.core.util.JSONUtils; 050import org.ametys.plugins.linkdirectory.DirectoryHelper; 051import org.ametys.plugins.linkdirectory.Link; 052import org.ametys.plugins.linkdirectory.Link.LinkType; 053import org.ametys.plugins.repository.RepositoryConstants; 054import org.ametys.plugins.repository.provider.RequestAttributeWorkspaceSelector; 055import org.ametys.web.repository.site.SiteManager; 056import org.ametys.web.url.UrlPreviewComponent; 057 058/** 059 * Adds a new user link in Ametys. 060 */ 061public class AddUserLinkAction extends ServiceableAction 062{ 063 private static int _ICO_REQUEST_TIMEOUT = 3000; 064 065 /** The DAO for {@link Link}s */ 066 protected LinkDAO _linkDAO; 067 /** The current user provider */ 068 protected CurrentUserProvider _currentUserProvider; 069 /** The directory helper */ 070 protected DirectoryHelper _directoryHelper; 071 /** The site manager */ 072 protected SiteManager _siteManager; 073 /** The url preview component */ 074 protected UrlPreviewComponent _urlPreviewComponent; 075 /** The upload manager */ 076 protected UploadManager _uploadManager; 077 /** The JSON utils */ 078 protected JSONUtils _jsonUtils; 079 080 @Override 081 public void service(ServiceManager smanager) throws ServiceException 082 { 083 super.service(smanager); 084 _currentUserProvider = (CurrentUserProvider) smanager.lookup(CurrentUserProvider.ROLE); 085 _linkDAO = (LinkDAO) smanager.lookup(LinkDAO.ROLE); 086 _directoryHelper = (DirectoryHelper) smanager.lookup(DirectoryHelper.ROLE); 087 _siteManager = (SiteManager) smanager.lookup(SiteManager.ROLE); 088 _urlPreviewComponent = (UrlPreviewComponent) smanager.lookup(UrlPreviewComponent.ROLE); 089 _uploadManager = (UploadManager) smanager.lookup(UploadManager.ROLE); 090 _jsonUtils = (JSONUtils) smanager.lookup(JSONUtils.ROLE); 091 } 092 093 @Override 094 public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws Exception 095 { 096 Request request = ObjectModelHelper.getRequest(objectModel); 097 Map<String, Object> result = new HashMap<>(); 098 099 UserIdentity user = _currentUserProvider.getUser(); 100 101 // Check user authenticated 102 if (user != null) 103 { 104 // Create link 105 Map<String, Object> createParameters = new HashMap<>(); 106 String siteName = request.getParameter("siteName"); 107 String lang = request.getParameter("lang"); 108 String url = request.getParameter("url"); 109 //The color key, default color will be used if it is not present or empty 110 String color = request.getParameter("color"); 111 112 createParameters.put("siteName", siteName); 113 createParameters.put("lang", lang); 114 createParameters.put("url-type", LinkType.URL.toString()); 115 createParameters.put("url", url); 116 createParameters.put("title", request.getParameter("title")); 117 createParameters.put("color", color); 118 119 String userAgent = request.getHeader("User-Agent"); 120 Upload upload = _getLinkFavicon(url, user, userAgent); 121 if (upload != null) 122 { 123 Map<String, Object> linkMap = new HashMap<>(); 124 linkMap.put("id", upload.getId()); 125 linkMap.put("type", "external"); 126 createParameters.put("picture", _jsonUtils.convertObjectToJson(linkMap)); 127 } 128 129 _handleTheme(request, createParameters, siteName, lang); 130 131 Map<String, Object> createdLinkresult = _createUserLink(request, createParameters); 132 133 if (createdLinkresult.containsKey("already-exists")) 134 { 135 result.put("error", "already-exists"); 136 } 137 else 138 { 139 result.put("error", "none"); 140 result.putAll(createdLinkresult); 141 } 142 } 143 else 144 { 145 result.put("error", "unauthenticated-user"); 146 } 147 148 Response response = ObjectModelHelper.getResponse(objectModel); 149 response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin")); 150 response.setHeader("Access-Control-Allow-Credentials", "true"); 151 152 request.setAttribute(JSonReader.OBJECT_TO_READ, result); 153 return EMPTY_MAP; 154 } 155 156 private void _handleTheme(Request request, Map<String, Object> createParameters, String siteName, String lang) 157 { 158 String themeName = request.getParameter("themes"); 159 160 List<String> themes = new ArrayList<>(); 161 // We have a theme name ... 162 if (StringUtils.isNotBlank(themeName)) 163 { 164 // ... retrieve JCR id from theme name 165 if (_directoryHelper.themeExists(themeName, siteName, lang)) 166 { 167 themes.add(themeName); 168 } 169 } 170 // if not, just send an empty list for the user link creation 171 createParameters.put("themes", themes); 172 } 173 174 private Map<String, Object> _createUserLink(Request request, Map<String, Object> parameters) 175 { 176 // Retrieve the current workspace. 177 String currentWsp = RequestAttributeWorkspaceSelector.getForcedWorkspace(request); 178 179 try 180 { 181 // Force the workspace. 182 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, RepositoryConstants.DEFAULT_WORKSPACE); 183 184 return _linkDAO.createUserLink(parameters); 185 } 186 finally 187 { 188 // Restore context 189 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, currentWsp); 190 } 191 } 192 193 /** 194 * Set the favicon of the link url 195 * @param url the url of the favicon 196 * @param user the user 197 * @param userAgent The user agent 198 * @return the favicon upload map 199 */ 200 protected Upload _getLinkFavicon(String url, UserIdentity user, String userAgent) 201 { 202 Upload upload = null; 203 try 204 { 205 String favIconLink = _urlPreviewComponent.getFavicon(url); 206 207 if (StringUtils.isBlank(favIconLink)) 208 { 209 return null; 210 } 211 212 URL favIconURL = new URL(favIconLink); 213 String favIconName = StringUtils.substringAfterLast(favIconURL.getPath(), "/"); 214 215 RequestConfig requestConfig = RequestConfig.custom() 216 .setConnectTimeout(_ICO_REQUEST_TIMEOUT) 217 .setConnectionRequestTimeout(_ICO_REQUEST_TIMEOUT) 218 .setSocketTimeout(_ICO_REQUEST_TIMEOUT) 219 .build(); 220 221 try (CloseableHttpClient httpclient = HttpClientBuilder.create() 222 .setDefaultRequestConfig(requestConfig) 223 .useSystemProperties() 224 .setUserAgent(userAgent) 225 .build()) 226 { 227 // Prepare a request object 228 HttpGet get = new HttpGet(favIconLink); 229 try (CloseableHttpResponse httpResponse = httpclient.execute(get)) 230 { 231 int statusCode = httpResponse.getStatusLine().getStatusCode(); 232 if (statusCode != 200) 233 { 234 getLogger().warn("Error " + statusCode + ". Can't set the favicon with url " + favIconLink + " to the link " + url); 235 return upload; 236 } 237 238 try (InputStream is = httpResponse.getEntity().getContent()) 239 { 240 HttpEntity entity = httpResponse.getEntity(); 241 String mimeType = ContentType.get(entity).getMimeType(); 242 if (mimeType.equals("image/x-icon") || mimeType.equals("image/vnd.microsoft.icon")) 243 { 244 // If the image is .ico, convert it in .png 245 InputStream convertedIcoToPngIS = _urlPreviewComponent.convertIcoToPng(is); 246 upload = _uploadManager.storeUpload(user, StringUtils.replace(favIconName, ".ico", ".png"), convertedIcoToPngIS); 247 } 248 else 249 { 250 upload = _uploadManager.storeUpload(user, favIconName, is); 251 } 252 } 253 } 254 } 255 catch (IOException e) 256 { 257 getLogger().warn("Unable to download favicon from url " + favIconLink + " to the link " + url, e); 258 } 259 } 260 catch (Exception e) 261 { 262 getLogger().warn("Unable to get favicon from link url " + url, e); 263 } 264 265 return upload; 266 } 267}