diff --git a/src/main/java/org/olat/core/util/vfs/FileStorage.java b/src/main/java/org/olat/core/util/vfs/FileStorage.java index de0ba64437d87a8546208145622f738cb8145e15..19e9b11cc290bc8ffeb205984daebfa7c41afb40 100644 --- a/src/main/java/org/olat/core/util/vfs/FileStorage.java +++ b/src/main/java/org/olat/core/util/vfs/FileStorage.java @@ -96,7 +96,7 @@ public class FileStorage { if(items.isEmpty()) { lastToken = "01"; } else { - Set<String> names = new HashSet<String>(); + Set<String> names = new HashSet<>(); for(VFSItem item:items) { names.add(item.getName()); } diff --git a/src/main/java/org/olat/modules/qpool/QuestionPoolModule.java b/src/main/java/org/olat/modules/qpool/QuestionPoolModule.java index 7acd6c6f34239f8797dac4e303aed46dfb19dd07..46c6c65a8c1c8d586c60530c9ec32a7cbfad0364 100644 --- a/src/main/java/org/olat/modules/qpool/QuestionPoolModule.java +++ b/src/main/java/org/olat/modules/qpool/QuestionPoolModule.java @@ -54,13 +54,16 @@ public class QuestionPoolModule extends AbstractSpringModule implements ConfigOn private static final String COLLECTIONS_ENABLED = "collections.enabled"; private static final String POOLS_ENABLED = "pools.enabled"; private static final String SHARES_ENABLED = "shares.enabled"; + private static final String TAXONOMY_QPOOL_KEY = "taxonomy.qpool.key"; + public static final String DEFAULT_TAXONOMY_QPOOL_IDENTIFIER = "QPOOL"; + private static final String DELETE_QUESTIONS_WITHOUT_AUTHOR = "delete.questions.without.author"; + private static final String REVIEW_PROCESS_ENABLED = "review.process.enabled"; private static final String REVIEW_DECISION_TYPE = "review.decision.type"; private static final String REVIEW_DECISION_NUMBER_OF_RATINGS = "review.decision.number.of.ratings"; private static final String REVIEW_LOWER_LIMIT = "review.decision.lower.limit"; private static final String FINAL_VISIBLE_TEACH = "final.visible.teach"; - private static final String TAXONOMY_QPOOL_KEY = "taxonomy.qpool.key"; - public static final String DEFAULT_TAXONOMY_QPOOL_IDENTIFIER = "QPOOL"; + private static final String POOL_ADMIN_METADATA = "pool.admin.metadata"; private static final String POOL_ADMIN_STATUS = "pool.admin.status"; private static final String POOL_ADMIN_REVIEW_PROCESS = "pool.admin.review.process"; @@ -73,10 +76,12 @@ public class QuestionPoolModule extends AbstractSpringModule implements ConfigOn private boolean collectionsEnabled = true; private boolean poolsEnabled = true; private boolean sharesEnabled = true; + private String taxonomyQPoolKey; + private boolean deleteQuestionsWithoutAuthor = false; + private boolean reviewProcessEnabled = false; private String reviewDecisionType = ProcesslessDecisionProvider.TYPE; private boolean finalVisibleTeach = false; - private String taxonomyQPoolKey; private boolean poolAdminAllowedToEditMetadata = false; private boolean poolAdminAllowedToEditStatus = false; @@ -147,6 +152,11 @@ public class QuestionPoolModule extends AbstractSpringModule implements ConfigOn reviewProcessEnabled = "true".equals(reviewProcessEnabledObj); } + String deleteQuestionsWithoutAuthorObj = getStringPropertyValue(DELETE_QUESTIONS_WITHOUT_AUTHOR, true); + if(StringHelper.containsNonWhitespace(deleteQuestionsWithoutAuthorObj)) { + deleteQuestionsWithoutAuthor = "true".equals(deleteQuestionsWithoutAuthorObj); + } + String reviewDecisionTypeObj = getStringPropertyValue(REVIEW_DECISION_TYPE, true); if(StringHelper.containsNonWhitespace(reviewDecisionTypeObj)) { reviewDecisionType = reviewDecisionTypeObj; @@ -273,6 +283,15 @@ public class QuestionPoolModule extends AbstractSpringModule implements ConfigOn setStringProperty(SHARES_ENABLED, Boolean.toString(sharesEnabled), true); } + public boolean isDeleteQuestionsWithoutAuthor() { + return deleteQuestionsWithoutAuthor; + } + + public void setDeleteQuestionsWithoutAuthor(boolean deleteQuestionsWithoutAuthor) { + this.deleteQuestionsWithoutAuthor = deleteQuestionsWithoutAuthor; + setStringProperty(DELETE_QUESTIONS_WITHOUT_AUTHOR, Boolean.toString(deleteQuestionsWithoutAuthor), true); + } + public boolean isReviewProcessEnabled() { return reviewProcessEnabled; } diff --git a/src/main/java/org/olat/modules/qpool/manager/QItemQueriesDAO.java b/src/main/java/org/olat/modules/qpool/manager/QItemQueriesDAO.java index b79fd69ae9b0b487f357a475ae5f652bce9981ee..0075dd83f7ff4a7a756b3ce2b8da7cc41f26438c 100644 --- a/src/main/java/org/olat/modules/qpool/manager/QItemQueriesDAO.java +++ b/src/main/java/org/olat/modules/qpool/manager/QItemQueriesDAO.java @@ -687,9 +687,16 @@ public class QItemQueriesDAO { sb.append(" where rating.resId=item.key and rating.resName='QuestionItem'"); sb.append(" and rating.creator.key=:excludeRatorKey)"); } - if (params.isMissingTaxonomyLevelOnly()) { + if (params.isWithoutTaxonomyLevelOnly()) { sb.append(" and taxonomyLevel is null"); } + if (params.isWithoutAuthorOnly()) { + sb.append(" and not exists (").append("select sgmi.key from "); + sb.append(SecurityGroupMembershipImpl.class.getName()).append(" as sgmi"); + sb.append(" inner join sgmi.identity ident"); + sb.append(" where sgmi.securityGroup=item.ownerGroup"); + sb.append(" )"); + } } private void appendParameters(SearchQuestionItemParams params, TypedQuery<?> query) { diff --git a/src/main/java/org/olat/modules/qpool/manager/QPoolFileStorage.java b/src/main/java/org/olat/modules/qpool/manager/QPoolFileStorage.java index fbec35f2b6e582f70d10a3e5ff579438cdba87f7..6bac2ecedff7e464368cf10ba95dc4f174675f15 100644 --- a/src/main/java/org/olat/modules/qpool/manager/QPoolFileStorage.java +++ b/src/main/java/org/olat/modules/qpool/manager/QPoolFileStorage.java @@ -68,4 +68,11 @@ public class QPoolFileStorage { VFSContainer container = fileStorage.getContainer(dir); return ((LocalImpl)container).getBasefile(); } + + public void deleteDir(String dir) { + VFSContainer container = fileStorage.getContainer(dir); + if (container != null) { + container.delete(); + } + } } \ No newline at end of file diff --git a/src/main/java/org/olat/modules/qpool/manager/QuestionItemDAO.java b/src/main/java/org/olat/modules/qpool/manager/QuestionItemDAO.java index 12ed76b7cb5cdc702119b2d49bca6ce390e4d130..eb9dd0c7c7c6ea69782b2e2dee1ba6d1d83c5fa6 100644 --- a/src/main/java/org/olat/modules/qpool/manager/QuestionItemDAO.java +++ b/src/main/java/org/olat/modules/qpool/manager/QuestionItemDAO.java @@ -236,6 +236,23 @@ public class QuestionItemDAO { .getResultList(); } + public List<QuestionItemShort> getItemsWithOneAuthor(Identity author) { + StringBuilder sb = new StringBuilder(); + sb.append("select item from questionitem item"); + sb.append(" where exists (").append("select sgmi.key from "); + sb.append(SecurityGroupMembershipImpl.class.getName()).append(" as sgmi"); + sb.append(" where sgmi.identity.key=:identityKey and sgmi.securityGroup=item.ownerGroup"); + sb.append(" )"); + sb.append(" and 1 = (").append("select count(sgmi.key) from "); + sb.append(SecurityGroupMembershipImpl.class.getName()).append(" as sgmi"); + sb.append(" where sgmi.securityGroup=item.ownerGroup"); + sb.append(" )"); + return dbInstance.getCurrentEntityManager() + .createQuery(sb.toString(), QuestionItemShort.class) + .setParameter("identityKey", author.getKey()) + .getResultList(); + } + public void delete(List<? extends QuestionItemShort> items) { EntityManager em = dbInstance.getCurrentEntityManager(); for(QuestionItemShort item:items) { diff --git a/src/main/java/org/olat/modules/qpool/manager/QuestionPoolServiceImpl.java b/src/main/java/org/olat/modules/qpool/manager/QuestionPoolServiceImpl.java index 3465757eb80558b7fbdfb7ca34e4ad956b792c84..625dcd7902de592552d10395320dfe88b70461ab 100644 --- a/src/main/java/org/olat/modules/qpool/manager/QuestionPoolServiceImpl.java +++ b/src/main/java/org/olat/modules/qpool/manager/QuestionPoolServiceImpl.java @@ -38,6 +38,7 @@ import org.olat.core.commons.persistence.DefaultResultInfos; import org.olat.core.commons.persistence.ResultInfos; import org.olat.core.commons.persistence.SortKey; import org.olat.core.commons.services.commentAndRating.CommentAndRatingService; +import org.olat.core.commons.services.mark.MarkManager; import org.olat.core.gui.media.MediaResource; import org.olat.core.id.Identity; import org.olat.core.id.Roles; @@ -118,6 +119,8 @@ public class QuestionPoolServiceImpl implements QPoolService { @Autowired private QuestionItemDAO questionItemDao; @Autowired + private QPoolFileStorage qpoolFileStorage; + @Autowired private QuestionPoolModule qpoolModule; @Autowired private BaseSecurity securityManager; @@ -134,6 +137,8 @@ public class QuestionPoolServiceImpl implements QPoolService { @Autowired private CommentAndRatingService commentAndRatingService; @Autowired + private MarkManager markManager; + @Autowired private ReviewService reviewService; @Override @@ -142,15 +147,29 @@ public class QuestionPoolServiceImpl implements QPoolService { return; //nothing to do } - poolDao.removeFromPools(items); - questionItemDao.removeFromShares(items); - collectionDao.deleteItemFromCollections(items); + List<SecurityGroup> secGroups = new ArrayList<>(); for (QuestionItemShort item: items) { + markManager.deleteMarks(item); commentAndRatingService.deleteAllIgnoringSubPath(item); + QuestionItem loadedItem = loadItemById(item.getKey()); + if (loadedItem instanceof QuestionItemImpl) { + QuestionItemImpl itemImpl = (QuestionItemImpl) loadedItem; + qpoolFileStorage.deleteDir(itemImpl.getDirectory()); + secGroups.add(itemImpl.getOwnerGroup()); + } + dbInstance.intermediateCommit(); } - //TODO unmark + + poolDao.removeFromPools(items); + questionItemDao.removeFromShares(items); + collectionDao.deleteItemFromCollections(items); questionItemDao.delete(items); + // Delete SecurityGroup after the item to avoid foreign key constraint violation. + for (SecurityGroup secGroup: secGroups) { + securityManager.deleteSecurityGroup(secGroup); + } + for(QuestionItemShort item:items) { lifeIndexer.deleteDocument(QItemDocument.TYPE, item.getKey()); } diff --git a/src/main/java/org/olat/modules/qpool/manager/QuestionPoolUserDataDeletable.java b/src/main/java/org/olat/modules/qpool/manager/QuestionPoolUserDataDeletable.java new file mode 100644 index 0000000000000000000000000000000000000000..3c49f8f53dd42f8270663cb513873af1dc9b569d --- /dev/null +++ b/src/main/java/org/olat/modules/qpool/manager/QuestionPoolUserDataDeletable.java @@ -0,0 +1,82 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.modules.qpool.manager; + +import java.io.File; +import java.util.List; + +import org.olat.core.id.Identity; +import org.olat.core.logging.OLog; +import org.olat.core.logging.Tracing; +import org.olat.modules.qpool.QPoolService; +import org.olat.modules.qpool.QuestionItemShort; +import org.olat.modules.qpool.QuestionPoolModule; +import org.olat.user.UserDataDeletable; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + * When a identity is deleted, the BaseSecurityManager removes the identity from + * all SecurityGroups. As a result of this, the identity is no longer the author + * of his question items. If the identity was the only author of a question + * item, the question item has no author anymore. This class deletes the + * question items before the deletion of the identity, if it is the only author + * of the question. The question items are only deleted, if the appropriate + * option is enabled. + * + * Initial date: 11.01.2018<br> + * + * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com + * + */ +@Service +public class QuestionPoolUserDataDeletable implements UserDataDeletable { + + private static final OLog log = Tracing.createLoggerFor(QuestionPoolUserDataDeletable.class); + + @Autowired + private QuestionPoolModule qpoolModule; + @Autowired + private QPoolService qpoolService; + @Autowired + private QuestionItemDAO questionItemDao; + + @Override + public void deleteUserData(Identity identity, String newDeletedUserName, File archivePath) { + if (!qpoolModule.isDeleteQuestionsWithoutAuthor()) return; + + List<QuestionItemShort> itemsWithOneAuthor = questionItemDao.getItemsWithOneAuthor(identity); + qpoolService.deleteItems(itemsWithOneAuthor); + + String logMessage = getLogMessage(identity, itemsWithOneAuthor); + log.info(logMessage); + } + + private String getLogMessage(Identity identity, List<QuestionItemShort> items) { + return new StringBuilder() + .append("Deleted ") + .append(items.size()) + .append(" question items form the question pool after the deletion of identity ") + .append(identity) + .append(".") + .toString(); + } + +} diff --git a/src/main/java/org/olat/modules/qpool/model/SearchQuestionItemParams.java b/src/main/java/org/olat/modules/qpool/model/SearchQuestionItemParams.java index 09ddcc00afaf2f5eee8efc798cc9aad167abb9c2..615ce133d1bb80843ece8c40309d78dfecbe528e 100644 --- a/src/main/java/org/olat/modules/qpool/model/SearchQuestionItemParams.java +++ b/src/main/java/org/olat/modules/qpool/model/SearchQuestionItemParams.java @@ -52,7 +52,8 @@ public class SearchQuestionItemParams { private Identity excludeAuthor; private Identity excludeRater; - private boolean missingTaxonomyLevelOnly; + private boolean withoutTaxonomyLevelOnly; + private boolean withoutAuthorOnly; private final Identity identity; private final Roles roles; @@ -142,12 +143,20 @@ public class SearchQuestionItemParams { this.excludeRater = excludeRater; } - public boolean isMissingTaxonomyLevelOnly() { - return missingTaxonomyLevelOnly; + public boolean isWithoutTaxonomyLevelOnly() { + return withoutTaxonomyLevelOnly; } - public void setMissingTaxonomyLevelOnly(boolean missingTaxonomyLevelOnly) { - this.missingTaxonomyLevelOnly = missingTaxonomyLevelOnly; + public void setWithoutTaxonomyLevelOnly(boolean withoutTaxonomyLevelOnly) { + this.withoutTaxonomyLevelOnly = withoutTaxonomyLevelOnly; + } + + public boolean isWithoutAuthorOnly() { + return withoutAuthorOnly; + } + + public void setWithoutAuthorOnly(boolean withoutAuthorOnly) { + this.withoutAuthorOnly = withoutAuthorOnly; } public boolean isFulltextSearch() { @@ -196,7 +205,8 @@ public class SearchQuestionItemParams { clone.onlyAuthor = onlyAuthor; clone.excludeAuthor = excludeAuthor; clone.excludeRater = excludeRater; - clone.missingTaxonomyLevelOnly = missingTaxonomyLevelOnly; + clone.withoutTaxonomyLevelOnly = withoutTaxonomyLevelOnly; + clone.withoutAuthorOnly = withoutAuthorOnly; return clone; } diff --git a/src/main/java/org/olat/modules/qpool/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/modules/qpool/ui/_i18n/LocalStrings_de.properties index c894ecd888b2ae3b627649031d9ff1988cc29725..79bb70da9f5c9d2e383e8f5ba52d8ea8a78b8401 100644 --- a/src/main/java/org/olat/modules/qpool/ui/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/modules/qpool/ui/_i18n/LocalStrings_de.properties @@ -129,7 +129,8 @@ menu.pools.main=Katalog menu.pools.main.alt=Katalog menu.queries=Fragen menu.queries.all=Alle Fragen -menu.queries.missing.taxonomy.level=Ohne Fachbereich +menu.queries.without.taxonomy.level=Ohne Fachbereich +menu.queries.without.author=Ohne Autor menu.review=Beurteilung menu.share=Freigaben metadatas=Metadaten diff --git a/src/main/java/org/olat/modules/qpool/ui/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/modules/qpool/ui/_i18n/LocalStrings_en.properties index 68249f07b4ffa70f6ab18d652ce7845d69dd10c4..caacb66fba56e93e0b908c74cf91bb96ed63c3ee 100644 --- a/src/main/java/org/olat/modules/qpool/ui/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/modules/qpool/ui/_i18n/LocalStrings_en.properties @@ -129,7 +129,8 @@ menu.pools.main=Catalog menu.pools.main.alt=Catalog menu.queries=Questions menu.queries.all=All questions -menu.queries.missing.taxonomy.level=Without Subject +menu.queries.whithout.taxonomy.level=Without subject +menu.queries.whithout.author=Without author menu.review=Review menu.share=Public shares metadatas=Metadata diff --git a/src/main/java/org/olat/modules/qpool/ui/admin/QuestionPoolAdminConfigurationController.java b/src/main/java/org/olat/modules/qpool/ui/admin/QuestionPoolAdminConfigurationController.java index 714abd694765312cb8364fa3ca37f01adc0cd4fc..746d638a61662826de1802758641cac84030ec0a 100644 --- a/src/main/java/org/olat/modules/qpool/ui/admin/QuestionPoolAdminConfigurationController.java +++ b/src/main/java/org/olat/modules/qpool/ui/admin/QuestionPoolAdminConfigurationController.java @@ -74,6 +74,7 @@ public class QuestionPoolAdminConfigurationController extends FormBasicControlle private MultipleSelectionElement collectionsEnabledEl; private MultipleSelectionElement poolsEnabledEl; private MultipleSelectionElement sharesEnabledEl; + private MultipleSelectionElement deleteQuestionsWithoutAuthorEl; private MultipleSelectionElement poolManagerRightsEl; private SingleSelection taxonomyTreeEl; @@ -123,6 +124,12 @@ public class QuestionPoolAdminConfigurationController extends FormBasicControlle if (qpoolModule.isSharesEnabled()) { sharesEnabledEl.select(onKeys[0], true); } + + deleteQuestionsWithoutAuthorEl = uifactory.addCheckboxesHorizontal("delete.qustions.without.author", moduleCont, onKeys, onValues); + deleteQuestionsWithoutAuthorEl.setHelpTextKey("delete.qustions.without.author.info", null); + if (qpoolModule.isDeleteQuestionsWithoutAuthor()) { + deleteQuestionsWithoutAuthorEl.select(onKeys[0], true); + } List<Taxonomy> taxonomyList = taxonomyService.getTaxonomyList(); String[] taxonomyKeys = new String[taxonomyList.size() + 1]; @@ -236,6 +243,9 @@ public class QuestionPoolAdminConfigurationController extends FormBasicControlle boolean sharesEnabled = sharesEnabledEl.isAtLeastSelected(1); qpoolModule.setSharesEnabled(sharesEnabled); + boolean deleteQuestionsWithoutAuthor = deleteQuestionsWithoutAuthorEl.isAtLeastSelected(1); + qpoolModule.setDeleteQuestionsWithoutAuthor(deleteQuestionsWithoutAuthor); + String selectedTaxonomyQPoolKey = taxonomyTreeEl.getSelectedKey(); qpoolModule.setTaxonomyQPoolKey(selectedTaxonomyQPoolKey); diff --git a/src/main/java/org/olat/modules/qpool/ui/admin/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/modules/qpool/ui/admin/_i18n/LocalStrings_de.properties index 88dfd21e4d39026ad22209b12bb5ac9406552c1b..36385a85161d30c5bf3e1f39060ecf725508f96c 100644 --- a/src/main/java/org/olat/modules/qpool/ui/admin/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/modules/qpool/ui/admin/_i18n/LocalStrings_de.properties @@ -19,6 +19,8 @@ delete.level=L\u00F6schen delete.level.confirm=Wollen Sie wirklich diese Stufe l\u00F6schen? delete.license=L\u00F6schen delete.license.confirm=Wollen Sie wirklich diese Lizenz l\u00F6schen? +delete.qustions.without.author=Fragen l\u00F6schen, wenn Autor gel\u00F6scht +delete.qustions.without.author.info=Beim L\u00F6schen eines Benutzers wird er als Autoren von allen Fragen ausgetragen. Wenn diese Option eingeschaltet ist und der Benutzer der einzige Autor einer Frage ist, wird die Frage ebenfalls gel\u00F6scht. delete.taxonomyLevel=L\u00F6schen delete.taxonomyLevel.confirm=Wollen Sie wirklich diese Fachbereich "{0}" l\u00F6schen? delete.type=L\u00F6schen diff --git a/src/main/java/org/olat/modules/qpool/ui/admin/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/modules/qpool/ui/admin/_i18n/LocalStrings_en.properties index dc4a4e4e2bfaa67a254896e9910002da2dd29efc..0d5ed6c12f478d6131d7fd5caf86b836dd85ebef 100644 --- a/src/main/java/org/olat/modules/qpool/ui/admin/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/modules/qpool/ui/admin/_i18n/LocalStrings_en.properties @@ -18,6 +18,8 @@ delete.level=Delete delete.level.confirm=Do you really want to delete this level? delete.taxonomyLevel=Delete delete.taxonomyLevel.confirm=Do you really want to delete this subject "{0}"? +delete.qustions.without.author=Delete questions when author deleted +delete.qustions.without.author.info=When a user is deleted, he is removed as author from his question items. If this option is enabled and the user is the only author of the question item, the question item is deleted as well. delete.license=Delete delete.license.confirm=Do you really want to delete this license? delete.type=Delete diff --git a/src/main/java/org/olat/modules/qpool/ui/datasource/WithoutAuthorItemSource.java b/src/main/java/org/olat/modules/qpool/ui/datasource/WithoutAuthorItemSource.java new file mode 100644 index 0000000000000000000000000000000000000000..02284a5f2b1bdbf1a265aab89833842a60732f97 --- /dev/null +++ b/src/main/java/org/olat/modules/qpool/ui/datasource/WithoutAuthorItemSource.java @@ -0,0 +1,38 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.modules.qpool.ui.datasource; + +import org.olat.core.id.Identity; +import org.olat.core.id.Roles; + +/** + * + * Initial date: 12.01.2018<br> + * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com + * + */ +public class WithoutAuthorItemSource extends AllItemsSource { + + public WithoutAuthorItemSource(Identity me, Roles roles, String name) { + super(me, roles, name); + getDefaultParams().setWithoutAuthorOnly(true); + } + +} diff --git a/src/main/java/org/olat/modules/qpool/ui/datasource/MissingTaxonomyLevelItemSource.java b/src/main/java/org/olat/modules/qpool/ui/datasource/WithoutTaxonomyLevelItemSource.java similarity index 85% rename from src/main/java/org/olat/modules/qpool/ui/datasource/MissingTaxonomyLevelItemSource.java rename to src/main/java/org/olat/modules/qpool/ui/datasource/WithoutTaxonomyLevelItemSource.java index b040fe5b43fcc0aa7cb1d55f4395b02c319b820c..9618e65de783ed019fdc0b86ed2afd4e24dc39c6 100644 --- a/src/main/java/org/olat/modules/qpool/ui/datasource/MissingTaxonomyLevelItemSource.java +++ b/src/main/java/org/olat/modules/qpool/ui/datasource/WithoutTaxonomyLevelItemSource.java @@ -28,11 +28,11 @@ import org.olat.core.id.Roles; * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com * */ -public class MissingTaxonomyLevelItemSource extends AllItemsSource { +public class WithoutTaxonomyLevelItemSource extends AllItemsSource { - public MissingTaxonomyLevelItemSource(Identity me, Roles roles, String name) { + public WithoutTaxonomyLevelItemSource(Identity me, Roles roles, String name) { super(me, roles, name); - getDefaultParams().setMissingTaxonomyLevelOnly(true); + getDefaultParams().setWithoutTaxonomyLevelOnly(true); } } diff --git a/src/main/java/org/olat/modules/qpool/ui/tree/QuestionPoolMenuTreeModel.java b/src/main/java/org/olat/modules/qpool/ui/tree/QuestionPoolMenuTreeModel.java index 0eb35b7169092a5bb2b93e4957c0d6428b731062..774a79e4bfdaa99939b8fbdecddf21e5eb73c079 100644 --- a/src/main/java/org/olat/modules/qpool/ui/tree/QuestionPoolMenuTreeModel.java +++ b/src/main/java/org/olat/modules/qpool/ui/tree/QuestionPoolMenuTreeModel.java @@ -355,7 +355,10 @@ public class QuestionPoolMenuTreeModel extends GenericTreeModel implements DnDTr TreeNode node = new AllQuestionsTreeNode(stackPanel, securityCallback, translator.translate("menu.queries.all")); queriesNode.addChild(node); - node = new MissingTaxonomyLevelTreeNode(stackPanel, securityCallback, translator.translate("menu.queries.missing.taxonomy.level")); + node = new WithoutTaxonomyLevelTreeNode(stackPanel, securityCallback, translator.translate("menu.queries.without.taxonomy.level")); + queriesNode.addChild(node); + + node = new WithoutAuthorTreeNode(stackPanel, securityCallback, translator.translate("menu.queries.without.author")); queriesNode.addChild(node); setFirstChildAsDelegate(queriesNode); diff --git a/src/main/java/org/olat/modules/qpool/ui/tree/WithoutAuthorTreeNode.java b/src/main/java/org/olat/modules/qpool/ui/tree/WithoutAuthorTreeNode.java new file mode 100644 index 0000000000000000000000000000000000000000..6cbea7631999d96e50848fb823acf51934f28a03 --- /dev/null +++ b/src/main/java/org/olat/modules/qpool/ui/tree/WithoutAuthorTreeNode.java @@ -0,0 +1,80 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.modules.qpool.ui.tree; + +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.stack.TooledStackedPanel; +import org.olat.core.gui.components.tree.GenericTreeNode; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.id.OLATResourceable; +import org.olat.core.id.context.BusinessControlFactory; +import org.olat.core.util.resource.OresHelper; +import org.olat.modules.qpool.QPoolSecurityCallback; +import org.olat.modules.qpool.ui.QuestionItemsSource; +import org.olat.modules.qpool.ui.QuestionsController; +import org.olat.modules.qpool.ui.datasource.WithoutAuthorItemSource; + +/** + * + * Initial date: 12.01.2018<br> + * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com + * + */ +public class WithoutAuthorTreeNode extends GenericTreeNode implements ControllerTreeNode { + + private static final long serialVersionUID = -1425835238632839745L; + + private static final String WITHOUT_AUTHOR = "WithoutAuthor"; + public static final OLATResourceable ORES = OresHelper.createOLATResourceableType(WITHOUT_AUTHOR); + + private final TooledStackedPanel stackPanel; + private QuestionsController questionsCtrl; + + private final QPoolSecurityCallback securityCallback; + + public WithoutAuthorTreeNode(TooledStackedPanel stackPanel, QPoolSecurityCallback securityCallback, String title) { + super(); + this.stackPanel = stackPanel; + this.securityCallback = securityCallback; + + this.setTitle(title); + + this.setUserObject(WITHOUT_AUTHOR); + } + + @Override + public Controller getController(UserRequest ureq, WindowControl wControl) { + if(questionsCtrl == null) { + QuestionItemsSource source = new WithoutAuthorItemSource( + ureq.getIdentity(), + ureq.getUserSession().getRoles(), + WITHOUT_AUTHOR); + WindowControl swControl = BusinessControlFactory.getInstance().createBusinessWindowControl(ureq, ORES, null, + wControl, true); + questionsCtrl = new QuestionsController(ureq, swControl, stackPanel, source, securityCallback, + WITHOUT_AUTHOR, true); + } else { + questionsCtrl.updateSource(); + } + return questionsCtrl; + } + +} diff --git a/src/main/java/org/olat/modules/qpool/ui/tree/MissingTaxonomyLevelTreeNode.java b/src/main/java/org/olat/modules/qpool/ui/tree/WithoutTaxonomyLevelTreeNode.java similarity index 83% rename from src/main/java/org/olat/modules/qpool/ui/tree/MissingTaxonomyLevelTreeNode.java rename to src/main/java/org/olat/modules/qpool/ui/tree/WithoutTaxonomyLevelTreeNode.java index b1051c3a6f39b9610a98dadca5c22baeefea8891..e5b4a9fb35ae783467185bcbd026a89d526fc70d 100644 --- a/src/main/java/org/olat/modules/qpool/ui/tree/MissingTaxonomyLevelTreeNode.java +++ b/src/main/java/org/olat/modules/qpool/ui/tree/WithoutTaxonomyLevelTreeNode.java @@ -30,7 +30,7 @@ import org.olat.core.util.resource.OresHelper; import org.olat.modules.qpool.QPoolSecurityCallback; import org.olat.modules.qpool.ui.QuestionItemsSource; import org.olat.modules.qpool.ui.QuestionsController; -import org.olat.modules.qpool.ui.datasource.MissingTaxonomyLevelItemSource; +import org.olat.modules.qpool.ui.datasource.WithoutTaxonomyLevelItemSource; /** * @@ -38,39 +38,39 @@ import org.olat.modules.qpool.ui.datasource.MissingTaxonomyLevelItemSource; * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com * */ -public class MissingTaxonomyLevelTreeNode extends GenericTreeNode implements ControllerTreeNode { +public class WithoutTaxonomyLevelTreeNode extends GenericTreeNode implements ControllerTreeNode { private static final long serialVersionUID = -4546025987046093829L; - private static final String MISSING_TAXONOMY_LEVEL = "MissingTaxonomyLevel"; - public static final OLATResourceable ORES = OresHelper.createOLATResourceableType(MISSING_TAXONOMY_LEVEL); + private static final String WITHOUT_TAXONOMY_LEVEL = "NoTaxonomyLevel"; + public static final OLATResourceable ORES = OresHelper.createOLATResourceableType(WITHOUT_TAXONOMY_LEVEL); private final TooledStackedPanel stackPanel; private QuestionsController questionsCtrl; private final QPoolSecurityCallback securityCallback; - public MissingTaxonomyLevelTreeNode(TooledStackedPanel stackPanel, QPoolSecurityCallback securityCallback, String title) { + public WithoutTaxonomyLevelTreeNode(TooledStackedPanel stackPanel, QPoolSecurityCallback securityCallback, String title) { super(); this.stackPanel = stackPanel; this.securityCallback = securityCallback; this.setTitle(title); - this.setUserObject(MISSING_TAXONOMY_LEVEL); + this.setUserObject(WITHOUT_TAXONOMY_LEVEL); } @Override public Controller getController(UserRequest ureq, WindowControl wControl) { if(questionsCtrl == null) { - QuestionItemsSource source = new MissingTaxonomyLevelItemSource( + QuestionItemsSource source = new WithoutTaxonomyLevelItemSource( ureq.getIdentity(), ureq.getUserSession().getRoles(), - MISSING_TAXONOMY_LEVEL); + WITHOUT_TAXONOMY_LEVEL); WindowControl swControl = BusinessControlFactory.getInstance().createBusinessWindowControl(ureq, ORES, null, wControl, true); questionsCtrl = new QuestionsController(ureq, swControl, stackPanel, source, securityCallback, - MISSING_TAXONOMY_LEVEL, true); + WITHOUT_TAXONOMY_LEVEL, true); } else { questionsCtrl.updateSource(); } diff --git a/src/test/java/org/olat/modules/qpool/manager/FileStorageTest.java b/src/test/java/org/olat/modules/qpool/manager/FileStorageTest.java index c6a855ace3fb25b48762c4be9368650db7e3ddaf..fb4ddf21b283cf523c8708f334b8659be6122fdf 100644 --- a/src/test/java/org/olat/modules/qpool/manager/FileStorageTest.java +++ b/src/test/java/org/olat/modules/qpool/manager/FileStorageTest.java @@ -61,4 +61,18 @@ public class FileStorageTest extends OlatTestCase { Assert.assertNotNull(dir2); Assert.assertFalse(dir1.equals(dir2)); } + + @Test + public void testDeleteDir() { + String uuid = UUID.randomUUID().toString(); + String dir = qpoolFileStorage.generateDir(uuid); + VFSContainer container = qpoolFileStorage.getContainer(dir); + container.createChildLeaf("abc.txt"); + container.createChildLeaf("xyzc.txt"); + Assert.assertTrue(container.getItems().size() > 0); + + qpoolFileStorage.deleteDir(dir); + + Assert.assertTrue(container.getParentContainer().resolve(container.getName()) == null); + } } diff --git a/src/test/java/org/olat/modules/qpool/manager/QItemQueriesDAOTest.java b/src/test/java/org/olat/modules/qpool/manager/QItemQueriesDAOTest.java index 2b5431499afe6c2e461fe160b0c0ad504a59c508..b98a9e98a5ff634c06ea57c0763a6241cd12f1cc 100644 --- a/src/test/java/org/olat/modules/qpool/manager/QItemQueriesDAOTest.java +++ b/src/test/java/org/olat/modules/qpool/manager/QItemQueriesDAOTest.java @@ -709,7 +709,7 @@ public class QItemQueriesDAOTest extends OlatTestCase { } @Test - public void shouldGetItemsFilteredByMissingTaxonomyLevel() { + public void shouldGetItemsFilteredByWithoutTaxonomyLevel() { Taxonomy taxonomy = taxonomyDao.createTaxonomy("QPool", "QPool", "", null); TaxonomyLevel taxonomyLevel = taxonomyLevelDao.createTaxonomyLevel("QPool", "QPool", "QPool", null, null, null, null, taxonomy); TaxonomyLevel taxonomySubLevel = taxonomyLevelDao.createTaxonomyLevel("QPool", "QPool", "QPool", null, null, taxonomyLevel, null, taxonomy); @@ -725,7 +725,7 @@ public class QItemQueriesDAOTest extends OlatTestCase { dbInstance.commitAndCloseSession(); SearchQuestionItemParams params = new SearchQuestionItemParams(createRandomIdentity(), null); - params.setMissingTaxonomyLevelOnly(true); + params.setWithoutTaxonomyLevelOnly(true); List<QuestionItemView> loadedItems = qItemQueriesDao.getItems(params, null, 0, -1); assertThat(loadedItems).hasSize(2); @@ -737,6 +737,29 @@ public class QItemQueriesDAOTest extends OlatTestCase { assertThat(countItems).isEqualTo(2); } + @Test + public void shouldGetItemsFilteredByWithoutAuthor() { + QuestionItem item11 = createRandomItem(null); + QuestionItem item12 = createRandomItem(createRandomIdentity()); + QuestionItem item21 = createRandomItem(null); + QuestionItem item22 = createRandomItem(createRandomIdentity()); + QuestionItem item23 = createRandomItem(createRandomIdentity()); + + dbInstance.commitAndCloseSession(); + + SearchQuestionItemParams params = new SearchQuestionItemParams(createRandomIdentity(), null); + params.setWithoutAuthorOnly(true); + List<QuestionItemView> loadedItems = qItemQueriesDao.getItems(params, null, 0, -1); + + assertThat(loadedItems).hasSize(2); + assertThat(keysOf(loadedItems)) + .containsOnlyElementsOf(keysOf(item11, item21)) + .doesNotContainAnyElementsOf(keysOf(item12, item22, item23)); + + int countItems = qItemQueriesDao.countItems(params); + assertThat(countItems).isEqualTo(2); + } + @Test public void shouldGetItemsFilteredByOnlyAuthor() { Identity owner1 = createRandomIdentity(); diff --git a/src/test/java/org/olat/modules/qpool/manager/QuestionDAOTest.java b/src/test/java/org/olat/modules/qpool/manager/QuestionDAOTest.java index b701f634f5dc1c35223f5eafa421bfd9d1e5e2fd..72ccf7a42c84246cf93081d8b980263b822ac3dd 100644 --- a/src/test/java/org/olat/modules/qpool/manager/QuestionDAOTest.java +++ b/src/test/java/org/olat/modules/qpool/manager/QuestionDAOTest.java @@ -226,6 +226,26 @@ public class QuestionDAOTest extends OlatTestCase { Assert.assertTrue(items.size() >= 1); Assert.assertTrue(items.contains(item)); } + + @Test + public void getItemsWithOneAuthor() { + QItemType fibType = qItemTypeDao.loadByType(QuestionType.FIB.name()); + Identity id = JunitTestHelper.createAndPersistIdentityAsUser("QOwn-all-" + UUID.randomUUID().toString()); + QuestionItem item1 = questionDao.createAndPersist(id, "NGC all", QTIConstants.QTI_12_FORMAT, Locale.ENGLISH.getLanguage(), null, null, null, fibType); + QuestionItem item2 = questionDao.createAndPersist(id, "NGC all", QTIConstants.QTI_12_FORMAT, Locale.ENGLISH.getLanguage(), null, null, null, fibType); + QuestionItem item3 = questionDao.createAndPersist(id, "NGC all", QTIConstants.QTI_12_FORMAT, Locale.ENGLISH.getLanguage(), null, null, null, fibType); + Identity id2 = JunitTestHelper.createAndPersistIdentityAsUser("QOwn-all-" + UUID.randomUUID().toString()); + questionDao.addAuthors(Collections.singletonList(id2), item3); + dbInstance.commitAndCloseSession(); + + List<QuestionItemShort> itemsWithOneAuthor = questionDao.getItemsWithOneAuthor(id); + + Assert.assertNotNull(itemsWithOneAuthor); + Assert.assertTrue(itemsWithOneAuthor.size() == 2); + Assert.assertTrue(itemsWithOneAuthor.contains(item1)); + Assert.assertTrue(itemsWithOneAuthor.contains(item2)); + } + @Test public void getNumOfQuestions() { diff --git a/src/test/java/org/olat/modules/qpool/manager/QuestionPoolUserDataDeletableTest.java b/src/test/java/org/olat/modules/qpool/manager/QuestionPoolUserDataDeletableTest.java new file mode 100644 index 0000000000000000000000000000000000000000..7b727bd1af1794d45a9e650d72d9a592411ed52a --- /dev/null +++ b/src/test/java/org/olat/modules/qpool/manager/QuestionPoolUserDataDeletableTest.java @@ -0,0 +1,96 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.modules.qpool.manager; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.File; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.olat.core.id.Identity; +import org.olat.modules.qpool.QPoolService; +import org.olat.modules.qpool.QuestionItemShort; +import org.olat.modules.qpool.QuestionPoolModule; + +/** + * + * Initial date: 11.01.2018<br> + * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com + * + */ +public class QuestionPoolUserDataDeletableTest { + + @Mock + private QuestionPoolModule qpoolModuleMock; + @Mock + private QPoolService qpoolServiceMock; + @Mock + private QuestionItemDAO questionItemDaoMock; + + @Mock + private File archivePathDummy; + @Mock + private Identity identityDummy; + @Mock + private List<QuestionItemShort> itemsDummy; + private int numberOfItems = 20; + private String newDeletedUserName; + + @InjectMocks + private QuestionPoolUserDataDeletable sut; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + itemsDummy = Stream.generate(() -> mock(QuestionItemShort.class)) + .limit(numberOfItems) + .collect(Collectors.toList()); + when(questionItemDaoMock.getItemsWithOneAuthor(identityDummy)).thenReturn(itemsDummy); + } + + @Test + public void shouldDeleteQuestionsOfUserIfEnabled() { + when(qpoolModuleMock.isDeleteQuestionsWithoutAuthor()).thenReturn(true); + + sut.deleteUserData(identityDummy, newDeletedUserName, archivePathDummy); + + verify(qpoolServiceMock).deleteItems(itemsDummy); + } + + @Test + public void shouldNotDeleteQuestionsOfUserIfNotEnabled() { + when(qpoolModuleMock.isDeleteQuestionsWithoutAuthor()).thenReturn(false); + + sut.deleteUserData(identityDummy, newDeletedUserName, archivePathDummy); + + verify(qpoolServiceMock, never()).deleteItems(itemsDummy); + } +} diff --git a/src/test/java/org/olat/test/AllTestsJunit4.java b/src/test/java/org/olat/test/AllTestsJunit4.java index 4dc1dc185f4a603c1cecf75215d6d5f023450fdb..026100208832a427393d66c0e6a92ef454d2858b 100644 --- a/src/test/java/org/olat/test/AllTestsJunit4.java +++ b/src/test/java/org/olat/test/AllTestsJunit4.java @@ -342,6 +342,7 @@ import org.junit.runners.Suite; org.olat.modules.edubase.manager.EdubaseManagerImplTest.class, org.olat.modules.fo.WordCountTest.class, org.olat.modules.qpool.manager.QuestionPoolServiceImplTest.class, + org.olat.modules.qpool.manager.QuestionPoolUserDataDeletableTest.class, org.olat.modules.qpool.manager.review.LowerLimitProviderTest.class, org.olat.modules.qpool.manager.review.ReviewServiceImplTest.class, org.olat.modules.qpool.ui.metadata.QPoolTaxonomyTreeBuilderTest.class,