Bulk-update document authors wiki
Last modified by Clément Aubin on 2026/06/02 17:54
| Allows to bulk update the wiki reference of author metadata stored in XWiki documents |
| Type | |
| Category | |
| Developed by | |
| Rating | |
| License | GNU Lesser General Public License 2.1 |
Table of contents
Description
This script allows to bulk-update documents to migrate author references from one wiki to another.
{{velocity}}
This script allows to perform bulk update document authors, to change the wiki in which their profiles exist.
Programming rights are required to use this script. The [[Job Macro>>url:https://extensions.xwiki.org/xwiki/bin/view/Extension/Job%20Macro/]] is also required for executing the script.
{{html clean="false"}}
<form class="xform" action="#" method="post">
<dl>
<dt>
<label for="initialWikiId">Initial wiki ID</label>
<span class="xHint">The job will look for any author defined within this wiki ID.</span>
</dt>
<dd>
<input type="text" name="initialWikiId" required/>
</dd>
<dt>
<label for="newWikiId">New wiki ID</label>
<span class="xHint">The job will update all authors to this new wiki ID if a user profile exists in this wiki.</span>
</dt>
<dd>
<input type="text" name="newWikiId" required/>
</dd>
<dt>
<input type="checkbox" name="updateCreator" id="updateCreator" value="true"/><label for="updateCreator">Update the "creator" property</label>
<span class="xHint">If this option is checked, the "creator" property of documents will be updated.</span>
</dt>
<dt>
<input type="checkbox" name="updateAuthor" id="updateAuthor" value="true"/><label for="updateAuthor">Update the "author" property</label>
<span class="xHint">If this option is checked, the "author" property of documents will be updated.</span>
</dt>
<dt>
<input type="checkbox" name="updateOriginalMetadataAuthor" id="updateOriginalMetadataAuthor" value="true"/><label for="updateOriginalMetadataAuthor">Update the "originalMetadataAuthor" property</label>
<span class="xHint">If this option is checked, the "originalMetadataAuthor" property of documents will be updated.</span>
</dt>
<dt>
<input type="checkbox" name="updateContentAuthor" id="updateContentAuthor" value="true"/><label for="updateContentAuthor">Update the "contentAuthor" property</label>
<span class="xHint">If this option is checked, the "contentAuthor" property of documents will be updated.</span>
</dt>
<dt>
<input type="checkbox" name="ignoreMissingUsers" id="ignoreMissingUsers" value="true"/><label for="ignoreMissingUsers">Ignore missing users</label>
<span class="xHint">By default, the script will not update authors if a user profile in the new wiki isn't found. Check this box to force the save of a document even if no user profile exists.</span>
</dt>
<dt>
<input type="checkbox" name="dryRun" id="dryRun" value="true" checked/><label for="dryRun">Dry-run</label>
<span class="xHint">If this option is checked, no document will be saved.</span>
</dt>
</dl>
<p>
<span class="buttonwrapper">
<input type="hidden" name="form_token" value="$!{services.csrf.token}"/>
<input type="hidden" name="confirm" value="true"/>
<input class="button" type="submit" value="Update document authors"/>
</span>
</p>
</form>
{{/html}}
{{/velocity}}
{{job id="updateDocumentAuthors" start="{{velocity}}$!{request.confirm}{{/velocity}}"}}
{{groovy}}
import org.apache.commons.lang3.StringUtils;
import java.lang.StringBuilder;
import org.xwiki.logging.LogLevel;
import org.xwiki.query.QueryManager;
import org.xwiki.model.reference.*;
import org.xwiki.user.UserReferenceResolver;
import org.xwiki.user.UserReferenceSerializer;
import org.xwiki.model.internal.document.DefaultDocumentAuthors;
import org.xwiki.component.util.DefaultParameterizedType;
logger = services.logging.getLogger('UpdateDocumentAuthors');
services.logging.setLevel('UpdateDocumentAuthors', LogLevel.INFO);
componentManager = services.component.getComponentManager();
userReferenceResolver = componentManager.getInstance(UserReferenceResolver.TYPE_DOCUMENT_REFERENCE, "document");
userReferenceSerializer = componentManager.getInstance(UserReferenceSerializer.TYPE_DOCUMENT_REFERENCE, "document");
ignoreMissingUsers = 'true'.equals(request.ignoreMissingUsers);
updateCreator = 'true'.equals(request.updateCreator);
updateAuthor = 'true'.equals(request.updateAuthor);
updateOriginalMetadataAuthor = 'true'.equals(request.updateOriginalMetadataAuthor);
updateContentAuthor = 'true'.equals(request.updateContentAuthor);
dryRun = 'true'.equals(request.dryRun);
initialWikiReference = new WikiReference(request.initialWikiId);
newWikiReference = new WikiReference(request.newWikiId);
def getEquivalentAuthor(documentReference) {
def newAuthorReference = documentReference.setWikiReference(newWikiReference);
logger.debug('New author reference is : [{}]', newAuthorReference);
if (ignoreMissingUsers || xwiki.exists(newAuthorReference)) {
return userReferenceResolver.resolve(newAuthorReference);
} else {
return null;
}
}
if (hasProgramming && services.csrf.isTokenValid(request.form_token)) {
// Check if we have enough to work on
if (request.confirm == 'true') {
// Get every page matching the space
// We will use a character that has a very low probability of being in a page name in order to make sure to avoid any char
def documents = services.query
.hql('select doc.fullName from XWikiDocument doc where (doc.author like :author or doc.contentAuthor like :author or doc.originalMetadataAuthorReference like :author or doc.creator like :author)')
.bindValue('author', request.initialWikiId + ':%').execute();
logger.debug('Found [{}] documents to verify', documents.size());
documents.each { document ->
logger.debug('Verifying document [{}]', document);
try {
// Load the document
def xwikiDoc = xwiki.getDocument(document);
// Get the editable authorsr
def authors = xwikiDoc.getDocument().getAuthors();
def creatorReference = userReferenceSerializer.serialize(authors.getCreator());
def effectiveMetadataAuthorReference = userReferenceSerializer.serialize(authors.getEffectiveMetadataAuthor());
def contentAuthorReference = userReferenceSerializer.serialize(authors.getContentAuthor());
def originalMetadataAuthorReference = userReferenceSerializer.serialize(authors.getOriginalMetadataAuthor());
logger.debug('References : [{}] / [{}] / [{}] / [{}]', creatorReference, effectiveMetadataAuthorReference, contentAuthorReference, originalMetadataAuthorReference);
def needSave = false;
if (updateCreator && creatorReference != null && initialWikiReference.equals(creatorReference.getWikiReference())) {
logger.info('Found creator [{}], updating it ...', creatorReference);
newAuthorReference = getEquivalentAuthor(creatorReference);
if (newAuthorReference) {
logger.info('Converting creator [{}] to [{}] on [{}]', creatorReference, newAuthorReference, xwikiDoc.getDocumentReference());
authors.setCreator(newAuthorReference);
needSave = true;
} else {
logger.warn('Failed to find new reference for [{}] : user does not exist in target wiki', creatorReference);
}
}
if (updateAuthor && effectiveMetadataAuthorReference != null && initialWikiReference.equals(effectiveMetadataAuthorReference.getWikiReference())) {
logger.info('Found effective author [{}], updating it ...', effectiveMetadataAuthorReference);
newAuthorReference = getEquivalentAuthor(effectiveMetadataAuthorReference);
if (newAuthorReference) {
logger.info('Converting effective author [{}] to [{}] on [{}]', effectiveMetadataAuthorReference, newAuthorReference, xwikiDoc.getDocumentReference());
authors.setEffectiveMetadataAuthor(newAuthorReference);
needSave = true;
} else {
logger.warn('Failed to find new reference for [{}] : user does not exist in target wiki', effectiveMetadataAuthorReference);
}
}
if (updateContentAuthor && contentAuthorReference != null && initialWikiReference.equals(contentAuthorReference.getWikiReference())) {
logger.info('Found content author [{}], updating it ...', contentAuthorReference);
newAuthorReference = getEquivalentAuthor(contentAuthorReference);
if (newAuthorReference) {
logger.info('Converting content author [{}] to [{}] on [{}]', contentAuthorReference, newAuthorReference, xwikiDoc.getDocumentReference());
authors.setContentAuthor(newAuthorReference);
needSave = true;
} else {
logger.warn('Failed to find new reference for [{}] : user does not exist in target wiki', contentAuthorReference);
}
}
if (updateOriginalMetadataAuthor && originalMetadataAuthorReference != null && initialWikiReference.equals(originalMetadataAuthorReference.getWikiReference())) {
logger.info('Found original metadata author [{}], updating it ...', originalMetadataAuthorReference);
newAuthorReference = getEquivalentAuthor(originalMetadataAuthorReference);
if (newAuthorReference) {
logger.info('Converting original metadata author [{}] to [{}] on [{}]', originalMetadataAuthorReference, newAuthorReference, xwikiDoc.getDocumentReference());
authors.setOriginalMetadataAuthor(newAuthorReference);
needSave = true;
} else {
logger.warn('Failed to find new reference for [{}] : user does not exist in target wiki', originalMetadataAuthorReference);
}
}
if (needSave && !dryRun) {
// Get the internal document to perform the save
def internalDoc = xwikiDoc.getDocument();
logger.info('Saving document [{}] following a change in authors', xwikiDoc.getDocumentReference());
internalDoc.setContentDirty(false);
internalDoc.setMetaDataDirty(false);
try {
xwiki.getXWiki().saveDocument(internalDoc, xcontext.getContext());
} catch (Exception e) {
logger.error('Failed to save document [{}]', xwikiDoc.getDocumentReference(), e);
}
}
} catch (Exception e) {
logger.error('Failed to update document [{}]', document, e);
}
}
} else {
logger.error('Insufficient parameters. Please provide a target space. Aborting.');
}
} else {
logger.error('Insufficient permissions or invalid CSRF token. Aborting.')
}
{{/groovy}}
{{/job}}