/
Search and Replace User Macro

Search and Replace User Macro

## @noparams
#set ( $containerManagerClass = $content.class.forName('com.atlassian.spring.container.ContainerManager') )
#set ( $getInstanceMethod = $containerManagerClass.getDeclaredMethod('getInstance',null) )
#set ( $containerManager = $getInstanceMethod.invoke(null,null) )
#set ( $containerContext = $containerManager.containerContext )
#set ( $webResourceManager = $containerContext.getComponent('webResourceManager') )
$webResourceManager.requireResource('com.atlassian.auiplugin:aui-select2')

#set ( $Integer = 0 )
#set ( $requestedSearch = "" )
#set ( $requestedSearch = $req.getParameter('requestedSearch') )
#set ( $requestedSpaces = "" )
#set ( $requestedSpaces = $req.getParameter('requestedSpaces') )
#set ( $replace = "" )
#set ( $replace = $req.getParameter('replace') )
#set ( $requestedReplace = "" )
#set ( $requestedReplace = $req.getParameter('requestedReplace') )
#set ( $escapedRequestedSearch = $generalUtil.escapeXml($requestedSearch) )
#set ( $escapedRequestedReplace = $generalUtil.escapeXml($requestedReplace) )
#set ( $requestCharacter = "?" )
#if ( $content.getUrlPath().contains("?") )
  #set ( $requestCharacter = "&" )
#end
<script>
  var qs = (function(a) {
    if (a == "") return {};
    var b = {};
    for (var i = 0; i < a.length; ++i) {
      var p=a[i].split('=', 2);
      if (p.length == 1)
        b[p[0]] = "";
      else
        b[p[0]] = decodeURIComponent(p[1].replace(/\+/g, " "));
    }
    return b;
  })(window.location.search.substr(1).split('&'));
   
  function htmlEncode (html) {
    return document.createElement('a').appendChild(document.createTextNode(html)).parentNode.innerHTML;
  }
  
  function htmlDecode (text) {
    return jQuery('<textarea/>').html(text).text();
  }

  var formatXml = this.formatXml = function (xml) {
    var reg = /(>)\s*(<)(\/*)/g;
    var wsexp = / *(.*) +\n/g;
    var contexp = /(<.+>)(.+\n)/g;
    xml = xml.replace(reg, '$1\n$2$3').replace(wsexp, '$1\n').replace(contexp, '$1\n$2');
    var pad = 0;
    var formatted = '';
    var lines = xml.split('\n');
    var indent = 0;
    var lastType = 'other';
    var transitions = {
      'single->single': 0,
      'single->closing': -1,
      'single->opening': 0,
      'single->other': 0,
      'closing->single': 0,
      'closing->closing': -1,
      'closing->opening': 0,
      'closing->other': 0,
      'opening->single': 1,
      'opening->closing': 0,
      'opening->opening': 1,
      'opening->other': 1,
      'other->single': 0,
      'other->closing': -1,
      'other->opening': 0,
      'other->other': 0
    };

    for (var i = 0; i < lines.length; i++) {
      var ln = lines[i];
      var single = Boolean(ln.match(/<.+\/>/));
      var closing = Boolean(ln.match(/<\/.+>/));
      var opening = Boolean(ln.match(/<[^!].*>/));
      var type = single ? 'single' : closing ? 'closing' : opening ? 'opening' : 'other';
      var fromTo = lastType + '->' + type;
      lastType = type;
      var padding = '';
    
      indent += transitions[fromTo];
      for (var j = 0; j < indent; j++) {
        padding += '  ';
      }
      if (fromTo == 'opening->closing')
        formatted = formatted.substr(0, formatted.length - 1) + ln + '\n';
      else
        formatted += padding + ln + '\n';
    }
    return formatted;
  };
</script>
#if ( $requestedSearch == "" )
  <form class="aui top-label" onsubmit="return false;">
    <div class="field-group top-label">
      <label for="requested-search">Enter Search Terms:</label>
      <input id="requested-search" class="text long-field" type="text" name="requestedSearch">
    </div>
    <div class="field-group">
      <input id="replace" type="checkbox" onclick="jQuery('#requested-replace').val('');jQuery('#replace-fields').toggle();">
      <span>Do Search/Replace</span>
    </div>
    <div class="field-group" id="replace-fields" style="display:none;">
      <label for="requested-replace">Text to replace:</label>
      <input id="requested-replace" class="text long-field" type="text" name="requestedReplace">
    </div>
    <script> jQuery(function() { if (document.getElementById('replace').checked) { jQuery('#replace-fields').show(); } }); </script> ## show replace field if user hits back button
    <div class="field-group">
      <label for="requestedSpaces">Spaces to search:</label>
      <select class="select" id="requestedSpaces" name="requestedSpaces" multiple>
      #set ( $allSpaces = $spaceManager.getAllSpaces() )
      #foreach ( $space in $allSpaces )
        #if ( $space.isGlobal() )
          <option value="$space.key">$space.name - $space.key</option>
        #end
      #end
    </div>
    <button class="button submit" onclick="window.location = '$content.getUrlPath()' + '${requestCharacter}requestedSearch=' + encodeURIComponent(document.getElementById('requested-search').value) + '&requestedSpaces=' + jQuery('#requestedSpaces').select2('val').join() + '&replace=' + jQuery('#replace').is(':checked') + '&requestedReplace=' + encodeURIComponent(document.getElementById('requested-replace').value);">Search</button>
  </form>
  <script>
    jQuery("#requestedSpaces").auiSelect2();
  </script>
#elseif ( $requestedSearch != "" && $replace == "false" )
  #set ( $searchTerm = $requestedSearch )
  #foreach ( $spaceKey in $requestedSpaces.split(",") )
    #set ( $space = $spaceManager.getSpace($spaceKey) )
    <p> Pages containing &quot;${escapedRequestedSearch}&quot; in $space.name </p>
    #set ( $spacePages = $pageManager.getPages($space, true) )
    <table>
      <thead>
        <tr>
          <th>
            <p>Page</p>
          </th>
          <th>
          </th>
        </tr>
      </thead>
      <tbody>
      #foreach ($page in $spacePages)
        #set ( $pageId = $page.id )
        #set ( $pageMarkup = $page.getBodyAsString() )
        #if ( $permissionHelper.canView($action.remoteUser, $page) && $pageMarkup.contains($searchTerm) )
          #set ( $quoteEscapedPageMarkup = $pageMarkup.replaceAll('"', '@@quot@@').replaceAll('&quot;', '@@quot@@') )
          <tr>
            <td style="vertical-align:middle">
              <p><a href="$action.getGlobalSettings().getBaseUrl()$page.getUrlPath()" target="_blank">$page.title</a></p>
              <input style="display:none;" class="page-checkbox" type="checkbox" id="page-$pageId" pageid="$pageId" original="$quoteEscapedPageMarkup">
            </td>
            <td>
              <button id="preview${pageId}" class="aui-button">Preview</button>
              <button class="aui-button" onclick="window.open(contextPath + '/pages/editpage.action?pageId=$pageId', '_blank');">Edit</button>
            </td>
          </tr>
        #end
      #end
      </tbody>
    </table>
  #end
  <p> </p>
  <button class="aui-button" onclick="window.location = '$content.getUrlPath()'">New Search</button>
#elseif ( $requestedSearch != "" && $replace == "true" )
  <button class="aui-button" onclick="jQuery('.page-checkbox').prop('checked', true);">Select All</button>
  <button class="aui-button" onclick="jQuery('.page-checkbox').prop('checked', false);">Select None</button>
  #set ( $searchResultCount = 0 )
  #set ( $searchTerm = $requestedSearch )
  #foreach ( $spaceKey in $requestedSpaces.split(",") )
    #set ( $space = $spaceManager.getSpace($spaceKey) )
    <p> Pages containing &quot;${escapedRequestedSearch}&quot; in $space.name </p>
    #set ( $spacePages = $pageManager.getPages($space, true) )
    <table>
      <thead>
        <colgroup> ## To prevent table from being sortable
        </colgroup>
        <tr>
          <th>
            <p>Page</p>
          </th>
          <th>
            <button class="aui-button" onclick="jQuery('.page-checkbox[space=\'$space.key\']').click();">X</button>
          </th>
          <th>
          </th>
        </tr>
      </thead>
      <tbody>
      #foreach ($page in $spacePages)
        #set ( $pageId = $page.id )
        #set ( $pageMarkup = $page.getBodyAsString() )
        #if ( $permissionHelper.canView($action.remoteUser, $page) && $pageMarkup.contains($searchTerm) )
          #set ( $searchResultCount = $searchResultCount + 1 )
          <tr>
            <td style="vertical-align:middle">
              <p><a href="$action.getGlobalSettings().getBaseUrl()$page.getUrlPath()" target="_blank">$page.title</a></p>
            </td>
            <td style="vertical-align:middle">
              #if ( $permissionHelper.canEdit($action.remoteUser, $page) )
                #set ( $replacedPageMarkup = $pageMarkup.replaceAll($requestedSearch, $requestedReplace) )
                #set ( $newPageMarkup = $replacedPageMarkup.replaceAll('"', '@@quot@@').replaceAll('&quot;', '@@quot@@') )
                #set ( $newPageMarkup = $newPageMarkup.replaceAll("&nbsp;", " ") ) ## because of some sort of bug where &nbsp; is replaced with Â
                #set ( $quoteEscapedPageMarkup = $pageMarkup.replaceAll('"', '@@quot@@').replaceAll('&quot;', '@@quot@@') )
                <input class="page-checkbox unchanged" type="checkbox" id="page-$pageId" pageid="$pageId" version="$page.version" title="$page.title" parent="$page.parent.id" space="$space.key" content="$newPageMarkup" original="$quoteEscapedPageMarkup" checked="checked">
                <span class="aui-lozenge">unchanged</span>
              #else
                <span class="aui-lozenge aui-lozenge-current">cannot edit</span>
              #end
            </td>
            <td>
              <button id="preview${pageId}" class="aui-button">Preview</button>
              <button class="aui-button" onclick="window.open(contextPath + '/pages/editpage.action?pageId=$pageId', '_blank');">Edit</button>
              <button style="display:none;" id="undo-${pageId}" class="aui-button" onclick="window.open(contextPath + '/pages/revertpagebacktoversion.action?pageId=${pageId}&version=$page.version', '_blank');">Undo</button>
            </td>
          </tr>
        #end
      #end
      </tbody>
    </table>
  #end
  <p> Found $searchResultCount results.  You are about to replace &quot;${escapedRequestedSearch}&quot; with &quot;${escapedRequestedReplace}&quot; on all selected pages.  Continue? </p>
  <p> </p>
  <button class="aui-button" onclick="replaceText()">Continue</button>
  <button class="aui-button" onclick="window.location = '$content.getUrlPath()'">New Search</button>
  <script>
    function replaceText() {
      searchReplace(jQuery(".page-checkbox.unchanged:checked").first())
    }

    function searchReplace(nextCheckbox) {
      var pageId = jQuery(nextCheckbox).attr("pageid");
      var pageObj = {
        "id": pageId,
        "type": "page",
        "title": jQuery(nextCheckbox).attr("title"),
        "space": {
          "key": jQuery(nextCheckbox).attr("space")
        },
        "body": {
          "storage": {
            "value": jQuery(nextCheckbox).attr("content").split('@@quot@@').join('"'),
            "representation": "storage"
          }
        },
        "version": {
          "number": parseInt(jQuery(nextCheckbox).attr("version")) + 1
        }
      }
      jQuery.ajax({
        dataType: "json",
        contentType: "application/json",
        type: "PUT",
        url: contextPath + "/rest/api/content/" + pageId,
        data: JSON.stringify(pageObj),
        success: function (response) {
          jQuery(nextCheckbox)
              .removeClass("unchanged")
              .addClass("changed");
          jQuery(nextCheckbox)
              .next()
              .removeClass("aui-lozenge-current")
              .removeClass("aui-lozenge-error")
              .addClass("aui-lozenge-complete")
              .html("updated");
          jQuery("#undo-" + pageObj.id).show();
          nextCheckbox = jQuery(".page-checkbox.unchanged:checked").first();
          if (nextCheckbox.length) {
            searchReplace(nextCheckbox);
          }
        },
        error: function() {
          jQuery(nextCheckbox).removeClass("unchanged").addClass("error");
          jQuery(nextCheckbox)
              .next()
              .removeClass("aui-lozenge-complete")
              .removeClass("aui-lozenge-current")
              .addClass("aui-lozenge-error")
              .html("error")
          console.log(response);
          nextCheckbox = jQuery(".page-checkbox.unchanged:checked").first();
          
          if (nextCheckbox.length) {
            searchReplace(nextCheckbox);
          }
        }
      });
    }
  </script>
#end
<script>
  var nextMatch;
  var dialogs = {};
  var requestedSearch = qs["requestedSearch"];
  var requestedReplace = qs["requestedReplace"];
  var replace = qs["replace"];
  
  if (replace !== 'true') {
    requestedReplace = requestedSearch;
  }
  if (requestedReplace === "") {
    requestedReplace = '[Blank]';
  }
  
  if (requestedSearch) {
    jQuery(".page-checkbox").each(function() {
      var pageId = jQuery(this).attr("pageid");
      dialogs[pageId] = new AJS.Dialog({
          width: 840,
          height: 640,
          id: "dialog" + pageId,
          closeOnOutsideClick: true
      });
      dialogs[pageId].addHeader("Preview");
       
      var replacedMarkup = jQuery(this)
          .attr("original")
          .split('@@quot@@')
          .join('"')
          .split(htmlDecode(requestedSearch))
          .join(' @@start@@ ' + htmlEncode(requestedReplace) + ' @@end@@ ');
      var formattedMarkup = formatXml(replacedMarkup);
      var previewMarkup = jQuery('<pre style="white-space: pre-wrap;white-space: -moz-pre-wrap;white-space: -pre-wrap;white-space: -o-pre-wrap;word-wrap: break-word;"><span>' + 
          formattedMarkup
          .split('<')
          .join('&lt;')
          .split('>')
          .join('&gt;') 
          .split(' @@start@@ ')
          .join('</span><span class="match" style="background-color: #ddfade;">')
          .split(' @@end@@ ')
          .join('</span><span>') + 
          '</span></pre>');
      dialogs[pageId].addPanel("SinglePanel", previewMarkup, "singlePanel");
      
      dialogs[pageId].addButton("Next Match", function () {
          if (!nextMatch) {
            nextMatch = jQuery(".match:visible").first();
            nextMatch.css("background-color", "yellow");
          } else {
            nextMatch.css("background-color", "#ddfade");
            nextMatch = nextMatch.next().next(".match").length ? nextMatch.next().next(".match") : jQuery(".match:visible").first();
            nextMatch.css("background-color", "yellow");
          }
          nextMatch.get(0).scrollIntoView();
      });
      
      dialogs[pageId].addButton("Close", function () {
          if (nextMatch) { 
            nextMatch.css("background-color", "#ddfade");
            nextMatch = undefined;
          }
          dialogs[pageId].hide();
      });
      
      jQuery("#preview" + pageId).click(function() {
          dialogs[pageId].gotoPage(0);
          dialogs[pageId].gotoPanel(0);
          dialogs[pageId].show();
      });
    });
  }
</script>

Related content

Security Vulnerability Policy
Security Vulnerability Policy
Read with this
Link to Other Space
Link to Other Space
More like this
Data Security and Privacy Statement
Data Security and Privacy Statement
Read with this
Page Creator
Page Creator
More like this
Privacy Policy
Privacy Policy
Read with this
Filtering with CQL
Filtering with CQL
More like this