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