Purge recycle bin attachments
Last modified by Anca Luca on 2025/02/12 12:25
![]() | Use this script to programmatically purge the attachments recycle bin. |
Type | Snippet |
Category | |
Developed by | |
Rating | |
License | GNU Lesser General Public License 2.1 |
Table of contents
Description
Simple version (deletes all attachments, from a single wiki)
{{groovy}}
import java.util.List
import com.xpn.xwiki.api.DeletedAttachment
// Send in request if you want to empty the trash bin for the whole wiki
def query = "select distinct ddoc.id from DeletedAttachment as ddoc "
def trashed = xwiki.search(query)
println "Total number of attachments to clean: ${trashed.size()}"
if (trashed) {
def deleted = 0
println "Trashed attachments"
trashed.each() {
def att = xwiki.getDeletedAttachment(it.toString())
def attachmentReference = services.model.createAttachmentReference(services.model.resolveDocument(att.docName), att.filename)
println "* ${services.model.serialize(attachmentReference)}"
if (request.trashed == '1') {
att.delete()
}
}
println ""
if (request.trashed != '1') {
println "[[Confirm Cleaning Attachments>>||queryString='trashed=1']]"
} else {
println "[[Back>>||queryString='']]"
}
} else {
println "Everything's clean. No trashed document found."
}
{{/groovy}}
import java.util.List
import com.xpn.xwiki.api.DeletedAttachment
// Send in request if you want to empty the trash bin for the whole wiki
def query = "select distinct ddoc.id from DeletedAttachment as ddoc "
def trashed = xwiki.search(query)
println "Total number of attachments to clean: ${trashed.size()}"
if (trashed) {
def deleted = 0
println "Trashed attachments"
trashed.each() {
def att = xwiki.getDeletedAttachment(it.toString())
def attachmentReference = services.model.createAttachmentReference(services.model.resolveDocument(att.docName), att.filename)
println "* ${services.model.serialize(attachmentReference)}"
if (request.trashed == '1') {
att.delete()
}
}
println ""
if (request.trashed != '1') {
println "[[Confirm Cleaning Attachments>>||queryString='trashed=1']]"
} else {
println "[[Back>>||queryString='']]"
}
} else {
println "Everything's clean. No trashed document found."
}
{{/groovy}}
Advanced version (deletes attachments older than a given date, from multiple wikis)
{{html clean='false'}}
<script type='text/javascript'>
function registerCheckboxSelectionListener(){
$('selector').observe('click', function(){
$('prepareform').select('.toggleable').each(function(item) {
if (item.checked){
item.checked=false;
} else {
item.checked=true;
}
});
});
}
Event.observe(document, 'xwiki:dom:loaded', function() {
registerCheckboxSelectionListener();
});
</script>
{{/html}}
{{groovy}}
import java.util.List;
import com.xpn.xwiki.api.DeletedAttachment;
import java.util.Arrays;
import java.util.Collections;
import org.xwiki.wiki.descriptor.WikiDescriptor;
import java.text.SimpleDateFormat;
import java.util.Date;
def wikis = request.getParameterValues('wikis');
def logger = org.slf4j.LoggerFactory.getLogger(doc.fullName);
services.logging.setLevel(doc.fullName, org.xwiki.logging.LogLevel.INFO);
if (wikis == null || wikis.size() == 0) {
/*
No wikis selected yet, allow to choose the wiki to execute operation on.
Also choose a date from which to delete.
*/
def allWikis = services.wiki.getAll();
int aThirdOfWikis = Math.floor(allWikis.size() / 3);
int twoThirdsOfWikis = aThirdOfWikis * 2;
println "{{info}}When many attachments found in a wiki - or on multiple wikis - the browser can display a timeout when executing the actions of this script. In that case, the progress of this script can be followed in the catalina.out logs, on the server.{{/info}}\n";
println """{{html clean='false' wiki='false'}}
<form id='prepareform' class='xform' method='post' action=''>
<dl>
<dt><label>Older than date:</label></dt>""";
xwiki.ssfx.use('uicomponents/widgets/datepicker/calendarDateSelect.css', true);
xwiki.jsfx.use('uicomponents/widgets/datepicker/calendarDateSelect.js', true);
xwiki.jsfx.use('uicomponents/widgets/datepicker/simpleDateFormat.js', true);
xwiki.ssfx.use('uicomponents/widgets/datepicker/dateTimePicker.css', true);
xwiki.jsfx.use('uicomponents/widgets/datepicker/dateTimePicker.js');
println """<dd><input type='text' class='datetime' title='dd/MM/yyyy' name='fromdate' /></dd>
<dt><label>Wikis:</label><dt>
<dd>
<div class='column third'>
<ul>
<li><input id='selector' type='checkbox' value='' /><strong>Select/Deselect all</strong></li>""";
Collections.sort(allWikis, new Comparator<WikiDescriptor> () {
int compare(WikiDescriptor w1, WikiDescriptor w2) {
return w1.getId().compareTo(w2.getId());
}
boolean equals(WikiDescriptor w1, WikiDescriptor w2) {
return w1.getId().equals(w2.getId());
}
});
for (int i = 0; i < allWikis.size(); i++) {
def w = allWikis.get(i);
println "<li><input name='wikis' type='checkbox' value='" + w.getId() + "' class='toggleable'>" + w.getId() + "</input></li>";
if (i == aThirdOfWikis || i == twoThirdsOfWikis) {
println """</ul></div>
<div class='column third'><ul>""";
}
}
println """</ul>
</dd></dl>
<div class='clearfloats'></div>
""";
// Display a skip preview button, to use only when preview timesout in the browser.
println """
<dl>
<dt><label for='skippreview'>Skip preview (delete directly) - use carefully, reserve for the cases when preview times out in browser but you have watched the progress of preview in the server logs.
<br/>Don't forget to set the same limit date as the preview case when deleting directly after a preview!</label></dt>
<dd>
<input type='checkbox' name='trashed' value='1' />
</dd>
</dl>
<div class='wikimodel-emptyline'></div>
""";
println """<div class='buttonwrapper'><input class='button' type='submit' name='preview' value='Preview (or DELETE, if preview is skipped)' /></div>
</form>
{{/html}}""";
} else {
// prepare the query, depending on the sent date
def queryString = "select distinct ddoc.id, ddoc.date from DeletedAttachment as ddoc";
def query = services.query.hql(queryString);
def date = null;
if (request.fromdate != null && request.fromdate != '') {
SimpleDateFormat formatter = new SimpleDateFormat("dd/MM/yyyy");
date = formatter.parse(request.fromdate);
queryString = queryString + " where ddoc.date < :d";
query = services.query.hql(queryString).bindValue('d', date);
}
println "Query: " + queryString;
println "From date: " + date;
for (w in wikis) {
def originalDb = xcontext.getDatabase();
try {
xcontext.setDatabase(w);
// Send in request if you want to empty the trash bin for the whole wiki
def trashed = query.execute();
println "==Wiki: " + w + "==";
println "Total number of attachments to clean: ${trashed.size()}"
logger.info("Wiki " + w + " . Total number of attachments " + trashed.size());
int no = 0;
if (trashed) {
def deleted = 0
println "Trashed attachments";
trashed.each() {
no++;
def att = xwiki.getDeletedAttachment(it[0].toString())
def attachmentReference = services.model.createAttachmentReference(services.model.resolveDocument(att.docName), att.filename)
logger.info("Wiki " + w + ": [" + no + "] Getting attachment " + services.model.serialize(attachmentReference) + " from " + it[1]);
println "|" + no + "|${services.model.serialize(attachmentReference)}|" + it[1];
if (request.trashed == '1') {
att.delete();
logger.info("Wiki " + w + ": [" + no + "] DELETED ATTACHMENT " + services.model.serialize(attachmentReference) + " from " + it[1]);
}
}
println ""
} else {
println "Everything's clean. No trashed document found."
}
} catch (Exception e) {
println "{{error}}An error ocurred while getting trashed attachments for this wiki.{{/error}}";
e.printStackTrace();
} finally {
xcontext.setDatabase(originalDb);
}
}
if (request.trashed != '1') {
// print confirm form
println """{{html clean='false' wiki='false'}}
<form method='post' action=''>""";
for(w in wikis) {
println "<input type='hidden' name='wikis' value='" + w + "' />";
}
if (request.fromdate != null && request.fromdate != '') {
println "<input type='hidden' name='fromdate' value='" + request.fromdate + "'></input>"
}
println """
<input type='hidden' name='trashed' value='1'></input>
<div class='buttonwrapper'>
<input type='submit' name='doconfirm' class='button' value='Confirm cleaning attachments'></input>
</div>
</form>
{{/html}}
""";
} else {
println "[[Back>>||queryString='']]"
}
}
{{/groovy}}
<script type='text/javascript'>
function registerCheckboxSelectionListener(){
$('selector').observe('click', function(){
$('prepareform').select('.toggleable').each(function(item) {
if (item.checked){
item.checked=false;
} else {
item.checked=true;
}
});
});
}
Event.observe(document, 'xwiki:dom:loaded', function() {
registerCheckboxSelectionListener();
});
</script>
{{/html}}
{{groovy}}
import java.util.List;
import com.xpn.xwiki.api.DeletedAttachment;
import java.util.Arrays;
import java.util.Collections;
import org.xwiki.wiki.descriptor.WikiDescriptor;
import java.text.SimpleDateFormat;
import java.util.Date;
def wikis = request.getParameterValues('wikis');
def logger = org.slf4j.LoggerFactory.getLogger(doc.fullName);
services.logging.setLevel(doc.fullName, org.xwiki.logging.LogLevel.INFO);
if (wikis == null || wikis.size() == 0) {
/*
No wikis selected yet, allow to choose the wiki to execute operation on.
Also choose a date from which to delete.
*/
def allWikis = services.wiki.getAll();
int aThirdOfWikis = Math.floor(allWikis.size() / 3);
int twoThirdsOfWikis = aThirdOfWikis * 2;
println "{{info}}When many attachments found in a wiki - or on multiple wikis - the browser can display a timeout when executing the actions of this script. In that case, the progress of this script can be followed in the catalina.out logs, on the server.{{/info}}\n";
println """{{html clean='false' wiki='false'}}
<form id='prepareform' class='xform' method='post' action=''>
<dl>
<dt><label>Older than date:</label></dt>""";
xwiki.ssfx.use('uicomponents/widgets/datepicker/calendarDateSelect.css', true);
xwiki.jsfx.use('uicomponents/widgets/datepicker/calendarDateSelect.js', true);
xwiki.jsfx.use('uicomponents/widgets/datepicker/simpleDateFormat.js', true);
xwiki.ssfx.use('uicomponents/widgets/datepicker/dateTimePicker.css', true);
xwiki.jsfx.use('uicomponents/widgets/datepicker/dateTimePicker.js');
println """<dd><input type='text' class='datetime' title='dd/MM/yyyy' name='fromdate' /></dd>
<dt><label>Wikis:</label><dt>
<dd>
<div class='column third'>
<ul>
<li><input id='selector' type='checkbox' value='' /><strong>Select/Deselect all</strong></li>""";
Collections.sort(allWikis, new Comparator<WikiDescriptor> () {
int compare(WikiDescriptor w1, WikiDescriptor w2) {
return w1.getId().compareTo(w2.getId());
}
boolean equals(WikiDescriptor w1, WikiDescriptor w2) {
return w1.getId().equals(w2.getId());
}
});
for (int i = 0; i < allWikis.size(); i++) {
def w = allWikis.get(i);
println "<li><input name='wikis' type='checkbox' value='" + w.getId() + "' class='toggleable'>" + w.getId() + "</input></li>";
if (i == aThirdOfWikis || i == twoThirdsOfWikis) {
println """</ul></div>
<div class='column third'><ul>""";
}
}
println """</ul>
</dd></dl>
<div class='clearfloats'></div>
""";
// Display a skip preview button, to use only when preview timesout in the browser.
println """
<dl>
<dt><label for='skippreview'>Skip preview (delete directly) - use carefully, reserve for the cases when preview times out in browser but you have watched the progress of preview in the server logs.
<br/>Don't forget to set the same limit date as the preview case when deleting directly after a preview!</label></dt>
<dd>
<input type='checkbox' name='trashed' value='1' />
</dd>
</dl>
<div class='wikimodel-emptyline'></div>
""";
println """<div class='buttonwrapper'><input class='button' type='submit' name='preview' value='Preview (or DELETE, if preview is skipped)' /></div>
</form>
{{/html}}""";
} else {
// prepare the query, depending on the sent date
def queryString = "select distinct ddoc.id, ddoc.date from DeletedAttachment as ddoc";
def query = services.query.hql(queryString);
def date = null;
if (request.fromdate != null && request.fromdate != '') {
SimpleDateFormat formatter = new SimpleDateFormat("dd/MM/yyyy");
date = formatter.parse(request.fromdate);
queryString = queryString + " where ddoc.date < :d";
query = services.query.hql(queryString).bindValue('d', date);
}
println "Query: " + queryString;
println "From date: " + date;
for (w in wikis) {
def originalDb = xcontext.getDatabase();
try {
xcontext.setDatabase(w);
// Send in request if you want to empty the trash bin for the whole wiki
def trashed = query.execute();
println "==Wiki: " + w + "==";
println "Total number of attachments to clean: ${trashed.size()}"
logger.info("Wiki " + w + " . Total number of attachments " + trashed.size());
int no = 0;
if (trashed) {
def deleted = 0
println "Trashed attachments";
trashed.each() {
no++;
def att = xwiki.getDeletedAttachment(it[0].toString())
def attachmentReference = services.model.createAttachmentReference(services.model.resolveDocument(att.docName), att.filename)
logger.info("Wiki " + w + ": [" + no + "] Getting attachment " + services.model.serialize(attachmentReference) + " from " + it[1]);
println "|" + no + "|${services.model.serialize(attachmentReference)}|" + it[1];
if (request.trashed == '1') {
att.delete();
logger.info("Wiki " + w + ": [" + no + "] DELETED ATTACHMENT " + services.model.serialize(attachmentReference) + " from " + it[1]);
}
}
println ""
} else {
println "Everything's clean. No trashed document found."
}
} catch (Exception e) {
println "{{error}}An error ocurred while getting trashed attachments for this wiki.{{/error}}";
e.printStackTrace();
} finally {
xcontext.setDatabase(originalDb);
}
}
if (request.trashed != '1') {
// print confirm form
println """{{html clean='false' wiki='false'}}
<form method='post' action=''>""";
for(w in wikis) {
println "<input type='hidden' name='wikis' value='" + w + "' />";
}
if (request.fromdate != null && request.fromdate != '') {
println "<input type='hidden' name='fromdate' value='" + request.fromdate + "'></input>"
}
println """
<input type='hidden' name='trashed' value='1'></input>
<div class='buttonwrapper'>
<input type='submit' name='doconfirm' class='button' value='Confirm cleaning attachments'></input>
</div>
</form>
{{/html}}
""";
} else {
println "[[Back>>||queryString='']]"
}
}
{{/groovy}}