001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software GmbH & Co. KG, please see the 018 * company website: http://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: http://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.workplace.editors; 029 030import org.opencms.file.CmsFile; 031import org.opencms.file.CmsObject; 032import org.opencms.file.CmsRequestContext; 033import org.opencms.file.CmsResource; 034import org.opencms.file.CmsResourceFilter; 035import org.opencms.file.collectors.A_CmsResourceCollector; 036import org.opencms.i18n.CmsEncoder; 037import org.opencms.i18n.CmsLocaleManager; 038import org.opencms.i18n.CmsMultiMessages; 039import org.opencms.json.JSONArray; 040import org.opencms.json.JSONException; 041import org.opencms.json.JSONObject; 042import org.opencms.jsp.CmsJspActionElement; 043import org.opencms.lock.CmsLockType; 044import org.opencms.main.CmsException; 045import org.opencms.main.CmsLog; 046import org.opencms.main.OpenCms; 047import org.opencms.util.CmsRequestUtil; 048import org.opencms.util.CmsStringUtil; 049import org.opencms.util.CmsUUID; 050import org.opencms.widgets.A_CmsWidget; 051import org.opencms.widgets.I_CmsWidget; 052import org.opencms.widgets.I_CmsWidgetDialog; 053import org.opencms.widgets.I_CmsWidgetParameter; 054import org.opencms.workplace.CmsWorkplace; 055import org.opencms.workplace.CmsWorkplaceSettings; 056import org.opencms.workplace.editors.directedit.CmsDirectEditButtonSelection; 057import org.opencms.xml.CmsXmlContentDefinition; 058import org.opencms.xml.CmsXmlEntityResolver; 059import org.opencms.xml.CmsXmlException; 060import org.opencms.xml.CmsXmlUtils; 061import org.opencms.xml.content.CmsXmlContent; 062import org.opencms.xml.content.CmsXmlContentErrorHandler; 063import org.opencms.xml.content.CmsXmlContentFactory; 064import org.opencms.xml.content.CmsXmlContentTab; 065import org.opencms.xml.content.CmsXmlContentValueSequence; 066import org.opencms.xml.types.CmsXmlNestedContentDefinition; 067import org.opencms.xml.types.I_CmsXmlContentValue; 068import org.opencms.xml.types.I_CmsXmlSchemaType; 069 070import java.io.IOException; 071import java.io.UnsupportedEncodingException; 072import java.util.ArrayList; 073import java.util.HashSet; 074import java.util.Iterator; 075import java.util.List; 076import java.util.Locale; 077import java.util.Map; 078import java.util.Set; 079 080import javax.servlet.ServletException; 081import javax.servlet.http.HttpServletRequest; 082import javax.servlet.jsp.JspException; 083 084import org.apache.commons.logging.Log; 085 086/** 087 * Creates the editor for XML content definitions.<p> 088 * 089 * @since 6.0.0 090 */ 091public class CmsXmlContentEditor extends CmsEditor implements I_CmsWidgetDialog { 092 093 /** Action for checking content before executing the direct edit action. */ 094 public static final int ACTION_CHECK = 151; 095 096 /** Action for confirming the XML content structure correction. */ 097 public static final int ACTION_CONFIRMCORRECTION = 152; 098 099 /** Value for the action: copy the current locale. */ 100 public static final int ACTION_COPYLOCALE = 141; 101 102 /** Action for correction of the XML content structure confirmed. */ 103 public static final int ACTION_CORRECTIONCONFIRMED = 153; 104 105 /** Action for optional element creation. */ 106 public static final int ACTION_ELEMENT_ADD = 154; 107 108 /** Action for element move down operation. */ 109 public static final int ACTION_ELEMENT_MOVE_DOWN = 155; 110 111 /** Action for element move up operation. */ 112 public static final int ACTION_ELEMENT_MOVE_UP = 156; 113 114 /** Action for optional element removal. */ 115 public static final int ACTION_ELEMENT_REMOVE = 157; 116 117 /** Action for new file creation. */ 118 public static final int ACTION_NEW = 158; 119 120 /** Action that sub choices should be determined. */ 121 public static final int ACTION_SUBCHOICES = 159; 122 123 /** Request context attribute for the page from which the editor was opened. */ 124 public static final String ATTRIBUTE_EDITCONTEXT = CmsXmlContentEditor.class.getName() + ".ATTRIBUTE_EDITCONTEXT"; 125 126 /** Indicates that the content should be checked before executing the direct edit action. */ 127 public static final String EDITOR_ACTION_CHECK = "check"; 128 129 /** Indicates that the correction of the XML content structure should be confirmed. */ 130 public static final String EDITOR_ACTION_CONFIRMCORRECTION = "confirmcorrect"; 131 132 /** Indicates an optional element should be created. */ 133 public static final String EDITOR_ACTION_ELEMENT_ADD = "addelement"; 134 135 /** Indicates an element should be moved down. */ 136 public static final String EDITOR_ACTION_ELEMENT_MOVE_DOWN = "elementdown"; 137 138 /** Indicates an element should be moved up. */ 139 public static final String EDITOR_ACTION_ELEMENT_MOVE_UP = "elementup"; 140 141 /** Indicates an optional element should be removed. */ 142 public static final String EDITOR_ACTION_ELEMENT_REMOVE = "removeelement"; 143 144 /** Indicates a new file should be created. */ 145 public static final String EDITOR_ACTION_NEW = CmsDirectEditButtonSelection.VALUE_NEW; 146 147 /** Indicates that sub choices should be determined. */ 148 public static final String EDITOR_ACTION_SUBCHOICES = "subchoices"; 149 150 /** Indicates that the contents of the current locale should be copied to other locales. */ 151 public static final String EDITOR_COPYLOCALE = "copylocale"; 152 153 /** Indicates that the correction of the XML content structure was confirmed by the user. */ 154 public static final String EDITOR_CORRECTIONCONFIRMED = "correctconfirmed"; 155 156 /** Parameter name for the request parameter "choiceelement". */ 157 public static final String PARAM_CHOICEELEMENT = "choiceelement"; 158 159 /** Parameter name for the request parameter "choicetype". */ 160 public static final String PARAM_CHOICETYPE = "choicetype"; 161 162 /** Parameter name for the request parameter "editcontext". */ 163 public static final String PARAM_EDITCONTEXT = "editcontext"; 164 165 /** Parameter name for the request parameter "elementindex". */ 166 public static final String PARAM_ELEMENTINDEX = "elementindex"; 167 168 /** Parameter name for the request parameter "elementname". */ 169 public static final String PARAM_ELEMENTNAME = "elementname"; 170 171 /** Parameter name for the request parameter "newlink". */ 172 public static final String PARAM_NEWLINK = "newlink"; 173 174 /** Constant for the editor type, must be the same as the editors subfolder name in the VFS. */ 175 private static final String EDITOR_TYPE = "xmlcontent"; 176 177 /** The log object for this class. */ 178 private static final Log LOG = CmsLog.getLog(CmsXmlContentEditor.class); 179 180 /** The content object to edit. */ 181 private CmsXmlContent m_content; 182 183 /** The currently active tab during form generation. */ 184 private CmsXmlContentTab m_currentTab; 185 186 /** The currently active tab index during form generation. */ 187 private int m_currentTabIndex; 188 189 /** The element locale. */ 190 private Locale m_elementLocale; 191 192 /** The list of tabs that have an element with an error. */ 193 private List<CmsXmlContentTab> m_errorTabs; 194 195 /** File object used to read and write contents. */ 196 private CmsFile m_file; 197 198 /** The set of help message IDs that have already been used. */ 199 private Set<String> m_helpMessageIds; 200 201 /** The content creation mode. */ 202 private String m_mode; 203 204 /** Indicates if an optional element is included in the form. */ 205 private boolean m_optionalElementPresent; 206 207 /** Parameter stores the name of the choice element to add. */ 208 private String m_paramChoiceElement; 209 210 /** Parameter stores the flag if the element to add is a choice type. */ 211 private String m_paramChoiceType; 212 213 /** The page in whose context the content is being edited. */ 214 private String m_paramEditContext; 215 216 /** Parameter stores the index of the element to add or remove. */ 217 private String m_paramElementIndex; 218 219 /** Parameter stores the name of the element to add or remove. */ 220 private String m_paramElementName; 221 222 /** The selected model file for the new resource. */ 223 private String m_paramModelFile; 224 225 /** Parameter to indicate if a new XML content resource should be created. */ 226 private String m_paramNewLink; 227 228 /** The post-create handler class. */ 229 private String m_postCreateHandler; 230 231 /** The error handler for the xml content. */ 232 private CmsXmlContentErrorHandler m_validationHandler; 233 234 /** The list of tabs that have an element with a warning. */ 235 private List<CmsXmlContentTab> m_warningTabs; 236 237 /** Visitor implementation that stored the widgets for the content. */ 238 private CmsXmlContentWidgetVisitor m_widgetCollector; 239 240 /** 241 * Public constructor.<p> 242 * 243 * @param jsp an initialized JSP action element 244 */ 245 public CmsXmlContentEditor(CmsJspActionElement jsp) { 246 247 super(jsp); 248 } 249 250 /** 251 * Performs the change element language action of the editor.<p> 252 */ 253 public void actionChangeElementLanguage() { 254 255 // save eventually changed content of the editor 256 Locale oldLocale = CmsLocaleManager.getLocale(getParamOldelementlanguage()); 257 Locale newLocale = getElementLocale(); 258 try { 259 setEditorValues(oldLocale); 260 if (!m_content.validate(getCms()).hasErrors(oldLocale)) { 261 // no errors found in content 262 if (!m_content.hasLocale(newLocale)) { 263 // check if we should copy the content from a default locale 264 boolean addNew = true; 265 List<Locale> locales = OpenCms.getLocaleManager().getDefaultLocales(getCms(), getParamResource()); 266 if (locales.size() > 1) { 267 // default locales have been set, try to find a match 268 try { 269 m_content.copyLocale(locales, newLocale); 270 addNew = false; 271 } catch (CmsXmlException e) { 272 // no matching default locale was available, we will create a new one later 273 } 274 } 275 if (addNew) { 276 // create new element if selected language element is not present 277 try { 278 m_content.addLocale(getCms(), newLocale); 279 } catch (CmsXmlException e) { 280 if (LOG.isErrorEnabled()) { 281 LOG.error(e.getLocalizedMessage(), e); 282 } 283 } 284 } 285 } 286 //save to temporary file 287 writeContent(); 288 // set default action to suppress error messages 289 setAction(ACTION_DEFAULT); 290 } else { 291 // errors found, switch back to old language to show errors 292 setParamElementlanguage(getParamOldelementlanguage()); 293 // set stored locale to null to reinitialize it 294 m_elementLocale = null; 295 } 296 } catch (Exception e) { 297 // should usually never happen 298 if (LOG.isInfoEnabled()) { 299 LOG.info(e.getLocalizedMessage(), e); 300 } 301 } 302 } 303 304 /** 305 * Deletes the temporary file and unlocks the edited resource when in direct edit mode.<p> 306 * 307 * @param forceUnlock if true, the resource will be unlocked anyway 308 */ 309 @Override 310 public void actionClear(boolean forceUnlock) { 311 312 // delete the temporary file 313 deleteTempFile(); 314 boolean directEditMode = Boolean.valueOf(getParamDirectedit()).booleanValue(); 315 boolean modified = Boolean.valueOf(getParamModified()).booleanValue(); 316 if (directEditMode || forceUnlock || !modified) { 317 // unlock the resource when in direct edit mode, force unlock is true or resource was not modified 318 try { 319 getCms().unlockResource(getParamResource()); 320 } catch (CmsException e) { 321 // should usually never happen 322 if (LOG.isInfoEnabled()) { 323 LOG.info(e.getLocalizedMessage(), e); 324 } 325 } 326 } 327 } 328 329 /** 330 * Performs the copy locale action.<p> 331 * 332 * @throws JspException if something goes wrong 333 */ 334 public void actionCopyElementLocale() throws JspException { 335 336 try { 337 setEditorValues(getElementLocale()); 338 if (!hasValidationErrors()) { 339 // save content of the editor only to the temporary file 340 writeContent(); 341 CmsObject cloneCms = getCloneCms(); 342 CmsUUID tempProjectId = OpenCms.getWorkplaceManager().getTempFileProjectId(); 343 cloneCms.getRequestContext().setCurrentProject(getCms().readProject(tempProjectId)); 344 // remove eventual release & expiration date from temporary file to make preview work 345 cloneCms.setDateReleased(getParamTempfile(), CmsResource.DATE_RELEASED_DEFAULT, false); 346 cloneCms.setDateExpired(getParamTempfile(), CmsResource.DATE_EXPIRED_DEFAULT, false); 347 } 348 } catch (CmsException e) { 349 // show error page 350 showErrorPage(this, e); 351 } 352 } 353 354 /** 355 * Performs the delete locale action.<p> 356 * 357 * @throws JspException if something goes wrong 358 */ 359 public void actionDeleteElementLocale() throws JspException { 360 361 try { 362 Locale loc = getElementLocale(); 363 m_content.removeLocale(loc); 364 //write the modified xml content 365 writeContent(); 366 List<Locale> locales = m_content.getLocales(); 367 if (locales.size() > 0) { 368 // set first locale as new display locale 369 Locale newLoc = locales.get(0); 370 setParamElementlanguage(newLoc.toString()); 371 m_elementLocale = newLoc; 372 } else { 373 if (LOG.isErrorEnabled()) { 374 LOG.error(Messages.get().getBundle().key(Messages.LOG_GET_LOCALES_1, getParamResource())); 375 } 376 } 377 378 } catch (CmsXmlException e) { 379 // an error occurred while trying to delete the locale, stop action 380 showErrorPage(e); 381 } catch (CmsException e) { 382 // should usually never happen 383 if (LOG.isInfoEnabled()) { 384 LOG.info(e.getLocalizedMessage(), e); 385 } 386 } 387 } 388 389 /** 390 * Performs a configurable action performed by the editor.<p> 391 * 392 * The default action is: save resource, clear temporary files and publish the resource directly.<p> 393 * 394 * @throws IOException if a forward fails 395 * @throws ServletException of a forward fails 396 * @throws JspException if including a JSP fails 397 */ 398 public void actionDirectEdit() throws IOException, JspException, ServletException { 399 400 // get the action class from the OpenCms runtime property 401 I_CmsEditorActionHandler actionClass = OpenCms.getWorkplaceManager().getEditorActionHandler(); 402 if (actionClass == null) { 403 // error getting the action class, save content and exit the editor 404 actionSave(); 405 actionExit(); 406 } else { 407 actionClass.editorAction(this, getJsp()); 408 } 409 } 410 411 /** 412 * Performs the exit editor action.<p> 413 * 414 * @see org.opencms.workplace.editors.CmsEditor#actionExit() 415 */ 416 @Override 417 public void actionExit() throws IOException, JspException, ServletException { 418 419 if (getAction() == ACTION_CANCEL) { 420 // save and exit was canceled 421 return; 422 } 423 // unlock resource if we are in direct edit mode 424 actionClear(false); 425 // close the editor 426 actionClose(); 427 } 428 429 /** 430 * Moves an element in the xml content either up or down.<p> 431 * 432 * Depends on the given action value.<p> 433 * 434 * @throws JspException if including the error page fails 435 */ 436 public void actionMoveElement() throws JspException { 437 438 // set editor values from request 439 try { 440 setEditorValues(getElementLocale()); 441 } catch (CmsXmlException e) { 442 // an error occurred while trying to set the values, stop action 443 showErrorPage(e); 444 return; 445 } 446 // get the necessary parameters to move the element 447 int index = 0; 448 try { 449 index = Integer.parseInt(getParamElementIndex()); 450 } catch (Exception e) { 451 // ignore, should not happen 452 } 453 454 // get the value to move 455 I_CmsXmlContentValue value = m_content.getValue(getParamElementName(), getElementLocale(), index); 456 457 if (getAction() == ACTION_ELEMENT_MOVE_DOWN) { 458 // move down the value 459 value.moveDown(); 460 } else { 461 // move up the value 462 value.moveUp(); 463 } 464 if (getValidationHandler().hasWarnings(getElementLocale())) { 465 // there were warnings for the edited content, reset validation handler to avoid display issues 466 resetErrorHandler(); 467 } 468 try { 469 // write the modified content to the temporary file 470 writeContent(); 471 } catch (CmsException e) { 472 // an error occurred while trying to save 473 showErrorPage(e); 474 } 475 } 476 477 /** 478 * Creates a new XML content item for editing.<p> 479 * 480 * @throws JspException in case something goes wrong 481 */ 482 public void actionNew() throws JspException { 483 484 String newFileName = ""; 485 try { 486 newFileName = A_CmsResourceCollector.createResourceForCollector( 487 getCms(), 488 m_paramNewLink, 489 getElementLocale(), 490 getParamResource(), 491 getParamModelFile(), 492 getParamMode(), 493 getParamPostCreateHandler()); 494 // wipe out parameters for the editor to ensure proper operation 495 setParamNewLink(null); 496 setParamAction(null); 497 setParamResource(newFileName); 498 setAction(ACTION_DEFAULT); 499 500 // create the temporary file to work with 501 setParamTempfile(createTempFile()); 502 503 // set the member variables for the content 504 m_file = getCms().readFile(getParamTempfile(), CmsResourceFilter.ALL); 505 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(getParamModelFile())) { 506 m_content = CmsXmlContentFactory.unmarshal( 507 getCms(), 508 getCms().readFile(newFileName, CmsResourceFilter.ALL)); 509 } else { 510 m_content = CmsXmlContentFactory.unmarshal(getCms(), m_file); 511 } 512 513 } catch (CmsException e) { 514 if (LOG.isErrorEnabled()) { 515 LOG.error(Messages.get().getBundle().key(Messages.LOG_CREATE_XML_CONTENT_ITEM_1, m_paramNewLink), e); 516 } 517 throw new JspException(e); 518 } finally { 519 try { 520 // delete the new file 521 getCms().deleteResource(newFileName, CmsResource.DELETE_REMOVE_SIBLINGS); 522 } catch (CmsException e) { 523 // ignore 524 } 525 } 526 } 527 528 /** 529 * Performs the preview XML content action in a new browser window.<p> 530 * 531 * @throws IOException if redirect fails 532 * @throws JspException if inclusion of error page fails 533 */ 534 public void actionPreview() throws IOException, JspException { 535 536 try { 537 // save content of the editor only to the temporary file 538 setEditorValues(getElementLocale()); 539 writeContent(); 540 CmsObject cloneCms = getCloneCms(); 541 CmsUUID tempProjectId = OpenCms.getWorkplaceManager().getTempFileProjectId(); 542 cloneCms.getRequestContext().setCurrentProject(getCms().readProject(tempProjectId)); 543 // remove eventual release & expiration date from temporary file to make preview work 544 cloneCms.setDateReleased(getParamTempfile(), CmsResource.DATE_RELEASED_DEFAULT, false); 545 cloneCms.setDateExpired(getParamTempfile(), CmsResource.DATE_EXPIRED_DEFAULT, false); 546 547 } catch (CmsException e) { 548 // show error page 549 showErrorPage(this, e); 550 } 551 552 // get preview uri from content handler 553 String previewUri = m_content.getHandler().getPreview(getCms(), m_content, getParamTempfile()); 554 555 // create locale request parameter 556 StringBuffer param = new StringBuffer(8); 557 if (previewUri.indexOf('?') != -1) { 558 param.append("&"); 559 } else { 560 param.append("?"); 561 } 562 param.append(CmsLocaleManager.PARAMETER_LOCALE); 563 param.append("="); 564 param.append(getParamElementlanguage()); 565 566 // redirect to the temporary file with currently active element language or to the specified preview uri 567 sendCmsRedirect(previewUri + param); 568 } 569 570 /** 571 * Performs the save content action.<p> 572 * 573 * @see org.opencms.workplace.editors.CmsEditor#actionSave() 574 */ 575 @Override 576 public void actionSave() throws JspException { 577 578 actionSave(getElementLocale()); 579 if (getAction() != ACTION_CANCEL) { 580 // save successful, set save action 581 setAction(ACTION_SAVE); 582 } 583 } 584 585 /** 586 * Performs the save content action.<p> 587 * 588 * This is also used when changing the element language.<p> 589 * 590 * @param locale the locale to save the content 591 * @throws JspException if including the error page fails 592 */ 593 public void actionSave(Locale locale) throws JspException { 594 595 try { 596 setEditorValues(locale); 597 // check if content has errors 598 if (!hasValidationErrors()) { 599 // no errors found, write content and copy temp file contents 600 writeContent(); 601 commitTempFile(); 602 // set the modified parameter 603 setParamModified(Boolean.TRUE.toString()); 604 // update the offline search indices 605 OpenCms.getSearchManager().updateOfflineIndexes(); 606 } 607 } catch (CmsException e) { 608 showErrorPage(e); 609 } 610 611 } 612 613 /** 614 * Adds an optional element to the XML content or removes an optional element from the XML content.<p> 615 * 616 * Depends on the given action value.<p> 617 * 618 * @throws JspException if including the error page fails 619 */ 620 public void actionToggleElement() throws JspException { 621 622 // set editor values from request 623 try { 624 setEditorValues(getElementLocale()); 625 } catch (CmsXmlException e) { 626 // an error occurred while trying to set the values, stop action 627 showErrorPage(e); 628 return; 629 } 630 631 // get the necessary parameters to add/remove the element 632 int index = 0; 633 try { 634 index = Integer.parseInt(getParamElementIndex()); 635 } catch (Exception e) { 636 // ignore, should not happen 637 } 638 639 if (getAction() == ACTION_ELEMENT_REMOVE) { 640 // remove the value , first get the value to remove 641 I_CmsXmlContentValue value = m_content.getValue(getParamElementName(), getElementLocale(), index); 642 m_content.removeValue(getParamElementName(), getElementLocale(), index); 643 // check if the value was a choice option and the last one 644 if (value.isChoiceOption() 645 && (m_content.getSubValues( 646 CmsXmlUtils.removeLastXpathElement(getParamElementName()), 647 getElementLocale()).size() == 0)) { 648 // also remove the parent choice type value 649 String xpath = CmsXmlUtils.removeLastXpathElement(getParamElementName()); 650 m_content.removeValue(xpath, getElementLocale(), CmsXmlUtils.getXpathIndexInt(xpath) - 1); 651 } 652 } else { 653 // add the new value after the clicked element 654 if (m_content.hasValue(getParamElementName(), getElementLocale())) { 655 // when other values are present, increase index to use right position 656 index += 1; 657 } 658 String elementPath = getParamElementName(); 659 if (CmsStringUtil.isNotEmpty(getParamChoiceElement())) { 660 // we have to add a choice element, first check if the element to add itself is part of a choice or not 661 boolean choiceType = Boolean.valueOf(getParamChoiceType()).booleanValue(); 662 I_CmsXmlSchemaType elemType = m_content.getContentDefinition().getSchemaType(elementPath); 663 if (!choiceType || (elemType.isChoiceOption() && elemType.isChoiceType())) { 664 // this is a choice option or a nested choice type to add, remove last element name from xpath 665 elementPath = CmsXmlUtils.removeLastXpathElement(elementPath); 666 } else { 667 // this is a choice type to add, first create type element 668 m_content.addValue(getCms(), elementPath, getElementLocale(), index); 669 elementPath = CmsXmlUtils.createXpathElement(elementPath, index + 1); 670 // all eventual following elements to create have to be at first position 671 index = 0; 672 } 673 // check if there are nested choice elements to add 674 if (CmsXmlUtils.isDeepXpath(getParamChoiceElement())) { 675 // create all missing elements except the last one 676 String pathToChoice = CmsXmlUtils.removeLastXpathElement(getParamChoiceElement()); 677 String newPath = elementPath; 678 while (CmsStringUtil.isNotEmpty(pathToChoice)) { 679 String createElement = CmsXmlUtils.getFirstXpathElement(pathToChoice); 680 newPath = CmsXmlUtils.concatXpath(newPath, createElement); 681 pathToChoice = CmsXmlUtils.isDeepXpath(pathToChoice) 682 ? CmsXmlUtils.removeFirstXpathElement(pathToChoice) 683 : null; 684 I_CmsXmlContentValue newVal = m_content.addValue(getCms(), newPath, getElementLocale(), index); 685 newPath = newVal.getPath(); 686 // all eventual following elements to create have to be at first position 687 index = 0; 688 } 689 // create the path to the last choice element 690 elementPath = CmsXmlUtils.concatXpath( 691 newPath, 692 CmsXmlUtils.getLastXpathElement(getParamChoiceElement())); 693 } else { 694 // create the path to the choice element 695 elementPath += "/" + getParamChoiceElement(); 696 } 697 } 698 // add the value 699 m_content.addValue(getCms(), elementPath, getElementLocale(), index); 700 } 701 702 if (getValidationHandler().hasWarnings(getElementLocale())) { 703 // there were warnings for the edited content, reset validation handler to avoid display issues 704 resetErrorHandler(); 705 } 706 707 try { 708 // write the modified content to the temporary file 709 writeContent(); 710 } catch (CmsException e) { 711 // an error occurred while trying to save 712 showErrorPage(e); 713 } 714 715 } 716 717 /** 718 * Returns the JSON array with information about the choices of a given element.<p> 719 * 720 * The returned array is only filled if the given element has choice options, otherwise an empty array is returned.<br/> 721 * Note: the first array element is an object containing information if the element itself is a choice type, 722 * the following elements are the choice option items.<p> 723 * 724 * @param elementName the element name to check (complete xpath) 725 * @param choiceType flag indicating if the given element name represents a choice type or not 726 * @param checkChoice flag indicating if the element name should be checked if it is a choice option and choice type 727 * 728 * @return the JSON array with information about the choices of a given element 729 */ 730 public JSONArray buildElementChoices(String elementName, boolean choiceType, boolean checkChoice) { 731 732 JSONArray choiceElements = new JSONArray(); 733 String choiceName = elementName; 734 I_CmsXmlSchemaType elemType = m_content.getContentDefinition().getSchemaType(elementName); 735 if (checkChoice && elemType.isChoiceOption() && elemType.isChoiceType()) { 736 // the element itself is a choice option and again a choice type, remove the last element to get correct choices 737 choiceName = CmsXmlUtils.removeLastXpathElement(elementName); 738 } 739 // use xpath to get choice information 740 if (m_content.hasChoiceOptions(choiceName, getElementLocale())) { 741 // we have choice options, first add information about type to create 742 JSONObject info = new JSONObject(); 743 try { 744 // put information if element is a choice type 745 info.put("choicetype", choiceType); 746 choiceElements.put(info); 747 // get the available choice options for the choice element 748 List<I_CmsXmlSchemaType> options = m_content.getChoiceOptions(choiceName, getElementLocale()); 749 for (Iterator<I_CmsXmlSchemaType> i = options.iterator(); i.hasNext();) { 750 // add the available element options 751 I_CmsXmlSchemaType type = i.next(); 752 JSONObject option = new JSONObject(); 753 String key = A_CmsWidget.LABEL_PREFIX 754 + type.getContentDefinition().getInnerName() 755 + "." 756 + type.getName(); 757 // add element name, label and help info 758 option.put("name", type.getName()); 759 option.put("label", keyDefault(key, type.getName())); 760 option.put("help", keyDefault(key + A_CmsWidget.HELP_POSTFIX, "")); 761 // add info if the choice itself is a (sub) choice type 762 option.put("subchoice", type.isChoiceType()); 763 choiceElements.put(option); 764 } 765 } catch (JSONException e) { 766 // ignore, should not happen 767 } 768 } 769 return choiceElements; 770 } 771 772 /** 773 * Builds the HTML String for the element language selector.<p> 774 * 775 * This method has to use the resource request parameter because the temporary file is 776 * not available in the upper button frame.<p> 777 * 778 * @param attributes optional attributes for the <select> tag 779 * @return the HTML for the element language select box 780 */ 781 public String buildSelectElementLanguage(String attributes) { 782 783 return buildSelectElementLanguage(attributes, getParamResource(), getElementLocale()); 784 } 785 786 /** 787 * Returns the available sub choices for a nested choice element.<p> 788 * 789 * @return the available sub choices for a nested choice element as JSON array string 790 */ 791 public String buildSubChoices() { 792 793 String elementPath = getParamElementName(); 794 795 // we have to add a choice element, first check if the element to add itself is part of a choice or not 796 boolean choiceType = Boolean.valueOf(getParamChoiceType()).booleanValue(); 797 I_CmsXmlSchemaType elemType = m_content.getContentDefinition().getSchemaType(elementPath); 798 if (!choiceType || (elemType.isChoiceOption() && elemType.isChoiceType())) { 799 // this is a choice option or a nested choice type to add, remove last element name from xpath 800 elementPath = CmsXmlUtils.removeLastXpathElement(elementPath); 801 } 802 elementPath = CmsXmlUtils.concatXpath(elementPath, getParamChoiceElement()); 803 return buildElementChoices(elementPath, choiceType, false).toString(); 804 } 805 806 /** 807 * @see org.opencms.widgets.I_CmsWidgetDialog#getButtonStyle() 808 */ 809 public int getButtonStyle() { 810 811 return getSettings().getUserSettings().getEditorButtonStyle(); 812 } 813 814 /** 815 * @see org.opencms.workplace.editors.CmsEditor#getEditorResourceUri() 816 */ 817 @Override 818 public String getEditorResourceUri() { 819 820 return getSkinUri() + "editors/" + EDITOR_TYPE + "/"; 821 } 822 823 /** 824 * Returns the current element locale.<p> 825 * 826 * @return the current element locale 827 */ 828 public Locale getElementLocale() { 829 830 if (m_elementLocale == null) { 831 if (CmsStringUtil.isNotEmpty(getParamElementlanguage()) && !"null".equals(getParamElementlanguage())) { 832 m_elementLocale = CmsLocaleManager.getLocale(getParamElementlanguage()); 833 } else { 834 initElementLanguage(); 835 m_elementLocale = CmsLocaleManager.getLocale(getParamElementlanguage()); 836 } 837 } 838 return m_elementLocale; 839 } 840 841 /** 842 * @see org.opencms.widgets.I_CmsWidgetDialog#getHelpMessageIds() 843 */ 844 public Set<String> getHelpMessageIds() { 845 846 if (m_helpMessageIds == null) { 847 m_helpMessageIds = new HashSet<String>(); 848 } 849 return m_helpMessageIds; 850 } 851 852 /** 853 * Returns the name of the choice element to add.<p> 854 * 855 * @return the name of the choice element to add 856 */ 857 public String getParamChoiceElement() { 858 859 return m_paramChoiceElement; 860 } 861 862 /** 863 * Returns the flag if the element to add is a choice type.<p> 864 * 865 * @return the flag if the element to add is a choice type 866 */ 867 public String getParamChoiceType() { 868 869 return m_paramChoiceType; 870 } 871 872 /** 873 * Gets the editor context path (usually either a container page path or null).<p> 874 * 875 * @return the editor context path 876 */ 877 public String getParamEditContext() { 878 879 return m_paramEditContext; 880 } 881 882 /** 883 * Returns the index of the element to add or remove.<p> 884 * 885 * @return the index of the element to add or remove 886 */ 887 public String getParamElementIndex() { 888 889 return m_paramElementIndex; 890 } 891 892 /** 893 * Returns the name of the element to add or remove.<p> 894 * 895 * @return the name of the element to add or remove 896 */ 897 public String getParamElementName() { 898 899 return m_paramElementName; 900 } 901 902 /** 903 * Returns the mode.<p> 904 * 905 * @return the mode 906 */ 907 public String getParamMode() { 908 909 return m_mode; 910 } 911 912 /** 913 * Returns the parameter that specifies the model file name.<p> 914 * 915 * @return the parameter that specifies the model file name 916 */ 917 public String getParamModelFile() { 918 919 return m_paramModelFile; 920 } 921 922 /** 923 * Returns the "new link" parameter.<p> 924 * 925 * @return the "new link" parameter 926 */ 927 public String getParamNewLink() { 928 929 return m_paramNewLink; 930 } 931 932 /** 933 * Returns the postCreateHandler.<p> 934 * 935 * @return the postCreateHandler 936 */ 937 public String getParamPostCreateHandler() { 938 939 return m_postCreateHandler; 940 } 941 942 /** 943 * @see org.opencms.widgets.I_CmsWidgetDialog#getUserAgent() 944 */ 945 public String getUserAgent() { 946 947 return getJsp().getRequest().getHeader(CmsRequestUtil.HEADER_USER_AGENT); 948 } 949 950 /** 951 * Returns the different xml editor widgets used in the form to display.<p> 952 * 953 * @return the different xml editor widgets used in the form to display 954 */ 955 public CmsXmlContentWidgetVisitor getWidgetCollector() { 956 957 if (m_widgetCollector == null) { 958 // create an instance of the widget collector 959 m_widgetCollector = new CmsXmlContentWidgetVisitor(getCms(), getElementLocale()); 960 m_content.visitAllValuesWith(m_widgetCollector); 961 } 962 return m_widgetCollector; 963 } 964 965 /** 966 * Generates the HTML form for the XML content editor.<p> 967 * 968 * @return the HTML that generates the form for the XML editor 969 */ 970 public String getXmlEditorForm() { 971 972 // set "editor mode" attribute (required for link replacement in the root site) 973 getCms().getRequestContext().setAttribute(CmsRequestContext.ATTRIBUTE_EDITOR, Boolean.TRUE); 974 975 // add customized message bundle eventually specified in XSD of XML content 976 addMessages(m_content.getHandler().getMessages(getLocale())); 977 ((CmsMultiMessages)getMessages()).setFallbackHandler(m_content.getHandler().getMessageKeyHandler()); 978 // initialize tab lists for error handling before generating the editor form 979 m_errorTabs = new ArrayList<CmsXmlContentTab>(); 980 m_warningTabs = new ArrayList<CmsXmlContentTab>(); 981 982 return getXmlEditorForm(m_content.getContentDefinition(), "", true, false).toString(); 983 } 984 985 /** 986 * Generates the HTML for the end of the HTML editor form page.<p> 987 * 988 * @return the HTML for the end of the HTML editor form page 989 * @throws JspException if including the error page fails 990 */ 991 public String getXmlEditorHtmlEnd() throws JspException { 992 993 StringBuffer result = new StringBuffer(16384); 994 if (m_optionalElementPresent) { 995 // disabled optional element(s) present, reset widgets to show help bubbles on optional form entries 996 resetWidgetCollector(); 997 } 998 try { 999 // get all widgets from collector 1000 Iterator<String> i = getWidgetCollector().getWidgets().keySet().iterator(); 1001 while (i.hasNext()) { 1002 // get the value of the widget 1003 String key = i.next(); 1004 I_CmsXmlContentValue value = getWidgetCollector().getValues().get(key); 1005 I_CmsWidget widget = getWidgetCollector().getWidgets().get(key); 1006 result.append(widget.getDialogHtmlEnd(getCms(), this, (I_CmsWidgetParameter)value)); 1007 1008 } 1009 1010 // add empty help text layer 1011 result.append("<div class=\"help\" id=\"helpText\" "); 1012 result.append("onmouseover=\"showHelpText();\" onmouseout=\"hideHelpText();\"></div>\n"); 1013 1014 // add empty element button layer 1015 result.append("<div class=\"xmlButtons\" id=\"xmlElementButtons\" "); 1016 result.append( 1017 "onmouseover=\"checkElementButtons(true);\" onmouseout=\"checkElementButtons(false);\"></div>\n"); 1018 1019 // return the HTML 1020 return result.toString(); 1021 } catch (Exception e) { 1022 showErrorPage(e); 1023 return ""; 1024 } 1025 } 1026 1027 /** 1028 * Generates the JavaScript includes for the used widgets in the editor form.<p> 1029 * 1030 * @return the JavaScript includes for the used widgets 1031 * @throws JspException if including the error page fails 1032 */ 1033 public String getXmlEditorIncludes() throws JspException { 1034 1035 StringBuffer result = new StringBuffer(1024); 1036 1037 // first include general JQuery JS and UI components 1038 result.append("<script src=\""); 1039 result.append(CmsWorkplace.getSkinUri()).append("jquery/packed/jquery.js"); 1040 result.append("\"></script>\n"); 1041 result.append("<script src=\""); 1042 result.append(CmsWorkplace.getSkinUri()).append("jquery/packed/jquery.ui.js"); 1043 result.append("\"></script>\n"); 1044 1045 // including dialog-helper.js to be used by ADE gallery widgets 1046 result.append("<script src=\""); 1047 result.append(CmsWorkplace.getSkinUri()).append("components/widgets/dialog-helper.js"); 1048 result.append("\"></script>\n"); 1049 1050 // import the JavaScript for JSON helper functions 1051 result.append("<script src=\""); 1052 result.append(CmsWorkplace.getSkinUri()).append("commons/json2.js"); 1053 result.append("\"></script>\n"); 1054 result.append("<link rel=\"stylesheet\" type=\"text/css\" href=\""); 1055 result.append(CmsWorkplace.getSkinUri()).append("jquery/css/ui-ocms/jquery.ui.css"); 1056 result.append("\">\n"); 1057 result.append("<link rel=\"stylesheet\" type=\"text/css\" href=\""); 1058 result.append(CmsWorkplace.getSkinUri()).append("jquery/css/ui-ocms/jquery.ui.ocms.css"); 1059 result.append("\">\n"); 1060 1061 try { 1062 // iterate over unique widgets from collector 1063 Iterator<I_CmsWidget> i = getWidgetCollector().getUniqueWidgets().iterator(); 1064 while (i.hasNext()) { 1065 I_CmsWidget widget = i.next(); 1066 result.append(widget.getDialogIncludes(getCms(), this)); 1067 result.append("\n"); 1068 } 1069 } catch (Exception e) { 1070 showErrorPage(e); 1071 } 1072 return result.toString(); 1073 } 1074 1075 /** 1076 * Generates the JavaScript initialization calls for the used widgets in the editor form.<p> 1077 * 1078 * @return the JavaScript initialization calls for the used widgets 1079 * @throws JspException if including the error page fails 1080 */ 1081 public String getXmlEditorInitCalls() throws JspException { 1082 1083 StringBuffer result = new StringBuffer(512); 1084 try { 1085 // iterate over unique widgets from collector 1086 Iterator<I_CmsWidget> i = getWidgetCollector().getUniqueWidgets().iterator(); 1087 while (i.hasNext()) { 1088 I_CmsWidget widget = i.next(); 1089 result.append(widget.getDialogInitCall(getCms(), this)); 1090 } 1091 } catch (Exception e) { 1092 showErrorPage(e); 1093 } 1094 return result.toString(); 1095 } 1096 1097 /** 1098 * Generates the JavaScript initialization methods for the used widgets.<p> 1099 * 1100 * @return the JavaScript initialization methods for the used widgets 1101 * 1102 * @throws JspException if an error occurs during JavaScript generation 1103 */ 1104 public String getXmlEditorInitMethods() throws JspException { 1105 1106 StringBuffer result = new StringBuffer(1024); 1107 1108 // first create JS for eventual tabs 1109 StringBuffer tabs = new StringBuffer(512); 1110 if (m_content.getHandler().getTabs().size() > 0) { 1111 // we have some tabs defined, initialize them using JQuery 1112 result.append("var xmlSelectedTab = 0;\n"); 1113 result.append("var xmlErrorTabs = new Array();\n"); 1114 result.append("var xmlWarningTabs = new Array();\n"); 1115 tabs.append("\t$xmltabs = $(\"#xmltabs\").tabs({});\n"); 1116 tabs.append("\t$xmltabs.tabs(\"select\", xmlSelectedTab);\n"); 1117 tabs.append("\tfor (var i=0; i<xmlErrorTabs.length; i++) {\n"); 1118 tabs.append("\t\t$(\"#OcmsTabTab\" + xmlErrorTabs[i]).addClass(\"ui-state-error\");\n"); 1119 tabs.append("\t}\n"); 1120 tabs.append("\tfor (var i=0; i<xmlWarningTabs.length; i++) {\n"); 1121 tabs.append("\t\t$(\"#OcmsTabTab\" + xmlWarningTabs[i]).addClass(\"ui-state-warning\");\n"); 1122 tabs.append("\t}\n"); 1123 } 1124 1125 // create JS for UI dialog 1126 result.append("var dialogTitleAddChoice = \""); 1127 result.append(key(Messages.GUI_EDITOR_XMLCONTENT_CHOICE_ADD_HL_0)); 1128 result.append("\";\n"); 1129 result.append("var dialogTitleAddSubChoice = \""); 1130 result.append(key(Messages.GUI_EDITOR_XMLCONTENT_CHOICE_SUB_ADD_HL_0)); 1131 result.append("\";\n"); 1132 result.append("var vfsPathEditorForm = \""); 1133 result.append(getJsp().link(CmsEditor.PATH_EDITORS + "xmlcontent/editor_form.jsp")); 1134 result.append("\";\n"); 1135 result.append("if (jQuery) {\n"); 1136 result.append("$(document).ready(function(){\n"); 1137 result.append(tabs); 1138 result.append("\t$(\"#xmladdelementdialog\").dialog({\n"); 1139 result.append("\t\ttitle: \""); 1140 result.append(key(Messages.GUI_EDITOR_XMLCONTENT_CHOICE_ADD_HL_0)); 1141 result.append("\",\n"); 1142 result.append("\t\tautoOpen: false,\n"); 1143 result.append("\t\tbgiframe: true,\n"); 1144 result.append("\t\tminHeight: 150,\n"); 1145 result.append("\t\tminWidth: 300,\n"); 1146 result.append("\t\twidth: 360,\n"); 1147 result.append("\t\tmodal: true,\n"); 1148 result.append("\t\tbuttons: { \""); 1149 result.append(key(org.opencms.workplace.Messages.GUI_DIALOG_BUTTON_CANCEL_0)); 1150 result.append("\": function() { $(this).dialog(\"close\"); } }\n"); 1151 result.append("\t});\n"); 1152 result.append("});\n"); 1153 result.append("}\n"); 1154 1155 try { 1156 // iterate over unique widgets from collector 1157 Iterator<I_CmsWidget> i = getWidgetCollector().getUniqueWidgets().iterator(); 1158 while (i.hasNext()) { 1159 I_CmsWidget widget = i.next(); 1160 result.append(widget.getDialogInitMethod(getCms(), this)); 1161 result.append("\n"); 1162 } 1163 } catch (Exception e) { 1164 showErrorPage(e); 1165 } 1166 return result.toString(); 1167 } 1168 1169 /** 1170 * Returns true if the edited content contains validation errors, otherwise false.<p> 1171 * 1172 * @return true if the edited content contains validation errors, otherwise false 1173 */ 1174 public boolean hasValidationErrors() { 1175 1176 return getValidationHandler().hasErrors(); 1177 } 1178 1179 /** 1180 * Returns true if the preview is available for the edited xml content.<p> 1181 * 1182 * This method has to use the resource request parameter and read the file from vfs because the temporary file is 1183 * not available in the upper button frame.<p> 1184 * 1185 * @return true if the preview is enabled, otherwise false 1186 */ 1187 public boolean isPreviewEnabled() { 1188 1189 try { 1190 // read the original file because temporary file is not created when opening button frame 1191 CmsFile file = getCms().readFile(getParamResource(), CmsResourceFilter.ALL); 1192 CmsXmlContent content = CmsXmlContentFactory.unmarshal(getCloneCms(), file); 1193 return content.getHandler().getPreview(getCms(), m_content, getParamResource()) != null; 1194 } catch (Exception e) { 1195 // error reading or unmarshalling, no preview available 1196 return false; 1197 } 1198 } 1199 1200 /** 1201 * Sets the editor values for the locale with the parameters from the request.<p> 1202 * 1203 * Called before saving the xml content, redisplaying the input form, 1204 * changing the language and adding or removing elements.<p> 1205 * 1206 * @param locale the locale of the content to save 1207 * @throws CmsXmlException if something goes wrong 1208 */ 1209 public void setEditorValues(Locale locale) throws CmsXmlException { 1210 1211 List<String> names = m_content.getNames(locale); 1212 Iterator<String> i = names.iterator(); 1213 while (i.hasNext()) { 1214 String path = i.next(); 1215 I_CmsXmlContentValue value = m_content.getValue(path, locale); 1216 if (value.isSimpleType()) { 1217 // We don't care about the old editor for the 'inheritable' widget configuration, 1218 // so we're using the old getWidget method here 1219 I_CmsWidget widget = value.getContentDefinition().getContentHandler().getWidget(value); 1220 widget.setEditorValue( 1221 getCms(), 1222 getJsp().getRequest().getParameterMap(), 1223 this, 1224 (I_CmsWidgetParameter)value); 1225 } 1226 } 1227 } 1228 1229 /** 1230 * Sets the name of the choice element to add.<p> 1231 * 1232 * @param choiceElement the name of the choice element to add 1233 */ 1234 public void setParamChoiceElement(String choiceElement) { 1235 1236 m_paramChoiceElement = choiceElement; 1237 } 1238 1239 /** 1240 * Sets the flag if the element to add is a choice type.<p> 1241 * 1242 * @param paramChoiceType the flag if the element to add is a choice type 1243 */ 1244 public void setParamChoiceType(String paramChoiceType) { 1245 1246 m_paramChoiceType = paramChoiceType; 1247 } 1248 1249 /** 1250 * Sets the edit context URI.<p> 1251 * 1252 * @param editContext the edit context URI. 1253 */ 1254 public void setParamEditContext(String editContext) { 1255 1256 m_paramEditContext = editContext; 1257 CmsObject cms = getCms(); 1258 if ((cms != null) && (editContext != null)) { 1259 cms.getRequestContext().setAttribute(ATTRIBUTE_EDITCONTEXT, editContext); 1260 } 1261 } 1262 1263 /** 1264 * Sets the index of the element to add or remove.<p> 1265 * 1266 * @param elementIndex the index of the element to add or remove 1267 */ 1268 public void setParamElementIndex(String elementIndex) { 1269 1270 m_paramElementIndex = elementIndex; 1271 } 1272 1273 /** 1274 * Sets the name of the element to add or remove.<p> 1275 * 1276 * @param elementName the name of the element to add or remove 1277 */ 1278 public void setParamElementName(String elementName) { 1279 1280 m_paramElementName = elementName; 1281 } 1282 1283 /** 1284 * Sets the content creation mode.<p> 1285 * 1286 * @param mode the content creation mode 1287 */ 1288 public void setParamMode(String mode) { 1289 1290 m_mode = mode; 1291 } 1292 1293 /** 1294 * Sets the parameter that specifies the model file name.<p> 1295 * 1296 * @param paramMasterFile the parameter that specifies the model file name 1297 */ 1298 public void setParamModelFile(String paramMasterFile) { 1299 1300 m_paramModelFile = paramMasterFile; 1301 } 1302 1303 /** 1304 * Sets the "new link" parameter.<p> 1305 * 1306 * @param paramNewLink the "new link" parameter to set 1307 */ 1308 public void setParamNewLink(String paramNewLink) { 1309 1310 m_paramNewLink = CmsEncoder.decode(paramNewLink); 1311 } 1312 1313 /** 1314 * Sets the post-create handler class name.<p> 1315 * 1316 * @param handler the post-create handler class name 1317 */ 1318 public void setParamPostCreateHandler(String handler) { 1319 1320 m_postCreateHandler = handler; 1321 } 1322 1323 /** 1324 * Determines if the element language selector is shown dependent on the available Locales.<p> 1325 * 1326 * @return true, if more than one Locale is available, otherwise false 1327 */ 1328 public boolean showElementLanguageSelector() { 1329 1330 List<Locale> locales = OpenCms.getLocaleManager().getAvailableLocales(getCms(), getParamResource()); 1331 if ((locales == null) || (locales.size() < 2)) { 1332 // for less than two available locales, do not create language selector 1333 return false; 1334 } 1335 return true; 1336 } 1337 1338 /** 1339 * @see org.opencms.workplace.tools.CmsToolDialog#useNewStyle() 1340 */ 1341 @Override 1342 public boolean useNewStyle() { 1343 1344 return false; 1345 } 1346 1347 /** 1348 * @see org.opencms.workplace.editors.CmsEditor#commitTempFile() 1349 */ 1350 @Override 1351 protected void commitTempFile() throws CmsException { 1352 1353 super.commitTempFile(); 1354 m_file = getCloneCms().readFile(getParamResource()); 1355 m_content = CmsXmlContentFactory.unmarshal(getCloneCms(), m_file); 1356 } 1357 1358 /** 1359 * Makes sure the requested locale node is present in the content document 1360 * by either copying an existing locale node or creating an empty one.<p> 1361 * 1362 * @param locale the requested locale 1363 * 1364 * @return the locale 1365 */ 1366 protected Locale ensureLocale(Locale locale) { 1367 1368 // get the default locale for the resource 1369 List<Locale> locales = OpenCms.getLocaleManager().getDefaultLocales(getCms(), getParamResource()); 1370 if (m_content != null) { 1371 if (!m_content.hasLocale(locale)) { 1372 try { 1373 1374 // to copy anything we need at least one locale 1375 if ((m_content.getLocales().size() > 0)) { 1376 // required locale not available, check if an existing default locale should be copied as "template" 1377 try { 1378 // a list of possible default locales has been set as property, try to find a match 1379 m_content.copyLocale(locales, locale); 1380 1381 } catch (CmsException e) { 1382 m_content.addLocale(getCms(), locale); 1383 } 1384 1385 } else { 1386 m_content.addLocale(getCms(), locale); 1387 } 1388 1389 writeContent(); 1390 } catch (CmsException e) { 1391 LOG.error(e.getMessageContainer(), e); 1392 } 1393 } 1394 if (!m_content.hasLocale(locale)) { 1395 // value may have changed because of the copy operation 1396 locale = m_content.getLocales().get(0); 1397 } 1398 } 1399 return locale; 1400 } 1401 1402 /** 1403 * Initializes the editor content when opening the editor for the first time.<p> 1404 * 1405 * Not necessary for the xmlcontent editor.<p> 1406 */ 1407 @Override 1408 protected void initContent() { 1409 1410 // nothing to be done for the xmlcontent editor form 1411 } 1412 1413 /** 1414 * Initializes the element language for the first call of the editor.<p> 1415 */ 1416 protected void initElementLanguage() { 1417 1418 // get the default locale for the resource 1419 List<Locale> locales = OpenCms.getLocaleManager().getDefaultLocales(getCms(), getParamResource()); 1420 Locale locale = locales.get(0); 1421 locale = ensureLocale(locale); 1422 setParamElementlanguage(locale.toString()); 1423 } 1424 1425 /** 1426 * @see org.opencms.workplace.CmsWorkplace#initWorkplaceRequestValues(org.opencms.workplace.CmsWorkplaceSettings, javax.servlet.http.HttpServletRequest) 1427 */ 1428 @Override 1429 protected void initWorkplaceRequestValues(CmsWorkplaceSettings settings, HttpServletRequest request) { 1430 1431 // fill the parameter values in the get/set methods 1432 fillParamValues(request); 1433 // set the dialog type 1434 setParamDialogtype(EDITOR_TYPE); 1435 1436 if (getParamNewLink() != null) { 1437 setParamAction(EDITOR_ACTION_NEW); 1438 } else { 1439 // initialize a content object from the temporary file 1440 if ((getParamTempfile() != null) && !"null".equals(getParamTempfile())) { 1441 try { 1442 m_file = getCms().readFile(getParamTempfile(), CmsResourceFilter.ALL); 1443 m_content = CmsXmlContentFactory.unmarshal(getCloneCms(), m_file); 1444 } catch (CmsException e) { 1445 // error during initialization, show error page 1446 try { 1447 showErrorPage(this, e); 1448 } catch (JspException exc) { 1449 // should usually never happen 1450 if (LOG.isInfoEnabled()) { 1451 LOG.info(exc); 1452 } 1453 } 1454 } 1455 } 1456 } 1457 1458 // set the action for the JSP switch 1459 if (EDITOR_SAVE.equals(getParamAction())) { 1460 setAction(ACTION_SAVE); 1461 } else if (EDITOR_SAVEEXIT.equals(getParamAction())) { 1462 setAction(ACTION_SAVEEXIT); 1463 } else if (EDITOR_EXIT.equals(getParamAction())) { 1464 setAction(ACTION_EXIT); 1465 } else if (EDITOR_ACTION_SUBCHOICES.equals(getParamAction())) { 1466 setAction(ACTION_SUBCHOICES); 1467 } else if (EDITOR_CLOSEBROWSER.equals(getParamAction())) { 1468 // closed browser window accidentally, unlock resource and delete temporary file 1469 actionClear(true); 1470 return; 1471 } else if (EDITOR_ACTION_CHECK.equals(getParamAction())) { 1472 setAction(ACTION_CHECK); 1473 } else if (EDITOR_SAVEACTION.equals(getParamAction())) { 1474 setAction(ACTION_SAVEACTION); 1475 try { 1476 actionDirectEdit(); 1477 } catch (Exception e) { 1478 // should usually never happen 1479 if (LOG.isInfoEnabled()) { 1480 LOG.info(e.getLocalizedMessage(), e); 1481 } 1482 } 1483 setAction(ACTION_EXIT); 1484 } else if (EDITOR_COPYLOCALE.equals(getParamAction())) { 1485 setAction(ACTION_COPYLOCALE); 1486 } else if (EDITOR_DELETELOCALE.equals(getParamAction())) { 1487 setAction(ACTION_DELETELOCALE); 1488 } else if (EDITOR_SHOW.equals(getParamAction())) { 1489 setAction(ACTION_SHOW); 1490 } else if (EDITOR_SHOW_ERRORMESSAGE.equals(getParamAction())) { 1491 setAction(ACTION_SHOW_ERRORMESSAGE); 1492 } else if (EDITOR_CHANGE_ELEMENT.equals(getParamAction())) { 1493 setAction(ACTION_SHOW); 1494 actionChangeElementLanguage(); 1495 } else if (EDITOR_ACTION_ELEMENT_ADD.equals(getParamAction())) { 1496 setAction(ACTION_ELEMENT_ADD); 1497 try { 1498 actionToggleElement(); 1499 } catch (JspException e) { 1500 if (LOG.isErrorEnabled()) { 1501 LOG.error( 1502 org.opencms.workplace.Messages.get().getBundle().key( 1503 org.opencms.workplace.Messages.LOG_INCLUDE_ERRORPAGE_FAILED_0)); 1504 } 1505 } 1506 } else if (EDITOR_ACTION_ELEMENT_REMOVE.equals(getParamAction())) { 1507 setAction(ACTION_ELEMENT_REMOVE); 1508 try { 1509 actionToggleElement(); 1510 } catch (JspException e) { 1511 if (LOG.isErrorEnabled()) { 1512 LOG.error( 1513 org.opencms.workplace.Messages.get().getBundle().key( 1514 org.opencms.workplace.Messages.LOG_INCLUDE_ERRORPAGE_FAILED_0)); 1515 } 1516 } 1517 } else if (EDITOR_ACTION_ELEMENT_MOVE_DOWN.equals(getParamAction())) { 1518 setAction(ACTION_ELEMENT_MOVE_DOWN); 1519 try { 1520 actionMoveElement(); 1521 } catch (JspException e) { 1522 if (LOG.isErrorEnabled()) { 1523 LOG.error( 1524 org.opencms.workplace.Messages.get().getBundle().key( 1525 org.opencms.workplace.Messages.LOG_INCLUDE_ERRORPAGE_FAILED_0)); 1526 } 1527 } 1528 } else if (EDITOR_ACTION_ELEMENT_MOVE_UP.equals(getParamAction())) { 1529 setAction(ACTION_ELEMENT_MOVE_UP); 1530 try { 1531 actionMoveElement(); 1532 } catch (JspException e) { 1533 if (LOG.isErrorEnabled()) { 1534 LOG.error( 1535 org.opencms.workplace.Messages.get().getBundle().key( 1536 org.opencms.workplace.Messages.LOG_INCLUDE_ERRORPAGE_FAILED_0)); 1537 } 1538 } 1539 } else if (EDITOR_ACTION_NEW.equals(getParamAction())) { 1540 setAction(ACTION_NEW); 1541 return; 1542 } else if (EDITOR_PREVIEW.equals(getParamAction())) { 1543 setAction(ACTION_PREVIEW); 1544 } else if (EDITOR_CORRECTIONCONFIRMED.equals(getParamAction())) { 1545 setAction(ACTION_SHOW); 1546 try { 1547 // correct the XML structure before showing the form 1548 correctXmlStructure(); 1549 } catch (CmsException e) { 1550 // error during correction 1551 try { 1552 showErrorPage(this, e); 1553 } catch (JspException exc) { 1554 // should usually never happen 1555 if (LOG.isInfoEnabled()) { 1556 LOG.info(exc); 1557 } 1558 } 1559 } 1560 } else { 1561 // initial call of editor 1562 setAction(ACTION_DEFAULT); 1563 try { 1564 // lock resource if autolock is enabled in configuration 1565 if (Boolean.valueOf(getParamDirectedit()).booleanValue()) { 1566 // set a temporary lock in direct edit mode 1567 checkLock(getParamResource(), CmsLockType.TEMPORARY); 1568 } else { 1569 // set common lock 1570 checkLock(getParamResource()); 1571 } 1572 // create the temporary file 1573 setParamTempfile(createTempFile()); 1574 // initialize a content object from the created temporary file 1575 m_file = getCms().readFile(getParamTempfile(), CmsResourceFilter.ALL); 1576 m_content = CmsXmlContentFactory.unmarshal(getCloneCms(), m_file); 1577 // check the XML content against the given XSD 1578 try { 1579 m_content.validateXmlStructure(new CmsXmlEntityResolver(getCms())); 1580 } catch (CmsXmlException eXml) { 1581 // validation failed, check the settings for handling the correction 1582 if (OpenCms.getWorkplaceManager().isXmlContentAutoCorrect()) { 1583 // correct the XML structure automatically according to the XSD 1584 correctXmlStructure(); 1585 } else { 1586 // show correction confirmation dialog 1587 setAction(ACTION_CONFIRMCORRECTION); 1588 } 1589 } 1590 1591 } catch (CmsException e) { 1592 // error during initialization 1593 try { 1594 showErrorPage(this, e); 1595 } catch (JspException exc) { 1596 // should usually never happen 1597 if (LOG.isInfoEnabled()) { 1598 LOG.info(exc); 1599 } 1600 } 1601 } 1602 // set the initial element language if not given in request parameters 1603 if (getParamElementlanguage() == null) { 1604 initElementLanguage(); 1605 } else { 1606 Locale locale = CmsLocaleManager.getLocale(getParamElementlanguage()); 1607 ensureLocale(locale); 1608 } 1609 } 1610 } 1611 1612 /** 1613 * Returns the HTML for the element operation buttons add, move, remove.<p> 1614 * 1615 * @param value the value for which the buttons are generated 1616 * @param addElement if true, the button to add an element is shown 1617 * @param removeElement if true, the button to remove an element is shown 1618 * @return the HTML for the element operation buttons 1619 */ 1620 private String buildElementButtons(I_CmsXmlContentValue value, boolean addElement, boolean removeElement) { 1621 1622 StringBuffer jsCall = new StringBuffer(512); 1623 String elementName = CmsXmlUtils.removeXpathIndex(value.getPath()); 1624 1625 // indicates if at least one button is active 1626 boolean buttonPresent = false; 1627 1628 int index = value.getIndex(); 1629 1630 jsCall.append("showElementButtons('"); 1631 jsCall.append(elementName); 1632 jsCall.append("', "); 1633 jsCall.append(index); 1634 jsCall.append(", "); 1635 1636 // build the remove element button if required 1637 if (removeElement) { 1638 jsCall.append(Boolean.TRUE); 1639 buttonPresent = true; 1640 } else { 1641 jsCall.append(Boolean.FALSE); 1642 } 1643 jsCall.append(", "); 1644 1645 // build the move down button (move down in API is move up for content editor) 1646 if (index > 0) { 1647 // build active move down button 1648 jsCall.append(Boolean.TRUE); 1649 buttonPresent = true; 1650 } else { 1651 jsCall.append(Boolean.FALSE); 1652 } 1653 jsCall.append(", "); 1654 1655 // build the move up button (move up in API is move down for content editor) 1656 int indexCount = m_content.getIndexCount(elementName, getElementLocale()); 1657 if (index < (indexCount - 1)) { 1658 // build active move up button 1659 jsCall.append(Boolean.TRUE); 1660 buttonPresent = true; 1661 } else { 1662 jsCall.append(Boolean.FALSE); 1663 } 1664 jsCall.append(", "); 1665 1666 // build the add element button if required 1667 if (addElement) { 1668 jsCall.append(Boolean.TRUE); 1669 buttonPresent = true; 1670 } else { 1671 jsCall.append(Boolean.FALSE); 1672 } 1673 jsCall.append(", "); 1674 1675 JSONArray newElements = buildElementChoices(elementName, value.isChoiceType(), true); 1676 jsCall.append("'").append(CmsEncoder.escape(newElements.toString(), CmsEncoder.ENCODING_UTF_8)).append("'"); 1677 jsCall.append(");"); 1678 1679 String result; 1680 if (buttonPresent) { 1681 // at least one button active, create mouseover button 1682 String btIcon = "xmledit.png"; 1683 String btAction = jsCall.toString(); 1684 // determine icon to use and if a direct click action is possible 1685 if (addElement && removeElement) { 1686 btIcon = "xmledit_del_add.png"; 1687 } else if (addElement) { 1688 btIcon = "xmledit_add.png"; 1689 // create button action to add element on button click 1690 StringBuffer action = new StringBuffer(128); 1691 action.append("addElement('"); 1692 action.append(elementName); 1693 action.append("', "); 1694 action.append(index); 1695 action.append(", '").append( 1696 CmsEncoder.escape(newElements.toString(), CmsEncoder.ENCODING_UTF_8)).append("'"); 1697 action.append(");"); 1698 btAction = action.toString(); 1699 } else if (removeElement) { 1700 btIcon = "xmledit_del.png"; 1701 // create button action to remove element on button click 1702 StringBuffer action = new StringBuffer(128); 1703 action.append("removeElement('"); 1704 action.append(elementName); 1705 action.append("', "); 1706 action.append(index); 1707 action.append(");"); 1708 btAction = action.toString(); 1709 } 1710 StringBuffer href = new StringBuffer(512); 1711 href.append("javascript:"); 1712 href.append(btAction); 1713 href.append("\" onmouseover=\""); 1714 href.append(jsCall); 1715 href.append("checkElementButtons(true);\" onmouseout=\"checkElementButtons(false);\" id=\"btimg."); 1716 href.append(elementName).append(".").append(index); 1717 result = button(href.toString(), null, btIcon, Messages.GUI_EDITOR_XMLCONTENT_ELEMENT_BUTTONS_0, 0); 1718 } else { 1719 // no active button, create a spacer 1720 result = buttonBarSpacer(1); 1721 } 1722 1723 return result; 1724 } 1725 1726 /** 1727 * Corrects the XML structure of the edited content according to the XSD.<p> 1728 * 1729 * @throws CmsException if the correction fails 1730 */ 1731 private void correctXmlStructure() throws CmsException { 1732 1733 m_content.setAutoCorrectionEnabled(true); 1734 m_content.correctXmlStructure(getCms()); 1735 // write the corrected temporary file 1736 writeContent(); 1737 } 1738 1739 /** 1740 * Returns the error handler for error handling of the edited xml content.<p> 1741 * 1742 * @return the error handler 1743 */ 1744 private CmsXmlContentErrorHandler getValidationHandler() { 1745 1746 if (m_validationHandler == null) { 1747 // errors were not yet checked, do this now and store result in member 1748 m_validationHandler = m_content.validate(getCms()); 1749 } 1750 return m_validationHandler; 1751 } 1752 1753 /** 1754 * Generates the HTML form for the XML content editor.<p> 1755 * 1756 * This is a recursive method because nested schemas are possible, 1757 * do not call this method directly.<p> 1758 * 1759 * @param contentDefinition the content definition to start with 1760 * @param pathPrefix for nested xml content 1761 * @param showHelpBubble if the code for a help bubble should be generated 1762 * @param superTabOpened if the super tab is opened 1763 * 1764 * @return the HTML that generates the form for the XML editor 1765 */ 1766 private StringBuffer getXmlEditorForm( 1767 CmsXmlContentDefinition contentDefinition, 1768 String pathPrefix, 1769 boolean showHelpBubble, 1770 boolean superTabOpened) { 1771 1772 StringBuffer result = new StringBuffer(1024); 1773 // only show errors if editor is not opened initially 1774 boolean showErrors = (getAction() != ACTION_NEW) 1775 && (getAction() != ACTION_DEFAULT) 1776 && (getAction() != ACTION_ELEMENT_ADD) 1777 && (getAction() != ACTION_ELEMENT_REMOVE) 1778 && (getAction() != ACTION_ELEMENT_MOVE_DOWN) 1779 && (getAction() != ACTION_ELEMENT_MOVE_UP); 1780 1781 try { 1782 // check if we are in a nested content definition 1783 boolean nested = CmsStringUtil.isNotEmpty(pathPrefix); 1784 boolean useTabs = false; 1785 boolean tabOpened = false; 1786 StringBuffer selectedTabScript = new StringBuffer(64); 1787 1788 boolean collapseLabel = false; 1789 boolean firstElement = true; 1790 1791 // show error header once if there were validation errors 1792 if (!nested && showErrors && (getValidationHandler().hasErrors())) { 1793 1794 result.append("<div class=\"ui-widget\">"); 1795 result.append( 1796 "<div class=\"ui-state-error ui-corner-all\" style=\"padding: 0pt 0.7em;\"><div style=\"padding: 3px 0;\">"); 1797 result.append( 1798 "<span class=\"ui-icon ui-icon-alert\" style=\"float: left; margin-right: 0.3em;\"></span>"); 1799 boolean differentLocaleErrors = false; 1800 if ((getValidationHandler().getErrors(getElementLocale()) == null) 1801 || (getValidationHandler().getErrors().size() > getValidationHandler().getErrors( 1802 getElementLocale()).size())) { 1803 1804 differentLocaleErrors = true; 1805 result.append( 1806 "<span id=\"xmlerrordialogbutton\" class=\"ui-icon ui-icon-newwin\" style=\"float: left; margin-right: 0.3em;\"></span>"); 1807 } 1808 result.append(key(Messages.ERR_EDITOR_XMLCONTENT_VALIDATION_ERROR_TITLE_0)); 1809 result.append("</div>"); 1810 1811 // show errors in different locales 1812 if (differentLocaleErrors) { 1813 1814 result.append("<div id=\"xmlerrordialog\" style=\"display: none;\">"); 1815 // iterate through all found errors 1816 Map<Locale, Map<String, String>> locErrors = getValidationHandler().getErrors(); 1817 Iterator<Map.Entry<Locale, Map<String, String>>> locErrorsIter = locErrors.entrySet().iterator(); 1818 while (locErrorsIter.hasNext()) { 1819 Map.Entry<Locale, Map<String, String>> locEntry = locErrorsIter.next(); 1820 Locale locale = locEntry.getKey(); 1821 1822 // skip errors in the actual locale 1823 if (getElementLocale().equals(locale)) { 1824 continue; 1825 } 1826 1827 result.append("<div style=\"padding: 3px;\"><strong>"); 1828 result.append( 1829 key( 1830 Messages.ERR_EDITOR_XMLCONTENT_VALIDATION_ERROR_LANG_1, 1831 new Object[] {locale.getLanguage()})); 1832 result.append("</strong></div>\n"); 1833 result.append("<ul>"); 1834 1835 // iterate through the found errors in a different locale 1836 Map<String, String> elErrors = locEntry.getValue(); 1837 Iterator<Map.Entry<String, String>> elErrorsIter = elErrors.entrySet().iterator(); 1838 while (elErrorsIter.hasNext()) { 1839 Map.Entry<String, String> elEntry = elErrorsIter.next(); 1840 String nodeName = elEntry.getKey(); 1841 String errorMsg = elEntry.getValue(); 1842 // output the error message 1843 result.append("<li>"); 1844 result.append(nodeName); 1845 result.append(": "); 1846 result.append(errorMsg); 1847 result.append("</li>\n"); 1848 } 1849 1850 result.append("</ul>"); 1851 } 1852 1853 result.append("</div>\n"); 1854 result.append("<script >\n"); 1855 result.append("$(\"#xmlerrordialog\").dialog({\n"); 1856 result.append("\tautoOpen: true,\n"); 1857 result.append("\tbgiframe: true,\n"); 1858 result.append("\twidth: 500,\n"); 1859 result.append("\tposition: 'center',\n"); 1860 result.append("\tdialogClass: 'ui-state-error',\n"); 1861 result.append("\ttitle: '").append( 1862 key(Messages.ERR_EDITOR_XMLCONTENT_VALIDATION_ERROR_TITLE_0)).append("',\n"); 1863 result.append("\tmaxHeight: 600\n"); 1864 result.append("});\n"); 1865 1866 result.append( 1867 "$(\"#xmlerrordialogbutton\").bind(\"click\", function(e) {$(\"#xmlerrordialog\").dialog(\"open\");});\n"); 1868 result.append("</script>"); 1869 } 1870 result.append("</div></div>"); 1871 } 1872 1873 if (!nested) { 1874 // check if tabs should be shown 1875 useTabs = contentDefinition.getContentHandler().getTabs().size() > 0; 1876 if (useTabs) { 1877 // we have some tabs available, generate them on first level 1878 result.append("<div id=\"xmltabs\" class=\"ui-tabs\">\n<ul>\n"); 1879 for (Iterator<CmsXmlContentTab> i = contentDefinition.getContentHandler().getTabs().iterator(); i.hasNext();) { 1880 CmsXmlContentTab tab = i.next(); 1881 result.append("\t<li id=\"OcmsTabTab").append(tab.getIdName()).append("\"><a href=\"#OcmsTab"); 1882 result.append(tab.getIdName()); 1883 result.append("\"><span>"); 1884 result.append( 1885 keyDefault( 1886 A_CmsWidget.LABEL_PREFIX + contentDefinition.getInnerName() + "." + tab.getTabName(), 1887 tab.getTabName())); 1888 result.append("</span></a></li>\n"); 1889 } 1890 result.append("</ul>\n"); 1891 } 1892 } 1893 1894 // if xsd:choice then we just need to get one element of the sequence 1895 Iterator<I_CmsXmlSchemaType> it = contentDefinition.getChoiceMaxOccurs() > 0 1896 ? contentDefinition.getTypeSequence().subList(0, 1).iterator() 1897 : contentDefinition.getTypeSequence().iterator(); 1898 1899 // iterate the type sequence 1900 while (it.hasNext()) { 1901 // get the type 1902 I_CmsXmlSchemaType type = it.next(); 1903 1904 boolean tabCurrentlyOpened = false; 1905 1906 if (useTabs) { 1907 // check if a tab is starting with this element 1908 for (int tabIndex = 0; tabIndex < contentDefinition.getContentHandler().getTabs().size(); tabIndex++) { 1909 CmsXmlContentTab checkTab = contentDefinition.getContentHandler().getTabs().get(tabIndex); 1910 if (checkTab.getStartName().equals(type.getName())) { 1911 // a tab is starting, add block element 1912 if (tabOpened) { 1913 // close a previously opened tab 1914 result.append("</table>\n</div>\n"); 1915 } 1916 result.append("<div id=\"OcmsTab"); 1917 result.append(checkTab.getIdName()); 1918 result.append("\" class=\"ui-tabs-hide\">\n"); 1919 // set necessary values 1920 tabOpened = true; 1921 tabCurrentlyOpened = true; 1922 collapseLabel = checkTab.isCollapsed(); 1923 m_currentTab = checkTab; 1924 m_currentTabIndex = tabIndex; 1925 // leave loop 1926 break; 1927 } 1928 } 1929 } 1930 1931 if (firstElement || tabCurrentlyOpened) { 1932 // create table before first element or if a tab has been opened before 1933 result.append("<table class=\"xmlTable"); 1934 if (nested && !superTabOpened) { 1935 // use other style for nested content definition table if tab was not opened on upper level 1936 result.append("Nested"); 1937 } 1938 result.append("\">\n"); 1939 firstElement = false; 1940 } 1941 1942 // get the element sequence of the current type 1943 CmsXmlContentValueSequence elementSequence = m_content.getValueSequence( 1944 pathPrefix + type.getName(), 1945 getElementLocale()); 1946 int elementCount = elementSequence.getElementCount(); 1947 1948 // check if value is optional or multiple 1949 boolean addValue = elementCount < elementSequence.getMaxOccurs(); 1950 boolean removeValue = elementCount > elementSequence.getMinOccurs(); 1951 1952 // assure that at least one element is present in sequence 1953 boolean disabledElement = false; 1954 1955 if ((contentDefinition.getChoiceMaxOccurs() == 0) && (elementCount < 1)) { 1956 // current element is disabled, create dummy element if it is a nested type or no choice option 1957 elementCount = 1; 1958 elementSequence.addValue(getCms(), 0); 1959 disabledElement = true; 1960 m_optionalElementPresent = true; 1961 } 1962 1963 // loop through multiple elements 1964 for (int j = 0; j < elementCount; j++) { 1965 // get value and corresponding widget 1966 I_CmsXmlContentValue value = elementSequence.getValue(j); 1967 1968 String key = value.getPath(); 1969 1970 // check if a tab should be preselected for an added, removed or moved up/down element 1971 if ((m_currentTab != null) && CmsStringUtil.isNotEmpty(getParamElementName())) { 1972 // an element was modified, add JS to preselect tab 1973 if (key.startsWith(getParamElementName()) && (selectedTabScript.length() == 0)) { 1974 selectedTabScript.append("<script >\n\txmlSelectedTab = ").append(m_currentTabIndex).append( 1975 ";\n</script>\n"); 1976 } 1977 } 1978 1979 // show errors and/or warnings 1980 if (showErrors 1981 && getValidationHandler().hasErrors(getElementLocale()) 1982 && getValidationHandler().getErrors(getElementLocale()).containsKey(key)) { 1983 // show error message 1984 if (collapseLabel) { 1985 result.append("<tr><td class=\"xmlTdError\"><img src=\""); 1986 result.append(getEditorResourceUri()); 1987 result.append("error.png\" border=\"0\" alt=\"\" align=\"left\" hspace=\"5\">"); 1988 result.append(resolveMacros(getValidationHandler().getErrors(getElementLocale()).get(key))); 1989 result.append("</td><td></td></tr>\n"); 1990 } else { 1991 result.append("<tr><td></td><td><img src=\""); 1992 result.append(getEditorResourceUri()); 1993 result.append("error.png"); 1994 result.append("\" border=\"0\" alt=\"\"></td><td class=\"xmlTdError\">"); 1995 result.append(resolveMacros(getValidationHandler().getErrors(getElementLocale()).get(key))); 1996 result.append("</td><td></td></tr>\n"); 1997 } 1998 1999 // mark tab as error tab if tab is present 2000 String elemName = CmsXmlUtils.getFirstXpathElement(value.getPath()); 2001 if (((m_currentTab != null) && !m_errorTabs.contains(m_currentTab)) 2002 && (elemName.equals(m_currentTab.getStartName()) 2003 || (!CmsXmlUtils.isDeepXpath(value.getPath()) && value.getName().equals(elemName)))) { 2004 m_errorTabs.add(m_currentTab); 2005 } 2006 2007 } 2008 // warnings can be additional to errors 2009 if (showErrors 2010 && getValidationHandler().hasWarnings(getElementLocale()) 2011 && getValidationHandler().getWarnings(getElementLocale()).containsKey(key)) { 2012 // show warning message 2013 if (collapseLabel) { 2014 result.append("<tr><td class=\"xmlTdError\"><img src=\""); 2015 result.append(getEditorResourceUri()); 2016 result.append("warning.png\" border=\"0\" alt=\"\" align=\"left\" hspace=\"5\">"); 2017 result.append( 2018 resolveMacros(getValidationHandler().getWarnings(getElementLocale()).get(key))); 2019 result.append("</td><td></td></tr>\n"); 2020 } else { 2021 result.append("<tr><td></td><td><img src=\""); 2022 result.append(getEditorResourceUri()); 2023 result.append("warning.png"); 2024 result.append("\" border=\"0\" alt=\"\"></td><td class=\"xmlTdWarning\">"); 2025 result.append( 2026 resolveMacros(getValidationHandler().getWarnings(getElementLocale()).get(key))); 2027 result.append("</td><td></td></tr>\n"); 2028 } 2029 2030 // mark tab as warning tab if tab is present 2031 String elemName = CmsXmlUtils.getFirstXpathElement(value.getPath()); 2032 if (((m_currentTab != null) && !m_warningTabs.contains(m_currentTab)) 2033 && (elemName.equals(m_currentTab.getStartName()) 2034 || (!CmsXmlUtils.isDeepXpath(value.getPath()) && value.getName().equals(elemName)))) { 2035 m_warningTabs.add(m_currentTab); 2036 } 2037 } 2038 2039 // We don't care about the old editor for the 'inheritable' widget configuration, 2040 // so we're using the old getWidget method here 2041 I_CmsWidget widget = value.isSimpleType() 2042 ? contentDefinition.getContentHandler().getWidget(value) 2043 : null; 2044 2045 int index = value.getIndex(); 2046 // create label and help bubble cells 2047 result.append("<tr>"); 2048 if (!collapseLabel) { 2049 result.append("<td class=\"xmlLabel"); 2050 if (disabledElement) { 2051 // element is disabled, mark it with CSS 2052 result.append("Disabled"); 2053 } 2054 result.append("\">"); 2055 result.append( 2056 keyDefault(A_CmsWidget.getLabelKey((I_CmsWidgetParameter)value), value.getName())); 2057 if (elementCount > 1) { 2058 result.append(" [").append(index + 1).append("]"); 2059 } 2060 result.append(": </td>"); 2061 if (showHelpBubble 2062 && (widget != null) 2063 && (CmsXmlUtils.getXpathIndexInt(value.getPath()) == 1)) { 2064 // show help bubble only on first element of each content definition 2065 result.append(widget.getHelpBubble(getCms(), this, (I_CmsWidgetParameter)value)); 2066 } else { 2067 // create empty cell for all following elements 2068 result.append(buttonBarSpacer(16)); 2069 } 2070 } 2071 2072 // append individual widget HTML cell if element is enabled 2073 if (disabledElement) { 2074 // disabled element, show message for optional element 2075 result.append("<td class=\"xmlTdDisabled maxwidth\">"); 2076 result.append(key(Messages.GUI_EDITOR_XMLCONTENT_OPTIONALELEMENT_0)); 2077 result.append("</td>"); 2078 2079 } else { 2080 // element is enabled, show it 2081 if ((widget != null) && value.isSimpleType()) { 2082 // this is a simple type, display widget 2083 result.append(widget.getDialogWidget(getCms(), this, (I_CmsWidgetParameter)value)); 2084 } else { 2085 // recurse into nested type sequence 2086 result.append("<td class=\"maxwidth\">"); 2087 boolean showHelp = (j == 0); 2088 superTabOpened = !nested && tabOpened && collapseLabel; 2089 result.append( 2090 getXmlEditorForm( 2091 ((CmsXmlNestedContentDefinition)value).getNestedContentDefinition(), 2092 value.getPath() + "/", 2093 showHelp, 2094 superTabOpened)); 2095 result.append("</td>"); 2096 } 2097 } 2098 2099 // append element operation (add, remove, move) buttons if required 2100 result.append(buildElementButtons(value, addValue, removeValue)); 2101 2102 // close row 2103 result.append("</tr>\n"); 2104 2105 // remove disabled element to avoid eventual side effects, e.g. in widget configurations 2106 if (disabledElement) { 2107 elementSequence.removeValue(0); 2108 } 2109 2110 } 2111 } 2112 // close table 2113 result.append("</table>\n"); 2114 if (tabOpened) { 2115 // close last open tab 2116 result.append("</div>\n"); 2117 } 2118 if (!nested && useTabs) { 2119 // close block element around tabs 2120 result.append("</div>\n"); 2121 // mark eventual warning and error tabs 2122 result.append("<script >\n"); 2123 for (Iterator<CmsXmlContentTab> i = m_warningTabs.iterator(); i.hasNext();) { 2124 CmsXmlContentTab checkTab = i.next(); 2125 if (!m_errorTabs.contains(checkTab)) { 2126 result.append("\txmlWarningTabs[xmlWarningTabs.length] = \"").append( 2127 checkTab.getIdName()).append("\";\n"); 2128 } 2129 } 2130 for (Iterator<CmsXmlContentTab> i = m_errorTabs.iterator(); i.hasNext();) { 2131 CmsXmlContentTab checkTab = i.next(); 2132 result.append("\txmlErrorTabs[xmlErrorTabs.length] = \"").append(checkTab.getIdName()).append( 2133 "\";\n"); 2134 } 2135 result.append("</script>\n"); 2136 } 2137 if (selectedTabScript.length() > 0) { 2138 result.append(selectedTabScript); 2139 } 2140 } catch (Throwable t) { 2141 LOG.error(Messages.get().getBundle().key(Messages.ERR_XML_EDITOR_0), t); 2142 } 2143 return result; 2144 } 2145 2146 /** 2147 * Resets the error handler member variable to reinitialize the error messages.<p> 2148 */ 2149 private void resetErrorHandler() { 2150 2151 m_validationHandler = null; 2152 } 2153 2154 /** 2155 * Resets the widget collector member variable to reinitialize the widgets.<p> 2156 * 2157 * This is needed to display the help messages of optional elements before building the html end of the form.<p> 2158 */ 2159 private void resetWidgetCollector() { 2160 2161 m_widgetCollector = null; 2162 } 2163 2164 /** 2165 * Writes the xml content to the vfs and re-initializes the member variables.<p> 2166 * 2167 * @throws CmsException if writing the file fails 2168 */ 2169 private void writeContent() throws CmsException { 2170 2171 String decodedContent = m_content.toString(); 2172 try { 2173 m_file.setContents(decodedContent.getBytes(getFileEncoding())); 2174 } catch (UnsupportedEncodingException e) { 2175 throw new CmsException(Messages.get().container(Messages.ERR_INVALID_CONTENT_ENC_1, getParamResource()), e); 2176 } 2177 // the file content might have been modified during the write operation 2178 CmsObject cloneCms = getCloneCms(); 2179 CmsUUID tempProjectId = OpenCms.getWorkplaceManager().getTempFileProjectId(); 2180 cloneCms.getRequestContext().setCurrentProject(getCms().readProject(tempProjectId)); 2181 m_file = cloneCms.writeFile(m_file); 2182 m_content = CmsXmlContentFactory.unmarshal(cloneCms, m_file); 2183 2184 } 2185}