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