001/* 002 * Copyright 2015 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.skinfactory.skins; 017 018import java.io.IOException; 019import java.io.InputStream; 020import java.io.OutputStream; 021import java.nio.file.Files; 022import java.nio.file.Path; 023import java.text.DateFormat; 024import java.text.SimpleDateFormat; 025import java.util.ArrayList; 026import java.util.Date; 027import java.util.HashMap; 028import java.util.LinkedHashMap; 029import java.util.List; 030import java.util.Map; 031import java.util.NoSuchElementException; 032import java.util.stream.Stream; 033 034import javax.xml.transform.TransformerConfigurationException; 035import javax.xml.xpath.XPath; 036import javax.xml.xpath.XPathExpressionException; 037import javax.xml.xpath.XPathFactory; 038 039import org.apache.avalon.framework.service.ServiceException; 040import org.apache.avalon.framework.service.ServiceManager; 041import org.apache.commons.io.IOUtils; 042import org.apache.commons.lang.StringUtils; 043import org.xml.sax.InputSource; 044import org.xml.sax.SAXException; 045 046import org.ametys.cms.languages.Language; 047import org.ametys.cms.languages.LanguagesManager; 048import org.ametys.core.right.RightManager; 049import org.ametys.core.right.RightManager.RightResult; 050import org.ametys.core.ui.Callable; 051import org.ametys.core.upload.Upload; 052import org.ametys.core.upload.UploadManager; 053import org.ametys.core.user.User; 054import org.ametys.core.user.UserIdentity; 055import org.ametys.core.user.UserManager; 056import org.ametys.core.util.I18nUtils; 057import org.ametys.core.util.LambdaUtils; 058import org.ametys.core.util.LambdaUtils.LambdaException; 059import org.ametys.core.util.path.PathUtils; 060import org.ametys.plugins.skincommons.AbstractCommonSkinDAO; 061import org.ametys.runtime.authentication.AccessDeniedException; 062import org.ametys.runtime.i18n.I18nizableText; 063import org.ametys.skinfactory.SkinFactoryComponent; 064import org.ametys.skinfactory.filefilter.FileFilter; 065import org.ametys.skinfactory.model.ModelDesignsManager; 066import org.ametys.skinfactory.parameters.AbstractSkinParameter; 067import org.ametys.skinfactory.parameters.I18nizableTextParameter; 068import org.ametys.skinfactory.parameters.ImageParameter; 069import org.ametys.skinfactory.parameters.ImageParameter.FileValue; 070import org.ametys.skinfactory.parameters.SkinParameterException; 071import org.ametys.skinfactory.parameters.Variant; 072import org.ametys.skinfactory.parameters.VariantParameter; 073import org.ametys.web.repository.site.Site; 074import org.ametys.web.skin.Skin; 075import org.ametys.web.skin.SkinModel; 076import org.ametys.web.skin.SkinModel.CssMenuItem; 077import org.ametys.web.skin.SkinModel.CssStyleItem; 078import org.ametys.web.skin.SkinModel.Separator; 079import org.ametys.web.skin.SkinModel.Theme; 080import org.ametys.web.skin.SkinModelsManager; 081 082/** 083 * Component to interact with a skin 084 */ 085public class SkinDAO extends AbstractCommonSkinDAO 086{ 087 /** Constant for skin editor tool id */ 088 public static final String SKIN_FACTORY_TOOL_ID = "uitool-skinfactory"; 089 090 private static final DateFormat _DATE_FORMAT = new SimpleDateFormat("yyyyMMdd-HHmm"); 091 092 private static final String __WORK_MODE = "work"; 093 private static final String __PROD_MODE = "prod"; 094 095 private I18nUtils _i18nUtils; 096 private LanguagesManager _languageManager; 097 private ModelDesignsManager _designsManager; 098 private SkinFactoryComponent _skinFactoryManager; 099 private SkinModelsManager _modelsManager; 100 private UploadManager _uploadManager; 101 private RightManager _rightManager; 102 103 @Override 104 public void service(ServiceManager manager) throws ServiceException 105 { 106 super.service(manager); 107 _designsManager = (ModelDesignsManager) manager.lookup(ModelDesignsManager.ROLE); 108 _i18nUtils = (I18nUtils) manager.lookup(org.ametys.core.util.I18nUtils.ROLE); 109 _languageManager = (LanguagesManager) manager.lookup(LanguagesManager.ROLE); 110 _modelsManager = (SkinModelsManager) manager.lookup(SkinModelsManager.ROLE); 111 _skinFactoryManager = (SkinFactoryComponent) manager.lookup(SkinFactoryComponent.ROLE); 112 _userManager = (UserManager) manager.lookup(UserManager.ROLE); 113 _uploadManager = (UploadManager) manager.lookup(UploadManager.ROLE); 114 _rightManager = (RightManager) manager.lookup(RightManager.ROLE); 115 } 116 117 @Override 118 protected void checkUserRight(String skinName) 119 { 120 UserIdentity user = _userProvider.getUser(); 121 if (!_skinsManager.getSkinNameFromRequest().equals(skinName) || _rightManager.hasRight(user, "Plugins_SkinFactory_EditCurrentSkin", "/${WorkspaceName}") != RightResult.RIGHT_ALLOW) 122 { 123 throw new AccessDeniedException("User '" + user + "' tried perform operation on skin '" + skinName + "' without sufficient right"); 124 } 125 } 126 127 /** 128 * Determines the skin directory is locked. If no, the lock owner is set in JSON map request attribute 129 * @param skinDir The skin directory 130 * @return information about the lock, or null if not locked 131 * @throws IOException if an error occurs when manipulating files 132 */ 133 protected Map<String, Object> checkLock(Path skinDir) throws IOException 134 { 135 if (!_lockManager.canWrite(skinDir)) 136 { 137 Map<String, Object> result = new HashMap<>(); 138 139 UserIdentity lockOwner = _lockManager.getLockOwner(skinDir); 140 User user = _userManager.getUser(lockOwner.getPopulationId(), lockOwner.getLogin()); 141 142 result.put("isLocked", true); 143 result.put("lockOwner", user != null ? user.getFullName() + " (" + lockOwner + ")" : lockOwner); 144 145 return result; 146 } 147 148 return null; 149 } 150 151 /** 152 * Affect a new design configuration 153 * @param skinName The skin name 154 * @param designId The design id 155 * @return the information on the design, or the error. 156 * @throws IOException if an error occurs when manipulating files 157 */ 158 @Callable(rights = "Plugins_SkinFactory_EditCurrentSkin", context = "/cms") 159 public Map<String, Object> affectDesign(String skinName, String designId) throws IOException 160 { 161 Map<String, Object> result = new HashMap<>(); 162 163 Path tempDir = _skinHelper.getTempDirectory(skinName); 164 String modelName = _skinHelper.getTempModel(skinName); 165 166 Map<String, Object> lockInfos = checkLock(tempDir); 167 if (lockInfos != null) 168 { 169 return lockInfos; 170 } 171 172 if (_modelsManager.getModel(modelName) == null) 173 { 174 result.put("unknownModel", true); 175 result.put("modelName", modelName); 176 return result; 177 } 178 179 _designsManager.applyDesign(modelName, designId, tempDir); 180 181 Map<String, Object> values = new HashMap<>(_skinFactoryManager.getParameterValues(tempDir, modelName)); 182 result.put("parameters", values); 183 184 String colorTheme = _skinFactoryManager.getColorTheme(tempDir); 185 if (colorTheme != null) 186 { 187 result.put("themeId", colorTheme); 188 result.put("colors", _modelsManager.getModel(modelName).getColors(colorTheme)); 189 } 190 191 result.put("designId", designId); 192 193 return result; 194 } 195 196 /** 197 * Change the model of a skin 198 * @param modelName The model name 199 * @param skinName The skin name 200 * @param useDefaults <code>true</code> to use default model parameters 201 * @return The skin parameters, or the error informations. 202 * @throws IOException if an error occurs when manipulating files 203 * @throws TransformerConfigurationException if something goes wrong when generating the model file 204 * @throws SAXException if an error occurs while saxing 205 */ 206 @Callable(rights = "Plugins_SkinFactory_EditCurrentSkin", context = "/cms") 207 public Map<String, Object> changeModel(String modelName, String skinName, boolean useDefaults) throws IOException, TransformerConfigurationException, SAXException 208 { 209 Path tempDir = _skinHelper.getTempDirectory(skinName); 210 211 Map<String, Object> lockInfos = checkLock(tempDir); 212 if (lockInfos != null) 213 { 214 return lockInfos; 215 } 216 217 String currentTheme = _skinFactoryManager.getColorTheme(tempDir); 218 219 // Prepare skin in temporary file 220 Path tmpDir = tempDir.getParent().resolve(skinName + "." + _DATE_FORMAT.format(new Date())); 221 222 // Copy new model 223 Path modelDir = _modelsManager.getModel(modelName).getPath(); 224 PathUtils.copyDirectory(modelDir, tmpDir, f -> !f.getFileName().equals("model"), false); 225 226 // Apply all parameters 227 _modelsManager.generateModelFile(tmpDir, modelName, currentTheme); 228 229 if (useDefaults) 230 { 231 String defaultColorTheme = _modelsManager.getModel(modelName).getDefaultColorTheme(); 232 _skinFactoryManager.saveColorTheme(tmpDir, defaultColorTheme); 233 _skinFactoryManager.applyModelParameters(modelName, tmpDir); 234 } 235 else 236 { 237 Map<String, Object> currentValues = _skinFactoryManager.getParameterValues(tempDir, modelName); 238 _skinFactoryManager.applyModelParameters(modelName, tmpDir, currentValues); 239 } 240 241 _skinHelper.deleteQuicklyDirectory(tempDir); 242 PathUtils.moveDirectory(tmpDir, tempDir); 243 244 // Invalidate i18n. 245 _skinHelper.invalidateTempSkinCatalogues(skinName); 246 247 // Update lock file 248 _lockManager.updateLockFile(tempDir, SKIN_FACTORY_TOOL_ID); 249 250 return new HashMap<>(_skinFactoryManager.getParameterValues(tempDir, modelName)); 251 } 252 253 /** 254 * Get the languages available on a site 255 * @param siteName The site name 256 * @return The languages informations 257 */ 258 @Callable(rights = Callable.NO_CHECK_REQUIRED) 259 public Map<String, Object> getLanguages(String siteName) 260 { 261 Map<String, Object> languages = new LinkedHashMap<>(); 262 263 Site site = _siteManager.getSite(siteName); 264 Skin skin = _skinsManager.getSkin(site.getSkinId()); 265 Path i18nDir = skin.getRawPath().resolve("i18n"); 266 267 Map<String, Language> allLanguages = _languageManager.getAvailableLanguages(); 268 269 try (Stream<Path> files = Files.list(i18nDir)) 270 { 271 files.forEach(file -> 272 { 273 String fileName = file.getFileName().toString(); 274 if (!Files.isDirectory(file) && fileName.startsWith("messages")) 275 { 276 String lang = null; 277 if (fileName.equals("messages.xml")) 278 { 279 lang = _getDefaultLanguage(file); 280 } 281 else 282 { 283 lang = fileName.substring("messages_".length(), fileName.lastIndexOf(".")); 284 } 285 286 if (allLanguages.containsKey(lang)) 287 { 288 Language language = allLanguages.get(lang); 289 290 Map<String, Object> langParams = new HashMap<>(); 291 langParams.put("label", language.getLabel()); 292 langParams.put("iconSmall", language.getSmallIcon()); 293 langParams.put("iconMedium", language.getMediumIcon()); 294 langParams.put("iconLarge", language.getLargeIcon()); 295 296 languages.put(lang, langParams); 297 } 298 } 299 }); 300 } 301 catch (IOException e) 302 { 303 getLogger().error("Cannot get languages for site " + siteName, e); 304 } 305 306 return languages; 307 } 308 309 private String _getDefaultLanguage (Path i18nFile) 310 { 311 try (InputStream is = Files.newInputStream(i18nFile)) 312 { 313 String string = org.apache.commons.io.IOUtils.toString(is, "UTF-8"); 314 315 // Not very pretty but more efficient than SAXparsing the all file to get the language 316 int i = string.indexOf("xml:lang=\""); 317 if (i != -1) 318 { 319 return string.substring(i + "xml:lang=\"".length(), i + "xml:lang=\"".length() + 2); 320 } 321 return null; 322 } 323 catch (IOException e) 324 { 325 throw new SkinParameterException ("Unable to parse file '" + i18nFile.getFileName().toString() + "'", e); 326 } 327 } 328 329 /** 330 * Get the colors of a model and its theme for a site. 331 * @param siteName The site name 332 * @return The colors and theme informations. 333 */ 334 @Callable(rights = Callable.NO_CHECK_REQUIRED) 335 public Map<String, Object> getColors(String siteName) 336 { 337 Map<String, Object> params = new LinkedHashMap<>(); 338 339 Site site = _siteManager.getSite(siteName); 340 String skinId = site.getSkinId(); 341 String modelName = _skinHelper.getTempModel(skinId); 342 343 Path tempDir = _skinHelper.getTempDirectory(skinId); 344 String colorTheme = _skinFactoryManager.getColorTheme(tempDir); 345 346 SkinModel model = _modelsManager.getModel(modelName); 347 List<String> defaultColors = model.getDefaultColors(); 348 params.put("colors", defaultColors); 349 350 if (StringUtils.isNotEmpty(colorTheme)) 351 { 352 Theme theme = model.getTheme(colorTheme); 353 if (theme != null) 354 { 355 params.put("themeId", theme.getId()); 356 params.put("themeColors", theme.getColors()); 357 } 358 } 359 360 return params; 361 } 362 363 /** 364 * Get the css style items used by a site 365 * @param siteName The site name 366 * @return The css style items. 367 */ 368 @Callable(rights = Callable.NO_CHECK_REQUIRED) 369 public Map<String, Object> getCssStyleItems(String siteName) 370 { 371 Map<String, Object> styles = new LinkedHashMap<>(); 372 373 Site site = _siteManager.getSite(siteName); 374 String skinId = site.getSkinId(); 375 String modelName = _skinHelper.getTempModel(skinId); 376 377 Map<String, List<CssMenuItem>> styleItems = _modelsManager.getModel(modelName).getStyleItems(); 378 379 for (String styleId : styleItems.keySet()) 380 { 381 List<Object> menuItems = new ArrayList<>(); 382 383 List<CssMenuItem> items = styleItems.get(styleId); 384 for (CssMenuItem item : items) 385 { 386 if (item instanceof CssStyleItem) 387 { 388 CssStyleItem cssItem = (CssStyleItem) item; 389 Map<String, String> itemParams = new HashMap<>(); 390 391 itemParams.put("value", cssItem.getValue()); 392 itemParams.put("label", _i18nUtils.translate(cssItem.getLabel())); 393 394 String iconCls = cssItem.getIconCls(); 395 if (iconCls != null) 396 { 397 itemParams.put("iconCls", iconCls); 398 } 399 400 String icon = cssItem.getIcon(); 401 if (icon != null) 402 { 403 itemParams.put("icon", icon); 404 } 405 406 String cssClass = cssItem.getCssClass(); 407 if (cssClass != null) 408 { 409 itemParams.put("cssclass", cssClass); 410 } 411 412 menuItems.add(itemParams); 413 } 414 else if (item instanceof Separator) 415 { 416 menuItems.add("separator"); 417 } 418 } 419 420 styles.put(styleId, menuItems); 421 } 422 423 return styles; 424 } 425 426 /** 427 * Get the parameters of the skin of a site 428 * @param siteName The site name 429 * @param paramIds If not null, specify the ids of the parameters to retrieve 430 * @return The parameters 431 */ 432 @Callable(rights = Callable.NO_CHECK_REQUIRED) 433 public Map<String, Object> getParametersValues(String siteName, List<String> paramIds) 434 { 435 Map<String, Object> values = new LinkedHashMap<>(); 436 437 Site site = _siteManager.getSite(siteName); 438 String skinId = site.getSkinId(); 439 String modelName = _skinHelper.getTempModel(skinId); 440 441 Path tempDir = _skinHelper.getTempDirectory(skinId); 442 443 if (paramIds != null) 444 { 445 values.putAll(_skinFactoryManager.getParameterValues(tempDir, modelName, paramIds)); 446 } 447 else 448 { 449 values.putAll(_skinFactoryManager.getParameterValues(tempDir, modelName)); 450 } 451 452 Map<String, Object> result = new LinkedHashMap<>(); 453 result.put("skinName", skinId); 454 result.put("modelName", modelName); 455 result.put("siteName", siteName); 456 result.put("values", values); 457 458 return result; 459 } 460 461 /** 462 * Open the skin of a site for edition 463 * @param siteName The site name 464 * @param mode The open mode 465 * @return The skin id, or an error message. 466 * @throws IOException if an error occurs when manipulating files 467 */ 468 @Callable(rights = "Plugins_SkinFactory_EditCurrentSkin", context = "/cms") 469 public Map<String, String> openSkin(String siteName, String mode) throws IOException 470 { 471 Map<String, String> result = new HashMap<>(); 472 473 Site site = _siteManager.getSite(siteName); 474 String skinId = site.getSkinId(); 475 476 Skin skin = _skinsManager.getSkin(skinId); 477 if (!skin.isModifiable()) 478 { 479 throw new IllegalStateException("The skin '" + skinId + "' is not modifiable and thus cannot be opened in skin editor."); 480 } 481 482 Path tempDir = _skinHelper.getTempDirectory(skinId); 483 Path workDir = _skinHelper.getWorkDirectory(skinId); 484 Path skinDir = _skinHelper.getSkinDirectory(skinId); 485 486 String modelName = null; 487 if (__PROD_MODE.equals(mode)) 488 { 489 modelName = _skinHelper.getSkinModel(skinId); 490 } 491 else if (__WORK_MODE.equals(mode)) 492 { 493 modelName = _skinHelper.getWorkModel(skinId); 494 } 495 else 496 { 497 modelName = _skinHelper.getTempModel(skinId); 498 } 499 500 SkinModel model = _modelsManager.getModel(modelName); 501 if (model == null) 502 { 503 result.put("model-not-found", "true"); 504 return result; 505 } 506 507 String modelHash = _modelsManager.getModelHash(modelName); 508 509 if (__PROD_MODE.equals(mode) || __WORK_MODE.equals(mode)) 510 { 511 // Delete temp directory if exists 512 if (Files.exists(tempDir)) 513 { 514 _skinHelper.deleteQuicklyDirectory(tempDir); 515 } 516 517 if (__PROD_MODE.equals(mode)) 518 { 519 // Delete work directory if exists 520 if (Files.exists(workDir)) 521 { 522 _skinHelper.deleteQuicklyDirectory(workDir); 523 } 524 525 // Copy from skin 526 PathUtils.copyDirectory(skinDir, workDir, FileFilter.getSkinFileFilter()); 527 } 528 529 boolean isUpTodate = modelHash.equals(_getHash (workDir)); 530 if (!isUpTodate) 531 { 532 // Re-apply model to work directory 533 _reapplyModel(workDir, model.getPath(), modelHash); 534 } 535 536 // Apply parameters 537 _skinFactoryManager.applyModelParameters(modelName, workDir); 538 539 // Copy work in temp 540 PathUtils.copyDirectory(workDir, tempDir); 541 542 // Create .lock file 543 _lockManager.updateLockFile(tempDir, SKIN_FACTORY_TOOL_ID); 544 } 545 else 546 { 547 boolean isUpTodate = modelHash.equals(_getHash (tempDir)); 548 if (!isUpTodate) 549 { 550 // Re-apply model to temp directory 551 _reapplyModel(tempDir, model.getPath(), modelHash); 552 } 553 554 // Apply parameters 555 _skinFactoryManager.applyModelParameters(modelName, tempDir); 556 557 // Update .lock file 558 _lockManager.updateLockFile(tempDir, SKIN_FACTORY_TOOL_ID); 559 } 560 561 result.put("skinId", skinId); 562 return result; 563 } 564 565 private void _reapplyModel (Path skinDir, Path modelDir, String hash) throws IOException 566 { 567 // Make a copy of model.xml file 568 Path xmlFile = skinDir.resolve("model.xml"); 569 _preserveFile (skinDir, xmlFile); 570 571 // Preserve uploaded images 572 Path uploadDir = skinDir.resolve("model/_uploads"); 573 if (Files.exists(uploadDir)) 574 { 575 _preserveFile(skinDir, uploadDir.getParent()); 576 } 577 578 // Delete old directory 579 PathUtils.deleteQuietly(skinDir); 580 581 // Copy the model 582 PathUtils.copyDirectory(modelDir, skinDir, FileFilter.getModelFilter(modelDir), false); 583 584 // Copy files to preserve 585 _copyFilesToPreserve (skinDir); 586 587 // Update hash 588 _skinFactoryManager.updateHash(xmlFile, hash); 589 } 590 591 private void _preserveFile(Path skinDir, Path fileToPreserve) throws IOException 592 { 593 Path toPreserveDir = skinDir.getParent().resolve(skinDir.getFileName().toString() + "_tmp"); 594 if (Files.isDirectory(fileToPreserve)) 595 { 596 PathUtils.moveDirectoryToDirectory(fileToPreserve, toPreserveDir, true); 597 } 598 else 599 { 600 PathUtils.moveFileToDirectory(fileToPreserve, toPreserveDir, true); 601 } 602 603 } 604 605 private void _copyFilesToPreserve(Path skinDir) throws IOException 606 { 607 Path toPreserveDir = skinDir.getParent().resolve(skinDir.getFileName().toString() + "_tmp"); 608 if (Files.exists(toPreserveDir)) 609 { 610 try (Stream<Path> children = Files.list(toPreserveDir)) 611 { 612 children.forEach(LambdaUtils.wrapConsumer(child -> 613 { 614 if (Files.isDirectory(child)) 615 { 616 PathUtils.moveDirectoryToDirectory(child, skinDir, false); 617 } 618 else 619 { 620 PathUtils.moveFileToDirectory(child, skinDir, false); 621 } 622 })); 623 } 624 catch (LambdaException e) 625 { 626 throw (IOException) e.getCause(); 627 } 628 629 PathUtils.deleteQuietly(toPreserveDir); 630 } 631 } 632 633 private String _getHash(Path skinDir) 634 { 635 Path modelFile = skinDir.resolve("model.xml"); 636 if (!Files.exists(modelFile)) 637 { 638 // No model 639 return null; 640 } 641 642 try (InputStream is = Files.newInputStream(modelFile)) 643 { 644 XPath xpath = XPathFactory.newInstance().newXPath(); 645 return xpath.evaluate("model/@hash", new InputSource(is)); 646 } 647 catch (XPathExpressionException e) 648 { 649 throw new IllegalStateException("The id of model is missing", e); 650 } 651 catch (IOException e) 652 { 653 getLogger().error("Can not determine the hash the skin", e); 654 return null; 655 } 656 } 657 658 /** 659 * Restore the default parameters for a skin 660 * @param skinName The skin name 661 * @return The skin informations, or an error code. 662 * @throws IOException if an error occurs when manipulating files 663 * @throws TransformerConfigurationException if something goes wrong when generating the model file 664 * @throws SAXException if an error occurs while saxing 665 */ 666 @Callable(rights = "Plugins_SkinFactory_EditCurrentSkin", context = "/cms") 667 public Map<String, Object> restoreDefaults(String skinName) throws IOException, TransformerConfigurationException, SAXException 668 { 669 Map<String, Object> result = new HashMap<>(); 670 671 Path tempDir = _skinHelper.getTempDirectory(skinName); 672 String modelName = _skinHelper.getTempModel(skinName); 673 674 Map<String, Object> lockInfos = checkLock(tempDir); 675 if (lockInfos != null) 676 { 677 return lockInfos; 678 } 679 680 if (_modelsManager.getModel(modelName) == null) 681 { 682 result.put("unknownModel", true); 683 result.put("modelName", modelName); 684 return result; 685 } 686 687 // Prepare skin in temporary file 688 Path tmpDir = tempDir.getParent().resolve(skinName + "." + _DATE_FORMAT.format(new Date())); 689 690 // Copy new model 691 SkinModel model = _modelsManager.getModel(modelName); 692 Path modelDir = model.getPath(); 693 PathUtils.copyDirectory(modelDir, tmpDir, f -> !f.getFileName().toString().equals("model"), false); 694 695 696 _modelsManager.generateModelFile(tmpDir, modelName); 697 698 String defaultColorTheme = model.getDefaultColorTheme(); 699 if (defaultColorTheme != null) 700 { 701 // Save color theme 702 _skinFactoryManager.saveColorTheme(tmpDir, defaultColorTheme); 703 704 result.put("themeId", defaultColorTheme); 705 result.put("colors", model.getColors(defaultColorTheme)); 706 } 707 708 // Apply all parameters 709 _skinFactoryManager.applyModelParameters(modelName, tmpDir); 710 711 _skinHelper.deleteQuicklyDirectory(tempDir); 712 PathUtils.moveDirectory(tmpDir, tempDir); 713 714 // Invalidate i18n. 715 _skinHelper.invalidateTempSkinCatalogues(skinName); 716 717 // Update lock file 718 _lockManager.updateLockFile(tempDir, SKIN_FACTORY_TOOL_ID); 719 720 Map<String, Object> values = new HashMap<>(_skinFactoryManager.getParameterValues(tempDir, modelName)); 721 result.put("parameters", values); 722 723 return result; 724 } 725 726 /** 727 * Set the theme used by a skin 728 * @param skinName The skin name 729 * @param themeId The theme id 730 * @return The theme informations, or an error code. 731 * @throws IOException if an error occurs when manipulating files 732 */ 733 @Callable(rights = "Plugins_SkinFactory_EditCurrentSkin", context = "/cms") 734 public Map<String, Object> updateColorTheme(String skinName, String themeId) throws IOException 735 { 736 Map<String, Object> result = new HashMap<>(); 737 738 Path tempDir = _skinHelper.getTempDirectory(skinName); 739 String modelName = _skinHelper.getTempModel(skinName); 740 741 Map<String, Object> lockInfos = checkLock(tempDir); 742 if (lockInfos != null) 743 { 744 return lockInfos; 745 } 746 747 if (_modelsManager.getModel(modelName) == null) 748 { 749 result.put("unknownModel", true); 750 result.put("modelName", modelName); 751 return result; 752 } 753 754 // Save color theme 755 _skinFactoryManager.saveColorTheme(tempDir, themeId); 756 757 // Apply new color theme 758 _skinFactoryManager.applyColorTheme (modelName, tempDir); 759 760 // Update lock 761 _lockManager.updateLockFile(tempDir, SKIN_FACTORY_TOOL_ID); 762 763 result.put("themeId", themeId); 764 result.put("colors", _modelsManager.getModel(modelName).getColors(themeId)); 765 766 return result; 767 } 768 769 /** 770 * Update a parameter of the skin 771 * @param skinName The skin name 772 * @param lang The current language 773 * @param parameterId The parameter id to update 774 * @param value The new value for the parameter 775 * @param uploaded <code>true</code> if the file was uploaded 776 * @return The skin parameters updated, or an error code. 777 * @throws IOException if an error occurs when manipulating files 778 */ 779 @Callable(rights = "Plugins_SkinFactory_EditCurrentSkin", context = "/cms") 780 public Map<String, Object> updateParameter(String skinName, String lang, String parameterId, String value, boolean uploaded) throws IOException 781 { 782 Path tempDir = _skinHelper.getTempDirectory(skinName); 783 784 Map<String, Object> lockInfos = checkLock(tempDir); 785 if (lockInfos != null) 786 { 787 return lockInfos; 788 } 789 790 String modelName = _skinHelper.getTempModel(skinName); 791 if (modelName == null) 792 { 793 Map<String, Object> result = new HashMap<>(); 794 795 result.put("unknownModel", true); 796 result.put("modelName", modelName); 797 return result; 798 } 799 800 Map<String, AbstractSkinParameter> modelParameters = _skinFactoryManager.getModelParameters(modelName); 801 AbstractSkinParameter skinParameter = modelParameters.get(parameterId); 802 if (skinParameter != null) 803 { 804 // Apply parameter 805 if (skinParameter instanceof ImageParameter) 806 { 807 FileValue fileValue = new FileValue(value, uploaded); 808 _skinFactoryManager.applyParameter(skinParameter, tempDir, modelName, fileValue, lang); 809 } 810 else 811 { 812 _skinFactoryManager.applyParameter(skinParameter, tempDir, modelName, value, lang); 813 } 814 815 // Save parameter 816 if (skinParameter instanceof I18nizableTextParameter) 817 { 818 Map<String, String> values = new HashMap<>(); 819 values.put(lang, value); 820 _skinFactoryManager.saveParameter(tempDir, parameterId, values); 821 822 _skinHelper.invalidateTempSkinCatalogue (skinName, lang); 823 } 824 else if (skinParameter instanceof ImageParameter) 825 { 826 FileValue fileValue = new FileValue(value, uploaded); 827 _skinFactoryManager.saveParameter(tempDir, parameterId, fileValue); 828 } 829 else 830 { 831 _skinFactoryManager.saveParameter(tempDir, parameterId, value); 832 } 833 834 835 // Update lock 836 _lockManager.updateLockFile(tempDir, SKIN_FACTORY_TOOL_ID); 837 } 838 839 return new HashMap<>(_skinFactoryManager.getParameterValues(tempDir, modelName)); 840 } 841 842 /** 843 * Upload a local image and set it as value for a image parameter 844 * @param uploadId The upload identifier 845 * @param fileName The name of uploaded file 846 * @param skinName The skin name 847 * @param parameterId The parameter id to update 848 * @return The skin parameters updated, or an error code. 849 * @throws IOException if an error occurs when manipulating files 850 */ 851 @Callable(rights = "Plugins_SkinFactory_EditCurrentSkin", context = "/cms") 852 public Map<String, Object> uploadLocalImage (String uploadId, String fileName, String skinName, String parameterId) throws IOException 853 { 854 Path tempDir = _skinHelper.getTempDirectory(skinName); 855 856 Map<String, Object> lockInfos = checkLock(tempDir); 857 if (lockInfos != null) 858 { 859 return lockInfos; 860 } 861 862 String modelName = _skinHelper.getTempModel(skinName); 863 if (modelName == null) 864 { 865 Map<String, Object> result = new HashMap<>(); 866 867 result.put("unknownModel", true); 868 result.put("modelName", modelName); 869 return result; 870 } 871 872 ImageParameter imgParam = (ImageParameter) _skinFactoryManager.getModelParamater(modelName, parameterId); 873 874 Upload upload = null; 875 try 876 { 877 upload = _uploadManager.getUpload(_userProvider.getUser(), uploadId); 878 879 // Copy uploaded file into model 880 Path uploadDir = _getUploadDir (tempDir, imgParam); 881 Path uploadFile = uploadDir.resolve(fileName); 882 883 try (OutputStream os = Files.newOutputStream(uploadFile); 884 InputStream is = upload.getInputStream()) 885 { 886 IOUtils.copy(is, os); 887 } 888 catch (IOException e) 889 { 890 // close quietly 891 } 892 } 893 catch (NoSuchElementException e) 894 { 895 // Invalid upload id 896 getLogger().error(String.format("Cannot find the temporary uploaded file for id '%s' and login '%s'.", uploadId, _userProvider.getUser()), e); 897 898 Map<String, Object> result = new HashMap<>(); 899 result.put("uploadFailed", true); 900 result.put("uploadId", uploadId); 901 return result; 902 } 903 904 FileValue fileValue = new ImageParameter.FileValue(fileName, true); 905 906 // Apply parameter 907 _skinFactoryManager.applyParameter(imgParam, tempDir, modelName, fileValue, null); 908 909 // Save parameter 910 _skinFactoryManager.saveParameter(tempDir, imgParam.getId(), fileValue); 911 912 // Update lock 913 _lockManager.updateLockFile(tempDir, SKIN_FACTORY_TOOL_ID); 914 915 return new HashMap<>(_skinFactoryManager.getParameterValues(tempDir, modelName)); 916 } 917 918 /** 919 * Retrieve the list of images for the skin and parameter 920 * @param skinName The skin name 921 * @param paramId The parameter id 922 * @return The map of images informations 923 * @throws IOException if an error occurs when manipulating files 924 */ 925 @Callable(rights = "Plugins_SkinFactory_EditCurrentSkin", context = "/cms") 926 public Map<String, Object> getGalleryImages(String skinName, String paramId) throws IOException 927 { 928 Map<String, Object> gallery = new HashMap<>(); 929 930 String modelName = _skinHelper.getTempModel(skinName); 931 SkinModel model = _modelsManager.getModel(modelName); 932 933 if (model != null) 934 { 935 AbstractSkinParameter skinParameter = _skinFactoryManager.getModelParamater(modelName, paramId); 936 if (skinParameter instanceof ImageParameter) 937 { 938 ImageParameter imageParam = (ImageParameter) skinParameter; 939 940 Path imageDir = model.getPath().resolve("model/images"); 941 Path libraryFile = imageDir.resolve(imageParam.getLibraryPath()); 942 gallery.put("gallery", _imageFiles2JsonObject(imageDir.toAbsolutePath().toString(), libraryFile.toAbsolutePath().toString(), libraryFile, modelName, true)); 943 944 // Uploaded local images 945 Path tempDir = _skinHelper.getTempDirectory(skinName); 946 Path uploadDir = tempDir.resolve("model/_uploads/" + imageParam.getLibraryPath()); 947 if (Files.exists(uploadDir)) 948 { 949 gallery.put("uploadedGroup", _uploadImages2JsonObject(uploadDir, skinName, imageParam)); 950 } 951 } 952 } 953 else 954 { 955 getLogger().warn("Unable to get gallery images : the model '" + modelName + "' does not exist anymore"); 956 } 957 958 return gallery; 959 } 960 961 private Map<String, Object> _uploadImages2JsonObject(Path uploadDir, String skinName, ImageParameter imageParam) throws IOException 962 { 963 Map<String, Object> uploadedGroup = new HashMap<>(); 964 965 uploadedGroup.put("label", new I18nizableText("plugin.skinfactory", "PLUGINS_SKINFACTORY_IMAGESGALLERY_GROUP_UPLOADED")); 966 967 List<Object> uploadedImages = new ArrayList<>(); 968 969 try (Stream<Path> files = Files.list(uploadDir)) 970 { 971 files 972 .filter(f -> _isImage(f)) 973 .forEach(child -> 974 { 975 Map<String, Object> jsonObject = new HashMap<>(); 976 jsonObject.put("type", "image"); 977 jsonObject.put("filename", child.getFileName().toString()); 978 jsonObject.put("src", child.getFileName().toString()); 979 jsonObject.put("thumbnail", "/plugins/skinfactory/" + skinName + "/_thumbnail/64/64/model/_uploads/" + (imageParam.getLibraryPath() + '/' + child.getFileName().toString()).replaceAll("\\\\", "/")); 980 jsonObject.put("thumbnailLarge", "/plugins/skinfactory/" + skinName + "/_thumbnail/100/100/model/_uploads/" + (imageParam.getLibraryPath() + '/' + child.getFileName().toString()).replaceAll("\\\\", "/")); 981 jsonObject.put("uploaded", true); 982 uploadedImages.add(jsonObject); 983 }); 984 } 985 uploadedGroup.put("images", uploadedImages); 986 987 return uploadedGroup; 988 } 989 990 private List<Object> _imageFiles2JsonObject(String imageDirPath, String libraryDirPath, Path file, String modelName, boolean deep) throws IOException 991 { 992 List<Object> imageFilesJsonObject = new ArrayList<>(); 993 994 try (Stream<Path> children = Files.list(file)) 995 { 996 children 997 .forEach(LambdaUtils.wrapConsumer(child -> 998 { 999 Map<String, Object> jsonObject = new HashMap<>(); 1000 1001 if (Files.isDirectory(child) && deep && !child.getFileName().equals(".svn")) 1002 { 1003 jsonObject.put("type", "group"); 1004 jsonObject.put("label", child.getFileName().toString()); 1005 jsonObject.put("childs", _imageFiles2JsonObject(imageDirPath, libraryDirPath, child, modelName, false)); 1006 1007 imageFilesJsonObject.add(jsonObject); 1008 } 1009 else if (_isImage(child)) 1010 { 1011 jsonObject.put("type", "image"); 1012 jsonObject.put("filename", child.getFileName().toString()); 1013 jsonObject.put("src", child.toAbsolutePath().toString().substring(libraryDirPath.length() + 1).replaceAll("\\\\", "/")); 1014 jsonObject.put("thumbnail", "/plugins/skinfactory/" + modelName + "/_thumbnail/64/64/model/images/" + child.toAbsolutePath().toString().substring(imageDirPath.length() + 1).replaceAll("\\\\", "/")); 1015 jsonObject.put("thumbnailLarge", "/plugins/skinfactory/" + modelName + "/_thumbnail/100/100/model/images/" + child.toAbsolutePath().toString().substring(imageDirPath.length() + 1).replaceAll("\\\\", "/")); 1016 1017 imageFilesJsonObject.add(jsonObject); 1018 } 1019 })); 1020 } 1021 catch (LambdaException e) 1022 { 1023 throw (IOException) e.getCause(); 1024 } 1025 1026 return imageFilesJsonObject; 1027 } 1028 1029 private Path _getUploadDir (Path tempDir, ImageParameter imgParam) throws IOException 1030 { 1031 Path uploadDir = tempDir.resolve("model/_uploads/" + imgParam.getLibraryPath()); 1032 if (!Files.exists(uploadDir)) 1033 { 1034 Files.createDirectories(uploadDir); 1035 } 1036 return uploadDir; 1037 } 1038 1039 private boolean _isImage(Path file) 1040 { 1041 String name = file.getFileName().toString().toLowerCase(); 1042 int index = name.lastIndexOf("."); 1043 String ext = name.substring(index + 1); 1044 1045 if (name.equals("thumbnail_16.png") || name.equals("thumbnail_32.png") || name.equals("thumbnail_48.png")) 1046 { 1047 return false; 1048 } 1049 1050 return "png".equals(ext) || "gif".equals(ext) || "jpg".equals(ext) || "jpeg".equals(ext); 1051 } 1052 1053 /** 1054 * Retrieve the list of gallery variants available for the specified skin and parameter 1055 * @param skinName The skin name 1056 * @param paramId The parameter id 1057 * @return The list of gallery variants 1058 * @throws IOException if an error occurs when manipulating files 1059 */ 1060 @Callable(rights = "Plugins_SkinFactory_EditCurrentSkin", context = "/cms") 1061 public List<Object> getGalleryVariants(String skinName, String paramId) throws IOException 1062 { 1063 List<Object> galleryVariants = new ArrayList<>(); 1064 1065 String modelName = _skinHelper.getTempModel(skinName); 1066 1067 AbstractSkinParameter skinParameter = _skinFactoryManager.getModelParamater(modelName, paramId); 1068 if (skinParameter instanceof VariantParameter) 1069 { 1070 VariantParameter variantParam = (VariantParameter) skinParameter; 1071 1072 List<Variant> variants = variantParam.getVariants(); 1073 for (Variant variant : variants) 1074 { 1075 Map<String, Object> jsonObject = new HashMap<>(); 1076 1077 jsonObject.put("value", variant.getId()); 1078 1079 String thumbnail = variant.getThumbnail(); 1080 if (thumbnail != null) 1081 { 1082 jsonObject.put("thumbnail", "/plugins/skinfactory/" + modelName + "/_thumbnail/32/32/model/variants/" + thumbnail); 1083 } 1084 else 1085 { 1086 jsonObject.put("thumbnail", "/plugins/skinfactory/resources/img/variant_default_32.png"); 1087 } 1088 1089 jsonObject.put("label", variant.getLabel()); 1090 jsonObject.put("description", variant.getDescription()); 1091 1092 galleryVariants.add(jsonObject); 1093 } 1094 } 1095 1096 return galleryVariants; 1097 } 1098 1099 /** 1100 * Retrieve the list of themes' colors for a site 1101 * @param siteName The site name 1102 * @return The model's themes colors 1103 */ 1104 @Callable(rights = Callable.NO_CHECK_REQUIRED) 1105 public List<Object> getThemeColors(String siteName) 1106 { 1107 List<Object> themesJsonObject = new ArrayList<>(); 1108 1109 String skinId = _siteManager.getSite(siteName).getSkinId(); 1110 String modelName = _skinHelper.getTempModel(skinId); 1111 1112 SkinModel model = _modelsManager.getModel(modelName); 1113 if (model != null) 1114 { 1115 Map<String, Theme> themes = model.getThemes(); 1116 for (String name : themes.keySet()) 1117 { 1118 Map<String, Object> jsonObject = new HashMap<>(); 1119 1120 Theme theme = themes.get(name); 1121 1122 jsonObject.put("id", theme.getId()); 1123 jsonObject.put("label", theme.getLabel()); 1124 jsonObject.put("colors", theme.getColors()); 1125 1126 themesJsonObject.add(jsonObject); 1127 } 1128 } 1129 else 1130 { 1131 getLogger().warn("Unable to get theme colors : the model '" + modelName + "' does not exist anymore"); 1132 } 1133 1134 return themesJsonObject; 1135 } 1136}