diff --git a/src/main/java/org/olat/basesecurity/BaseSecurityManager.java b/src/main/java/org/olat/basesecurity/BaseSecurityManager.java
index 0aca12db49039a08ea2808687fa96249b44c5672..eb60a5c02ac2a003b5985c3f229d740c2bee7dbe 100644
--- a/src/main/java/org/olat/basesecurity/BaseSecurityManager.java
+++ b/src/main/java/org/olat/basesecurity/BaseSecurityManager.java
@@ -1063,6 +1063,7 @@ public class BaseSecurityManager implements BaseSecurity, UserDataDeletable {
 	public Identity saveIdentityStatus(Identity identity, Integer status, Identity doer) {
 		IdentityImpl reloadedIdentity = loadForUpdate(identity);
 		if(reloadedIdentity != null) {
+			Integer previousStatus = reloadedIdentity.getStatus();
 			reloadedIdentity.setStatus(status);
 			if(status.equals(Identity.STATUS_DELETED)) {
 				if(doer != null && reloadedIdentity.getDeletedBy() == null) {
@@ -1071,8 +1072,13 @@ public class BaseSecurityManager implements BaseSecurity, UserDataDeletable {
 				reloadedIdentity.setDeletedDate(new Date());
 			} else if(status.equals(Identity.STATUS_INACTIVE)) {
 				reloadedIdentity.setInactivationDate(new Date());
-			} else if(status.equals(Identity.STATUS_ACTIV)
-					|| status.equals(Identity.STATUS_PERMANENT)
+				reloadedIdentity.setReactivationDate(null);
+			} else if(status.equals(Identity.STATUS_ACTIV)) {
+				reloadedIdentity.setInactivationDate(null);
+				if(Identity.STATUS_INACTIVE.equals(previousStatus)) {
+					reloadedIdentity.setReactivationDate(new Date());
+				}
+			} else if(status.equals(Identity.STATUS_PERMANENT)
 					|| status.equals(Identity.STATUS_PENDING)
 					|| status.equals(Identity.STATUS_LOGIN_DENIED)) {
 				reloadedIdentity.setInactivationDate(null);
diff --git a/src/main/java/org/olat/basesecurity/IdentityImpl.hbm.xml b/src/main/java/org/olat/basesecurity/IdentityImpl.hbm.xml
index d2fa5c975c6077a7fbd84619b24235b07ed943fd..7d37aad994bb338ae3675b72a66e82fb4a83481f 100644
--- a/src/main/java/org/olat/basesecurity/IdentityImpl.hbm.xml
+++ b/src/main/java/org/olat/basesecurity/IdentityImpl.hbm.xml
@@ -28,6 +28,7 @@
     
     <property name="inactivationDate" column="inactivationdate" type="timestamp" />
     <property name="inactivationEmailDate" column="inactivationemaildate" type="timestamp" />
+    <property name="reactivationDate" column="reactivationdate" type="timestamp" />
     
     <one-to-one name="user" property-ref="identity" class="org.olat.user.UserImpl" cascade="persist"/>
   </class>
diff --git a/src/main/java/org/olat/basesecurity/IdentityImpl.java b/src/main/java/org/olat/basesecurity/IdentityImpl.java
index 3fa7df457bdf35f2025b3be417f813e8e7ea620e..6b4a09d1412f4d889b5a035f6edee2ee18a9b906 100644
--- a/src/main/java/org/olat/basesecurity/IdentityImpl.java
+++ b/src/main/java/org/olat/basesecurity/IdentityImpl.java
@@ -58,6 +58,7 @@ public class IdentityImpl implements Identity, IdentityRef, CreateInfo, Persista
 
 	private Date inactivationDate;
 	private Date inactivationEmailDate;
+	private Date reactivationDate;
 	
 	private Date deletedDate;
 	private String deletedBy;
@@ -219,6 +220,14 @@ public class IdentityImpl implements Identity, IdentityRef, CreateInfo, Persista
 		this.inactivationEmailDate = inactivationEmailDate;
 	}
 
+	public Date getReactivationDate() {
+		return reactivationDate;
+	}
+
+	public void setReactivationDate(Date reactivationDate) {
+		this.reactivationDate = reactivationDate;
+	}
+
 	@Override
 	public int hashCode() {
 		int hash = 7;
diff --git a/src/main/java/org/olat/core/commons/services/vfs/manager/VFSMetadataDAO.java b/src/main/java/org/olat/core/commons/services/vfs/manager/VFSMetadataDAO.java
index 983cde4f85525fcf499920230846680595c7558c..5aa43e9e5b324b703c5acb5b4e7191d7e70a6616 100644
--- a/src/main/java/org/olat/core/commons/services/vfs/manager/VFSMetadataDAO.java
+++ b/src/main/java/org/olat/core/commons/services/vfs/manager/VFSMetadataDAO.java
@@ -219,6 +219,13 @@ public class VFSMetadataDAO {
 			.getResultList();
 	}
 	
+	public List<VFSMetadata> getMetadatasOnly(VFSMetadataRef parentMetadata) {
+		return dbInstance.getCurrentEntityManager()
+			.createNamedQuery("metadataOnlyByParent", VFSMetadata.class)
+			.setParameter("parentKey", parentMetadata.getKey())
+			.getResultList();
+	}
+	
 	/**
 	 * This is an exact match to find the direct children of a specific
 	 * directory.
diff --git a/src/main/java/org/olat/core/commons/services/vfs/manager/VFSRepositoryServiceImpl.java b/src/main/java/org/olat/core/commons/services/vfs/manager/VFSRepositoryServiceImpl.java
index 7795e2f5ac205928b310a9a1b5d68cd90bd61fb3..2b6ad501264411ffab3e09ca6a9218d1e09f415c 100644
--- a/src/main/java/org/olat/core/commons/services/vfs/manager/VFSRepositoryServiceImpl.java
+++ b/src/main/java/org/olat/core/commons/services/vfs/manager/VFSRepositoryServiceImpl.java
@@ -468,46 +468,69 @@ public class VFSRepositoryServiceImpl implements VFSRepositoryService, GenericEv
 	public int deleteMetadata(VFSMetadata data) {
 		if(data == null) return 0; // nothing to do
 		
-		List<VFSThumbnailMetadata> thumbnails = thumbnailDao.loadByMetadata(data);
-		for(VFSThumbnailMetadata thumbnail:thumbnails) {
-			VFSItem item = VFSManager.olatRootLeaf("/" + data.getRelativePath(), thumbnail.getFilename());
-			if(item != null && item.exists()) {
-				item.deleteSilently();
-			}
-			thumbnailDao.removeThumbnail(thumbnail);
+		int deleted = 0;
+		List<VFSMetadata> children = metadataDao.getMetadatasOnly(data);
+		for(VFSMetadata child:children) {
+			deleted += deleteMetadata(child);
 		}
 		
-		List<VFSRevision> revisions = getRevisions(data);
+		deleteThumbnailsOfMetadata(data);
+		deleteRevisionsOfMetadata(data);
+
+		data = dbInstance.getCurrentEntityManager().getReference(VFSMetadataImpl.class, data.getKey());
+		metadataDao.removeMetadata(data);
+		dbInstance.commit();
+		
+		deleted++;
+		return deleted;
+	}
+	
+	private void deleteRevisionsOfMetadata(VFSMetadata data) {
+		List<VFSRevision> revisions = revisionDao.getRevisionsOnly(data);
 		for(VFSRevision revision:revisions) {
 			File revFile = getRevisionFile(revision);
 			if(revFile != null && revFile.exists()) {
 				try {
 					Files.delete(revFile.toPath());
 				} catch (IOException e) {
-					log.error("Cannot delete thumbnail: {}", revFile, e);
+					log.error("Cannot delete revision: {}", revFile, e);
 				}
 			}
 			revisionDao.deleteRevision(revision);
 		}
-		
-		dbInstance.commit();
+		if(!revisions.isEmpty()) {
+			dbInstance.commit();
+		}
+	}
 	
-		int count = 0;
-		int deleted = 0;
-		List<VFSMetadata> children = getChildren(data);
-		for(VFSMetadata child:children) {
-			deleted += deleteMetadata(child);
-			if(count++ % 10 == 0) {
-				dbInstance.commitAndCloseSession();
+	private void deleteThumbnailsOfMetadata(VFSMetadata data) {
+		boolean hasThumbnailMetadata = false;
+		List<VFSThumbnailMetadata> thumbnails = thumbnailDao.loadByMetadata(data);
+		for(VFSThumbnailMetadata thumbnail:thumbnails) {
+			VFSItem item = VFSManager.olatRootLeaf("/" + data.getRelativePath(), thumbnail.getFilename());
+			if(item != null && item.exists()) {
+				if(item instanceof LocalFileImpl) {
+					File thumbnailFile = ((LocalFileImpl)item).getBasefile();
+					try {
+						Files.delete(thumbnailFile.toPath());
+					} catch (IOException e) {
+						log.error("Cannot delete thumbnail: {}", thumbnailFile, e);
+					}
+					
+					VFSMetadata thumbnailMetadata = metadataDao.getMetadata(data.getRelativePath(), thumbnail.getFilename(), false);
+					if(thumbnailMetadata != null) {
+						metadataDao.removeMetadata(thumbnailMetadata);
+						hasThumbnailMetadata = true;
+					}
+				} else {
+					item.deleteSilently();
+				}
 			}
+			thumbnailDao.removeThumbnail(thumbnail);
+		}
+		if(!thumbnails.isEmpty() || hasThumbnailMetadata) {
+			dbInstance.commit();
 		}
-		
-		data = dbInstance.getCurrentEntityManager().getReference(VFSMetadataImpl.class, data.getKey());
-		metadataDao.removeMetadata(data);
-		dbInstance.commit();
-		
-		deleted++;
-		return deleted;
 	}
 
 	@Override
diff --git a/src/main/java/org/olat/core/commons/services/vfs/manager/VFSRevisionDAO.java b/src/main/java/org/olat/core/commons/services/vfs/manager/VFSRevisionDAO.java
index bd5242efc79f8dd3ac081d6b08685ecba8fe8242..26021bc0c3c4871f4cadae10e77d46a10552b6a8 100644
--- a/src/main/java/org/olat/core/commons/services/vfs/manager/VFSRevisionDAO.java
+++ b/src/main/java/org/olat/core/commons/services/vfs/manager/VFSRevisionDAO.java
@@ -115,6 +115,20 @@ public class VFSRevisionDAO {
 				.setParameter("metadataKey", metadata.getKey())
 				.getResultList();
 	}
+	
+	/**
+	 * @param metadata The metadata
+	 * @return A list of revisions, without any fetch data, not ordered
+	 */
+	public List<VFSRevision> getRevisionsOnly(VFSMetadataRef metadata) {
+		if(metadata == null) return new ArrayList<>();
+
+		String sb = "select rev from vfsrevision rev where rev.metadata.key=:metadataKey";
+		return dbInstance.getCurrentEntityManager()
+				.createQuery(sb, VFSRevision.class)
+				.setParameter("metadataKey", metadata.getKey())
+				.getResultList();
+	}
 
 	public List<VFSRevision> getRevisions(List<VFSMetadataRef> metadatas) {
 		if(metadatas == null || metadatas.isEmpty()) return new ArrayList<>();
diff --git a/src/main/java/org/olat/core/commons/services/vfs/model/VFSMetadataImpl.java b/src/main/java/org/olat/core/commons/services/vfs/model/VFSMetadataImpl.java
index fda80a17555a73857d01a243cc66b159acb169dd..ada572638bd8bac75fb3ca28a7ab6b8863372a99 100644
--- a/src/main/java/org/olat/core/commons/services/vfs/model/VFSMetadataImpl.java
+++ b/src/main/java/org/olat/core/commons/services/vfs/model/VFSMetadataImpl.java
@@ -29,6 +29,7 @@ import javax.persistence.GenerationType;
 import javax.persistence.Id;
 import javax.persistence.JoinColumn;
 import javax.persistence.ManyToOne;
+import javax.persistence.NamedQuery;
 import javax.persistence.Table;
 import javax.persistence.Temporal;
 import javax.persistence.TemporalType;
@@ -50,6 +51,7 @@ import org.olat.core.util.StringHelper;
  */
 @Entity(name="filemetadata")
 @Table(name="o_vfs_metadata")
+@NamedQuery(name="metadataOnlyByParent", query="select metadata from filemetadata metadata where metadata.parent.key=:parentKey")
 public class VFSMetadataImpl implements Persistable, VFSMetadata {
 
 	private static final long serialVersionUID = 1360000029480576628L;
diff --git a/src/main/java/org/olat/core/util/vfs/LocalFileImpl.java b/src/main/java/org/olat/core/util/vfs/LocalFileImpl.java
index 865ee312a03fbf6e993acf4afabff926b97072ac..1a475c7087f96d00c4b20a72667c3c1c25dec5b8 100644
--- a/src/main/java/org/olat/core/util/vfs/LocalFileImpl.java
+++ b/src/main/java/org/olat/core/util/vfs/LocalFileImpl.java
@@ -162,7 +162,7 @@ public class LocalFileImpl extends LocalImpl implements VFSLeaf {
 	public VFSStatus deleteSilently() {
 		if(canMeta() == VFSConstants.YES) {
 			CoreSpringFactory.getImpl(VFSRepositoryService.class).deleteMetadata(getMetaInfo());
-			CoreSpringFactory.getImpl(DB.class).commitAndCloseSession();
+			CoreSpringFactory.getImpl(DB.class).commit();
 			
 		} else {
 			// some lock can create a metadata object with canMeta() == NO
diff --git a/src/main/java/org/olat/course/CourseFactory.java b/src/main/java/org/olat/course/CourseFactory.java
index aa7acbbdc6270a095db1694fe75ccfdafb99adf3..f694a0f87612e1c3b3dd57b30f06c891fa282719 100644
--- a/src/main/java/org/olat/course/CourseFactory.java
+++ b/src/main/java/org/olat/course/CourseFactory.java
@@ -72,6 +72,7 @@ import org.olat.core.id.context.ContextEntry;
 import org.olat.core.logging.AssertException;
 import org.olat.core.logging.OLATRuntimeException;
 import org.olat.core.logging.Tracing;
+import org.olat.core.util.CodeHelper;
 import org.olat.core.util.ExportUtil;
 import org.olat.core.util.FileUtils;
 import org.olat.core.util.Formatter;
@@ -439,7 +440,9 @@ public class CourseFactory {
 
 		// delete course directory
 		VFSContainer fCourseBasePath = getCourseBaseContainer(res.getResourceableId());
+		long start4 = System.nanoTime();
 		VFSStatus status = fCourseBasePath.deleteSilently();
+		CodeHelper.printMilliSecondTime(start4, "Delete all files");
 		boolean deletionSuccessful = (status == VFSConstants.YES || status == VFSConstants.SUCCESS);
 		log.info("deleteCourse: finished deletion. res="+res+", deletion successful: "+deletionSuccessful+", duration: "+(System.currentTimeMillis()-start)+" ms.");
 	}
diff --git a/src/main/java/org/olat/course/nodes/gta/ui/AbstractAssignmentEditController.java b/src/main/java/org/olat/course/nodes/gta/ui/AbstractAssignmentEditController.java
index 774ff26be25692f8b71d24448a7e3a108ebe445e..a4a5ba69dd0c748a452b5eb8179bdb897f69725e 100644
--- a/src/main/java/org/olat/course/nodes/gta/ui/AbstractAssignmentEditController.java
+++ b/src/main/java/org/olat/course/nodes/gta/ui/AbstractAssignmentEditController.java
@@ -355,6 +355,7 @@ abstract class AbstractAssignmentEditController extends FormBasicController impl
 		String text = translate("warning.tasks.in.process.delete.text");
 		confirmDeleteCtrl = activateOkCancelDialog(ureq, title, text, confirmDeleteCtrl);
 		confirmDeleteCtrl.setUserObject(row);
+		confirmDeleteCtrl.setCssClass("o_warning");
 	}
 	
 	private void doDelete(UserRequest ureq, TaskDefinition taskDef) {
diff --git a/src/main/java/org/olat/course/nodes/gta/ui/DirectoryController.java b/src/main/java/org/olat/course/nodes/gta/ui/DirectoryController.java
index 8eb55a18b0a6e34c23397d0145e679aa3a97540f..26ed8046db4d5f8a7505cdcc58a9bf20db965efc 100644
--- a/src/main/java/org/olat/course/nodes/gta/ui/DirectoryController.java
+++ b/src/main/java/org/olat/course/nodes/gta/ui/DirectoryController.java
@@ -140,6 +140,7 @@ public class DirectoryController extends BasicController implements Activateable
 			linkNames.add(new DocumentInfos(link.getComponentName(), uploadedBy, lastModified));
 		}
 		mainVC.contextPut("linkNames", linkNames);
+		bulkReviewLink.setVisible(!linkNames.isEmpty());
 
 		putInitialPanel(mainVC);
 	}
diff --git a/src/main/java/org/olat/course/nodes/gta/ui/GTAAssignedTaskController.java b/src/main/java/org/olat/course/nodes/gta/ui/GTAAssignedTaskController.java
index 0f380edece5fe4f649a8f6ad990988e5d4e82aa1..f54867a3b9569f4c2f4a61dfdee09dd2e82ef366 100644
--- a/src/main/java/org/olat/course/nodes/gta/ui/GTAAssignedTaskController.java
+++ b/src/main/java/org/olat/course/nodes/gta/ui/GTAAssignedTaskController.java
@@ -111,6 +111,7 @@ public class GTAAssignedTaskController extends BasicController {
 			downloadButton.setTitle(taskInfos);
 			downloadButton.setIconLeftCSS("o_icon o_icon_download");
 			downloadButton.setTarget("_blank");
+			downloadButton.setVisible(taskFile.exists());
 	
 			downloadLink = LinkFactory.createCustomLink("download.link", "download.link", null, Link.NONTRANSLATED, mainVC, this);
 			if(taskDef != null) {
diff --git a/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_de.properties
index 0fd9a0a92ab5edb6c3bac7c7bf0108a138ef1146..aab3658eaa86506050eef816a6e02976c2517164 100644
--- a/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_de.properties
+++ b/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_de.properties
@@ -358,7 +358,7 @@ warning.no.task.choosed=Wie es scheint, war es Ihnen aufgrund von \u00C4nderunge
 warning.no.task.choosed.coach=Wie es scheint, war es der Assignee aufgrund von \u00C4nderungen am Kurselement nicht m\u00F6glich, eine Aufgabe f\u00FCr dieses Aufgabenelement auszuw\u00E4hlen.
 warning.reopen=Wenn Sie das Abgabedatum verl\u00E4ngern, wird diese Aufgabe neu er\u00F6ffnet. 
 warning.submit.documents.edited=Sie k\u00F6nnen die Aufgabe nicht abgeben, da das Dokument "{1}" noch von "{0}" bearbeitet wird.
-warning.tasks.in.process.delete.text=Wollen Sie wirklich dieser Aufgabe l\u00F6schen? Es gibt bereits Benutzer die den Aufgabenprozess gestartet haben. Das kann f\u00FCr diese Benutzer zu Problemen f\u00FChren.
+warning.tasks.in.process.delete.text=Wollen Sie wirklich dieser Aufgabe l\u00F6schen? <strong>Es gibt bereits Benutzer die den Aufgabenprozess gestartet haben. Das kann f\u00FCr diese Benutzer zu Problemen f\u00FChren.</strong>
 warning.tasks.in.process.delete.title=$\:warning.tasks.in.process.title
 warning.tasks.in.process.text=Es gibt bereits Benutzer die den Aufgabenprozess gestartet haben. \u00C4nderungen an der Workflow-Konfiguration kann f\u00FCr diese Benutzer zu Problemen f\u00FChren. Informationen dazu finden Sie im <a href\="{0}" target\="_blank"><i class\='o_icon o_icon_help'> </i> Handbuch</a>.
 warning.tasks.in.process.title=Aufgabenprozess bereits gestartet
diff --git a/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_en.properties
index 6c00ad7e2f8fd30fe6b37b5df9b0d601b87033b0..5cda911e2e3783244a6b9fc756a37f79d80a163c 100644
--- a/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_en.properties
+++ b/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_en.properties
@@ -358,7 +358,7 @@ warning.no.task.choosed=It seems that due to a change in the course element conf
 warning.no.task.choosed.coach=It seems that due to a change in the course element configuration, the assignee wasn't able to choose a task for this task element.
 warning.reopen=If you decide to extend the submission deadline, this will automatically reopen the task submission.
 warning.submit.documents.edited=You cannot submit the task. The document "{1}" is currently being edited by "{0}".
-warning.tasks.in.process.delete.text=Do you really wan to delete this task? There are already users who have started the task process. It could result in problems for these users.
+warning.tasks.in.process.delete.text=Do you really wan to delete this task? <strong>There are already users who have started the task process. It could result in problems for these users.</strong>
 warning.tasks.in.process.delete.title=$\:warning.tasks.in.process.title
 warning.tasks.in.process.text=There are already users who have started the task process. Changing the workflow configuration could result in problems for these users. Please refer to the <a href\="{0}" target\="_blank"><i class\='o_icon o_icon_help'> </i> manual</a> for more information.
 warning.tasks.in.process.title=Task already started
diff --git a/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_fr.properties b/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_fr.properties
index 402c47b46f0838139847829744d74d9ec1ca9196..7195050770675d09c95f65b998bfe06cb5744241 100644
--- a/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_fr.properties
+++ b/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_fr.properties
@@ -358,7 +358,7 @@ warning.no.task.choosed=Il semble qu'il n'a pas \u00E9t\u00E9 possible de vous c
 warning.no.task.choosed.coach=Il semble qu'en raison d'un changement dans la configuration de l'\u00E9l\u00E9ment de cours, un utilisateur n'a pas pu se voir assigner un devoir.
 warning.reopen=Si vous d\u00E9cidez de prolonger la date de d\u00E9p\u00F4t, cela rouvrira automatiquement la possibilit\u00E9 de remettre le devoir.
 warning.submit.documents.edited=Vous ne pouvez pas soumettre le devoir. Le document "{1}" est actuellement \u00E9dit\u00E9 par "{0}".
-warning.tasks.in.process.delete.text=Voulez-vous vraiment supprimer ce devoir? Des utilisateurs l'ont d\u00E9j\u00E0 commenc\u00E9. Cela entra\u00EEnera des probl\u00E8mes pour ces utilisateurs.
+warning.tasks.in.process.delete.text=Voulez-vous vraiment supprimer ce devoir? <strong>Des utilisateurs l'ont d\u00E9j\u00E0 commenc\u00E9. Cela entra\u00EEnera des probl\u00E8mes pour ces utilisateurs.</strong>
 warning.tasks.in.process.delete.title=$\:warning.tasks.in.process.title
 warning.tasks.in.process.text=Il ya d\u00E9j\u00E0 des utilisateurs qui ont commenc\u00E9 les devoirs. Des modifications de la configuration du processus pourra entra\u00EEner des probl\u00E8mes pour ces utilisateurs. Pour plus d'informations, vous pouvez vous r\u00E9f\u00E9rer au <a href\="{0}" target\="_blank"><i class\='o_icon o_icon_help'> </i> manuel d'utilisation</a>.
 warning.tasks.in.process.title=Devoirs d\u00E9j\u00E0 commenc\u00E9s
diff --git a/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_it.properties b/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_it.properties
index 3c3762fe9ab9313e8b5c18b0ebfb0b6be21f9c5c..afaa44a3bad219daaf2c5d7fda144a97c09204f6 100644
--- a/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_it.properties
+++ b/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_it.properties
@@ -358,7 +358,7 @@ warning.no.task.choosed=Sembra che, a causa di un cambiamento nella configurazio
 warning.no.task.choosed.coach=Sembra che, a causa di un cambiamento nella configurazione, all'assegnatario non sia stato possibile selezionare un compito da svolgere per questo elemento di corso.
 warning.reopen=Se si decide di prolungare il termine di presentazione, questo riaprir\u00E0 automaticamente la presentazione dell'attivit\u00E0.
 warning.submit.documents.edited=Non puoi consegnare il compito. Il documento "{1}" \u00E8 attualmente in modifica da parte di "{0}".
-warning.tasks.in.process.delete.text=Vuoi veramente eliminare questo compito? Ci sono utenti che hanno gi\u00E0 iniziato a svolgerlo. Potrebbero verificarsi problemi per questi utenti.
+warning.tasks.in.process.delete.text=Vuoi veramente eliminare questo compito? <strong>Ci sono utenti che hanno gi\u00E0 iniziato a svolgerlo. Potrebbero verificarsi problemi per questi utenti.</strong>
 warning.tasks.in.process.delete.title=$\:warning.tasks.in.process.title
 warning.tasks.in.process.text=Ci sono gi\u00E0 alcuni utenti che hanno iniziato lo svolgimento del compito. Modificare la configurazione del flusso di lavoro potrebbe creare un problema a questi utenti. Prego, consulta il <a href\="{0}" target\="_blank"><i class\='o_icon o_icon_help'> </i> manuale</a> per maggiori informazioni.
 warning.tasks.in.process.title=Compiti gi\u00E0 iniziati
diff --git a/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_pt_BR.properties b/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_pt_BR.properties
index 5d92109246ec5a1739d83d4bbc6d45b91d58fe2d..05a1d0c0e9efc507a2c3206583621ddd23e84890 100644
--- a/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_pt_BR.properties
+++ b/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_pt_BR.properties
@@ -358,7 +358,7 @@ warning.no.task.choosed=Parece que, devido a uma altera\u00E7\u00E3o na configur
 warning.no.task.choosed.coach=Parece que, devido a uma altera\u00E7\u00E3o na configura\u00E7\u00E3o do elemento de curso, o designado n\u00E3o foi capaz de escolher uma tarefa para este elemento.
 warning.reopen=Se voc\u00EA decidir estender o prazo de envio, isso reabrir\u00E1 automaticamente o envio da tarefa.
 warning.submit.documents.edited=Voc\u00EA n\u00E3o pode enviar a tarefa. O documento "{1}" est\u00E1 sendo editado por "{0}".
-warning.tasks.in.process.delete.text=Voc\u00EA realmente deseja apagar esta tarefa? J\u00E1 existem usu\u00E1rios que iniciaram o processo da tarefa. Isso pode resultar em problemas para esses usu\u00E1rios.
+warning.tasks.in.process.delete.text=Voc\u00EA realmente deseja apagar esta tarefa? <strong>J\u00E1 existem usu\u00E1rios que iniciaram o processo da tarefa. Isso pode resultar em problemas para esses usu\u00E1rios.</strong>
 warning.tasks.in.process.delete.title=$\:warning.tasks.in.process.title
 warning.tasks.in.process.text=J\u00E1 existem usu\u00E1rios que iniciaram a tarefa. Alterar a configura\u00E7\u00E3o do Workflow pode resultar em problemas para esses usu\u00E1rios.
 warning.tasks.in.process.title=Tarefas j\u00E1 iniciadas
diff --git a/src/main/java/org/olat/upgrade/_spring/databaseUpgradeContext.xml b/src/main/java/org/olat/upgrade/_spring/databaseUpgradeContext.xml
index c2b6b0777b2433ab6006198a014a3c8c64b4b6b8..c73ac324d1b759592c9b66021992c2da731d5681 100644
--- a/src/main/java/org/olat/upgrade/_spring/databaseUpgradeContext.xml
+++ b/src/main/java/org/olat/upgrade/_spring/databaseUpgradeContext.xml
@@ -288,6 +288,10 @@
 					<constructor-arg index="0" value="OLAT_15.2.2" />
 					<property name="alterDbStatements" value="alter_15_2_x_to_15_2_2.sql" />
 				</bean>
+				<bean id="database_upgrade_15_2_3" class="org.olat.upgrade.DatabaseUpgrade">
+					<constructor-arg index="0" value="OLAT_15.2.3" />
+					<property name="alterDbStatements" value="alter_15_2_x_to_15_2_3.sql" />
+				</bean>
 				<bean id="database_upgrade_15_3_0" class="org.olat.upgrade.DatabaseUpgrade">
 					<constructor-arg index="0" value="OLAT_15.3.0" />
 					<property name="alterDbStatements" value="alter_15_2_x_to_15_3_0.sql" />
diff --git a/src/main/java/org/olat/user/manager/lifecycle/UserLifecycleManagerImpl.java b/src/main/java/org/olat/user/manager/lifecycle/UserLifecycleManagerImpl.java
index 5f723d9980bf65475a69bedb18ded9ca5a57d368..a980694995ca9d34c35cf86d43e6187de61ce8b0 100644
--- a/src/main/java/org/olat/user/manager/lifecycle/UserLifecycleManagerImpl.java
+++ b/src/main/java/org/olat/user/manager/lifecycle/UserLifecycleManagerImpl.java
@@ -96,22 +96,23 @@ public class UserLifecycleManagerImpl implements UserLifecycleManager {
 	private RepositoryDeletionModule repositoryDeletionModule;
 	
 
-	public List<Identity> getReadyToInactivateIdentities(Date loginDate) {
+	public List<Identity> getReadyToInactivateIdentities(Date loginDate, Date reactivationDateLimit) {
 		StringBuilder sb = new StringBuilder(512);
 		sb.append("select ident from ").append(IdentityImpl.class.getName()).append(" as ident")
 		  .append(" inner join fetch ident.user as user")
 		  .append(" where ident.status in (:statusList) and ((ident.lastLogin = null and ident.creationDate < :lastLogin) or ident.lastLogin < :lastLogin)")
-		  .append(" and ident.inactivationEmailDate is null");
+		  .append(" and ident.inactivationEmailDate is null and (ident.reactivationDate is null or ident.reactivationDate<:reactivationDateLimit)");
 		
 		List<Integer> statusList = Arrays.asList(Identity.STATUS_ACTIV, Identity.STATUS_PENDING, Identity.STATUS_LOGIN_DENIED);
 		return dbInstance.getCurrentEntityManager()
 				.createQuery(sb.toString(), Identity.class)
 				.setParameter("statusList", statusList)
 				.setParameter("lastLogin", loginDate, TemporalType.TIMESTAMP)
+				.setParameter("reactivationDateLimit", reactivationDateLimit)
 				.getResultList();
 	}
 	
-	public List<Identity> getIdentitiesToInactivate(Date loginDate, Date emailBeforeDate) {
+	public List<Identity> getIdentitiesToInactivate(Date loginDate, Date emailBeforeDate, Date reactivationDateLimit) {
 		StringBuilder sb = new StringBuilder(512);
 		sb.append("select ident from ").append(IdentityImpl.class.getName()).append(" as ident")
 		  .append(" inner join fetch ident.user as user")
@@ -119,12 +120,14 @@ public class UserLifecycleManagerImpl implements UserLifecycleManager {
 		if(emailBeforeDate != null) {
 			sb.append(" and (ident.inactivationEmailDate<:emailDate or ident.lastLogin is null)");	
 		}
+		sb.append(" and (ident.reactivationDate is null or ident.reactivationDate<:reactivationDateLimit)");
 
 		List<Integer> statusList = Arrays.asList(Identity.STATUS_ACTIV, Identity.STATUS_PENDING, Identity.STATUS_LOGIN_DENIED);
 		TypedQuery<Identity> query = dbInstance.getCurrentEntityManager()
 				.createQuery(sb.toString(), Identity.class)
 				.setParameter("statusList", statusList)
-				.setParameter("lastLogin", loginDate, TemporalType.TIMESTAMP);
+				.setParameter("lastLogin", loginDate, TemporalType.TIMESTAMP)
+				.setParameter("reactivationDateLimit", reactivationDateLimit);
 		if(emailBeforeDate != null) {
 			query.setParameter("emailDate", emailBeforeDate);	
 		}
@@ -182,11 +185,12 @@ public class UserLifecycleManagerImpl implements UserLifecycleManager {
 	public void inactivateIdentities(Set<Identity> vetoed) {
 		int numOfDaysBeforeDeactivation = userModule.getNumberOfInactiveDayBeforeDeactivation();
 		int numOfDaysBeforeEmail = userModule.getNumberOfDayBeforeDeactivationMail();
+		Date reactivationDatebefore = getDate(30);
 		boolean sendMailBeforeDeactivation = userModule.isMailBeforeDeactivation() && numOfDaysBeforeEmail > 0;
 		if(sendMailBeforeDeactivation) {
 			int days = numOfDaysBeforeDeactivation - numOfDaysBeforeEmail;
 			Date lastLoginDate = getDate(days);
-			List<Identity> identities = getReadyToInactivateIdentities(lastLoginDate);
+			List<Identity> identities = getReadyToInactivateIdentities(lastLoginDate, reactivationDatebefore);
 			if(!identities.isEmpty()) {
 				for(Identity identity:identities) {
 					if(identity.getLastLogin() != null) {
@@ -202,9 +206,9 @@ public class UserLifecycleManagerImpl implements UserLifecycleManager {
 		List<Identity> identities;
 		if(sendMailBeforeDeactivation) {
 			Date emailBeforeDate = getDate(numOfDaysBeforeEmail);
-			identities = getIdentitiesToInactivate(lastLoginDate, emailBeforeDate);
+			identities = getIdentitiesToInactivate(lastLoginDate, emailBeforeDate, reactivationDatebefore);
 		} else {
-			identities = getIdentitiesToInactivate(lastLoginDate, null);
+			identities = getIdentitiesToInactivate(lastLoginDate, null, reactivationDatebefore);
 		}
 		
 		for(Identity identity:identities) {
diff --git a/src/main/resources/database/mysql/alter_15_2_x_to_15_2_3.sql b/src/main/resources/database/mysql/alter_15_2_x_to_15_2_3.sql
new file mode 100644
index 0000000000000000000000000000000000000000..97e38dfb99d317d2e872bced2de6941763ab5430
--- /dev/null
+++ b/src/main/resources/database/mysql/alter_15_2_x_to_15_2_3.sql
@@ -0,0 +1,3 @@
+-- Reactivate user
+alter table o_bs_identity add column reactivationdate datetime;
+
diff --git a/src/main/resources/database/mysql/setupDatabase.sql b/src/main/resources/database/mysql/setupDatabase.sql
index 0143e107bad5c63c6922196ac611ff0f958f2e18..cef3c804f3f6bde98e318e1d7b49d2a729a31a0f 100644
--- a/src/main/resources/database/mysql/setupDatabase.sql
+++ b/src/main/resources/database/mysql/setupDatabase.sql
@@ -177,6 +177,7 @@ create table if not exists o_bs_identity (
    deletedby varchar(128),
    inactivationdate datetime,
    inactivationemaildate datetime,
+   reactivationdate datetime,
    deletionemaildate datetime,
    primary key (id)
 );
diff --git a/src/main/resources/database/oracle/alter_15_2_x_to_15_2_3.sql b/src/main/resources/database/oracle/alter_15_2_x_to_15_2_3.sql
new file mode 100644
index 0000000000000000000000000000000000000000..b67085a313c59f25f4d891e28d71313af3e90eb2
--- /dev/null
+++ b/src/main/resources/database/oracle/alter_15_2_x_to_15_2_3.sql
@@ -0,0 +1,2 @@
+-- Reactivate user
+alter table o_bs_identity add reactivationdate date;
diff --git a/src/main/resources/database/oracle/setupDatabase.sql b/src/main/resources/database/oracle/setupDatabase.sql
index 7ddf6dfa9f4640961ffb39aab172df94b6d8830c..22b60948ba1c6281f5bbd27e93958fedf9e69a6c 100644
--- a/src/main/resources/database/oracle/setupDatabase.sql
+++ b/src/main/resources/database/oracle/setupDatabase.sql
@@ -193,6 +193,7 @@ CREATE TABLE o_bs_identity (
   deletedby varchar(128),
   inactivationdate date,
   inactivationemaildate date,
+  reactivationdate date,
   deletionemaildate date,
   CONSTRAINT u_o_bs_identity UNIQUE (name),
   PRIMARY KEY (id)
diff --git a/src/main/resources/database/postgresql/alter_15_2_x_to_15_2_3.sql b/src/main/resources/database/postgresql/alter_15_2_x_to_15_2_3.sql
new file mode 100644
index 0000000000000000000000000000000000000000..940b638f23114f351402405745ef71b069037dca
--- /dev/null
+++ b/src/main/resources/database/postgresql/alter_15_2_x_to_15_2_3.sql
@@ -0,0 +1,2 @@
+-- Reactivate user
+alter table o_bs_identity add column reactivationdate timestamp;
diff --git a/src/main/resources/database/postgresql/setupDatabase.sql b/src/main/resources/database/postgresql/setupDatabase.sql
index 9d2df9787ff2f995b89e5d8a287b60448f12f175..e694b2fdb783d901ba144da1d38774ea239c632d 100644
--- a/src/main/resources/database/postgresql/setupDatabase.sql
+++ b/src/main/resources/database/postgresql/setupDatabase.sql
@@ -175,6 +175,7 @@ create table o_bs_identity (
    deletedby varchar(128),
    inactivationdate timestamp,
    inactivationemaildate timestamp,
+   reactivationdate timestamp,
    deletionemaildate timestamp,
    primary key (id)
 );
diff --git a/src/test/java/org/olat/core/commons/services/vfs/manager/IMG_1491.jpg b/src/test/java/org/olat/core/commons/services/vfs/manager/IMG_1491.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..bb3e25cf30125bac42957fbafcf22cd5a5d986be
Binary files /dev/null and b/src/test/java/org/olat/core/commons/services/vfs/manager/IMG_1491.jpg differ
diff --git a/src/test/java/org/olat/core/commons/services/vfs/manager/VFSRepositoryServiceTest.java b/src/test/java/org/olat/core/commons/services/vfs/manager/VFSRepositoryServiceTest.java
index 19a20486b83e1b06be1fe687d4e71c21a1358849..c7360a387bce81f5bba6d1e5a1a4ad80f23451a8 100644
--- a/src/test/java/org/olat/core/commons/services/vfs/manager/VFSRepositoryServiceTest.java
+++ b/src/test/java/org/olat/core/commons/services/vfs/manager/VFSRepositoryServiceTest.java
@@ -22,8 +22,10 @@ package org.olat.core.commons.services.vfs.manager;
 import static org.assertj.core.api.Assertions.assertThat;
 
 import java.io.ByteArrayInputStream;
+import java.io.File;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.util.List;
 import java.util.UUID;
 
 import org.apache.commons.io.IOUtils;
@@ -39,6 +41,9 @@ import org.olat.core.commons.services.license.manager.LicenseCleaner;
 import org.olat.core.commons.services.vfs.VFSMetadata;
 import org.olat.core.commons.services.vfs.VFSRepositoryService;
 import org.olat.core.logging.Tracing;
+import org.olat.core.util.FileUtils;
+import org.olat.core.util.vfs.LocalFileImpl;
+import org.olat.core.util.vfs.LocalFolderImpl;
 import org.olat.core.util.vfs.VFSConstants;
 import org.olat.core.util.vfs.VFSContainer;
 import org.olat.core.util.vfs.VFSLeaf;
@@ -47,6 +52,7 @@ import org.olat.test.OlatTestCase;
 import org.springframework.beans.factory.annotation.Autowired;
 
 /**
+ * Versioning is test with @see org.olat.core.commons.services.vfs.manager.VFSVersioningTest
  * 
  * Initial date: 12 mars 2019<br>
  * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
@@ -72,7 +78,7 @@ public class VFSRepositoryServiceTest extends OlatTestCase {
 	}
 	
 	@Test
-	public void getMetadataFor_file() {
+	public void getMetadataForVFSLeaf() {
 		VFSLeaf leaf = createFile();
 		
 		// create metadata
@@ -87,6 +93,83 @@ public class VFSRepositoryServiceTest extends OlatTestCase {
 		Assert.assertFalse(metadata.isDirectory());	
 	}
 	
+	@Test
+	public void getMetadataForFile() {
+		VFSLeaf leaf = createFile();
+		File file = ((LocalFileImpl)leaf).getBasefile();
+		
+		// create metadata
+		VFSMetadata metadata = vfsRepositoryService.getMetadataFor(file);
+		dbInstance.commitAndCloseSession();
+		Assert.assertNotNull(metadata);
+		Assert.assertNotNull(metadata.getKey());
+		Assert.assertNotNull(metadata.getCreationDate());
+		Assert.assertNotNull(metadata.getLastModified());
+		Assert.assertNotNull(metadata.getFileLastModified());
+		Assert.assertEquals(leaf.getName(), metadata.getFilename());
+		Assert.assertFalse(metadata.isDirectory());	
+	}
+	
+	@Test
+	public void deleteMetadata() {
+		VFSLeaf leaf = createImage();
+		VFSMetadata metadata = vfsRepositoryService.getMetadataFor(leaf);
+		Assert.assertNotNull(metadata);
+		
+		VFSLeaf thumbnail1 = vfsRepositoryService.getThumbnail(leaf, 200, 200, true);
+		Assert.assertNotNull(thumbnail1);
+		Assert.assertTrue(thumbnail1.getSize() > 32);
+		VFSLeaf thumbnail2 = vfsRepositoryService.getThumbnail(leaf, 180, 180, false);
+		Assert.assertNotNull(thumbnail2);
+		Assert.assertTrue(thumbnail2.getSize() > 32);
+		
+		vfsRepositoryService.deleteMetadata(metadata);
+		dbInstance.commitAndCloseSession();
+	}
+	
+	@Test
+	public void deleteMetadataFolder() {
+		VFSContainer testContainer = VFSManager.olatRootContainer(VFS_TEST_DIR, null);
+		VFSContainer container = createContainerRecursive(testContainer, 0, 2, 5, 5);
+	
+		VFSMetadata metadata = vfsRepositoryService.getMetadataFor(container);
+		dbInstance.commitAndCloseSession();
+		
+		String containerPath = metadata.getRelativePath() + "/" + metadata.getFilename();
+		List<VFSMetadata> children = vfsRepositoryService.getChildren(containerPath);
+		Assert.assertNotNull(children);
+		Assert.assertEquals(10, children.size());
+		
+		container.deleteSilently();
+		dbInstance.commitAndCloseSession();
+		
+		List<VFSMetadata> afterChildren = vfsRepositoryService.getChildren(containerPath);
+		Assert.assertNotNull(afterChildren);
+		Assert.assertEquals(0, afterChildren.size());
+	}
+	
+	@Test
+	public void deleteDirsAndFiles() {
+		VFSContainer testContainer = VFSManager.olatRootContainer(VFS_TEST_DIR, null);
+		VFSContainer container = createContainerRecursive(testContainer, 0, 2, 3, 3);
+	
+		VFSMetadata metadata = vfsRepositoryService.getMetadataFor(container);
+		dbInstance.commitAndCloseSession();
+		
+		String containerPath = metadata.getRelativePath() + "/" + metadata.getFilename();
+		List<VFSMetadata> children = vfsRepositoryService.getChildren(containerPath);
+		Assert.assertNotNull(children);
+		Assert.assertEquals(6, children.size());
+		
+		File dir = ((LocalFolderImpl)container).getBasefile();
+		FileUtils.deleteDirsAndFiles(dir, true, true);
+		dbInstance.commitAndCloseSession();
+		
+		List<VFSMetadata> afterChildren = vfsRepositoryService.getChildren(containerPath);
+		Assert.assertNotNull(afterChildren);
+		Assert.assertEquals(0, afterChildren.size());
+	}
+	
 	@Test
 	public void shouldLoadExistingLicenseType() {
 		String typeName = "name";
@@ -140,7 +223,7 @@ public class VFSRepositoryServiceTest extends OlatTestCase {
 		VFSContainer testContainer = VFSManager.olatRootContainer(VFS_TEST_DIR, null);
 		VFSLeaf leaf = testContainer.createChildLeaf(filename);
 		Assert.assertEquals(VFSConstants.YES, leaf.canMeta());
-		copyTestTxt(leaf);
+		copyTestTxt(leaf, "test.txt");
 		
 		VFSMetadata metaInfo = leaf.getMetaInfo();
 		metaInfo.setComment("A little comment");
@@ -153,7 +236,7 @@ public class VFSRepositoryServiceTest extends OlatTestCase {
 		
 		String secondFilename = UUID.randomUUID() + ".txt";
 		VFSLeaf secondLeaf = testContainer.createChildLeaf(secondFilename);
-		copyTestTxt(secondLeaf);
+		copyTestTxt(secondLeaf, "test.txt");
 
 		VFSMetadata secondMetaInfo = leaf.getMetaInfo();
 		String comment = null;
@@ -170,13 +253,49 @@ public class VFSRepositoryServiceTest extends OlatTestCase {
 		String filename = UUID.randomUUID() + ".txt";
 		VFSContainer testContainer = VFSManager.olatRootContainer(VFS_TEST_DIR, null);
 		VFSLeaf firstLeaf = testContainer.createChildLeaf(filename);
-		copyTestTxt(firstLeaf);
+		copyTestTxt(firstLeaf, "test.txt");
 		return firstLeaf;
 	}
 	
-	private int copyTestTxt(VFSLeaf file) {
+	private VFSLeaf createImage() {
+		String filename = UUID.randomUUID() + ".jpg";
+		VFSContainer testContainer = VFSManager.olatRootContainer(VFS_TEST_DIR, null);
+		VFSLeaf firstLeaf = testContainer.createChildLeaf(filename);
+		copyTestTxt(firstLeaf, "IMG_1491.jpg");
+		return firstLeaf;
+	}
+	
+	private VFSContainer createContainerRecursive(VFSContainer parent, int depth, int maxDepth, int numOfFiles, int numOfContainers) {
+		if(depth > maxDepth) {
+			return null;
+		}
+		
+		String filename = UUID.randomUUID().toString();
+		VFSContainer container = parent.createChildContainer(filename);
+		
+		for(int i=0; i<numOfFiles; i++) {
+			String imageName = "IMG_" + depth + "_" + i + ".jpg";
+			VFSLeaf image = container.createChildLeaf(imageName);
+			copyTestTxt(image, "IMG_1491.jpg");
+			
+			for(int j=1; j<5; j++) {
+				VFSLeaf thumbnail = vfsRepositoryService.getThumbnail(image, j * 20, j * 20, true);
+				Assert.assertNotNull(thumbnail);
+				Assert.assertTrue(thumbnail.getSize() > 32);
+			}
+			dbInstance.commitAndCloseSession();
+		}
+		
+		for(int i=0; i<numOfContainers; i++) {
+			createContainerRecursive(container, depth + 1, maxDepth, numOfFiles, numOfContainers);
+		}
+		dbInstance.commitAndCloseSession();
+		return container;
+	}
+	
+	private int copyTestTxt(VFSLeaf file, String sourceFilename) {
 		try(OutputStream out = file.getOutputStream(false);
-				InputStream in = VFSRepositoryServiceTest.class.getResourceAsStream("test.txt")) {
+				InputStream in = VFSRepositoryServiceTest.class.getResourceAsStream(sourceFilename)) {
 			return IOUtils.copy(in, out);
 		} catch(Exception e) {
 			log.error("", e);
diff --git a/src/test/java/org/olat/user/manager/lifecycle/UserLifecycleManagerTest.java b/src/test/java/org/olat/user/manager/lifecycle/UserLifecycleManagerTest.java
index a69965e61cc0e5349929702d19a772282f9aaa73..86eb8c95892af0ed69ccf7336302e271dfcfa9eb 100644
--- a/src/test/java/org/olat/user/manager/lifecycle/UserLifecycleManagerTest.java
+++ b/src/test/java/org/olat/user/manager/lifecycle/UserLifecycleManagerTest.java
@@ -104,7 +104,8 @@ public class UserLifecycleManagerTest extends OlatTestCase {
 		dbInstance.commitAndCloseSession();
 		
 		Date beforeDate = DateUtils.addDays(new Date(), -900);
-		List<Identity> identitiesToInactivate = lifecycleManager.getReadyToInactivateIdentities(beforeDate);
+		Date reactivationDateLimite = DateUtils.addDays(new Date(), -30);
+		List<Identity> identitiesToInactivate = lifecycleManager.getReadyToInactivateIdentities(beforeDate, reactivationDateLimite);
 		Assert.assertNotNull(identitiesToInactivate);
 		Assert.assertTrue(identitiesToInactivate.contains(id1));
 		Assert.assertFalse(identitiesToInactivate.contains(id2));
@@ -133,7 +134,8 @@ public class UserLifecycleManagerTest extends OlatTestCase {
 		dbInstance.commitAndCloseSession();
 		
 		Date beforeDate = DateUtils.addDays(new Date(), -900);
-		List<Identity> identitiesToInactivate = lifecycleManager.getIdentitiesToInactivate(beforeDate, null);
+		Date reactivationDatebefore = DateUtils.addDays(new Date(), -30);
+		List<Identity> identitiesToInactivate = lifecycleManager.getIdentitiesToInactivate(beforeDate, null, reactivationDatebefore);
 		Assert.assertNotNull(identitiesToInactivate);
 		Assert.assertTrue(identitiesToInactivate.contains(id1));
 		Assert.assertFalse(identitiesToInactivate.contains(id2));
@@ -231,6 +233,30 @@ public class UserLifecycleManagerTest extends OlatTestCase {
 		getSmtpServer().reset();
 	}
 	
+	@Test
+	public void reactivateIdentity() {
+		Assert.assertTrue(userModule.isUserAutomaticDeactivation());
+		userModule.setMailBeforeDeactivation(true);
+		userModule.setNumberOfInactiveDayBeforeDeactivation(720);
+		userModule.setNumberOfDayBeforeDeactivationMail(30);
+		
+		Identity id1 = JunitTestHelper.createAndPersistIdentityAsRndUser("lifecycle-1");
+		identityDao.setIdentityLastLogin(id1, DateUtils.addDays(new Date(), -910));
+		id1 = securityManager.saveIdentityStatus(id1, Identity.STATUS_INACTIVE, id1);
+		id1 = securityManager.saveIdentityStatus(id1, Identity.STATUS_ACTIV, id1);
+		dbInstance.commitAndCloseSession();
+		
+		Identity reloadedId1 = securityManager.loadIdentityByKey(id1.getKey());
+		Assert.assertNotNull(((IdentityImpl)reloadedId1).getReactivationDate());
+
+		Set<Identity> vetoed = new HashSet<>();
+		lifecycleManager.inactivateIdentities(vetoed);
+		dbInstance.commitAndCloseSession();
+		
+		reloadedId1 = securityManager.loadIdentityByKey(id1.getKey());
+		Assert.assertEquals(Identity.STATUS_ACTIV, reloadedId1.getStatus());
+	}
+	
 	@Test
 	public void inactivateBIdentities() {
 		Assert.assertTrue(userModule.isUserAutomaticDeactivation());