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}