001/*
002 *  Copyright 2016 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.userdirectory.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;
024import java.util.stream.Collectors;
025
026import javax.jcr.Node;
027import javax.jcr.RepositoryException;
028import javax.jcr.Value;
029
030import org.apache.avalon.framework.service.ServiceException;
031import org.apache.avalon.framework.service.ServiceManager;
032import org.apache.commons.lang3.StringUtils;
033import org.apache.jackrabbit.value.StringValue;
034
035import org.ametys.cms.contenttype.ContentType;
036import org.ametys.cms.contenttype.ContentTypeExtensionPoint;
037import org.ametys.core.observation.Event;
038import org.ametys.core.observation.ObservationManager;
039import org.ametys.core.ui.Callable;
040import org.ametys.core.util.LambdaUtils;
041import org.ametys.plugins.repository.AmetysObjectResolver;
042import org.ametys.plugins.repository.jcr.JCRAmetysObject;
043import org.ametys.plugins.userdirectory.UserDirectoryPageHandler;
044import org.ametys.plugins.userdirectory.observation.ObservationConstants;
045import org.ametys.plugins.userdirectory.page.VirtualUserDirectoryPageFactory;
046import org.ametys.runtime.i18n.I18nizableTextParameter;
047import org.ametys.runtime.i18n.I18nizableText;
048import org.ametys.web.clientsideelement.AbstractPageClientSideElement;
049import org.ametys.web.repository.page.ModifiablePage;
050import org.ametys.web.repository.page.Page;
051
052/**
053 * Client side element for a controller wich set/remove the root page of a user directory.
054 */
055public class SetUserDirectoryRootClientSideElement extends AbstractPageClientSideElement
056{
057    /** Observer manager. */
058    protected ObservationManager _observationManager;
059    /** The extension point for content types */
060    protected ContentTypeExtensionPoint _contentTypeEP;
061    /** The User Directory page handler */
062    protected UserDirectoryPageHandler _userDirectoryPageHandler;
063
064    @Override
065    public void service(ServiceManager smanager) throws ServiceException
066    {
067        super.service(smanager);
068        _observationManager = (ObservationManager) smanager.lookup(ObservationManager.ROLE);
069        _contentTypeEP = (ContentTypeExtensionPoint) smanager.lookup(ContentTypeExtensionPoint.ROLE);
070        _userDirectoryPageHandler = (UserDirectoryPageHandler) smanager.lookup(UserDirectoryPageHandler.ROLE);
071    }
072    
073    /**
074     * Gets the status of the given page
075     * @param pageId The page id
076     * @return the status of the given page
077     */
078    @Callable
079    public Map<String, Object> getStatus(String pageId)
080    {
081        Map<String, Object> result = new HashMap<>();
082        
083        Map<String, Object> parameters = this._script.getParameters();
084        
085        Page page = _resolver.resolveById(pageId);
086
087        if (page instanceof JCRAmetysObject)
088        {
089            if (_isUserDirectoryRootPage((JCRAmetysObject) page))
090            {
091                List<String> i18nParameters = new ArrayList<>();
092                i18nParameters.add(page.getTitle());
093    
094                I18nizableText ed = (I18nizableText) parameters.get("user-directory-page-description");
095                I18nizableText msg = new I18nizableText(ed.getCatalogue(), ed.getKey(), i18nParameters);
096                result.put("user-directory-page-title", msg);
097                
098                ed = (I18nizableText) parameters.get("remove-user-directory-page-description");
099                msg = new I18nizableText(ed.getCatalogue(), ed.getKey(), i18nParameters);
100                result.put("remove-user-directory-page-title", msg);
101                
102                String contentTypeId = page.getValue(UserDirectoryPageHandler.CONTENT_TYPE_DATA_NAME, StringUtils.EMPTY);
103                
104                if (StringUtils.isNotEmpty(contentTypeId))
105                {
106                    I18nizableText contentTypeText = _contentTypeEP.hasExtension(contentTypeId) ? _contentTypeEP.getExtension(contentTypeId).getLabel() : new I18nizableText(contentTypeId);
107                    
108                    Map<String, I18nizableTextParameter> contentTypeI18nParameters = new HashMap<>();
109                    contentTypeI18nParameters.put("0", contentTypeText);
110                    
111                    ed = (I18nizableText) parameters.get("contenttype-user-directory-page-description");
112                    msg = new I18nizableText(ed.getCatalogue(), ed.getKey(), contentTypeI18nParameters);
113                    result.put("contenttype-user-directory-page-description", msg);
114                }
115                
116                result.put("user-directory-page-id", new I18nizableText(page.getId()));
117                
118                I18nizableText defaultDirectoryDeep = (I18nizableText) parameters.get("default-depth");
119                long depthData = page.getValue(UserDirectoryPageHandler.DEPTH_DATA_NAME, -1L);
120                I18nizableText depth = depthData == -1 ? defaultDirectoryDeep : new I18nizableText(String.valueOf(depthData));
121                result.put("depth", depth);
122            }
123            else
124            {
125                List<String> i18nParameters = new ArrayList<>();
126                i18nParameters.add(page.getTitle());
127    
128                I18nizableText ed = (I18nizableText) parameters.get("add-user-directory-page-description");
129                I18nizableText msg = new I18nizableText(ed.getCatalogue(), ed.getKey(), i18nParameters);
130                
131                result.put("add-user-directory-page-id", new I18nizableText(page.getId()));
132                result.put("add-user-directory-page-title", msg);
133            }
134        }
135        else
136        {
137            List<String> noJcrI18nParameters = new ArrayList<>();
138            noJcrI18nParameters.add(page.getTitle());
139
140            I18nizableText ed = (I18nizableText) parameters.get("no-jcr-page-description");
141            I18nizableText msg = new I18nizableText(ed.getCatalogue(), ed.getKey(), noJcrI18nParameters);
142            
143            result.put("no-jcr-page-id", new I18nizableText(page.getId()));
144            result.put("no-jcr-page-title", msg);
145        }
146        
147        return result;
148    }
149    
150    private boolean _isUserDirectoryRootPage (JCRAmetysObject jcrPage)
151    {
152        try
153        {
154            Node node = jcrPage.getNode();
155            
156            if (node.hasProperty(AmetysObjectResolver.VIRTUAL_PROPERTY))
157            {
158                List<Value> values = Arrays.asList(node.getProperty(AmetysObjectResolver.VIRTUAL_PROPERTY).getValues());
159                
160                return values.stream()
161                        .map(LambdaUtils.wrap(Value::getString))
162                        .anyMatch(v -> VirtualUserDirectoryPageFactory.class.getName().equals(v));
163            }
164            else
165            {
166                return false;
167            }
168        }
169        catch (RepositoryException e)
170        {
171            return false;
172        }
173    }
174    
175    /**
176     * Gets the content types which can build a user directory
177     * @param pageId The id of the page being edited
178     * @return the content types which can build a user directory
179     */
180    @Callable
181    public List<Map<String, Object>> getSupportedContentTypes(String pageId)
182    {
183        List<Map<String, Object>> result = new ArrayList<>();
184        Page page = _resolver.resolveById(pageId);
185        
186        for (String contentTypeId : _contentTypeEP.getSubTypes(UserDirectoryPageHandler.ABSTRACT_USER_CONTENT_TYPE))
187        {
188            ContentType contentType = _contentTypeEP.getExtension(contentTypeId);
189            Page userDirectoryRootPage = _userDirectoryPageHandler.getUserDirectoryRootPage(page.getSiteName(), page.getSitemapName(), contentTypeId);
190            if (!contentType.isAbstract() && (userDirectoryRootPage == null || userDirectoryRootPage.equals(page)))
191            {
192                // The content type is not already a root of a user directory or is the root of the currently edited page
193                Map<String, Object> entry = new HashMap<>();
194                entry.put("value", contentType.getId());
195                entry.put("text", contentType.getLabel());
196                result.add(entry);
197            }
198        }
199        
200        return result;
201    }
202    
203    /**
204     * Sets the given page as the root of a user directory
205     * @param pageId The id of the page
206     * @param contentType The id of the content type
207     * @param viewName The view name for users rendering
208     * @param attribute The classification attribute
209     * @param depth The depth of the tree structure
210     * @return A result map
211     * @throws RepositoryException if a repository error occurred
212     */
213    @Callable
214    public Map<String, Object> setUserDirectoryRoot(String pageId, String contentType, String viewName, String attribute, int depth) throws RepositoryException
215    {
216        Map<String, Object> result = new HashMap<>();
217        if (!_contentTypeEP.isDescendant(contentType, UserDirectoryPageHandler.ABSTRACT_USER_CONTENT_TYPE))
218        {
219            result.put("error", "invalid-content-type");
220            return result;
221        }
222        
223        Page page = _resolver.resolveById(pageId);
224        String oldContentType = page.getValue(UserDirectoryPageHandler.CONTENT_TYPE_DATA_NAME, StringUtils.EMPTY);
225        String oldAttribute = page.getValue(UserDirectoryPageHandler.CLASSIFICATION_ATTRIBUTE_DATA_NAME, StringUtils.EMPTY);
226        String oldViewName = page.getValue(UserDirectoryPageHandler.USER_VIEW_NAME, StringUtils.EMPTY);
227        long oldDepth = page.getValue(UserDirectoryPageHandler.DEPTH_DATA_NAME, -1L);
228        
229        // Do nothing if page attribute are the same
230        if (!oldContentType.equals(contentType) || !oldAttribute.equals(attribute) || oldDepth != depth || !oldViewName.equals(viewName))
231        {
232            Set<Page> currentUserDirectoryPages = _userDirectoryPageHandler.getUserDirectoryRootPages(page.getSiteName(), page.getSitemapName());
233            
234            Map<String, Object> eventParams = new HashMap<>();
235            eventParams.put(org.ametys.web.ObservationConstants.ARGS_PAGE, page);
236            
237            if (currentUserDirectoryPages.contains(page))
238            {
239                eventParams.put(ObservationConstants.ARGS_USER_CONTENT_VIEW_UPDATED, !oldViewName.equals(viewName));
240                
241                // Unindex pages for all workspaces before the properties changed 
242                _observationManager.notify(new Event(ObservationConstants.EVENT_USER_DIRECTORY_ROOT_UPDATING, _currentUserProvider.getUser(), eventParams));
243                
244                _updateUserDirectoryRootProperty(page, contentType, viewName, attribute, depth);
245            }
246            else
247            {
248                _addUserDirectoryRootProperty(page, contentType, viewName, attribute, depth);
249            }
250            
251            _userDirectoryPageHandler.clearCache(page);
252            
253            // Live synchronization
254            _notifyPageUpdated(page);
255            
256            // Indexation
257            _observationManager.notify(new Event(ObservationConstants.EVENT_USER_DIRECTORY_ROOT_UPDATED, _currentUserProvider.getUser(), eventParams));
258        }
259        
260        return result;
261    }
262    
263    /**
264     * Remove the user directory root status to the given page
265     * @param pageId The id of the page
266     * @return A result map
267     * @throws RepositoryException if a repository error occured
268     */
269    @Callable
270    public Map<String, Object> removeUserDirectoryRoot(String pageId) throws RepositoryException
271    {
272        Map<String, Object> result = new HashMap<>();
273        
274        Page page = _resolver.resolveById(pageId);
275        
276        if (page instanceof JCRAmetysObject)
277        {
278            if (!_isUserDirectoryRootPage((JCRAmetysObject) page))
279            {
280                result.put("error", "no-root");
281                return result;
282            }
283            
284            Map<String, Object> eventParams = new HashMap<>();
285            eventParams.put(org.ametys.web.ObservationConstants.ARGS_PAGE, page);
286            
287            // Unindex pages for all workspaces before the properties were removed 
288            _observationManager.notify(new Event(ObservationConstants.EVENT_USER_DIRECTORY_ROOT_DELETING, _currentUserProvider.getUser(), eventParams));
289            
290            _userDirectoryPageHandler.clearCache(page);
291            _removeUserDirectoryRootProperty(page);
292            
293            _notifyPageUpdated(page);
294            
295            // After live synchronization
296            _observationManager.notify(new Event(ObservationConstants.EVENT_USER_DIRECTORY_ROOT_DELETED, _currentUserProvider.getUser(), eventParams));
297        }
298        else
299        {
300            result.put("error", "no-root");
301        }
302        return result;
303    }
304    
305    /**
306     * Gets information about user directory root status on the given.
307     * @param pageId The id of the page
308     * @return information about user directory root status on the given.
309     */
310    @Callable
311    public Map<String, Object> getRootPageInfo(String pageId)
312    {
313        Map<String, Object> result = new HashMap<>();
314        
315        Page page = _resolver.resolveById(pageId);
316        Set<Page> currentUserDirectoryPages = _userDirectoryPageHandler.getUserDirectoryRootPages(page.getSiteName(), page.getSitemapName());
317        
318        if (currentUserDirectoryPages.contains(page))
319        {
320            result.put("isRoot", true);
321            result.put("contentType", page.getValue(UserDirectoryPageHandler.CONTENT_TYPE_DATA_NAME));
322            result.put("viewName", page.getValue(UserDirectoryPageHandler.USER_VIEW_NAME, StringUtils.EMPTY));
323            result.put("metadata", page.getValue(UserDirectoryPageHandler.CLASSIFICATION_ATTRIBUTE_DATA_NAME));
324            result.put("depth", page.getValue(UserDirectoryPageHandler.DEPTH_DATA_NAME));
325        }
326        else
327        {
328            result.put("isRoot", false);
329        }
330        
331        return result;
332    }
333    
334    private void _addUserDirectoryRootProperty(Page page, String contentType, String viewName, String attribute, int depth) throws RepositoryException
335    {
336        if (page instanceof JCRAmetysObject)
337        {
338            JCRAmetysObject jcrPage = (JCRAmetysObject) page;
339            Node node = jcrPage.getNode();
340            
341            List<Value> values = new ArrayList<>();
342            if (node.hasProperty(AmetysObjectResolver.VIRTUAL_PROPERTY))
343            {
344                values.addAll(Arrays.asList(node.getProperty(AmetysObjectResolver.VIRTUAL_PROPERTY).getValues()));
345            }
346            
347            StringValue virtualUserDirectoryPageFactoryClassName = new StringValue(VirtualUserDirectoryPageFactory.class.getName());
348            if (!values.contains(virtualUserDirectoryPageFactoryClassName))
349            {
350                values.add(virtualUserDirectoryPageFactoryClassName);
351            }
352            
353            node.setProperty(AmetysObjectResolver.VIRTUAL_PROPERTY, values.toArray(new Value[values.size()]));
354
355            // Set the user directory root property
356            if (page instanceof ModifiablePage)
357            {
358                ModifiablePage modifiablePage = (ModifiablePage) page;
359                modifiablePage.setValue(UserDirectoryPageHandler.CONTENT_TYPE_DATA_NAME, contentType);
360                modifiablePage.setValue(UserDirectoryPageHandler.USER_VIEW_NAME, viewName);
361                modifiablePage.setValue(UserDirectoryPageHandler.CLASSIFICATION_ATTRIBUTE_DATA_NAME, attribute);
362                modifiablePage.setValue(UserDirectoryPageHandler.DEPTH_DATA_NAME, depth);
363            }
364            
365            jcrPage.saveChanges();
366        }
367    }
368    
369    private void _updateUserDirectoryRootProperty(Page page, String contentType, String viewName, String attribute, int depth)
370    {
371        if (page instanceof ModifiablePage)
372        {
373            ModifiablePage modifiablePage = (ModifiablePage) page;
374            
375            // Set the user directory root property
376            modifiablePage.setValue(UserDirectoryPageHandler.CONTENT_TYPE_DATA_NAME, contentType);
377            modifiablePage.setValue(UserDirectoryPageHandler.USER_VIEW_NAME, viewName);
378            modifiablePage.setValue(UserDirectoryPageHandler.CLASSIFICATION_ATTRIBUTE_DATA_NAME, attribute);
379            modifiablePage.setValue(UserDirectoryPageHandler.DEPTH_DATA_NAME, depth);
380            
381            modifiablePage.saveChanges();
382        }
383    }
384    
385    private void _removeUserDirectoryRootProperty(Page page) throws RepositoryException
386    {
387        if (page instanceof JCRAmetysObject)
388        {
389            JCRAmetysObject jcrPage = (JCRAmetysObject) page;
390            Node node = jcrPage.getNode();
391            
392            if (node.hasProperty(AmetysObjectResolver.VIRTUAL_PROPERTY))
393            {
394                List<Value> values = new ArrayList<>(Arrays.asList(node.getProperty(AmetysObjectResolver.VIRTUAL_PROPERTY).getValues()));
395                int index = values.stream()
396                        .map(LambdaUtils.wrap(Value::getString))
397                        .collect(Collectors.toList())
398                        .indexOf(VirtualUserDirectoryPageFactory.class.getName());
399                if (index != -1)
400                {
401                    values.remove(index);
402                    node.setProperty(AmetysObjectResolver.VIRTUAL_PROPERTY, values.toArray(new Value[values.size()]));
403                }
404
405                // Remove the user directory root property
406                if (page instanceof ModifiablePage)
407                {
408                    ModifiablePage modifiablePage = (ModifiablePage) page;
409                    modifiablePage.removeValue(UserDirectoryPageHandler.CONTENT_TYPE_DATA_NAME);
410                    modifiablePage.removeValue(UserDirectoryPageHandler.USER_VIEW_NAME);
411                    modifiablePage.removeValue(UserDirectoryPageHandler.CLASSIFICATION_ATTRIBUTE_DATA_NAME);
412                    modifiablePage.removeValue(UserDirectoryPageHandler.DEPTH_DATA_NAME);
413                }
414                
415                jcrPage.saveChanges();
416            }
417        }
418    }
419    
420    private void _notifyPageUpdated(Page page)
421    {
422        Map<String, Object> eventParams = new HashMap<>();
423        eventParams.put(org.ametys.web.ObservationConstants.ARGS_PAGE, page);
424        _observationManager.notify(new Event(org.ametys.web.ObservationConstants.EVENT_PAGE_CHANGED, _currentUserProvider.getUser(), eventParams));
425    }
426}