Bulk-update document authors wiki

Last modified by Clément Aubin on 2026/06/02 17:54

cogAllows to bulk update the wiki reference of author metadata stored in XWiki documents
Type
Category
Developed by

Clément Aubin

Rating
0 Votes
LicenseGNU 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}}

Get Connected