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