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