001/*
002 *  Copyright 2010 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.cms.clientsideelement;
017
018import java.util.ArrayList;
019import java.util.Arrays;
020import java.util.HashMap;
021import java.util.List;
022import java.util.Map;
023import java.util.Set;
024
025import org.apache.avalon.framework.context.Context;
026import org.apache.avalon.framework.context.ContextException;
027import org.apache.avalon.framework.context.Contextualizable;
028import org.apache.avalon.framework.service.ServiceException;
029import org.apache.avalon.framework.service.ServiceManager;
030import org.apache.commons.lang.StringUtils;
031
032import org.ametys.cms.repository.Content;
033import org.ametys.cms.repository.ModifiableContent;
034import org.ametys.cms.repository.WorkflowAwareContent;
035import org.ametys.cms.workflow.AbstractContentWorkflowComponent;
036import org.ametys.core.right.RightManager.RightResult;
037import org.ametys.core.ui.Callable;
038import org.ametys.core.ui.StaticClientSideElement;
039import org.ametys.core.user.CurrentUserProvider;
040import org.ametys.core.user.User;
041import org.ametys.core.user.UserIdentity;
042import org.ametys.core.user.UserManager;
043import org.ametys.plugins.repository.AmetysObjectResolver;
044import org.ametys.plugins.repository.lock.LockHelper;
045import org.ametys.plugins.repository.lock.LockableAmetysObject;
046import org.ametys.plugins.workflow.AbstractWorkflowComponent;
047import org.ametys.plugins.workflow.support.WorkflowProvider;
048import org.ametys.plugins.workflow.support.WorkflowProvider.AmetysObjectWorkflow;
049import org.ametys.runtime.i18n.I18nizableText;
050
051import com.opensymphony.workflow.spi.Step;
052
053/**
054 * Edit HMI item
055 */
056public class SmartContentClientSideElement extends StaticClientSideElement implements Contextualizable
057{
058    /** Runtime users manager */
059    protected UserManager _userManager;
060    /** Workflow provider */
061    protected WorkflowProvider _workflowProvider;
062    /** Ametys object resolver */
063    protected AmetysObjectResolver _resolver;
064    /** The current user provider */
065    protected CurrentUserProvider _userProvider;
066    /** The context */
067    protected Context _context;
068    
069    @Override
070    public void contextualize(Context context) throws ContextException
071    {
072        _context = context;
073    }
074    
075    @Override
076    public void service(ServiceManager manager) throws ServiceException
077    {
078        super.service(manager);
079        _userManager = (UserManager) manager.lookup(UserManager.ROLE);
080        _workflowProvider = (WorkflowProvider) manager.lookup(WorkflowProvider.ROLE);
081        _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
082        _userProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE);
083    }
084    
085    /**
086     * Get informations on contents' state
087     * @param contentsId the ids of contents
088     * @return informations on contents' state
089     */
090    @Callable
091    public Map<String, Object> getStatus(List<String> contentsId)
092    {
093        Map<String, Object> results = new HashMap<>();
094        
095        results.put("unmodifiable-contents", new ArrayList<Map<String, Object>>());
096        results.put("locked-contents", new ArrayList<Map<String, Object>>());
097        results.put("noright-contents", new ArrayList<Map<String, Object>>());
098        results.put("invalidworkflowaction-contents", new ArrayList<Map<String, Object>>());
099        results.put("invalidworkflowstep-contents", new ArrayList<Map<String, Object>>());
100        results.put("allright-contents", new ArrayList<Map<String, Object>>());
101        
102        for (String contentId : contentsId)
103        {
104            Content content = _resolver.resolveById(contentId);
105            
106            boolean error = false;
107            
108            if (content instanceof WorkflowAwareContent)
109            {
110                // Is modifiable
111                String enabledOnModifiableOnly = (String) this._script.getParameters().get("enabled-on-modifiable-only"); 
112                if ("true".equals(enabledOnModifiableOnly) && !_isModifiable(content))
113                {
114                    Map<String, Object> contentParams = getContentDefaultParameters (content);
115                    contentParams.put("description", _getNoModifiableDescription(content));
116                    
117                    @SuppressWarnings("unchecked")
118                    List<Map<String, Object>> unModifiableContents = (List<Map<String, Object>>) results.get("unmodifiable-contents");
119                    unModifiableContents.add(contentParams);
120
121                    error = true;
122                }
123                
124                // Is locked
125                String enabledOnUnlockOnly = (String) this._script.getParameters().get("enabled-on-unlock-only"); 
126                if ("true".equals(enabledOnUnlockOnly) && _isLocked(content))
127                {
128                    Map<String, Object> contentParams = getContentDefaultParameters (content);
129                    contentParams.put("description", _getLockedDescription(content));
130                    
131                    @SuppressWarnings("unchecked")
132                    List<Map<String, Object>> lockedContents = (List<Map<String, Object>>) results.get("locked-contents");
133                    lockedContents.add(contentParams);
134                    
135                    error = true;
136                }
137                
138                // Has right correct
139                String enabledOnRightOnly = (String) this._script.getParameters().get("enabled-on-right-only"); 
140                if ("true".equals(enabledOnRightOnly) && !_hasRight(content))
141                {
142                    Map<String, Object> contentParams = getContentDefaultParameters (content);
143                    contentParams.put("description", _getNoRightDescription (content));
144                    
145                    @SuppressWarnings("unchecked")
146                    List<Map<String, Object>> norightContents = (List<Map<String, Object>>) results.get("noright-contents");
147                    norightContents.add(contentParams);
148                    
149                    error = true;
150                }
151                
152                // Is workflow action correct
153                String enabledOnWorkflowActionOnly = (String) this._script.getParameters().get("enabled-on-workflow-action-only"); 
154                if (enabledOnWorkflowActionOnly != null)
155                {
156                    int actionId = _workflowAction(content);
157                    if (actionId == -1)
158                    {
159                        Map<String, Object> contentParams = getContentDefaultParameters (content);
160                        contentParams.put("description", _getWorkflowActionUnvailableDescription(content));
161                        
162                        @SuppressWarnings("unchecked")
163                        List<Map<String, Object>> invalidActionContents = (List<Map<String, Object>>) results.get("invalidworkflowaction-contents");
164                        invalidActionContents.add(contentParams);
165                        
166                        error = true;
167                    }
168                    else
169                    {
170                        results.put("workflowaction-content-actionId", actionId);
171                    }
172                }
173                
174                // Is workflow step correct
175                String enabledOnWorkflowStepOnly = (String) this._script.getParameters().get("enabled-on-workflow-step-only"); 
176                if (enabledOnWorkflowStepOnly != null && !_isWorkflowStepCorrect(content))
177                {
178                    Map<String, Object> contentParams = getContentDefaultParameters (content);
179                    contentParams.put("description", _getIncorrectWorkflowStepDescription(content));
180                    
181                    @SuppressWarnings("unchecked")
182                    List<Map<String, Object>> invalidStepContents = (List<Map<String, Object>>) results.get("invalidworkflowstep-contents");
183                    invalidStepContents.add(contentParams);
184                    
185                    error = true;
186                }
187                
188                if (_isAllRight (content, error, results))
189                {
190                    Map<String, Object> contentParams = getContentDefaultParameters (content);
191                    contentParams.put("description", _getAllRightDescription(content));
192                    
193                    @SuppressWarnings("unchecked")
194                    List<Map<String, Object>> allrightContents = (List<Map<String, Object>>) results.get("allright-contents");
195                    allrightContents.add(contentParams);
196                }
197            }
198        }
199        
200        return results;
201    }
202    
203    /**
204     * Determines if the user can finally do action on content
205     * @param content The content
206     * @param hasError true if a error has already occurs
207     * @param results The result parameters to be passed to client side
208     * @return true if the user can finally do action on content
209     */
210    protected boolean _isAllRight (Content content, boolean hasError, Map<String, Object> results)
211    {
212        return !hasError;
213    }
214    
215    
216    /**
217     * Get the default content's parameters
218     * @param content The content
219     * @return The default parameters
220     */
221    protected Map<String, Object> getContentDefaultParameters (Content content)
222    {
223        Map<String, Object> contentParams = new HashMap<>();
224        contentParams.put("id", content.getId());
225        contentParams.put("title", content.getTitle());
226        
227        return contentParams;
228    }
229    
230    /**
231     * Determines if the content is locked
232     * @param content the content
233     * @return true if the content is locked
234     */
235    protected boolean _isLocked(Content content)
236    {
237        if (content instanceof LockableAmetysObject)
238        {
239            LockableAmetysObject lockableContent = (LockableAmetysObject) content;
240            if (lockableContent.isLocked() && !LockHelper.isLockOwner(lockableContent, _currentUserProvider.getUser()))
241            {
242                return true;
243            }
244        }
245        
246        return false;
247    }
248    
249    /**
250     * Determines if the content is modifiable
251     * @param content the content
252     * @return true if the content is modifiable
253     */
254    protected boolean _isModifiable(Content content)
255    {
256        return content instanceof ModifiableContent;
257    }
258    
259    /**
260     * Determines if the user has sufficient right for the given content
261     * @param content the content
262     * @return true if user has sufficient right
263     */
264    protected boolean _hasRight (Content content)
265    {
266        if (_rights.isEmpty())
267        {
268            return true;
269        }
270        
271        Set<String> rightsToCheck = _rights.keySet();
272        for (String rightToCheck : rightsToCheck)
273        {
274            if (StringUtils.isNotEmpty(rightToCheck))
275            {
276                if (_rightManager.hasRight(_userProvider.getUser(), rightToCheck, content) == RightResult.RIGHT_ALLOW)
277                {
278                    return true;
279                }
280            }
281        }
282        
283        return false;
284    }
285    
286    /**
287     * Determines if the workflow action is correct
288     * @param content the content
289     * @return true if the workflow action is incorrect
290     */
291    protected int _workflowAction(Content content)
292    {
293        WorkflowAwareContent waContent = (WorkflowAwareContent) content;
294        AmetysObjectWorkflow workflow = _workflowProvider.getAmetysObjectWorkflow(waContent);
295        long wId = waContent.getWorkflowId();
296        
297        Map<String, Object> vars = new HashMap<>();
298        vars.put(AbstractContentWorkflowComponent.CONTENT_KEY, content);
299        vars.put(AbstractWorkflowComponent.FAIL_CONDITIONS_KEY, new ArrayList<String>());
300        int[] actionIds = workflow.getAvailableActions(wId, vars);
301        Arrays.sort(actionIds);
302        
303        String correctActionsAsString = this._script.getParameters().get("enabled-on-workflow-action-only").toString();
304        String[] correctActionsAsStringArray = correctActionsAsString.split(", ?");
305        
306        int correctActionId = -1;
307        for (String correctActionAsString : correctActionsAsStringArray)
308        {
309            int actionId = Integer.parseInt(correctActionAsString);
310            if (!StringUtils.isEmpty(correctActionAsString) && Arrays.binarySearch(actionIds, actionId) >= 0)
311            {
312                correctActionId = actionId;
313                break;
314            }
315        }
316        
317        return correctActionId;
318    }
319    
320
321    /**
322     * Determines if the workflow step is correct
323     * @param content the content
324     * @return true if the workflow step is incorrect
325     */
326    protected boolean _isWorkflowStepCorrect(Content content)
327    {
328        WorkflowAwareContent waContent = (WorkflowAwareContent) content;
329        AmetysObjectWorkflow workflow = _workflowProvider.getAmetysObjectWorkflow(waContent);
330        long wId = waContent.getWorkflowId();
331        
332        List<Step> steps = workflow.getCurrentSteps(wId);
333        
334        List<Integer> stepIds = new ArrayList<>();
335        for (Step step : steps)
336        {
337            stepIds.add(step.getStepId());
338        }
339
340        String correctStepsAsString = this._script.getParameters().get("enabled-on-workflow-step-only").toString();
341        String[] correctStepsAsStringArray = correctStepsAsString.split(", ?");
342        
343        for (String correctStepAsString : correctStepsAsStringArray)
344        {
345            if (!StringUtils.isEmpty(correctStepAsString) && stepIds.contains(Integer.parseInt(correctStepAsString)))
346            {
347                return true;
348            }
349        }
350        
351        return false;
352    }
353    
354    /**
355     * Get i18n description when user can not do action because he has no right on content
356     * @param content The content
357     * @return The {@link I18nizableText} description
358     */
359    protected I18nizableText _getNoRightDescription (Content content)
360    {
361        List<String> rightI18nParameters = new ArrayList<>();
362        rightI18nParameters.add(content.getTitle());
363        
364        I18nizableText ed = (I18nizableText) this._script.getParameters().get("noright-content-description");
365        return  new I18nizableText(ed.getCatalogue(), ed.getKey(), rightI18nParameters);
366    }
367    
368    /**
369     * Get i18n description when user can not do action because the content is locked
370     * @param content The content
371     * @return The {@link I18nizableText} description
372     */
373    protected I18nizableText _getLockedDescription (Content content)
374    {
375        UserIdentity currentLockerIdentity = ((LockableAmetysObject) content).getLockOwner();
376        User currentLocker = currentLockerIdentity != null ? _userManager.getUser(currentLockerIdentity.getPopulationId(), currentLockerIdentity.getLogin()) : null;
377        
378        List<String> lockI18nParameters = new ArrayList<>();
379        lockI18nParameters.add(content.getTitle());
380        lockI18nParameters.add(currentLocker != null ? currentLocker.getFullName() : "");
381        lockI18nParameters.add(currentLockerIdentity != null ? currentLockerIdentity.getLogin() : "Anonymous");
382        
383        I18nizableText ed = (I18nizableText) this._script.getParameters().get("locked-content-description");
384        return new I18nizableText(ed.getCatalogue(), ed.getKey(), lockI18nParameters);
385    }
386    
387    /**
388     * Get i18n description when user can not do action because there is no workflow action unvailable
389     * @param content The content
390     * @return The {@link I18nizableText} description
391     */
392    protected I18nizableText _getWorkflowActionUnvailableDescription (Content content)
393    {
394        List<String> workflowI18nParameters = new ArrayList<>();
395        workflowI18nParameters.add(content.getTitle());
396        
397        I18nizableText ed = (I18nizableText) this._script.getParameters().get("workflowaction-content-description");
398        return new I18nizableText(ed.getCatalogue(), ed.getKey(), workflowI18nParameters);
399    }
400    
401    /**
402     * Get i18n description when user can not do action because the workflow step is incorrect
403     * @param content The content
404     * @return The {@link I18nizableText} description
405     */
406    protected I18nizableText _getIncorrectWorkflowStepDescription (Content content)
407    {
408        List<String> workflowI18nParameters = new ArrayList<>();
409        workflowI18nParameters.add(content.getTitle());
410        
411        I18nizableText ed = (I18nizableText) this._script.getParameters().get("workflowstep-content-description");
412        return new I18nizableText(ed.getCatalogue(), ed.getKey(), workflowI18nParameters);
413    }
414    
415    /**
416     * Get i18n description when user can not do action because the content is not modifiable
417     * @param content The content
418     * @return The {@link I18nizableText} description
419     */
420    protected I18nizableText _getNoModifiableDescription (Content content)
421    {
422        List<String> modifiableI18nParameters = new ArrayList<>();
423        modifiableI18nParameters.add(content.getTitle());
424        
425        I18nizableText ed = (I18nizableText) this._script.getParameters().get("nomodifiable-content-description");
426        return new I18nizableText(ed.getCatalogue(), ed.getKey(), modifiableI18nParameters);
427    }
428    
429    /**
430     * Get i18n description when user can do action
431     * @param content The content
432     * @return The {@link I18nizableText} description
433     */
434    protected I18nizableText _getAllRightDescription (Content content)
435    {
436        List<String> allrightI18nParameters = new ArrayList<>();
437        allrightI18nParameters.add(content.getTitle());
438        
439        I18nizableText ed = (I18nizableText) this._script.getParameters().get("allright-content-description");
440        return new I18nizableText(ed.getCatalogue(), ed.getKey(), allrightI18nParameters);
441    }
442}