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.File; 019import java.io.FileFilter; 020import java.io.FileInputStream; 021import java.io.FileOutputStream; 022import java.io.IOException; 023import java.io.InputStream; 024import java.io.OutputStream; 025import java.text.DateFormat; 026import java.text.SimpleDateFormat; 027import java.util.ArrayList; 028import java.util.Date; 029import java.util.HashMap; 030import java.util.LinkedHashMap; 031import java.util.List; 032import java.util.Map; 033import java.util.NoSuchElementException; 034 035import javax.xml.transform.TransformerConfigurationException; 036import javax.xml.xpath.XPath; 037import javax.xml.xpath.XPathExpressionException; 038import javax.xml.xpath.XPathFactory; 039 040import org.apache.avalon.framework.component.Component; 041import org.apache.avalon.framework.logger.AbstractLogEnabled; 042import org.apache.avalon.framework.service.ServiceException; 043import org.apache.avalon.framework.service.ServiceManager; 044import org.apache.avalon.framework.service.Serviceable; 045import org.apache.commons.io.FileUtils; 046import org.apache.commons.io.IOUtils; 047import org.apache.commons.lang.StringUtils; 048import org.xml.sax.InputSource; 049import org.xml.sax.SAXException; 050 051import org.ametys.cms.languages.Language; 052import org.ametys.cms.languages.LanguagesManager; 053import org.ametys.core.ui.Callable; 054import org.ametys.core.upload.Upload; 055import org.ametys.core.upload.UploadManager; 056import org.ametys.core.user.CurrentUserProvider; 057import org.ametys.core.user.User; 058import org.ametys.core.user.UserIdentity; 059import org.ametys.core.user.UserManager; 060import org.ametys.core.util.I18nUtils; 061import org.ametys.plugins.skincommons.SkinEditionHelper; 062import org.ametys.plugins.skincommons.SkinLockManager; 063import org.ametys.runtime.i18n.I18nizableText; 064import org.ametys.runtime.parameter.ParameterHelper; 065import org.ametys.skinfactory.SkinFactoryComponent; 066import org.ametys.skinfactory.filefilter.ModelFileFilter; 067import org.ametys.skinfactory.filefilter.SkinFileFilter; 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 File tempDir = _skinHelper.getTempDirectory(skinId); 140 File workDir = _skinHelper.getWorkDirectory(skinId); 141 File skinDir = _skinHelper.getSkinDirectory(skinId); 142 143 long lastModifiedLock = _lockManager.lastModified(tempDir).getTime(); 144 if (lastModifiedLock <= workDir.lastModified()) 145 { 146 if (workDir.lastModified() > skinDir.lastModified()) 147 { 148 // The modifications were not committed in production 149 return "saved-but-not-commit"; 150 } 151 else 152 { 153 FileUtils.deleteDirectory(workDir); 154 } 155 } 156 else if (lastModifiedLock >= skinDir.lastModified()) 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 File 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", ParameterHelper.valueToString(_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", ParameterHelper.valueToString(_lockManager.lastModified(tempDir))); 209 } 210 } 211 212 File workDir = _skinHelper.getWorkDirectory(skinId); 213 if (workDir.exists()) 214 { 215 result.put("work-version", ParameterHelper.valueToString(new Date(workDir.lastModified()))); 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(File 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 File 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 File 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 File tmpDir = new File (tempDir.getParentFile(), skinName + "." + _DATE_FORMAT.format(new Date())); 315 316 // Copy new model 317 File modelDir = _modelsManager.getModel(modelName).getFile(); 318 FileUtils.copyDirectory(modelDir, tmpDir, new FileFilter() 319 { 320 @Override 321 public boolean accept(File pathname) 322 { 323 return !pathname.getName().equals("model"); 324 } 325 }, false); 326 327 // Apply all parameters 328 _modelsManager.generateModelFile(tmpDir, modelName, currentTheme); 329 330 if (useDefaults) 331 { 332 String defaultColorTheme = _modelsManager.getModel(modelName).getDefaultColorTheme(); 333 _skinFactoryManager.saveColorTheme(tmpDir, defaultColorTheme); 334 _skinFactoryManager.applyModelParameters(modelName, tmpDir); 335 } 336 else 337 { 338 Map<String, Object> currentValues = _skinFactoryManager.getParameterValues(tempDir, modelName); 339 _skinFactoryManager.applyModelParameters(modelName, tmpDir, currentValues); 340 } 341 342 _skinHelper.deleteQuicklyDirectory(tempDir); 343 FileUtils.moveDirectory(tmpDir, tempDir); 344 345 // Invalidate i18n. 346 _skinHelper.invalidateTempSkinCatalogues(skinName); 347 348 // Update lock file 349 _lockManager.updateLockFile(tempDir, SKIN_FACTORY_TOOL_ID); 350 351 return new HashMap<>(_skinFactoryManager.getParameterValues(tempDir, modelName)); 352 } 353 354 /** 355 * Get the languages available on a site 356 * @param siteName The site name 357 * @return The languages informations 358 */ 359 @Callable 360 public Map<String, Object> getLanguages(String siteName) 361 { 362 Map<String, Object> languages = new LinkedHashMap<>(); 363 364 Site site = _siteManager.getSite(siteName); 365 Skin skin = _skinsManager.getSkin(site.getSkinId()); 366 File i18nDir = new File(skin.getFile(), "i18n"); 367 368 Map<String, Language> allLanguages = _languageManager.getAvailableLanguages(); 369 370 for (File file : i18nDir.listFiles()) 371 { 372 String fileName = file.getName(); 373 if (file.isFile() && fileName.startsWith("messages")) 374 { 375 String lang = null; 376 if (fileName.equals("messages.xml")) 377 { 378 lang = _getDefaultLanguage (file); 379 } 380 else 381 { 382 lang = fileName.substring("messages_".length(), fileName.lastIndexOf(".")); 383 } 384 385 if (allLanguages.containsKey(lang)) 386 { 387 Language language = allLanguages.get(lang); 388 389 Map<String, Object> langParams = new HashMap<>(); 390 langParams.put("label", language.getLabel()); 391 langParams.put("iconSmall", language.getSmallIcon()); 392 langParams.put("iconMedium", language.getMediumIcon()); 393 langParams.put("iconLarge", language.getLargeIcon()); 394 395 languages.put(lang, langParams); 396 } 397 } 398 } 399 400 return languages; 401 } 402 403 private String _getDefaultLanguage (File i18nFile) 404 { 405 try (InputStream is = new FileInputStream(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.getName() + "'", 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 File 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 File 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 File tempDir = _skinHelper.getTempDirectory(skinId); 571 File workDir = _skinHelper.getWorkDirectory(skinId); 572 File skinDir = _skinHelper.getSkinDirectory(skinId); 573 574 String modelName = null; 575 if (__PROD_MODE.equals(mode)) 576 { 577 modelName = _skinHelper.getSkinModel(skinId); 578 } 579 else if (__WORK_MODE.equals(mode)) 580 { 581 modelName = _skinHelper.getWorkModel(skinId); 582 } 583 else 584 { 585 modelName = _skinHelper.getTempModel(skinId); 586 } 587 588 SkinModel model = _modelsManager.getModel(modelName); 589 if (model == null) 590 { 591 result.put("model-not-found", "true"); 592 return result; 593 } 594 595 String modelHash = _modelsManager.getModelHash(modelName); 596 597 if (__PROD_MODE.equals(mode) || __WORK_MODE.equals(mode)) 598 { 599 // Delete temp directory if exists 600 if (tempDir.exists()) 601 { 602 _skinHelper.deleteQuicklyDirectory(tempDir); 603 } 604 605 if (__PROD_MODE.equals(mode)) 606 { 607 // Delete work directory if exists 608 if (workDir.exists()) 609 { 610 _skinHelper.deleteQuicklyDirectory(workDir); 611 } 612 613 // Copy from skin 614 FileUtils.copyDirectory(skinDir, workDir, new SkinFileFilter()); 615 } 616 617 boolean isUpTodate = modelHash.equals(_getHash (workDir)); 618 if (!isUpTodate) 619 { 620 // Re-apply model to work directory 621 _reapplyModel(workDir, model.getFile(), modelHash); 622 } 623 624 // Apply parameters 625 _skinFactoryManager.applyModelParameters(modelName, workDir); 626 627 // Copy work in temp 628 FileUtils.copyDirectory(workDir, tempDir); 629 630 // Create .lock file 631 _lockManager.updateLockFile(tempDir, SKIN_FACTORY_TOOL_ID); 632 } 633 else 634 { 635 boolean isUpTodate = modelHash.equals(_getHash (tempDir)); 636 if (!isUpTodate) 637 { 638 // Re-apply model to temp directory 639 _reapplyModel(tempDir, model.getFile(), modelHash); 640 } 641 642 // Apply parameters 643 _skinFactoryManager.applyModelParameters(modelName, tempDir); 644 645 // Update .lock file 646 _lockManager.updateLockFile(tempDir, SKIN_FACTORY_TOOL_ID); 647 } 648 649 result.put("skinId", skinId); 650 return result; 651 } 652 653 private void _reapplyModel (File skinDir, File modelDir, String hash) throws IOException 654 { 655 // Make a copy of model.xml file 656 File xmlFile = new File(skinDir, "model.xml"); 657 _preserveFile (skinDir, xmlFile); 658 659 // Preserve uploaded images 660 File uploadDir = new File(skinDir, "model/_uploads"); 661 if (uploadDir.exists()) 662 { 663 _preserveFile(skinDir, uploadDir.getParentFile()); 664 } 665 666 // Delete old directory 667 FileUtils.deleteQuietly(skinDir); 668 669 // Copy the model 670 FileUtils.copyDirectory(modelDir, skinDir, new ModelFileFilter(modelDir), false); 671 672 // Copy files to preserve 673 _copyFilesToPreserve (skinDir); 674 675 // Update hash 676 _skinFactoryManager.updateHash(xmlFile, hash); 677 } 678 679 private void _preserveFile (File skinDir, File fileToPreserve) throws IOException 680 { 681 File toPreserveDir = new File(skinDir.getParentFile(), skinDir.getName() + "_tmp"); 682 if (fileToPreserve.isDirectory()) 683 { 684 FileUtils.moveDirectoryToDirectory(fileToPreserve, toPreserveDir, true); 685 } 686 else 687 { 688 FileUtils.moveFileToDirectory(fileToPreserve, toPreserveDir, true); 689 } 690 691 } 692 693 private void _copyFilesToPreserve (File skinDir) throws IOException 694 { 695 File toPreserveDir = new File(skinDir.getParentFile(), skinDir.getName() + "_tmp"); 696 if (toPreserveDir.exists()) 697 { 698 for (File child : toPreserveDir.listFiles()) 699 { 700 if (child.isDirectory()) 701 { 702 FileUtils.moveDirectoryToDirectory (child, skinDir, false); 703 } 704 else 705 { 706 FileUtils.moveFileToDirectory(child, skinDir, false); 707 } 708 } 709 710 FileUtils.deleteQuietly(toPreserveDir); 711 } 712 } 713 714 private String _getHash(File skinDir) 715 { 716 File modelFile = new File (skinDir, "model.xml"); 717 if (!modelFile.exists()) 718 { 719 // No model 720 return null; 721 } 722 723 try (InputStream is = new FileInputStream(modelFile)) 724 { 725 XPath xpath = XPathFactory.newInstance().newXPath(); 726 return xpath.evaluate("model/@hash", new InputSource(is)); 727 } 728 catch (XPathExpressionException e) 729 { 730 throw new IllegalStateException("The id of model is missing", e); 731 } 732 catch (IOException e) 733 { 734 getLogger().error("Can not determine the hash the skin", e); 735 return null; 736 } 737 } 738 739 /** 740 * Restore the default parameters for a skin 741 * @param skinName The skin name 742 * @return The skin informations, or an error code. 743 * @throws IOException if an error occurs when manipulating files 744 * @throws TransformerConfigurationException if something goes wrong when generating the model file 745 * @throws SAXException if an error occurs while saxing 746 */ 747 @Callable 748 public Map<String, Object> restoreDefaults(String skinName) throws IOException, TransformerConfigurationException, SAXException 749 { 750 Map<String, Object> result = new HashMap<>(); 751 752 File tempDir = _skinHelper.getTempDirectory(skinName); 753 String modelName = _skinHelper.getTempModel(skinName); 754 755 Map<String, Object> lockInfos = checkLock(tempDir); 756 if (lockInfos != null) 757 { 758 return lockInfos; 759 } 760 761 if (_modelsManager.getModel(modelName) == null) 762 { 763 result.put("unknownModel", true); 764 result.put("modelName", modelName); 765 return result; 766 } 767 768 // Prepare skin in temporary file 769 File tmpDir = new File (tempDir.getParentFile(), skinName + "." + _DATE_FORMAT.format(new Date())); 770 771 // Copy new model 772 SkinModel model = _modelsManager.getModel(modelName); 773 File modelDir = model.getFile(); 774 FileUtils.copyDirectory(modelDir, tmpDir, new FileFilter() 775 { 776 @Override 777 public boolean accept(File pathname) 778 { 779 return !pathname.getName().equals("model"); 780 } 781 }, false); 782 783 784 _modelsManager.generateModelFile(tmpDir, modelName); 785 786 String defaultColorTheme = model.getDefaultColorTheme(); 787 if (defaultColorTheme != null) 788 { 789 // Save color theme 790 _skinFactoryManager.saveColorTheme(tmpDir, defaultColorTheme); 791 792 result.put("themeId", defaultColorTheme); 793 result.put("colors", model.getColors(defaultColorTheme)); 794 } 795 796 // Apply all parameters 797 _skinFactoryManager.applyModelParameters(modelName, tmpDir); 798 799 _skinHelper.deleteQuicklyDirectory(tempDir); 800 FileUtils.moveDirectory(tmpDir, tempDir); 801 802 // Invalidate i18n. 803 _skinHelper.invalidateTempSkinCatalogues(skinName); 804 805 // Update lock file 806 _lockManager.updateLockFile(tempDir, SKIN_FACTORY_TOOL_ID); 807 808 Map<String, Object> values = new HashMap<>(_skinFactoryManager.getParameterValues(tempDir, modelName)); 809 result.put("parameters", values); 810 811 return result; 812 } 813 814 /** 815 * Set the theme used by a skin 816 * @param skinName The skin name 817 * @param themeId The theme id 818 * @return The theme informations, or an error code. 819 * @throws IOException if an error occurs when manipulating files 820 */ 821 @Callable 822 public Map<String, Object> updateColorTheme(String skinName, String themeId) throws IOException 823 { 824 Map<String, Object> result = new HashMap<>(); 825 826 File tempDir = _skinHelper.getTempDirectory(skinName); 827 String modelName = _skinHelper.getTempModel(skinName); 828 829 Map<String, Object> lockInfos = checkLock(tempDir); 830 if (lockInfos != null) 831 { 832 return lockInfos; 833 } 834 835 if (_modelsManager.getModel(modelName) == null) 836 { 837 result.put("unknownModel", true); 838 result.put("modelName", modelName); 839 return result; 840 } 841 842 // Save color theme 843 _skinFactoryManager.saveColorTheme(tempDir, themeId); 844 845 // Apply new color theme 846 _skinFactoryManager.applyColorTheme (modelName, tempDir); 847 848 // Update lock 849 _lockManager.updateLockFile(tempDir, SKIN_FACTORY_TOOL_ID); 850 851 result.put("themeId", themeId); 852 result.put("colors", _modelsManager.getModel(modelName).getColors(themeId)); 853 854 return result; 855 } 856 857 /** 858 * Update a parameter of the skin 859 * @param skinName The skin name 860 * @param lang The current language 861 * @param parameterId The parameter id to update 862 * @param value The new value for the parameter 863 * @param uploaded <code>true</code> if the file was uploaded 864 * @return The skin parameters updated, or an error code. 865 * @throws IOException if an error occurs when manipulating files 866 */ 867 @Callable 868 public Map<String, Object> updateParameter(String skinName, String lang, String parameterId, String value, boolean uploaded) throws IOException 869 { 870 File tempDir = _skinHelper.getTempDirectory(skinName); 871 872 Map<String, Object> lockInfos = checkLock(tempDir); 873 if (lockInfos != null) 874 { 875 return lockInfos; 876 } 877 878 String modelName = _skinHelper.getTempModel(skinName); 879 if (modelName == null) 880 { 881 Map<String, Object> result = new HashMap<>(); 882 883 result.put("unknownModel", true); 884 result.put("modelName", modelName); 885 return result; 886 } 887 888 Map<String, AbstractSkinParameter> modelParameters = _skinFactoryManager.getModelParameters(modelName); 889 AbstractSkinParameter skinParameter = modelParameters.get(parameterId); 890 if (skinParameter != null) 891 { 892 // Apply parameter 893 if (skinParameter instanceof ImageParameter) 894 { 895 FileValue fileValue = new FileValue(value, uploaded); 896 _skinFactoryManager.applyParameter(skinParameter, tempDir, modelName, fileValue, lang); 897 } 898 else 899 { 900 _skinFactoryManager.applyParameter(skinParameter, tempDir, modelName, value, lang); 901 } 902 903 // Save parameter 904 if (skinParameter instanceof I18nizableTextParameter) 905 { 906 Map<String, String> values = new HashMap<>(); 907 values.put(lang, value); 908 _skinFactoryManager.saveParameter(tempDir, parameterId, values); 909 910 _skinHelper.invalidateTempSkinCatalogue (skinName, lang); 911 } 912 else if (skinParameter instanceof ImageParameter) 913 { 914 FileValue fileValue = new FileValue(value, uploaded); 915 _skinFactoryManager.saveParameter(tempDir, parameterId, fileValue); 916 } 917 else 918 { 919 _skinFactoryManager.saveParameter(tempDir, parameterId, value); 920 } 921 922 923 // Update lock 924 _lockManager.updateLockFile(tempDir, SKIN_FACTORY_TOOL_ID); 925 } 926 927 return new HashMap<>(_skinFactoryManager.getParameterValues(tempDir, modelName)); 928 } 929 930 /** 931 * Upload a local image and set it as value for a image parameter 932 * @param uploadId The upload identifier 933 * @param fileName The name of uploaded file 934 * @param skinName The skin name 935 * @param parameterId The parameter id to update 936 * @return The skin parameters updated, or an error code. 937 * @throws IOException if an error occurs when manipulating files 938 */ 939 @Callable 940 public Map<String, Object> uploadLocalImage (String uploadId, String fileName, String skinName, String parameterId) throws IOException 941 { 942 File tempDir = _skinHelper.getTempDirectory(skinName); 943 944 Map<String, Object> lockInfos = checkLock(tempDir); 945 if (lockInfos != null) 946 { 947 return lockInfos; 948 } 949 950 String modelName = _skinHelper.getTempModel(skinName); 951 if (modelName == null) 952 { 953 Map<String, Object> result = new HashMap<>(); 954 955 result.put("unknownModel", true); 956 result.put("modelName", modelName); 957 return result; 958 } 959 960 ImageParameter imgParam = (ImageParameter) _skinFactoryManager.getModelParamater(modelName, parameterId); 961 962 Upload upload = null; 963 try 964 { 965 upload = _uploadManager.getUpload(_userProvider.getUser(), uploadId); 966 967 // Copy uploaded file into model 968 File uploadDir = _getUploadDir (tempDir, imgParam); 969 File uploadFile = new File(uploadDir, fileName); 970 971 try (OutputStream os = new FileOutputStream(uploadFile); 972 InputStream is = upload.getInputStream()) 973 { 974 IOUtils.copy(is, os); 975 } 976 catch (IOException e) 977 { 978 // close quietly 979 } 980 } 981 catch (NoSuchElementException e) 982 { 983 // Invalid upload id 984 getLogger().error(String.format("Cannot find the temporary uploaded file for id '%s' and login '%s'.", uploadId, _userProvider.getUser()), e); 985 986 Map<String, Object> result = new HashMap<>(); 987 result.put("uploadFailed", true); 988 result.put("uploadId", uploadId); 989 return result; 990 } 991 992 FileValue fileValue = new ImageParameter.FileValue(fileName, true); 993 994 // Apply parameter 995 _skinFactoryManager.applyParameter(imgParam, tempDir, modelName, fileValue, null); 996 997 // Save parameter 998 _skinFactoryManager.saveParameter(tempDir, imgParam.getId(), fileValue); 999 1000 // Update lock 1001 _lockManager.updateLockFile(tempDir, SKIN_FACTORY_TOOL_ID); 1002 1003 return new HashMap<>(_skinFactoryManager.getParameterValues(tempDir, modelName)); 1004 } 1005 1006 /** 1007 * Retrieve the list of images for the skin and parameter 1008 * @param skinName The skin name 1009 * @param paramId The parameter id 1010 * @return The map of images informations 1011 * @throws IOException if an error occurs when manipulating files 1012 */ 1013 @Callable 1014 public Map<String, Object> getGalleryImages(String skinName, String paramId) throws IOException 1015 { 1016 Map<String, Object> gallery = new HashMap<>(); 1017 1018 String modelName = _skinHelper.getTempModel(skinName); 1019 SkinModel model = _modelsManager.getModel(modelName); 1020 1021 if (model != null) 1022 { 1023 AbstractSkinParameter skinParameter = _skinFactoryManager.getModelParamater(modelName, paramId); 1024 if (skinParameter instanceof ImageParameter) 1025 { 1026 ImageParameter imageParam = (ImageParameter) skinParameter; 1027 1028 File imageDir = new File(model.getFile(), "model/images"); 1029 File libraryFile = new File(imageDir, imageParam.getLibraryPath()); 1030 gallery.put("gallery", _imageFiles2JsonObject(imageDir.getAbsolutePath(), libraryFile.getAbsolutePath(), libraryFile, modelName, true)); 1031 1032 // Uploaded local images 1033 File tempDir = _skinHelper.getTempDirectory(skinName); 1034 File uploadDir = new File (tempDir, "model/_uploads/" + imageParam.getLibraryPath()); 1035 if (uploadDir.exists()) 1036 { 1037 gallery.put("uploadedGroup", _uploadImages2JsonObject (uploadDir, skinName, imageParam)); 1038 } 1039 } 1040 } 1041 else 1042 { 1043 getLogger().warn("Unable to get gallery images : the model '" + modelName + "' does not exist anymore"); 1044 } 1045 1046 return gallery; 1047 } 1048 1049 private Map<String, Object> _uploadImages2JsonObject(File uploadDir, String skinName, ImageParameter imageParam) 1050 { 1051 Map<String, Object> uploadedGroup = new HashMap<>(); 1052 1053 uploadedGroup.put("label", new I18nizableText("plugin.skinfactory", "PLUGINS_SKINFACTORY_IMAGESGALLERY_GROUP_UPLOADED")); 1054 1055 List<Object> uploadedImages = new ArrayList<>(); 1056 for (File child : uploadDir.listFiles()) 1057 { 1058 if (_isImage(child)) 1059 { 1060 Map<String, Object> jsonObject = new HashMap<>(); 1061 jsonObject.put("type", "image"); 1062 jsonObject.put("filename", child.getName()); 1063 jsonObject.put("src", child.getName()); 1064 jsonObject.put("thumbnail", "/plugins/skinfactory/" + skinName + "/_thumbnail/64/64/model/_uploads/" + (imageParam.getLibraryPath() + '/' + child.getName()).replaceAll("\\\\", "/")); 1065 jsonObject.put("thumbnailLarge", "/plugins/skinfactory/" + skinName + "/_thumbnail/100/100/model/_uploads/" + (imageParam.getLibraryPath() + '/' + child.getName()).replaceAll("\\\\", "/")); 1066 jsonObject.put("uploaded", true); 1067 uploadedImages.add(jsonObject); 1068 } 1069 } 1070 1071 uploadedGroup.put("images", uploadedImages); 1072 1073 return uploadedGroup; 1074 } 1075 1076 private List<Object> _imageFiles2JsonObject(String imageDirPath, String libraryDirPath, File file, String modelName, boolean deep) 1077 { 1078 List<Object> imageFilesJsonObject = new ArrayList<>(); 1079 1080 for (File child : file.listFiles()) 1081 { 1082 Map<String, Object> jsonObject = new HashMap<>(); 1083 1084 if (child.isDirectory() && deep && !child.getName().equals(".svn")) 1085 { 1086 jsonObject.put("type", "group"); 1087 jsonObject.put("label", child.getName()); 1088 jsonObject.put("childs", _imageFiles2JsonObject(imageDirPath, libraryDirPath, child, modelName, false)); 1089 1090 imageFilesJsonObject.add(jsonObject); 1091 } 1092 else if (_isImage(child)) 1093 { 1094 jsonObject.put("type", "image"); 1095 jsonObject.put("filename", child.getName()); 1096 jsonObject.put("src", child.getAbsolutePath().substring(libraryDirPath.length() + 1).replaceAll("\\\\", "/")); 1097 jsonObject.put("thumbnail", "/plugins/skinfactory/" + modelName + "/_thumbnail/64/64/model/images/" + child.getAbsolutePath().substring(imageDirPath.length() + 1).replaceAll("\\\\", "/")); 1098 jsonObject.put("thumbnailLarge", "/plugins/skinfactory/" + modelName + "/_thumbnail/100/100/model/images/" + child.getAbsolutePath().substring(imageDirPath.length() + 1).replaceAll("\\\\", "/")); 1099 1100 imageFilesJsonObject.add(jsonObject); 1101 } 1102 } 1103 1104 return imageFilesJsonObject; 1105 } 1106 1107 private File _getUploadDir (File tempDir, ImageParameter imgParam) 1108 { 1109 File uploadDir = new File (tempDir, "model/_uploads/" + imgParam.getLibraryPath()); 1110 if (!uploadDir.exists()) 1111 { 1112 uploadDir.mkdirs(); 1113 } 1114 return uploadDir; 1115 } 1116 1117 private boolean _isImage(File file) 1118 { 1119 String name = file.getName().toLowerCase(); 1120 int index = name.lastIndexOf("."); 1121 String ext = name.substring(index + 1); 1122 1123 if (name.equals("thumbnail_16.png") || name.equals("thumbnail_32.png") || name.equals("thumbnail_48.png")) 1124 { 1125 return false; 1126 } 1127 1128 return "png".equals(ext) || "gif".equals(ext) || "jpg".equals(ext) || "jpeg".equals(ext); 1129 } 1130 1131 /** 1132 * Retrieve the list of gallery variants available for the specified skin and parameter 1133 * @param skinName The skin name 1134 * @param paramId The parameter id 1135 * @return The list of gallery variants 1136 * @throws IOException if an error occurs when manipulating files 1137 */ 1138 @Callable 1139 public List<Object> getGalleryVariants(String skinName, String paramId) throws IOException 1140 { 1141 List<Object> galleryVariants = new ArrayList<>(); 1142 1143 String modelName = _skinHelper.getTempModel(skinName); 1144 1145 AbstractSkinParameter skinParameter = _skinFactoryManager.getModelParamater(modelName, paramId); 1146 if (skinParameter instanceof VariantParameter) 1147 { 1148 VariantParameter variantParam = (VariantParameter) skinParameter; 1149 1150 List<Variant> variants = variantParam.getVariants(); 1151 for (Variant variant : variants) 1152 { 1153 Map<String, Object> jsonObject = new HashMap<>(); 1154 1155 jsonObject.put("value", variant.getId()); 1156 1157 String thumbnail = variant.getThumbnail(); 1158 if (thumbnail != null) 1159 { 1160 jsonObject.put("thumbnail", "/plugins/skinfactory/" + modelName + "/_thumbnail/32/32/model/variants/" + thumbnail); 1161 } 1162 else 1163 { 1164 jsonObject.put("thumbnail", "/plugins/skinfactory/resources/img/variant_default_32.png"); 1165 } 1166 1167 jsonObject.put("label", variant.getLabel()); 1168 jsonObject.put("description", variant.getDescription()); 1169 1170 galleryVariants.add(jsonObject); 1171 } 1172 } 1173 1174 return galleryVariants; 1175 } 1176 1177 /** 1178 * Get the skin model's parameters 1179 * @param modelName The model name 1180 * @return The parameters 1181 */ 1182 @Callable 1183 public Map<String, Object> getSkinModelParameters(String modelName) 1184 { 1185 Map<String, Object> skinModelParameters = new HashMap<>(); 1186 1187 skinModelParameters.put("modelName", modelName); 1188 1189 Map<String, AbstractSkinParameter> skinParameters = _skinFactoryManager.getModelParameters(modelName); 1190 1191 List<Object> parameters = new ArrayList<>(); 1192 1193 for (String id : skinParameters.keySet()) 1194 { 1195 AbstractSkinParameter skinParameter = skinParameters.get(id); 1196 parameters.add(skinParameter.toJson(modelName)); 1197 } 1198 1199 skinModelParameters.put("parameters", parameters); 1200 1201 return skinModelParameters; 1202 } 1203 1204 /** 1205 * Retrieve the list of themes' colors for a site 1206 * @param siteName The site name 1207 * @return The model's themes colors 1208 */ 1209 @Callable 1210 public List<Object> getThemeColors(String siteName) 1211 { 1212 List<Object> themesJsonObject = new ArrayList<>(); 1213 1214 String skinId = _siteManager.getSite(siteName).getSkinId(); 1215 String modelName = _skinHelper.getTempModel(skinId); 1216 1217 SkinModel model = _modelsManager.getModel(modelName); 1218 if (model != null) 1219 { 1220 Map<String, Theme> themes = model.getThemes(); 1221 for (String name : themes.keySet()) 1222 { 1223 Map<String, Object> jsonObject = new HashMap<>(); 1224 1225 Theme theme = themes.get(name); 1226 1227 jsonObject.put("id", theme.getId()); 1228 jsonObject.put("label", theme.getLabel()); 1229 jsonObject.put("colors", theme.getColors()); 1230 1231 themesJsonObject.add(jsonObject); 1232 } 1233 } 1234 else 1235 { 1236 getLogger().warn("Unable to get theme colors : the model '" + modelName + "' does not exist anymore"); 1237 } 1238 1239 return themesJsonObject; 1240 } 1241}