001/*
002 *  Copyright 2010 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.skineditor.skin;
017
018import java.io.IOException;
019import java.nio.file.Files;
020import java.nio.file.Path;
021import java.util.HashMap;
022import java.util.HashSet;
023import java.util.List;
024import java.util.Map;
025import java.util.Set;
026import java.util.function.Predicate;
027import java.util.stream.Collectors;
028
029import org.apache.avalon.framework.component.Component;
030import org.apache.avalon.framework.service.ServiceException;
031import org.apache.avalon.framework.service.ServiceManager;
032import org.apache.avalon.framework.service.Serviceable;
033
034import org.ametys.core.right.RightManager;
035import org.ametys.core.right.RightManager.RightResult;
036import org.ametys.core.ui.Callable;
037import org.ametys.core.user.CurrentUserProvider;
038import org.ametys.core.user.UserIdentity;
039import org.ametys.core.util.path.PathUtils;
040import org.ametys.plugins.skincommons.SkinEditionHelper;
041import org.ametys.plugins.skincommons.SkinLockManager;
042import org.ametys.runtime.i18n.I18nizableText;
043import org.ametys.web.repository.site.Site;
044import org.ametys.web.repository.site.SiteManager;
045import org.ametys.web.skin.Skin;
046import org.ametys.web.skin.SkinsManager;
047
048/**
049 * DAO for files and folders inside a skin directory
050 */
051public class SkinDAO implements Serviceable, Component
052{
053    /** The Avalon role */
054    public static final String ROLE = SkinDAO.class.getName();
055    
056    /** Constant for the id of right to edit all skins */
057    public static final String EDIT_SKINS_RIGHT_ID = "Plugins_SkinEditor_EditAllSkin";
058    /** Constant for the id of right to edit teh current skin */
059    public static final String EDIT_CURRENT_SKIN_RIGHT_ID = "Plugins_SkinEditor_EditCurrentSkin";
060    
061    /** Constant for skin editor tool id */
062    public static final String SKIN_EDITOR_TOOL_ID = "uitool-skineditor";
063    
064    private static final String __WORK_MODE = "work";
065    private static final String __PROD_MODE = "prod";
066    
067    /** The lock manager */
068    protected SkinLockManager _lockManager;
069    /** The skin edition helper */
070    protected SkinEditionHelper _skinHelper;
071
072    private SkinsManager _skinManager;
073
074    private CurrentUserProvider _userProvider;
075
076    private RightManager _rightManager;
077
078    private SiteManager _siteManager;
079    
080    @Override
081    public void service(ServiceManager manager) throws ServiceException
082    {
083        _skinHelper = (SkinEditionHelper) manager.lookup(SkinEditionHelper.ROLE);
084        _lockManager = (SkinLockManager) manager.lookup(SkinLockManager.ROLE);
085        _skinManager = (SkinsManager) manager.lookup(SkinsManager.ROLE);
086        _userProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE);
087        _rightManager = (RightManager) manager.lookup(RightManager.ROLE);
088        _siteManager = (SiteManager) manager.lookup(SiteManager.ROLE);
089    }
090    
091    /**
092     * Get the list of available and modifiable skins for a site
093     * @param siteName The site name
094     * @return the list of skins
095     */
096    @Callable
097    public List<Object> getSkinsList(String siteName) 
098    {
099        Site site = _siteManager.getSite(siteName);
100        String currentSkinId = site.getSkinId();
101        
102        Set<String> skins = new HashSet<>();
103        
104        UserIdentity user = _userProvider.getUser();
105        if (_rightManager.hasRight(user, EDIT_SKINS_RIGHT_ID, "/${WorkspaceName}") == RightResult.RIGHT_ALLOW)
106        {
107            skins.addAll(_skinManager.getSkins());
108        }
109        else if (_rightManager.hasRight(user, EDIT_CURRENT_SKIN_RIGHT_ID, "/${WorkspaceName}") == RightResult.RIGHT_ALLOW)
110        {
111            skins.add(currentSkinId);
112        }
113        
114        return skins.stream()
115            .map(id -> _skinManager.getSkin(id))
116            .filter(Skin::isModifiable)
117            .filter(Predicate.not(Skin::isAbstract))
118            .map(s -> _skin2JsonObject(s, s.getId().equals(currentSkinId)))
119            .collect(Collectors.toList());
120    }
121    
122    private Map<String, Object> _skin2JsonObject (Skin skin, boolean current)
123    {
124        Map<String, Object> jsonObject = new HashMap<>();
125        
126        I18nizableText label = skin.getLabel();
127        String icon = skin.getLargeImage();
128        
129        jsonObject.put("id", skin.getId());
130        jsonObject.put("current", current);
131        jsonObject.put("label", label);
132        jsonObject.put("icon", icon);
133        
134        return jsonObject;
135    }
136    
137    /**
138     * Open a skin for editing
139     * @param skinId the skin id
140     * @param mode the edition mode
141     * @param unlinkModel True to remove any existing change
142     * @return the skin id
143     * @throws Exception if an error occurs during the skin opening process
144     */
145    @Callable
146    public String openSkin(String skinId, String mode, boolean unlinkModel) throws Exception
147    {
148        Skin skin = _skinManager.getSkin(skinId);
149        if (!skin.isModifiable())
150        {
151            throw new IllegalStateException("The skin '" + skinId + "' is not modifiable and thus cannot be opened in skin editor.");
152        }
153        
154        Path tempDir = _skinHelper.getTempDirectory(skinId);
155        Path workDir = _skinHelper.getWorkDirectory(skinId);
156        Path skinDir = _skinHelper.getSkinDirectory(skinId);
157        
158        if (unlinkModel)
159        {
160            unlinkModel (skinDir);
161            unlinkModel(workDir);
162            unlinkModel(tempDir);
163        }
164        
165        if (__PROD_MODE.equals(mode) || __WORK_MODE.equals(mode))
166        {
167            // Delete temp directory if exists
168            if (Files.exists(tempDir))
169            {
170                _skinHelper.deleteQuicklyDirectory(tempDir);
171            }
172            
173            if (__PROD_MODE.equals(mode))
174            {
175                // Delete work directory if exists
176                if (Files.exists(workDir))
177                {
178                    _skinHelper.deleteQuicklyDirectory(workDir);
179                }
180                
181                // Copy from skin
182                PathUtils.copyDirectory(skinDir, workDir);
183            }
184                    
185            // Copy work in temp
186            PathUtils.copyDirectory(workDir, tempDir);
187            
188            // Create .lock file
189            _lockManager.updateLockFile(tempDir, SKIN_EDITOR_TOOL_ID);
190        }
191        else
192        {
193            // Update .lock file
194            _lockManager.updateLockFile(tempDir, SKIN_EDITOR_TOOL_ID);
195        }
196        
197        return skinId;
198    }
199    
200    /**
201     * Unlink model
202     * @param skinDir The skin directory
203     * @throws IOException If an error occurred
204     */
205    protected void unlinkModel (Path skinDir) throws IOException
206    {
207        Path modelFile = skinDir.resolve("model.xml");
208        Path bakFile = skinDir.resolve("model.xml.bak");
209        
210        if (Files.exists(bakFile))
211        {
212            // Delete old bak file if exists
213            PathUtils.deleteQuietly(bakFile);
214        }
215        
216        if (Files.exists(modelFile))
217        {
218            Files.move(modelFile, bakFile);
219        }
220    }
221}