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