001/*
002 *  Copyright 2012 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 */
016
017package org.ametys.core.util;
018
019import java.util.ArrayList;
020import java.util.Arrays;
021import java.util.HashMap;
022import java.util.List;
023import java.util.Map;
024import java.util.Set;
025
026import org.apache.avalon.framework.context.Context;
027import org.apache.avalon.framework.context.ContextException;
028import org.apache.avalon.framework.context.Contextualizable;
029import org.apache.avalon.framework.service.ServiceException;
030import org.apache.avalon.framework.service.ServiceManager;
031import org.apache.avalon.framework.service.Serviceable;
032import org.apache.cocoon.components.ContextHelper;
033import org.apache.cocoon.environment.Request;
034import org.apache.cocoon.xml.dom.DOMBuilder;
035import org.apache.commons.lang.StringEscapeUtils;
036import org.apache.commons.lang3.StringUtils;
037import org.w3c.dom.Node;
038import org.w3c.dom.NodeList;
039import org.xml.sax.SAXException;
040
041import org.ametys.core.DevMode;
042import org.ametys.core.group.Group;
043import org.ametys.core.group.GroupIdentity;
044import org.ametys.core.group.GroupManager;
045import org.ametys.core.user.CurrentUserProvider;
046import org.ametys.core.user.User;
047import org.ametys.core.user.UserIdentity;
048import org.ametys.core.user.UserManager;
049import org.ametys.core.util.dom.AmetysNodeList;
050import org.ametys.core.util.dom.MapElement;
051import org.ametys.core.util.dom.StringElement;
052import org.ametys.core.version.Version;
053import org.ametys.core.version.VersionsHandler;
054import org.ametys.plugins.core.user.UserHelper;
055import org.ametys.runtime.config.Config;
056import org.ametys.runtime.i18n.I18nizableText;
057import org.ametys.runtime.parameter.ParameterHelper;
058import org.ametys.runtime.servlet.RuntimeConfig;
059import org.ametys.runtime.workspace.WorkspaceManager;
060import org.ametys.runtime.workspace.WorkspaceMatcher;
061
062/**
063 * Helper component to be used from XSL stylesheets.
064 */
065public class AmetysXSLTHelper implements Contextualizable, Serviceable
066{
067    /** The i18n utils instance */
068    protected static I18nUtils _i18nUtils;
069    
070    /** The versions handler */
071    protected static VersionsHandler _versionHandler;
072
073    /** The current user provider */
074    protected static CurrentUserProvider _currentUserProvider;
075    /** The users manager */
076    protected static UserManager _userManager;
077    /** The groups manager */
078    protected static GroupManager _groupManager;
079    /** The user helper */
080    protected static UserHelper _userHelper;
081    
082    private static Context _context;
083    
084    @Override
085    public void contextualize(Context context) throws ContextException
086    {
087        _context = context;
088    }
089    
090    public void service(ServiceManager manager) throws ServiceException
091    {
092        _i18nUtils = (I18nUtils) manager.lookup(I18nUtils.ROLE);
093        _versionHandler = (VersionsHandler) manager.lookup(VersionsHandler.ROLE);
094        
095        _currentUserProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE);
096        _userManager = (UserManager) manager.lookup(UserManager.ROLE);
097        _groupManager = (GroupManager) manager.lookup(GroupManager.ROLE);
098        _userHelper = (UserHelper) manager.lookup(UserHelper.ROLE);
099    }
100    
101    /**
102     * Returns the current URI prefix.
103     * @return the current URI prefix.
104     */
105    public static String uriPrefix()
106    {
107        return uriPrefix(true);
108    }
109    
110    /**
111     * Returns the current URI prefix.
112     * @param withWorkspaceURI true to add the workspace URI (recommended)
113     * @return the current URI prefix.
114     */
115    public static String uriPrefix(boolean withWorkspaceURI)
116    {
117        return getUriPrefix(withWorkspaceURI);
118    }
119    
120    /**
121     * Returns the absolute URI prefix.
122     * @return the absolute URI prefix.
123     */
124    public static String absoluteUriPrefix()
125    {
126        return absoluteUriPrefix(true);
127    }
128    
129    /**
130     * Returns the absolute URI prefix.
131     * @param withWorkspaceURI true to add the workspace URI (recommended)
132     * @return the absolute URI prefix.
133     */
134    public static String absoluteUriPrefix(boolean withWorkspaceURI)
135    {
136        return getAbsoluteUriPrefix(withWorkspaceURI);
137    }
138    
139    /**
140     * Return the current workspace name
141     * @return The workspace name. Cannot be empty.
142     */
143    public static String workspaceName()
144    {
145        return getWorkspaceName();
146    }
147    
148    /**
149     * Return the current workspace URI
150     * @return The workspace name. Can be empty.
151     */
152    public static String workspacePrefix()
153    {
154        return getWorkspacePrefix();
155    }
156    
157    /**
158     * Return the current workspace theme name
159     * @return The name
160     */
161    public static String workspaceTheme()
162    {
163        Request request = ContextHelper.getRequest(_context);
164        return (String) request.getAttribute(WorkspaceMatcher.WORKSPACE_THEME);
165    }
166
167    /**
168     * Return the current workspace theme url
169     * @return The url without any prefix
170     */
171    public static String workspaceThemeURL()
172    {
173        Request request = ContextHelper.getRequest(_context);
174        
175        String workspaceThemeUrl = (String) request.getAttribute(WorkspaceMatcher.WORKSPACE_THEME_URL);
176        if (workspaceThemeUrl == null)
177        {
178            // fallback to the default workspace
179            String workspaceName = RuntimeConfig.getInstance().getDefaultWorkspace();
180            WorkspaceManager wm = WorkspaceManager.getInstance();
181            if (wm.getWorkspaceNames().contains(workspaceName))
182            {
183                workspaceThemeUrl = wm.getWorkspaces().get(workspaceName).getThemeURL();
184            }
185        }
186        
187        return workspaceThemeUrl;
188    }
189    
190    /**
191     * Get the application context path. Can be empty if the application
192     * resides in the root context. Use it to create a link beginning with
193     * the application root.
194     * @param withWorkspaceURI true to add the workspace URI (recommended)
195     * @return The application context path with workspace URI
196     * @see Request#getContextPath()
197     */
198    protected static String getUriPrefix(boolean withWorkspaceURI)
199    {
200        Request request = ContextHelper.getRequest(_context);
201        String workspaceURI = withWorkspaceURI ? getWorkspacePrefix() : "";
202        
203        return request.getContextPath() + workspaceURI;
204    }
205    
206    /**
207     * Get the absolutized version of the context path. Use it to create an absolute
208     * link beginning with the application root, for instance when sending a mail
209     * linking to the application.
210     * @param withWorkspaceURI true to add the workspace URI (recommended)
211     * @return The absolute context path.
212     */
213    protected static String getAbsoluteUriPrefix(boolean withWorkspaceURI)
214    {
215        Request request = ContextHelper.getRequest(_context);
216        
217        String uriPrefix = getUriPrefix(withWorkspaceURI);
218        
219        if (!uriPrefix.startsWith("http"))
220        {
221            uriPrefix = request.getScheme() + "://" + request.getServerName() + (request.getServerPort() != 80 ? ":" + request.getServerPort() : "") + uriPrefix;
222        }
223        
224        return uriPrefix;
225    }  
226    
227    /**
228     * Return the current workspace name
229     * @return The workspace name. Cannot be empty.
230     */
231    protected static String getWorkspaceName()
232    {
233        Request request = ContextHelper.getRequest(_context);
234        return (String) request.getAttribute(WorkspaceMatcher.WORKSPACE_NAME);
235    }
236    
237    /**
238     * Return the current workspace URI
239     * @return The workspace name. Can be empty for the default workspace.
240     */
241    protected static String getWorkspacePrefix()
242    {
243        Request request = ContextHelper.getRequest(_context);
244        return (String) request.getAttribute(WorkspaceMatcher.WORKSPACE_URI);
245    }
246
247    /**
248     * Returns the configuration value associated with the given parameter.
249     * @param id the configuration parameter.
250     * @return the configuration value associated with the given parameter.
251     */
252    public static String config(String id)
253    {
254        if (Config.getInstance() != null)
255        {
256            return Config.getInstance().getValueAsString(id);
257        }
258        else
259        {
260            return null;
261        }
262    }
263    
264    /**
265     * Return the value of a request parameter.
266     * @param parameter the parameter name.
267     * @return the request parameter.
268     */
269    public static String requestParameter(String parameter)
270    {
271        Request request = ContextHelper.getRequest(_context);
272        return request.getParameter(parameter);
273    }
274    
275    /**
276     * Translate an i18n key using current user language.
277     * @param key The key to translate. Specify the catalog this way: "catalogue:KEY"
278     * @return The translation or null.
279     */
280    public static String translate(String key)
281    {
282        return translate(key, null, null);
283    }
284    
285    /**
286     * Translate an i18n key
287     * @param key The key to translate. Specify the catalog this way: "catalogue:KEY"
288     * @param lang The language. Can be null to use current user language.
289     * @param parameters The key parameters. Can be empty.
290     * @return The translation or null.
291     */
292    public static String translate(String key, String lang, String[] parameters)
293    {
294        List<String> parametersAsString = parameters != null ? Arrays.asList(parameters) : null;
295        
296        I18nizableText i18nKey = new I18nizableText(null, key, parametersAsString);
297        return _i18nUtils.translate(i18nKey, lang);
298    }
299    
300    /**
301     * Escape the given string to be used as JS variable.
302     * @param str the string to escape.
303     * @return the escaped String.
304     */
305    public static String escapeJS(String str)
306    {
307        return StringEscapeUtils.escapeJavaScript(str);
308    }
309    
310    /**
311     * Encode the string to be url compliant
312     * @param url The url to encode
313     * @return The encoded url
314     */
315    public static String urlEncode(String url)
316    {
317        return URLEncoder.encodePath(url);
318    }
319    
320    /**
321     * Split the text.
322     * @param textToSplit the text to split.
323     * @param tokenizers the tokenizer characters.
324     * @param startIndex the minimum number of characters of the result string
325     * @return the split text.
326     */
327    public static String splitText(String textToSplit, String tokenizers, int startIndex)
328    {
329        String tokenizableText = textToSplit.substring(startIndex != 0 ? startIndex - 1 : 0, textToSplit.length());
330        
331        int tokenPlace = StringUtils.indexOfAny(tokenizableText, tokenizers);
332        
333        if (tokenPlace == -1)
334        {
335            return textToSplit;
336        }
337        else
338        {
339            return textToSplit.substring(0, startIndex - 1 + tokenPlace);
340        }
341    }
342    
343    /**
344     * Split the text.
345     * @param textToSplit the text to split.
346     * @param tokenizers the tokenizer characters.
347     * @param maxCharacters the maximum number of characters of the result string
348     * @param currentCharactersNumber the current character number.
349     * @return the split text.
350     */
351    @Deprecated
352    public static String splitText(String textToSplit, String tokenizers, int maxCharacters, int currentCharactersNumber)
353    {
354        int tokenStartIndex = maxCharacters - currentCharactersNumber - 1;
355        String tokenizableText = textToSplit.substring(tokenStartIndex, textToSplit.length());
356        
357        int tokenPlace = StringUtils.indexOfAny(tokenizableText, tokenizers);
358        
359        if (tokenPlace == -1)
360        {
361            return textToSplit;
362        }
363        else
364        {
365            return textToSplit.substring(0, tokenStartIndex + tokenPlace);
366        }
367    }
368    
369    /**
370     * Get the versions of the application.
371     * Default VersionsHandler impl will return Ametys and Application versions
372     * @return The versions &lt;Version&gt;&lt;Version&gt;&lt;Name&gt;X&lt;/Name&gt;&lt;Version&gt;X&lt;/Version&gt;&lt;Date&gt;X&lt;/Date&gt;&lt;/Version&gt;&lt;/Versions&gt; (empty tags are removed)
373     */
374    public static Node versions()
375    {
376        Map<String, Object> versionsMap = new HashMap<>();
377
378        List<Object> versionList = new ArrayList<>();
379
380        for (Version version : _versionHandler.getVersions())
381        {
382            Map<String, Object> versionMap = new HashMap<>();
383
384            String componentName = version.getName();
385            String componentVersion = version.getVersion();
386            String componentDate = ParameterHelper.valueToString(version.getDate());
387            
388            if (StringUtils.isNotEmpty(componentName))
389            {
390                versionMap.put("Name", componentName);
391            }
392            if (StringUtils.isNotEmpty(componentVersion))
393            {
394                versionMap.put("Version", componentVersion);
395            }
396            if (StringUtils.isNotEmpty(componentDate))
397            {
398                versionMap.put("Date", componentDate);
399            }
400            
401            versionList.add(versionMap);
402        }
403        
404        versionsMap.put("Component", versionList);
405
406        return new MapElement("Versions", versionsMap);
407    }
408    
409    /**
410     * Get the current mode of the application for the current user.
411     * @return True if the application is in developer mode, false if in production mode.
412     */
413    public static boolean isDeveloperMode()
414    {
415        Request request = ContextHelper.getRequest(_context);
416        
417        return DevMode.isDeveloperMode(request);
418    }
419    
420    /**
421     * Return the user
422     * @return The current connected user object or null
423     * @throws SAXException if a problem occured while getting the user
424     */
425    public static Node user() throws SAXException
426    {
427        DOMBuilder domBuilder = new DOMBuilder();
428        
429        UserIdentity userIdentity = _currentUserProvider.getUser();
430        if (userIdentity != null)
431        {
432            User user = _userManager.getUser(userIdentity.getPopulationId(), userIdentity.getLogin());
433            if (user != null)
434            {
435                _userHelper.saxUser(user, domBuilder);
436            }
437        }
438        
439        return domBuilder.getDocument();
440    }
441    
442    /**
443     * Returns the list of the current user's groups.
444     * @return the list of the current user's groups. Can be null if there is no connected user.
445     */
446    public static NodeList groups()
447    {
448        UserIdentity userIdentity = _currentUserProvider.getUser();
449        return userIdentity != null ? groups(userIdentity.getLogin(), userIdentity.getPopulationId()) : null;
450    }
451    
452    /**
453     * Returns the of the given user's group.
454     * @param login the concerned user's login.
455     * @param populationId the concerned user's population.
456     * @return the of the given user's group.
457     */
458    public static NodeList groups(String login, String populationId)
459    {
460        ArrayList<Node> groups = new ArrayList<>();
461        
462        Set<GroupIdentity> userGroups = _groupManager.getUserGroups(new UserIdentity(login, populationId));
463        for (GroupIdentity groupId : userGroups)
464        {
465            Group group = _groupManager.getGroup(groupId);
466            if (group != null)
467            {
468                Map<String, String> attributes = new HashMap<>();
469                attributes.put("name", groupId.getId());
470                attributes.put("directory", groupId.getDirectoryId());
471                groups.add(new StringElement("group", attributes, group.getLabel()));
472            }
473        }
474        
475        return new AmetysNodeList(groups);
476    }
477}