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