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