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.Collection;
021import java.util.Date;
022import java.util.HashMap;
023import java.util.List;
024import java.util.Map;
025import java.util.Set;
026
027import org.apache.avalon.framework.context.Context;
028import org.apache.avalon.framework.context.ContextException;
029import org.apache.avalon.framework.context.Contextualizable;
030import org.apache.avalon.framework.service.ServiceException;
031import org.apache.avalon.framework.service.ServiceManager;
032import org.apache.avalon.framework.service.Serviceable;
033import org.apache.cocoon.components.ContextHelper;
034import org.apache.cocoon.environment.Request;
035import org.apache.cocoon.xml.dom.DOMBuilder;
036import org.apache.commons.lang3.StringUtils;
037import org.apache.commons.text.StringEscapeUtils;
038import org.slf4j.Logger;
039import org.slf4j.LoggerFactory;
040import org.w3c.dom.Node;
041import org.w3c.dom.NodeList;
042import org.xml.sax.SAXException;
043
044import org.ametys.core.DevMode;
045import org.ametys.core.DevMode.DEVMODE;
046import org.ametys.core.group.Group;
047import org.ametys.core.group.GroupIdentity;
048import org.ametys.core.group.GroupManager;
049import org.ametys.core.right.RightManager;
050import org.ametys.core.right.RightManager.RightResult;
051import org.ametys.core.user.CurrentUserProvider;
052import org.ametys.core.user.User;
053import org.ametys.core.user.UserIdentity;
054import org.ametys.core.user.UserManager;
055import org.ametys.core.user.directory.NotUniqueUserException;
056import org.ametys.core.util.dom.AmetysNodeList;
057import org.ametys.core.util.dom.MapElement;
058import org.ametys.core.util.dom.StringElement;
059import org.ametys.core.version.Version;
060import org.ametys.core.version.VersionsHandler;
061import org.ametys.plugins.core.user.UserHelper;
062import org.ametys.runtime.config.Config;
063import org.ametys.runtime.i18n.I18nizableText;
064import org.ametys.runtime.plugin.PluginsManager;
065import org.ametys.runtime.servlet.RuntimeConfig;
066import org.ametys.runtime.workspace.WorkspaceManager;
067import org.ametys.runtime.workspace.WorkspaceMatcher;
068
069/**
070 * Helper component to be used from XSL stylesheets.
071 */
072public class AmetysXSLTHelper implements Contextualizable, Serviceable
073{
074    /** The logger */
075    protected static final Logger _LOGGER = LoggerFactory.getLogger(AmetysXSLTHelper.class.getName());
076    
077    /** The i18n utils instance */
078    protected static I18nUtils _i18nUtils;
079    
080    /** The versions handler */
081    protected static VersionsHandler _versionHandler;
082
083    /** The current user provider */
084    protected static CurrentUserProvider _currentUserProvider;
085    /** The groups manager */
086    protected static GroupManager _groupManager;
087    /** The user helper */
088    protected static UserHelper _userHelper;
089    /** The json utils */
090    protected static JSONUtils _jsonUtils;
091    /** The right manager */
092    protected static RightManager _rightManager;
093    /** The user manager */
094    protected static UserManager _userManager;
095    
096    private static Context _context;
097
098    @Override
099    public void contextualize(Context context) throws ContextException
100    {
101        _context = context;
102    }
103    
104    public void service(ServiceManager manager) throws ServiceException
105    {
106        _i18nUtils = (I18nUtils) manager.lookup(I18nUtils.ROLE);
107        _versionHandler = (VersionsHandler) manager.lookup(VersionsHandler.ROLE);
108        
109        _currentUserProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE);
110        _groupManager = (GroupManager) manager.lookup(GroupManager.ROLE);
111        _userHelper = (UserHelper) manager.lookup(UserHelper.ROLE);
112        _jsonUtils = (JSONUtils) manager.lookup(JSONUtils.ROLE);
113        _rightManager = (RightManager) manager.lookup(RightManager.ROLE);
114        _userManager = (UserManager) manager.lookup(UserManager.ROLE);
115    }
116    
117    /**
118     * Returns the current URI prefix.
119     * @return the current URI prefix.
120     */
121    public static String uriPrefix()
122    {
123        return uriPrefix(true);
124    }
125    
126    /**
127     * Returns the current URI prefix.
128     * @param withWorkspaceURI true to add the workspace URI (recommended)
129     * @return the current URI prefix.
130     */
131    public static String uriPrefix(boolean withWorkspaceURI)
132    {
133        return getUriPrefix(withWorkspaceURI);
134    }
135    
136    /**
137     * Returns the absolute URI prefix.
138     * @return the absolute URI prefix.
139     */
140    public static String absoluteUriPrefix()
141    {
142        return absoluteUriPrefix(true);
143    }
144    
145    /**
146     * Returns the absolute URI prefix.
147     * @param withWorkspaceURI true to add the workspace URI (recommended)
148     * @return the absolute URI prefix.
149     */
150    public static String absoluteUriPrefix(boolean withWorkspaceURI)
151    {
152        return getAbsoluteUriPrefix(withWorkspaceURI);
153    }
154    
155    /**
156     * Return the current workspace name
157     * @return The workspace name. Cannot be empty.
158     */
159    public static String workspaceName()
160    {
161        return getWorkspaceName();
162    }
163    
164    /**
165     * Return the current workspace URI
166     * @return The workspace name. Can be empty.
167     */
168    public static String workspacePrefix()
169    {
170        return getWorkspacePrefix();
171    }
172    
173    /**
174     * Return the current workspace theme name
175     * @return The name
176     */
177    public static String workspaceTheme()
178    {
179        Request request = ContextHelper.getRequest(_context);
180        return (String) request.getAttribute(WorkspaceMatcher.WORKSPACE_THEME);
181    }
182
183    /**
184     * Return the current workspace theme url
185     * @return The url without any prefix
186     */
187    public static String workspaceThemeURL()
188    {
189        Request request = ContextHelper.getRequest(_context);
190        
191        String workspaceThemeUrl = (String) request.getAttribute(WorkspaceMatcher.WORKSPACE_THEME_URL);
192        if (workspaceThemeUrl == null)
193        {
194            // fallback to the default workspace
195            String workspaceName = RuntimeConfig.getInstance().getDefaultWorkspace();
196            WorkspaceManager wm = WorkspaceManager.getInstance();
197            if (wm.getWorkspaceNames().contains(workspaceName))
198            {
199                workspaceThemeUrl = wm.getWorkspaces().get(workspaceName).getThemeURL();
200            }
201        }
202        
203        return workspaceThemeUrl;
204    }
205    
206    /**
207     * Get the application context path. Can be empty if the application
208     * resides in the root context. Use it to create a link beginning with
209     * the application root.
210     * @param withWorkspaceURI true to add the workspace URI (recommended)
211     * @return The application context path with workspace URI
212     * @see Request#getContextPath()
213     */
214    protected static String getUriPrefix(boolean withWorkspaceURI)
215    {
216        Request request = ContextHelper.getRequest(_context);
217        String workspaceURI = withWorkspaceURI ? getWorkspacePrefix() : "";
218        
219        return request.getContextPath() + workspaceURI;
220    }
221    
222    /**
223     * Get the absolutized version of the context path. Use it to create an absolute
224     * link beginning with the application root, for instance when sending a mail
225     * linking to the application.
226     * @param withWorkspaceURI true to add the workspace URI (recommended)
227     * @return The absolute context path.
228     */
229    protected static String getAbsoluteUriPrefix(boolean withWorkspaceURI)
230    {
231        Request request = ContextHelper.getRequest(_context);
232        
233        String uriPrefix = getUriPrefix(withWorkspaceURI);
234        
235        if (!uriPrefix.startsWith("http"))
236        {
237            uriPrefix = request.getScheme() + "://" + request.getServerName() + (request.getServerPort() != 80 ? ":" + request.getServerPort() : "") + uriPrefix;
238        }
239        
240        return uriPrefix;
241    }  
242    
243    /**
244     * Return the current workspace name
245     * @return The workspace name. Cannot be empty.
246     */
247    protected static String getWorkspaceName()
248    {
249        Request request = ContextHelper.getRequest(_context);
250        return (String) request.getAttribute(WorkspaceMatcher.WORKSPACE_NAME);
251    }
252    
253    /**
254     * Return the current workspace URI
255     * @return The workspace name. Can be empty for the default workspace.
256     */
257    protected static String getWorkspacePrefix()
258    {
259        Request request = ContextHelper.getRequest(_context);
260        return (String) request.getAttribute(WorkspaceMatcher.WORKSPACE_URI);
261    }
262
263    /**
264     * Returns the configuration value associated with the given parameter.
265     * @param id the configuration parameter.
266     * @return the configuration value associated with the given parameter.
267     */
268    public static Object config(String id)
269    {
270        if (Config.getInstance() != null)
271        {
272            return Config.getInstance().getValue(id);
273        }
274        else
275        {
276            return null;
277        }
278    }
279    
280    /**
281     * Return the value of a request parameter.
282     * @param parameter the parameter name.
283     * @return the request parameter.
284     */
285    public static String requestParameter(String parameter)
286    {
287        Request request = ContextHelper.getRequest(_context);
288        return request.getParameter(parameter);
289    }
290    
291    /**
292     * Translate an i18n key using current user language.
293     * @param key The key to translate. Specify the catalog this way: "catalogue:KEY"
294     * @return The translation or null.
295     */
296    public static String translate(String key)
297    {
298        return translate(key, null, null);
299    }
300    
301    /**
302     * Translate an i18n key
303     * @param key The key to translate. Specify the catalog this way: "catalogue:KEY"
304     * @param lang The language. Can be null to use current user language.
305     * @return The translation or null.
306     */
307    public static String translate(String key, String lang)
308    {
309        return translate(key, lang, null);
310    }
311    
312    /**
313     * Translate an i18n key
314     * @param key The key to translate. Specify the catalog this way: "catalogue:KEY"
315     * @param lang The language. Can be null to use current user language.
316     * @param parameters The key parameters. Can be empty.
317     * @return The translation or null.
318     */
319    public static String translate(String key, String lang, NodeList parameters)
320    {
321        List<String> i18nparams = new ArrayList<>();
322        if (parameters != null && parameters.getLength() == 1)
323        {
324            NodeList childNodes = parameters.item(0).getChildNodes();
325            for (int i = 0; i < childNodes.getLength(); i++) 
326            {  
327                i18nparams.add(childNodes.item(i).getTextContent());
328            }
329        }
330        
331        I18nizableText i18nKey = new I18nizableText(null, key, i18nparams);
332        return _i18nUtils.translate(i18nKey, lang);
333    }
334    
335    /**
336     * Escape the given string to be used as JS variable.
337     * @param str the string to escape.
338     * @return the escaped String.
339     */
340    public static String escapeJS(String str)
341    {
342        return StringEscapeUtils.escapeEcmaScript(str);
343    }
344    
345    /**
346     * Split the text.
347     * @param textToSplit the text to split.
348     * @param tokenizers the tokenizer characters.
349     * @param startIndex the minimum number of characters of the result string
350     * @return the split text.
351     */
352    public static String splitText(String textToSplit, String tokenizers, int startIndex)
353    {
354        String tokenizableText = textToSplit.substring(startIndex != 0 ? startIndex - 1 : 0, textToSplit.length());
355        
356        int tokenPlace = StringUtils.indexOfAny(tokenizableText, tokenizers);
357        
358        if (tokenPlace == -1)
359        {
360            return textToSplit;
361        }
362        else
363        {
364            return textToSplit.substring(0, startIndex - 1 + tokenPlace);
365        }
366    }
367    
368    /**
369     * Split the text.
370     * @param textToSplit the text to split.
371     * @param tokenizers the tokenizer characters.
372     * @param maxCharacters the maximum number of characters of the result string
373     * @param currentCharactersNumber the current character number.
374     * @return the split text.
375     */
376    @Deprecated
377    public static String splitText(String textToSplit, String tokenizers, int maxCharacters, int currentCharactersNumber)
378    {
379        int tokenStartIndex = maxCharacters - currentCharactersNumber - 1;
380        String tokenizableText = textToSplit.substring(tokenStartIndex, textToSplit.length());
381        
382        int tokenPlace = StringUtils.indexOfAny(tokenizableText, tokenizers);
383        
384        if (tokenPlace == -1)
385        {
386            return textToSplit;
387        }
388        else
389        {
390            return textToSplit.substring(0, tokenStartIndex + tokenPlace);
391        }
392    }
393    
394    /**
395     * Get the versions of the application.
396     * Default VersionsHandler impl will return Ametys and Application versions
397     * @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)
398     */
399    public static Node versions()
400    {
401        Map<String, Object> versionsMap = new HashMap<>();
402
403        List<Object> versionList = new ArrayList<>();
404
405        for (Version version : _versionHandler.getVersions())
406        {
407            Map<String, Object> versionMap = new HashMap<>();
408
409            String componentName = version.getName();
410            String componentVersion = version.getVersion();
411            String componentDate =  DateUtils.dateToString(version.getDate());
412            
413            if (StringUtils.isNotEmpty(componentName))
414            {
415                versionMap.put("Name", componentName);
416            }
417            if (StringUtils.isNotEmpty(componentVersion))
418            {
419                versionMap.put("Version", componentVersion);
420            }
421            if (StringUtils.isNotEmpty(componentDate))
422            {
423                versionMap.put("Date", componentDate);
424            }
425            
426            versionList.add(versionMap);
427        }
428        
429        versionsMap.put("Component", versionList);
430
431        return new MapElement("Versions", versionsMap);
432    }
433    
434    /**
435     * Get the current mode of the application for the current user.
436     * @return True if the application is in developer mode, false if in production mode.
437     */
438    public static boolean isDeveloperMode()
439    {
440        Request request = ContextHelper.getRequest(_context);
441        
442        DEVMODE developerMode = DevMode.getDeveloperMode(request);
443        return developerMode == DEVMODE.DEVELOPMENT 
444                || developerMode == DEVMODE.SUPER_DEVELOPPMENT;
445    }
446    
447    /**
448     * Return the user
449     * @return The current connected user object or null
450     * @throws SAXException if a problem occured while getting the user
451     */
452    public static Node user() throws SAXException
453    {
454        UserIdentity userIdentity = _currentUserProvider.getUser();
455        if (userIdentity != null)
456        {
457            return user(userIdentity.getLogin(), userIdentity.getPopulationId());
458        }
459        
460        return null;
461    }
462    
463    /**
464     * Return the given user
465     * @param userIdentity the concerned user's login + population
466     * @return The informations about the given user
467     * @throws SAXException If an error occurred while saxing the user
468     */
469    public static Node user(String userIdentity) throws SAXException
470    {
471        UserIdentity userIdentityObject = UserIdentity.stringToUserIdentity(userIdentity);
472        if (userIdentityObject == null)
473        {
474            return null;
475        }
476        else
477        {
478            return user(userIdentityObject.getLogin(), userIdentityObject.getPopulationId());
479        }
480    }
481    
482    /**
483     * Return the given user
484     * @param login the concerned user's login
485     * @param populationId the concerned user's population id
486     * @return The informations about the given user
487     * @throws SAXException If an error occurred while saxing the user
488     */
489    public static Node user(String login, String populationId) throws SAXException
490    {
491        DOMBuilder domBuilder = new DOMBuilder();
492        
493        UserIdentity userIdentity = new UserIdentity(login, populationId);
494        _userHelper.saxUserIdentity(userIdentity, domBuilder);
495        
496        return domBuilder.getDocument();
497    }
498    
499    /**
500     * Return the given user
501     * @param email the concerned user's email
502     * @param populationId the concerned user's population id
503     * @return The informations about the given user
504     * @throws SAXException If an error occurred while saxing the user
505     */
506    public static Node userByMail(String email, String populationId) throws SAXException
507    {
508        try
509        {
510            User user = _userManager.getUserByEmail(populationId, email);
511            if (user != null)
512            {
513                return user(UserIdentity.userIdentityToString(user.getIdentity()));
514            }
515        }
516        catch (NotUniqueUserException e)
517        {
518            return null;
519        }
520        return null;
521    }
522    
523    /**
524     * Returns the list of the current user's groups.
525     * @return the list of the current user's groups. Can be null if there is no connected user.
526     */
527    public static NodeList groups()
528    {
529        UserIdentity userIdentity = _currentUserProvider.getUser();
530        return userIdentity != null ? groups(userIdentity.getLogin(), userIdentity.getPopulationId()) : null;
531    }
532    
533    /**
534     * Returns the of the given user's group.
535     * @param userIdentity the concerned user's login + population
536     * @return the of the given user's group.
537     */
538    public static NodeList groups(String userIdentity)
539    {
540        UserIdentity userIdentityObject = UserIdentity.stringToUserIdentity(userIdentity);
541        if (userIdentityObject == null)
542        {
543            return null;
544        }
545        else
546        {
547            return groups(userIdentityObject.getLogin(), userIdentityObject.getPopulationId());
548        }
549    }
550    
551    /**
552     * Returns the of the given user's group.
553     * @param login the concerned user's login.
554     * @param populationId the concerned user's population.
555     * @return the of the given user's group.
556     */
557    public static NodeList groups(String login, String populationId)
558    {
559        ArrayList<Node> groups = new ArrayList<>();
560        
561        Set<GroupIdentity> userGroups = _groupManager.getUserGroups(new UserIdentity(login, populationId));
562        for (GroupIdentity groupId : userGroups)
563        {
564            Group group = _groupManager.getGroup(groupId);
565            if (group != null)
566            {
567                Map<String, String> attributes = new HashMap<>();
568                attributes.put("name", groupId.getId());
569                attributes.put("directory", groupId.getDirectoryId());
570                groups.add(new StringElement("group", attributes, group.getLabel()));
571            }
572        }
573        
574        return new AmetysNodeList(groups);
575    }
576
577    /**
578     * Parse a JSON string as a Map and return the value with the desired key
579     * @param jsonString the JSON representation of the object.
580     * @param key name of the value to return
581     * @return the value as a String, or empty string if an error occurred or the key was not found.
582     */
583    public static String getValueFromJsonObject(String jsonString, String key)
584    {
585        try
586        {
587            Map<String, Object> jsonMap = _jsonUtils.convertJsonToMap(jsonString);
588            if (jsonMap.containsKey(key))
589            {
590                Object value = jsonMap.get(key);
591                if (value instanceof Map || value instanceof Collection)
592                {
593                    _LOGGER.warn("Unable to get string value for key '{}' from json object {}: the value can not be a map nor collection", key, jsonString);
594                }
595                else if (value instanceof Date)
596                {
597                    return DateUtils.dateToString((Date) value);
598                }
599                else
600                {
601                    return value.toString();
602                }
603            }
604        }
605        catch (Exception e)
606        {
607            _LOGGER.warn("Unable to parse json object {}", jsonString, e);
608        }
609        
610        return "";
611    }
612    
613    /**
614     * Determines if the current logged user has right on a String context
615     * @param rightId The id of right
616     * @param objectCtx the context. Can be null to search on any context.
617     * @return true if the current user is allowed, false otherwise
618     */
619    public static boolean hasRight(String rightId, String objectCtx)
620    {
621        return _rightManager.currentUserHasRight(rightId, objectCtx) == RightResult.RIGHT_ALLOW;
622    }
623
624    /**
625     * Check if a given plugin is enabled
626     * @param plugin the plugin's name.
627     * @return the plugin is enabled
628     */
629    public static boolean isPluginEnabled(String plugin)
630    {
631        return PluginsManager.getInstance().getPluginNames().contains(plugin);
632    }
633}