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