001/*
002 *  Copyright 2018 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.ugc.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.ugc.observation.ObservationConstants;
044import org.ametys.plugins.ugc.page.UGCPageHandler;
045import org.ametys.plugins.ugc.page.VirtualUGCPageFactory;
046import org.ametys.runtime.i18n.I18nizableText;
047import org.ametys.runtime.i18n.I18nizableTextParameter;
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 which set/remove the root page of a ugc.
054 */
055public class SetUGCRootClientSideElement extends AbstractPageClientSideElement
056{
057    /** Observer manager. */
058    protected ObservationManager _observationManager;
059    /** The extension point for content types */
060    protected ContentTypeExtensionPoint _contentTypeEP;
061    /** The UGC page handler */
062    protected UGCPageHandler _ugcPageHandler;
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        _ugcPageHandler = (UGCPageHandler) smanager.lookup(UGCPageHandler.ROLE);
071        
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 (_isUGCRootPage((JCRAmetysObject) page))
091            {
092                List<String> i18nParameters = new ArrayList<>();
093                i18nParameters.add(page.getTitle());
094    
095                I18nizableText ed = (I18nizableText) parameters.get("ugc-page-description");
096                I18nizableText msg = new I18nizableText(ed.getCatalogue(), ed.getKey(), i18nParameters);
097                result.put("ugc-page-title", msg);
098                
099                ed = (I18nizableText) parameters.get("remove-ugc-page-description");
100                msg = new I18nizableText(ed.getCatalogue(), ed.getKey(), i18nParameters);
101                result.put("remove-ugc-page-title", msg);
102                
103                String contentTypeId = page.getValue(UGCPageHandler.CONTENT_TYPE_DATA_NAME, StringUtils.EMPTY);
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-ugc-page-description");
112                    msg = new I18nizableText(ed.getCatalogue(), ed.getKey(), contentTypeI18nParameters);
113                    result.put("contenttype-ugc-page-description", msg);
114                }
115                
116                result.put("ugc-page-id", new I18nizableText(page.getId()));
117            }
118            else if (!_ugcPageHandler.getUGCRootPages(page.getSiteName(), page.getSitemapName()).isEmpty())
119            {
120                I18nizableText ed = (I18nizableText) parameters.get("ugc-page-already-exist");
121                I18nizableText msg = new I18nizableText(ed.getCatalogue(), ed.getKey());
122                result.put("ugc-page-already-exist", msg);
123            }
124            else
125            {
126                List<String> i18nParameters = new ArrayList<>();
127                i18nParameters.add(page.getTitle());
128    
129                I18nizableText ed = (I18nizableText) parameters.get("add-ugc-page-description");
130                I18nizableText msg = new I18nizableText(ed.getCatalogue(), ed.getKey(), i18nParameters);
131                
132                result.put("add-ugc-page-id", new I18nizableText(page.getId()));
133                result.put("add-ugc-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    /**
152     * Sets the given page as the root of a ugc
153     * @param pageId The id of the page
154     * @param contentTypeId The id of the content type
155     * @param attributePath The classification attribute path
156     * @param classificationPageVisible the visibility of transitional pages
157     * @return A result map
158     * @throws RepositoryException if a repository error occurred
159     */
160    @Callable
161    public Map<String, Object> setUGCRoot(String pageId, String contentTypeId, String attributePath, boolean classificationPageVisible) throws RepositoryException
162    {
163        Map<String, Object> result = new HashMap<>();
164        
165        ContentType contentType = _contentTypeEP.getExtension(contentTypeId);
166        if (contentType == null)
167        {
168            result.put("error", "wrong-content-type");
169            return result;
170        }
171        else if (StringUtils.isNotBlank(attributePath))
172        {
173            if (!contentType.hasModelItem(attributePath))
174            {
175                result.put("error", "wrong-metadata");
176                return result;
177            }
178        }
179        
180        Page page = _resolver.resolveById(pageId);
181        Page currentUGCPage = _ugcPageHandler.getUGCRootPage(page.getSiteName(), page.getSitemapName(), contentTypeId);
182        
183        Map<String, Object> eventParams = new HashMap<>();
184        eventParams.put(org.ametys.web.ObservationConstants.ARGS_PAGE, page);
185        
186        if (currentUGCPage != null && currentUGCPage.getId().equals(pageId))
187        {
188            // Unindex pages for all workspaces before the properties changed 
189            _observationManager.notify(new Event(ObservationConstants.EVENT_UGC_ROOT_UPDATING, _currentUserProvider.getUser(), eventParams));
190            
191            _updateUGCRootProperty(page, contentTypeId, attributePath, classificationPageVisible);
192        }
193        else
194        {
195            _addUGCRootProperty(page, contentTypeId, attributePath, classificationPageVisible);
196        }
197        
198        // Live synchronization
199        _notifyPageUpdated(page);
200        
201        // Indexation
202        _observationManager.notify(new Event(ObservationConstants.EVENT_UGC_ROOT_UPDATED, _currentUserProvider.getUser(), eventParams));
203        return result;
204    }
205    
206    /**
207     * Remove the ugc root status to the given page
208     * @param pageId The id of the page
209     * @return A result map
210     * @throws RepositoryException if a repository error occured
211     */
212    @Callable
213    public Map<String, Object> removeUGCRoot(String pageId) throws RepositoryException
214    {
215        Map<String, Object> result = new HashMap<>();
216        
217        Page page = _resolver.resolveById(pageId);
218        
219        if (page instanceof JCRAmetysObject)
220        {
221            if (!_isUGCRootPage((JCRAmetysObject) page))
222            {
223                result.put("error", "no-root");
224                return result;
225            }
226            
227            Map<String, Object> eventParams = new HashMap<>();
228            eventParams.put(org.ametys.web.ObservationConstants.ARGS_PAGE, page);
229            
230            // Unindex pages for all workspaces before the properties were removed 
231            _observationManager.notify(new Event(ObservationConstants.EVENT_UGC_ROOT_DELETING, _currentUserProvider.getUser(), eventParams));
232            
233            _removeUGCRootProperty(page);
234            
235            _notifyPageUpdated(page);
236            
237            // After live synchronization
238            _observationManager.notify(new Event(ObservationConstants.EVENT_UGC_ROOT_DELETED, _currentUserProvider.getUser(), eventParams));
239        }
240        else
241        {
242            result.put("error", "no-root");
243        }
244        return result;
245    }
246    
247    /**
248     * Gets information about ugc root status on the given.
249     * @param pageId The id of the page
250     * @return information about ugc root status on the given.
251     */
252    @Callable
253    public Map<String, Object> getRootPageInfo(String pageId)
254    {
255        Map<String, Object> result = new HashMap<>();
256        
257        Page page = _resolver.resolveById(pageId);
258        Set<Page> currentUGCPages = _ugcPageHandler.getUGCRootPages(page.getSiteName(), page.getSitemapName());
259        
260        if (currentUGCPages.contains(page))
261        {
262            result.put("isRoot", true);
263            result.put("contentType", page.getValue(UGCPageHandler.CONTENT_TYPE_DATA_NAME));
264            result.put("metadata", page.getValue(UGCPageHandler.CLASSIFICATION_ATTRIBUTE_DATA_NAME));
265            result.put("is-visible", page.getValue(UGCPageHandler.CLASSIFICATION_PAGE_VISIBLE_DATA_NAME, false));
266        }
267        else
268        {
269            result.put("isRoot", false);
270        }
271        
272        return result;
273    }
274    
275    private void _addUGCRootProperty(Page page, String contentType, String metadata, boolean classificationPageVisible) throws RepositoryException
276    {
277        if (page instanceof JCRAmetysObject)
278        {
279            JCRAmetysObject jcrPage = (JCRAmetysObject) page;
280            Node node = jcrPage.getNode();
281            
282            List<Value> values = new ArrayList<>();
283            if (node.hasProperty(AmetysObjectResolver.VIRTUAL_PROPERTY))
284            {
285                values.addAll(Arrays.asList(node.getProperty(AmetysObjectResolver.VIRTUAL_PROPERTY).getValues()));
286            }
287            
288            StringValue virtualUGCPageFactoryClassName = new StringValue(VirtualUGCPageFactory.class.getName());
289            if (!values.contains(virtualUGCPageFactoryClassName))
290            {
291                values.add(virtualUGCPageFactoryClassName);
292            }
293            
294            node.setProperty(AmetysObjectResolver.VIRTUAL_PROPERTY, values.toArray(new Value[values.size()]));
295
296            // Set the ugc root property
297            if (page instanceof ModifiablePage)
298            {
299                ((ModifiablePage) page).setValue(UGCPageHandler.CONTENT_TYPE_DATA_NAME, contentType);
300                ((ModifiablePage) page).setValue(UGCPageHandler.CLASSIFICATION_ATTRIBUTE_DATA_NAME, metadata);
301                ((ModifiablePage) page).setValue(UGCPageHandler.CLASSIFICATION_PAGE_VISIBLE_DATA_NAME, classificationPageVisible);
302            }
303            
304            jcrPage.saveChanges();
305        }
306    }
307    
308    private void _updateUGCRootProperty(Page page, String contentType, String metadata, boolean classificationPageVisible)
309    {
310        if (page instanceof ModifiablePage)
311        {
312            ModifiablePage modifiablePage = (ModifiablePage) page;
313            
314            // Set the ugc root property
315            modifiablePage.setValue(UGCPageHandler.CONTENT_TYPE_DATA_NAME, contentType);
316            modifiablePage.setValue(UGCPageHandler.CLASSIFICATION_ATTRIBUTE_DATA_NAME, metadata);
317            modifiablePage.setValue(UGCPageHandler.CLASSIFICATION_PAGE_VISIBLE_DATA_NAME, classificationPageVisible);
318            
319            modifiablePage.saveChanges();
320        }
321    }
322    
323    private void _removeUGCRootProperty(Page page) throws RepositoryException
324    {
325        if (page instanceof JCRAmetysObject)
326        {
327            JCRAmetysObject jcrPage = (JCRAmetysObject) page;
328            Node node = jcrPage.getNode();
329            
330            if (node.hasProperty(AmetysObjectResolver.VIRTUAL_PROPERTY))
331            {
332                List<Value> values = new ArrayList<>(Arrays.asList(node.getProperty(AmetysObjectResolver.VIRTUAL_PROPERTY).getValues()));
333                int index = values.stream()
334                        .map(LambdaUtils.wrap(Value::getString))
335                        .collect(Collectors.toList())
336                        .indexOf(VirtualUGCPageFactory.class.getName());
337                if (index != -1)
338                {
339                    values.remove(index);
340                    node.setProperty(AmetysObjectResolver.VIRTUAL_PROPERTY, values.toArray(new Value[values.size()]));
341                }
342
343                // Remove the ugc root property
344                if (page instanceof ModifiablePage)
345                {
346                    ModifiablePage modifiablePage = (ModifiablePage) page;
347                    modifiablePage.removeValue(UGCPageHandler.CONTENT_TYPE_DATA_NAME);
348                    modifiablePage.removeValue(UGCPageHandler.CLASSIFICATION_ATTRIBUTE_DATA_NAME);
349                    modifiablePage.removeValue(UGCPageHandler.CLASSIFICATION_PAGE_VISIBLE_DATA_NAME);
350                }
351                
352                jcrPage.saveChanges();
353            }
354        }
355    }
356    
357    private boolean _isUGCRootPage (JCRAmetysObject jcrPage)
358    {
359        try
360        {
361            Node node = jcrPage.getNode();
362            
363            if (node.hasProperty(AmetysObjectResolver.VIRTUAL_PROPERTY))
364            {
365                List<Value> values = Arrays.asList(node.getProperty(AmetysObjectResolver.VIRTUAL_PROPERTY).getValues());
366                
367                return values.stream()
368                        .map(LambdaUtils.wrap(Value::getString))
369                        .anyMatch(v -> VirtualUGCPageFactory.class.getName().equals(v));
370            }
371            else
372            {
373                return false;
374            }
375        }
376        catch (RepositoryException e)
377        {
378            return false;
379        }
380    }
381    
382    private void _notifyPageUpdated(Page page)
383    {
384        Map<String, Object> eventParams = new HashMap<>();
385        eventParams.put(org.ametys.web.ObservationConstants.ARGS_PAGE, page);
386        _observationManager.notify(new Event(org.ametys.web.ObservationConstants.EVENT_PAGE_UPDATED, _currentUserProvider.getUser(), eventParams));
387    }
388}