001/* 002 * Copyright 2021 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.plugins.forms.repository; 017 018import java.time.LocalDate; 019import java.time.ZonedDateTime; 020import java.util.ArrayList; 021import java.util.HashMap; 022import java.util.List; 023import java.util.Map; 024import java.util.Optional; 025import java.util.stream.Collectors; 026 027import javax.jcr.Node; 028 029import org.apache.commons.lang3.StringUtils; 030 031import org.ametys.cms.data.ametysobject.ModifiableModelAwareDataAwareAmetysObject; 032import org.ametys.cms.data.holder.ModifiableIndexableDataHolder; 033import org.ametys.cms.data.holder.impl.DefaultModifiableModelAwareDataHolder; 034import org.ametys.core.user.UserIdentity; 035import org.ametys.plugins.forms.FormAndDirectoryCommonMethods; 036import org.ametys.plugins.forms.dao.FormEntryDAO; 037import org.ametys.plugins.forms.question.FormQuestionType; 038import org.ametys.plugins.forms.question.types.ChoicesListQuestionType; 039import org.ametys.plugins.forms.repository.type.Rule; 040import org.ametys.plugins.repository.AmetysObject; 041import org.ametys.plugins.repository.AmetysRepositoryException; 042import org.ametys.plugins.repository.CopiableAmetysObject; 043import org.ametys.plugins.repository.ModifiableTraversableAmetysObject; 044import org.ametys.plugins.repository.MovableAmetysObject; 045import org.ametys.plugins.repository.RepositoryIntegrityViolationException; 046import org.ametys.plugins.repository.data.repositorydata.impl.JCRRepositoryData; 047import org.ametys.plugins.repository.jcr.DefaultTraversableAmetysObject; 048import org.ametys.web.repository.SiteAwareAmetysObject; 049import org.ametys.web.repository.site.Site; 050 051/** 052 * Class representing a form, backed by a JCR node.<br> 053 */ 054public class Form extends DefaultTraversableAmetysObject<FormFactory> implements ModifiableModelAwareDataAwareAmetysObject, MovableAmetysObject, SiteAwareAmetysObject, CopiableAmetysObject 055{ 056 /** Property name for form displayed name */ 057 public static final String TITLE = "title"; 058 /** Property name for form description */ 059 public static final String DESCRIPTION = "description"; 060 /** Property name for form author */ 061 public static final String AUTHOR = "author"; 062 /** Property name for form last contributor */ 063 public static final String CONTRIBUTOR = "contributor"; 064 /** Property name for form creation date */ 065 public static final String CREATIONDATE = "creationDate"; 066 /** Property name for form last modification date */ 067 public static final String LASTMODIFICATIONDATE = "lastModificationDate"; 068 /** Property name for form workflow name */ 069 public static final String WORKFLOWNAME = "workflowName"; 070 /** Property name for form to limit one entry by user */ 071 public static final String LIMIT_TO_ONE_ENTRY_BY_USER = "limit-to-one-entry-by-user"; 072 /** Property name for form to limit the entries */ 073 public static final String LIMIT_ENTRIES_ENABLED = "limit-entries-enabled"; 074 /** Property name for form to enable queue */ 075 public static final String QUEUE_ENABLED = "queue-enabled"; 076 /** Property name for form's queue size */ 077 public static final String QUEUE_SIZE = "queue-size"; 078 /** Property name for form displayed message when closed because of limited number of entries and a queue is enabled */ 079 public static final String QUEUE_CLOSED_MSG = "queue-closed-message"; 080 /** Property name for form's authorized max number of entries */ 081 public static final String MAX_ENTRIES = "max-entries"; 082 /** Property name for form displayed message when entries are limited in number */ 083 public static final String REMAINING_MSG = "remaining-message"; 084 /** Property name for form displayed message when closed because of limited number of entries */ 085 public static final String CLOSED_MSG = "closed-message"; 086 /** Property name for form entries admin emails */ 087 public static final String ADMIN_EMAILS = "admin-emails"; 088 /** Property name for form acknowledgement of receipt's sender */ 089 public static final String RECEIPT_SENDER = "receipt-sender"; 090 /** Property name for form acknowledgement of receipt's receiver*/ 091 public static final String RECEIPT_RECEIVER = "receipt-receiver"; 092 /** Property name for form acknowledgement of receipt's subject */ 093 public static final String RECEIPT_SUBJECT = "receipt-subject"; 094 /** Property name for form acknowledgement of receipt's body */ 095 public static final String RECEIPT_BODY = "receipt-body"; 096 /** Property name for form acknowledgement of queue's sender */ 097 public static final String QUEUE_SENDER = "queue-sender"; 098 /** Property name for form acknowledgement of queue's receiver*/ 099 public static final String QUEUE_RECEIVER = "queue-receiver"; 100 /** Property name for form acknowledgement of queue's subject */ 101 public static final String QUEUE_SUBJECT = "queue-subject"; 102 /** Property name for form acknowledgement of queue's body */ 103 public static final String QUEUE_BODY = "queue-body"; 104 /** Property name for form start date */ 105 public static final String START_DATE = "startDate"; 106 /** Property name for form end date */ 107 public static final String END_DATE = "endDate"; 108 109 /** 110 * Creates an {@link Form}. 111 * @param node the node backing this {@link AmetysObject} 112 * @param parentPath the parentPath in the Ametys hierarchy 113 * @param factory the DefaultAmetysObjectFactory which created the AmetysObject 114 */ 115 public Form(Node node, String parentPath, FormFactory factory) 116 { 117 super(node, parentPath, factory); 118 } 119 120 /** 121 * Set the title of this form. 122 * @param title the short title 123 */ 124 public void setTitle (String title) 125 { 126 this.setValue(TITLE, title); 127 } 128 129 /** 130 * Set the description of this form. 131 * @param description the description 132 */ 133 public void setDescription (String description) 134 { 135 this.setValue(DESCRIPTION, description); 136 } 137 138 /** 139 * Set the author of this form. 140 * @param author the author 141 */ 142 public void setAuthor(UserIdentity author) 143 { 144 this.setValue(AUTHOR, author); 145 } 146 147 /** 148 * Set the last contributor of this form. 149 * @param contributor the last contributor 150 */ 151 public void setContributor(UserIdentity contributor) 152 { 153 this.setValue(CONTRIBUTOR, contributor); 154 } 155 156 /** 157 * Set the date of the creation. 158 * @param creationDate the last modification date to set. 159 */ 160 public void setCreationDate(ZonedDateTime creationDate) 161 { 162 this.setValue(CREATIONDATE, creationDate); 163 } 164 165 /** 166 * Set the date of the last modification. 167 * @param lastModificationDate the last modification date to set. 168 */ 169 public void setLastModificationDate(ZonedDateTime lastModificationDate) 170 { 171 this.setValue(LASTMODIFICATIONDATE, lastModificationDate); 172 } 173 174 /** 175 * Get the title of the form 176 * @return The title 177 */ 178 public String getTitle() 179 { 180 return this.getValue(TITLE); 181 } 182 183 /** 184 * Get the description of the form 185 * @return The description 186 */ 187 public String getDescription() 188 { 189 return this.getValue(DESCRIPTION); 190 } 191 192 /** 193 * Get the author of the form 194 * @return The author 195 */ 196 public UserIdentity getAuthor () 197 { 198 return this.getValue(AUTHOR); 199 } 200 201 /** 202 * Get the last contributor of the form 203 * @return The contributor 204 */ 205 public UserIdentity getContributor() 206 { 207 return this.getValue(CONTRIBUTOR); 208 } 209 210 /** 211 * Get the date of the last modification of the form 212 * @return The date 213 */ 214 public ZonedDateTime getCreationDate() 215 { 216 return this.getValue(CREATIONDATE); 217 } 218 219 /** 220 * Get the date of the last modification of the form 221 * @return The date 222 */ 223 public ZonedDateTime getLastModificationDate() 224 { 225 return this.getValue(LASTMODIFICATIONDATE); 226 } 227 228 /** 229 * Get the name of the workflow applied to the form 230 * @return The workflow name 231 */ 232 public String getWorkflowName() 233 { 234 return this.getValue(WORKFLOWNAME); 235 } 236 237 /** 238 * Set the name of the workflow. 239 * @param workflowName the name of the workflow to set. 240 */ 241 public void setWorkflowName(String workflowName) 242 { 243 this.setValue(WORKFLOWNAME, workflowName); 244 } 245 246 /** 247 * Get the receipt's sender of this form. 248 * @return The receipt's sender 249 */ 250 public Optional<String> getReceiptSender() 251 { 252 return Optional.ofNullable(getValue(RECEIPT_SENDER)); 253 } 254 255 /** 256 * Set the receipt's sender of this form. 257 * @param sender The receipt's sender 258 */ 259 public void setReceiptSender(String sender) 260 { 261 this.setValue(RECEIPT_SENDER, sender); 262 } 263 /** 264 * Get the receipt's receiver of this form. 265 * @return The receipt's receiver 266 */ 267 public Optional<String> getReceiptReceiver() 268 { 269 return Optional.ofNullable(getValue(RECEIPT_RECEIVER)); 270 } 271 272 /** 273 * Set the receipt's receiver of this form. 274 * @param receiver The receipt's receiver 275 */ 276 public void setReceiptReceiver(String receiver) 277 { 278 this.setValue(RECEIPT_RECEIVER, receiver); 279 } 280 /** 281 * Get the receipt's subject of this form. 282 * @return The receipt's subject 283 */ 284 public Optional<String> getReceiptSubject() 285 { 286 return Optional.ofNullable(getValue(RECEIPT_SUBJECT)); 287 } 288 289 /** 290 * Set the receipt's subject of this form. 291 * @param subject The receipt's subject 292 */ 293 public void setReceiptSubject(String subject) 294 { 295 this.setValue(RECEIPT_SUBJECT, subject); 296 } 297 /** 298 * Get the receipt's body of this form. 299 * @return The receipt's body 300 */ 301 public Optional<String> getReceiptBody() 302 { 303 return Optional.ofNullable(getValue(RECEIPT_BODY)); 304 } 305 306 /** 307 * Set the receipt's body of this form. 308 * @param body The receipt's body 309 */ 310 public void setReceiptBody(String body) 311 { 312 this.setValue(RECEIPT_BODY, body); 313 } 314 315 /** 316 * Indicate is this form can be considered as a mini survey 317 * @return true if the form as only one question of type list and is limited to one answer by user 318 */ 319 public boolean isMiniSurvey() 320 { 321 // do the cheap test before any expensive computation 322 // Mini survey is restricted to only one entry by user 323 if (!this.isLimitedToOneEntryByUser()) 324 { 325 return false; 326 } 327 else 328 { 329 // There must be exactly one question that is not display only. And this question must be a choice list 330 boolean hasAQuestion = false; 331 for (FormQuestion question : this.getQuestions()) 332 { 333 FormQuestionType type = question.getType(); 334 if (!type.onlyForDisplay(question)) 335 { 336 // first ChoicesListQuestion 337 if (type instanceof ChoicesListQuestionType && !hasAQuestion) 338 { 339 hasAQuestion = true; 340 } 341 // not a ChoicesListQuestion or second one 342 else 343 { 344 return false; 345 } 346 } 347 } 348 return hasAQuestion; 349 } 350 } 351 352 /** 353 * Get if this form is limited to one entry by user 354 * @return <code>true</code> if the form is limited to one entry by user 355 */ 356 public boolean isLimitedToOneEntryByUser() 357 { 358 return this.getValue(LIMIT_TO_ONE_ENTRY_BY_USER, false, false); 359 } 360 361 /** 362 * Limit or not to one entry by user 363 * @param limit <code>true</code> to limit to one entry by user 364 */ 365 public void limitToOneEntryByUser(boolean limit) 366 { 367 this.setValue(LIMIT_TO_ONE_ENTRY_BY_USER, limit); 368 } 369 370 /** 371 * Get if the form entries are limited 372 * @return <code>true</code> if the form entries are limited 373 */ 374 public boolean isEntriesLimited() 375 { 376 return this.getValue(LIMIT_ENTRIES_ENABLED, false, false); 377 } 378 379 /** 380 * Limit or not the entries of the form 381 * @param limitEntries <code>true</code> to limit the entries 382 */ 383 public void limitEntries(boolean limitEntries) 384 { 385 this.setValue(LIMIT_ENTRIES_ENABLED, limitEntries); 386 } 387 388 /** 389 * Get if a queue is enabled 390 * @return <code>true</code> if a queue is enabled 391 */ 392 public boolean isQueueEnabled() 393 { 394 return isEntriesLimited() && this.getValue(QUEUE_ENABLED, false, false); 395 } 396 397 /** 398 * Enable of not the queue 399 * @param enabled <code>true</code> to enable the queue 400 */ 401 public void enableQueue(boolean enabled) 402 { 403 this.setValue(QUEUE_ENABLED, enabled); 404 } 405 406 /** 407 * Get the max number limit for entries in this form 408 * @return the max number 409 */ 410 public Optional<Long> getMaxEntries() 411 { 412 return Optional.ofNullable(getValue(MAX_ENTRIES)); 413 } 414 415 /** 416 * Set the max number limit for entries in this form 417 * @param max The max number 418 */ 419 public void setMaxEntries(Long max) 420 { 421 this.setValue(MAX_ENTRIES, max); 422 } 423 424 /** 425 * Get the message that will be displayed to users when filling the form if a max entries limit is set 426 * @return a message for users 427 */ 428 public Optional<String> getRemainingMessage() 429 { 430 return Optional.ofNullable(getValue(REMAINING_MSG)); 431 } 432 433 /** 434 * Set the message that will be displayed to users when filling the form if a max entries limit is set 435 * @param msg The message for users 436 */ 437 public void setRemainingMessage(String msg) 438 { 439 this.setValue(REMAINING_MSG, msg); 440 } 441 442 /** 443 * Get the message that will be displayed to users when the entries limit is reached 444 * @return a message for users 445 */ 446 public Optional<String> getClosedMessage() 447 { 448 return Optional.ofNullable(getValue(CLOSED_MSG)); 449 } 450 451 /** 452 * Set the message that will be displayed to users when the entries limit is reached 453 * @param msg The message for users 454 */ 455 public void setClosedMessage(String msg) 456 { 457 this.setValue(CLOSED_MSG, msg); 458 } 459 460 /** 461 * Get the queue max size in this form 462 * @return the max number 463 */ 464 public Optional<Long> getQueueSize() 465 { 466 return Optional.ofNullable(getValue(QUEUE_SIZE)); 467 } 468 469 /** 470 * Set the queue max size in this form 471 * @param max The max number 472 */ 473 public void setQueueSize(Long max) 474 { 475 this.setValue(QUEUE_SIZE, max); 476 } 477 478 /** 479 * Get the message that will be displayed to users when the entries limit is reached 480 * and a queue is enabled 481 * @return a message for users 482 */ 483 public Optional<String> getClosedQueueMessage() 484 { 485 return Optional.ofNullable(getValue(QUEUE_CLOSED_MSG)); 486 } 487 488 /** 489 * Set the message that will be displayed to users when the entries limit is reached 490 * and a queue is enabled 491 * @param msg The message for users 492 */ 493 public void setClosedQueueMessage(String msg) 494 { 495 this.setValue(QUEUE_CLOSED_MSG, msg); 496 } 497 498 /** 499 * Get the email's sender for notifying exit of queue. 500 * @return The email's sender 501 */ 502 public Optional<String> getQueueMailSender() 503 { 504 return Optional.ofNullable(getValue(QUEUE_SENDER)); 505 } 506 507 /** 508 * Set the email's sender for notifying exit of queue. 509 * @param sender The email's sender 510 */ 511 public void setQueueMailSender(String sender) 512 { 513 this.setValue(QUEUE_SENDER, sender); 514 } 515 /** 516 * Get the email's receiver for notifying exit of queue. 517 * @return The email's receiver 518 */ 519 public Optional<String> getQueueMailReceiver() 520 { 521 return Optional.ofNullable(getValue(QUEUE_RECEIVER)); 522 } 523 524 /** 525 * Set the email's receiver for notifying exit of queue. 526 * @param receiver The email's receiver 527 */ 528 public void setQueueMailtReceiver(String receiver) 529 { 530 this.setValue(QUEUE_RECEIVER, receiver); 531 } 532 /** 533 * Get the email's subject for notifying exit of queue. 534 * @return The email's subject 535 */ 536 public Optional<String> getQueueMailSubject() 537 { 538 return Optional.ofNullable(getValue(QUEUE_SUBJECT)); 539 } 540 541 /** 542 * Set the email's subject for notifying exit of queue. 543 * @param subject The email's subject 544 */ 545 public void setQueueMailSubject(String subject) 546 { 547 this.setValue(QUEUE_SUBJECT, subject); 548 } 549 /** 550 * Get the email's body for notifying exit of queue. 551 * @return The email's body 552 */ 553 public Optional<String> getQueueMailBody() 554 { 555 return Optional.ofNullable(getValue(QUEUE_BODY)); 556 } 557 558 /** 559 * Set the email's body for notifying exit of queue. 560 * @param body The email's body 561 */ 562 public void setQueueMailBody(String body) 563 { 564 this.setValue(QUEUE_BODY, body); 565 } 566 567 /** 568 * Get the admin emails 569 * @return the admin emails 570 */ 571 public Optional<String[]> getAdminEmails() 572 { 573 return Optional.ofNullable(getValue(ADMIN_EMAILS)); 574 } 575 576 /** 577 * Set the admin emails 578 * @param emails the admin emails 579 */ 580 public void setAdminEmails(String[] emails) 581 { 582 this.setValue(ADMIN_EMAILS, emails); 583 } 584 585 /** 586 * Set the start date 587 * @param date The start date 588 */ 589 public void setStartDate(LocalDate date) 590 { 591 setValue(START_DATE, date); 592 } 593 594 /** 595 * Get the start date 596 * @return The the start date 597 */ 598 public LocalDate getStartDate() 599 { 600 return this.getValue(START_DATE); 601 } 602 603 /** 604 * Set the end date 605 * @param date The end date 606 */ 607 public void setEndDate(LocalDate date) 608 { 609 setValue(END_DATE, date); 610 } 611 612 /** 613 * Get the end date 614 * @return The the end date 615 */ 616 public LocalDate getEndDate() 617 { 618 return this.getValue(END_DATE); 619 } 620 621 public ModifiableIndexableDataHolder getDataHolder() 622 { 623 JCRRepositoryData repositoryData = new JCRRepositoryData(getNode()); 624 return new DefaultModifiableModelAwareDataHolder(repositoryData, this._getFactory().getModel()); 625 } 626 627 public String getSiteName() throws AmetysRepositoryException 628 { 629 return getSite().getName(); 630 } 631 632 public Site getSite() throws AmetysRepositoryException 633 { 634 AmetysObject parent = getParent(); 635 while (parent != null && !(parent instanceof Site)) 636 { 637 parent = parent.getParent(); 638 } 639 if (parent == null) 640 { 641 throw new AmetysRepositoryException("An error occurred with form with id '" + getId() + "'. Forms must always be linked to a site"); 642 } 643 return (Site) parent; 644 } 645 646 @Override 647 public Form copyTo(ModifiableTraversableAmetysObject parent, String name) throws AmetysRepositoryException 648 { 649 Form form = parent.createChild(name, FormFactory.FORM_NODETYPE); 650 form.setTitle(getTitle()); 651 652 for (FormPage page : getPages()) 653 { 654 page.copyTo(form, page.getName()); 655 } 656 657 return form; 658 } 659 660 @Override 661 public AmetysObject copyTo(ModifiableTraversableAmetysObject parent, String name, List<String> restrictTo) throws AmetysRepositoryException 662 { 663 return copyTo(parent, name); 664 } 665 666 /** 667 * Get the form pages. 668 * @return the form pages. 669 * @throws AmetysRepositoryException if an error occurs when retrieving the pages of the form 670 */ 671 public List<FormPage> getPages() throws AmetysRepositoryException 672 { 673 return getChildren().stream() 674 .filter(child -> child instanceof FormPage) 675 .map(child -> (FormPage) child) 676 .collect(Collectors.toList()); 677 } 678 679 /** 680 * <code>true</code> if the form has entries 681 * @return <code>true</code> if the form has entries 682 */ 683 public boolean hasEntries() 684 { 685 return !getEntries().isEmpty(); 686 } 687 /** 688 * Get the form entries. 689 * @return the form entries. 690 * @throws AmetysRepositoryException if an error occurs when retrieving the entries of the form 691 */ 692 public List<FormEntry> getEntries() throws AmetysRepositoryException 693 { 694 List<FormEntry> entries = new ArrayList<>(); 695 if (hasChild(FormEntryDAO.ENTRIES_ROOT)) 696 { 697 ModifiableTraversableAmetysObject entryRoot = getChild(FormEntryDAO.ENTRIES_ROOT); 698 699 entries.addAll(entryRoot.getChildren() 700 .stream() 701 .map(child -> (FormEntry) child) 702 .collect(Collectors.toList())); 703 } 704 return entries; 705 } 706 707 /** 708 * Get the active form entries. 709 * @return a list of the active form entries. 710 * @throws AmetysRepositoryException if an error occurs when retrieving the entries of the form 711 */ 712 public List<FormEntry> getActiveEntries() throws AmetysRepositoryException 713 { 714 return getEntries().stream() 715 .filter(e -> e.isActive()) 716 .collect(Collectors.toList()); 717 } 718 719 /** 720 * Get a question by its name. 721 * @param name the question name. 722 * @return the question. 723 * @throws AmetysRepositoryException if an error occurs when retrieving a question of a form 724 */ 725 public FormQuestion getQuestion(String name) throws AmetysRepositoryException 726 { 727 return getQuestions().stream() 728 .filter(q -> q.getNameForForm().equals(name)) 729 .findFirst() 730 .orElse(null); 731 } 732 733 /** 734 * Get the form questions. 735 * @return the form questions. 736 * @throws AmetysRepositoryException if an error occurs when retrieving all the questions of a form 737 */ 738 public List<FormQuestion> getQuestions() throws AmetysRepositoryException 739 { 740 return getPages().stream() 741 .map(FormPage::getQuestions) 742 .flatMap(List::stream) 743 .collect(Collectors.toList()); 744 } 745 746 /** 747 * Returns a unique question name in the form 748 * @param originalName The original name 749 * @return a unique question name 750 */ 751 public String findUniqueQuestionName (String originalName) 752 { 753 String name = originalName; 754 int index = 1; 755 List<String> questionNames = getQuestionsNames(); 756 while (questionNames.contains(name)) 757 { 758 name = originalName + "-" + (index++); 759 } 760 return name; 761 } 762 763 /** 764 * Verify that no question has the same JCR id as the current question 765 * @param uniqueName jcr name for the current question 766 * @return false if id already exist, true if not 767 */ 768 public boolean isQuestionNameUnique(String uniqueName) 769 { 770 return !getQuestionsNames().contains(uniqueName); 771 } 772 773 /** 774 * Get all the nameForForms of the questions in this form 775 * @return a list of the names for form 776 */ 777 public List<String> getQuestionsNames() 778 { 779 return getQuestions().stream() 780 .map(FormQuestion::getNameForForm) 781 .toList(); 782 } 783 784 /** 785 * Returns a unique question title in the form 786 * @param originalTitle The original title 787 * @return a unique question title 788 */ 789 public String findUniqueQuestionTitle (String originalTitle) 790 { 791 String title = originalTitle; 792 int index = 1; 793 List<String> questionTitles = getQuestions().stream() 794 .map(FormQuestion::getTitle) 795 .toList(); 796 while (questionTitles.contains(title)) 797 { 798 title = originalTitle + " " + (index++); 799 } 800 return title; 801 } 802 803 /** 804 * Get the rules having sourceQuestionId as sourceId 805 * @param sourceQuestionId The source question 806 * @return a map of question target's name for form and associated rule 807 */ 808 public Map<FormQuestion, Rule> getQuestionsRule(String sourceQuestionId) 809 { 810 Map<FormQuestion, Rule> questionsRule = new HashMap<>(); 811 for (FormQuestion question : getQuestions()) 812 { 813 Optional<Rule> questionRule = question.getFirstQuestionRule(); 814 if (questionRule.map(q -> q.getSourceId().equals(sourceQuestionId)).orElse(false)) 815 { 816 questionsRule.put(question, questionRule.get()); 817 } 818 } 819 return questionsRule; 820 } 821 822 /** 823 * Delete the rules having sourceQuestionId as sourceId 824 * @param sourceQuestionId The source question 825 */ 826 public void deleteQuestionsRule(String sourceQuestionId) 827 { 828 for (FormQuestion question : getQuestions()) 829 { 830 Optional<Rule> questionRule = question.getFirstQuestionRule(); 831 832 if (questionRule.map(q -> q.getSourceId().equals(sourceQuestionId)).orElse(false)) 833 { 834 question.getRepeater(FormQuestion.ATTRIBUTE_RULES).removeEntry(0); 835 } 836 } 837 saveChanges(); 838 } 839 840 /** 841 * <code>true</code> if the form has a workflow 842 * @return <code>true</code> if the form has a workflow 843 */ 844 public boolean hasWorkflow() 845 { 846 return StringUtils.isNotBlank(getWorkflowName()); 847 } 848 849 @Override 850 public boolean canMoveTo(AmetysObject newParent) throws AmetysRepositoryException 851 { 852 return FormAndDirectoryCommonMethods.canMoveTo(getSiteName(), newParent, this, _getFactory().getFormDirectoriesDAO()); 853 } 854 855 @Override 856 public void moveTo(AmetysObject newParent, boolean renameIfExist) throws AmetysRepositoryException, RepositoryIntegrityViolationException 857 { 858 FormAndDirectoryCommonMethods.moveTo(newParent, renameIfExist, this); 859 } 860 861 @Override 862 public void orderBefore(AmetysObject siblingNode) throws AmetysRepositoryException 863 { 864 FormAndDirectoryCommonMethods.orderBefore(siblingNode, this); 865 } 866 867 /** 868 * Rights profiles 869 */ 870 public enum FormProfile 871 { 872 /** Read access */ 873 READ_ACCESS, 874 /** Write access */ 875 WRITE_ACCESS, 876 /** Right access */ 877 RIGHT_ACCESS; 878 879 @Override 880 public String toString() 881 { 882 return name().toLowerCase(); 883 } 884 } 885 886}