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.page;
017
018import java.util.ArrayList;
019import java.util.Collections;
020import java.util.HashMap;
021import java.util.List;
022import java.util.Map;
023import java.util.Set;
024
025import javax.jcr.Node;
026import javax.jcr.RepositoryException;
027import javax.jcr.Value;
028
029import org.apache.commons.lang.StringUtils;
030
031import org.ametys.cms.repository.Content;
032import org.ametys.cms.repository.DefaultContent;
033import org.ametys.core.group.GroupIdentity;
034import org.ametys.core.right.RightManager;
035import org.ametys.core.user.UserIdentity;
036import org.ametys.plugins.contentio.synchronize.SynchronizableContentsCollection;
037import org.ametys.plugins.contentio.synchronize.SynchronizableContentsCollectionDAO;
038import org.ametys.plugins.explorer.resources.ResourceCollection;
039import org.ametys.plugins.repository.ACLAmetysObject;
040import org.ametys.plugins.repository.AmetysObject;
041import org.ametys.plugins.repository.AmetysObjectIterable;
042import org.ametys.plugins.repository.AmetysObjectResolver;
043import org.ametys.plugins.repository.AmetysRepositoryException;
044import org.ametys.plugins.repository.CollectionIterable;
045import org.ametys.plugins.repository.UnknownAmetysObjectException;
046import org.ametys.plugins.repository.metadata.CompositeMetadata;
047import org.ametys.plugins.userdirectory.UserDirectoryPageHandler;
048import org.ametys.web.repository.page.Page;
049import org.ametys.web.repository.page.UnknownZoneException;
050import org.ametys.web.repository.page.Zone;
051import org.ametys.web.repository.site.Site;
052import org.ametys.web.repository.sitemap.Sitemap;
053import org.ametys.web.skin.Skin;
054import org.ametys.web.skin.SkinsManager;
055
056/**
057 * Page representing a second-level page.
058 */
059public class UserPage implements Page, ACLAmetysObject
060{
061    private static final String __USER_PAGE_TEMPLATE = "user-page";
062    
063    private Page _root;
064    private int _initialDepth;
065    private String _title;
066    private AmetysObjectResolver _resolver;
067    private UserDirectoryPageHandler _userDirectoryPageHandler;
068    private SynchronizableContentsCollectionDAO _syncContentsCollectionDAO;
069    private SkinsManager _skinsManager;
070    private String _path;
071    private Content _syncContent;
072
073
074    
075    /**
076     * Constructor.
077     * @param root the root page.
078     * @param syncContent the synchronized content
079     * @param path the path
080     * @param resolver the {@link AmetysObjectResolver}.
081     * @param userDirectoryPageHandler the user directory page handler
082     * @param syncContentsCollectionDAO The DAO for synchronizable collections
083     * @param skinsManager the skins manager
084     */
085    public UserPage(Page root, Content syncContent, String path, AmetysObjectResolver resolver, UserDirectoryPageHandler userDirectoryPageHandler, SynchronizableContentsCollectionDAO syncContentsCollectionDAO, SkinsManager skinsManager)
086    {
087        _root = root;
088        _path = path;
089        _resolver = resolver;
090        _userDirectoryPageHandler = userDirectoryPageHandler;
091        _syncContentsCollectionDAO = syncContentsCollectionDAO;
092        _skinsManager = skinsManager;
093        _syncContent = syncContent;
094        
095        _title = _syncContent.getTitle();
096        _initialDepth = _userDirectoryPageHandler.getDepth(_root);
097    }
098    
099    /**
100     * Returns the associated synchronizable {@link Content}.
101     * @return the associated synchronizable {@link Content}.
102     */
103    public Content getSyncContent()
104    {
105        return _syncContent;
106    }
107    
108    @Override
109    public int getDepth() throws AmetysRepositoryException
110    {
111        return _root.getDepth() + _initialDepth + 1;
112    }
113
114    @Override
115    public Set<String> getReferers() throws AmetysRepositoryException
116    {
117        return null;
118    }
119
120    @Override
121    public ResourceCollection getRootAttachments() throws AmetysRepositoryException
122    {
123        return null;
124    }
125
126    @Override
127    public String getTemplate() throws AmetysRepositoryException
128    {
129        Skin skin = _skinsManager.getSkin(getSite().getSkinId());
130        
131        if (skin.getTemplate(__USER_PAGE_TEMPLATE) != null)
132        {
133            return __USER_PAGE_TEMPLATE;
134        }
135        
136        return "page"; 
137    }
138
139    @Override
140    public String getTitle() throws AmetysRepositoryException
141    {
142        return _title;
143    }
144    
145    @Override
146    public String getLongTitle() throws AmetysRepositoryException
147    {
148        return _title;
149    }
150
151    @Override
152    public PageType getType() throws AmetysRepositoryException
153    {
154        return PageType.CONTAINER;
155    }
156
157    @Override
158    public String getURL() throws AmetysRepositoryException
159    {
160        throw new UnsupportedOperationException("#getURL is not supported on virtual user pages");
161    }
162
163    @Override
164    public LinkType getURLType() throws AmetysRepositoryException
165    {
166        throw new UnsupportedOperationException("#getURLType is not supported on virtual user pages");
167    }
168
169    @Override
170    public Zone getZone(String name) throws UnknownZoneException, AmetysRepositoryException
171    {
172        if (!"default".equals(name))
173        {
174            throw new IllegalArgumentException("Only the zone named 'default' is actually supported on virtual user pages.");
175        }
176        
177        return new UserZone(this);
178    }
179
180    @Override
181    public AmetysObjectIterable< ? extends Zone> getZones() throws AmetysRepositoryException
182    {
183        ArrayList<Zone> zones = new ArrayList<>();
184        zones.add(new UserZone(this));
185        return new CollectionIterable<>(zones);
186    }
187
188    @Override
189    public boolean hasZone(String name) throws AmetysRepositoryException
190    {
191        return "default".equals(name);
192    }
193
194    @Override
195    public AmetysObjectIterable<? extends Page> getChildrenPages() throws AmetysRepositoryException
196    {
197        ArrayList<Page> children = new ArrayList<>();
198        return new CollectionIterable<>(children);
199    }
200
201    @Override
202    public String getPathInSitemap() throws AmetysRepositoryException
203    {
204        if (_path.equals("_root"))
205        {
206            return _root.getPathInSitemap() + "/" + getName();
207        }
208        else
209        {
210            String path = StringUtils.lowerCase(_path);
211            return _root.getPathInSitemap() + "/" + path + "/" + getName();
212        }
213    }
214
215    @Override
216    public Site getSite() throws AmetysRepositoryException
217    {
218        return _root.getSite();
219    }
220
221    @Override
222    public String getSiteName() throws AmetysRepositoryException
223    {
224        return _root.getSiteName();
225    }
226
227    @Override
228    public Sitemap getSitemap() throws AmetysRepositoryException
229    {
230        return _root.getSitemap();
231    }
232
233    @Override
234    public String getSitemapName() throws AmetysRepositoryException
235    {
236        return _root.getSitemapName();
237    }
238
239    @Override
240    public <A extends AmetysObject> A getChild(String path) throws AmetysRepositoryException, UnknownAmetysObjectException
241    {
242        if (path.isEmpty())
243        {
244            throw new AmetysRepositoryException("path must be non empty");
245        }
246        
247        return null;
248    }
249
250    @SuppressWarnings("unchecked")
251    @Override
252    public AmetysObjectIterable<? extends AmetysObject> getChildren() throws AmetysRepositoryException
253    {
254        return getChildrenPages();
255    }
256
257    @Override
258    public boolean hasChild(String name) throws AmetysRepositoryException
259    {
260        return false;
261    }
262    
263    @Override
264    public String getId() throws AmetysRepositoryException
265    {
266        // E.g: uduser://path?rootId=...&contentId=...
267        return "uduser://" + _path + "?rootId=" + _root.getId() + "&contentId=" + _syncContent.getId();
268    }
269
270    @Override
271    public String getName() throws AmetysRepositoryException
272    {
273        return _syncContent.getName();
274    }
275    
276    @SuppressWarnings("unchecked")
277    @Override
278    public Page getParent() throws AmetysRepositoryException
279    {
280        if (_initialDepth > 0)
281        {
282            String pathName = StringUtils.substringAfterLast(_path, "/");
283            String name = _userDirectoryPageHandler.getName(pathName);
284            return new TransitionalPage(_root, name, _path, _resolver, _userDirectoryPageHandler, _syncContentsCollectionDAO, _skinsManager);
285        }
286        else
287        {
288            return _root;
289        }
290    }
291
292    @Override
293    public String getParentPath() throws AmetysRepositoryException
294    {
295        if (_initialDepth > 0)
296        {
297            String path = StringUtils.lowerCase(_path);
298            return _root.getPath() + "/" + path;
299        }
300        else
301        {
302            return _root.getPath();
303        }
304    }
305
306    @Override
307    public String getPath() throws AmetysRepositoryException
308    {
309        return getParentPath() + "/" + getName();
310    }
311
312    @Override
313    public CompositeMetadata getMetadataHolder()
314    {
315        return new StaticCompositeMetadata(new HashMap<>());
316    }
317
318    @Override
319    public Set<String> getTags() throws AmetysRepositoryException
320    {
321        return Collections.emptySet();
322    }
323
324    @Override
325    public AmetysObjectIterable< ? extends Page> getChildrenPages(boolean includeInvisiblePage) throws AmetysRepositoryException
326    {
327        ArrayList<Page> children = new ArrayList<>();
328        return new CollectionIterable<>(children);
329    }
330
331    @Override
332    public boolean isVisible() throws AmetysRepositoryException
333    {
334        return false;
335    }
336
337    @Override
338    public Page getChildPageAt(int index) throws UnknownAmetysObjectException, AmetysRepositoryException
339    {
340        throw new UnknownAmetysObjectException("There is no child for user page");
341    }
342
343    @Override
344    public Set<String> getAllowedProfilesForAnyConnectedUser()
345    {
346        return Collections.EMPTY_SET;
347    }
348
349    @Override
350    public Set<String> getDeniedProfilesForAnyConnectedUser()
351    {
352        return Collections.EMPTY_SET;
353    }
354    
355    @Override
356    public Set<String> getAllowedProfilesForAnonymous()
357    {
358        return Collections.EMPTY_SET;
359    }
360
361    @Override
362    public Set<String> getDeniedProfilesForAnonymous()
363    {
364        List<String> collectionIds = _getCollectionIds();
365        for (String collectionId : collectionIds)
366        {
367            SynchronizableContentsCollection collection = _syncContentsCollectionDAO.getSynchronizableContentsCollection(collectionId);
368            String restrictedField = collection.getRestrictedField();
369            if (!StringUtils.isEmpty(restrictedField) && _isContentRestricted(restrictedField))
370            {
371                return Collections.singleton(RightManager.READER_PROFILE_ID);
372            }
373        }
374        return Collections.EMPTY_SET;
375    }
376
377    @Override
378    public Set<String> getAllowedProfilesForUser(UserIdentity user)
379    {
380        return Collections.EMPTY_SET;
381    }
382
383    @Override
384    public Map<UserIdentity, Set<String>> getAllowedProfilesForUsers()
385    {
386        return Collections.EMPTY_MAP;
387    }
388
389    @Override
390    public Set<UserIdentity> getAllowedUsers(String profileId)
391    {
392        return Collections.EMPTY_SET;
393    }
394
395    @Override
396    public Map<GroupIdentity, Set<String>> getAllowedProfilesForGroups()
397    {
398        return Collections.EMPTY_MAP;
399    }
400
401    @Override
402    public Set<GroupIdentity> getAllowedGroups(String profileId)
403    {
404        return Collections.EMPTY_SET;
405    }
406    
407    @Override
408    public Set<String> getDeniedProfilesForUser(UserIdentity user)
409    {
410        return Collections.EMPTY_SET;
411    }
412
413    @Override
414    public Map<UserIdentity, Set<String>> getDeniedProfilesForUsers()
415    {
416        return Collections.EMPTY_MAP;
417    }
418
419    @Override
420    public Set<UserIdentity> getDeniedUsers(String profileId)
421    {
422        return Collections.EMPTY_SET;
423    }
424
425    @Override
426    public Map<GroupIdentity, Set<String>> getDeniedProfilesForGroups()
427    {
428        return Collections.EMPTY_MAP;
429    }
430
431    @Override
432    public Set<GroupIdentity> getDeniedGroups(String profileId)
433    {
434        return Collections.EMPTY_SET;
435    }
436    
437    private boolean _isContentRestricted (String metadataPath)
438    {
439        CompositeMetadata metadataHolder = _syncContent.getMetadataHolder();
440        
441        String[] pathSegments = metadataPath.split("/");
442        
443        for (int i = 0;  i < pathSegments.length - 1; i++)
444        {
445            if (!metadataHolder.hasMetadata(pathSegments[i]))
446            {
447                return false;
448            }
449            metadataHolder = metadataHolder.getCompositeMetadata(pathSegments[i]);
450        }
451        
452        String metadataName = pathSegments[pathSegments.length - 1];
453        return metadataHolder.getBoolean(metadataName, false);
454    }
455    
456    private List<String> _getCollectionIds() throws AmetysRepositoryException
457    {
458        List<String> collectionIds = new ArrayList<>();
459        
460        if (_syncContent instanceof DefaultContent)
461        {
462            try
463            {
464                Node node = ((DefaultContent) _syncContent).getNode();
465                if (node.hasProperty(SynchronizableContentsCollection.COLLECTION_ID_PROPERTY))
466                {
467                    Value[] values = node.getProperty(SynchronizableContentsCollection.COLLECTION_ID_PROPERTY).getValues();
468                    for (Value value : values)
469                    {
470                        collectionIds.add(value.getString());
471                    }
472                }
473            }
474            catch (RepositoryException e)
475            {
476                throw new AmetysRepositoryException("Failed to get linked synchronizable collections for content " + _syncContent.getId(), e);
477            }
478        }
479        
480        return collectionIds;
481    }
482}