Wiki source code of GitLabGroovy
Last modified by René Vögeli on 2026/01/23 08:43
Show last authors
| author | version | line-number | content |
|---|---|---|---|
| 1 | import org.gitlab.api.*; | ||
| 2 | import org.gitlab.api.http.*; | ||
| 3 | import org.gitlab.api.models.*; | ||
| 4 | |||
| 5 | import groovy.json.*; | ||
| 6 | |||
| 7 | import java.util.Collection; | ||
| 8 | import java.util.Iterator; | ||
| 9 | import java.util.Calendar; | ||
| 10 | |||
| 11 | import java.io.IOException; | ||
| 12 | import java.io.OutputStream; | ||
| 13 | import java.io.UnsupportedEncodingException; | ||
| 14 | |||
| 15 | import java.text.SimpleDateFormat; | ||
| 16 | |||
| 17 | import org.apache.velocity.tools.generic.EscapeTool; | ||
| 18 | import org.apache.commons.lang3.StringUtils; | ||
| 19 | import org.apache.commons.codec.binary.Base64; | ||
| 20 | |||
| 21 | import org.dom4j.Node; | ||
| 22 | import org.dom4j.io.OutputFormat; | ||
| 23 | import org.dom4j.io.XMLWriter; | ||
| 24 | import org.dom4j.Document; | ||
| 25 | import org.dom4j.Element; | ||
| 26 | import org.dom4j.io.SAXReader; | ||
| 27 | |||
| 28 | import com.xpn.xwiki.doc.XWikiDocument; | ||
| 29 | |||
| 30 | @SuppressWarnings("GrUnnecessarySemicolon") | ||
| 31 | public class GitLabGroovy { | ||
| 32 | def xwiki; | ||
| 33 | def context; | ||
| 34 | def services; | ||
| 35 | def logger; | ||
| 36 | def gitlabapi; | ||
| 37 | def gitlabproject; | ||
| 38 | def prjname = ""; | ||
| 39 | def prjdesc = ""; | ||
| 40 | def prjversion = ""; | ||
| 41 | def contribVersion = ""; | ||
| 42 | def xmlVersion = ""; | ||
| 43 | def gitlaburl = ""; | ||
| 44 | def repuser = ""; | ||
| 45 | def repname = ""; | ||
| 46 | def reppath = ""; | ||
| 47 | def repsrcpath = "src/main/resources"; | ||
| 48 | def repbranch = ""; | ||
| 49 | def defaultspace = ""; | ||
| 50 | def configdoc; | ||
| 51 | def savedlist = ""; | ||
| 52 | def repository = null; | ||
| 53 | def username = ""; | ||
| 54 | def email = ""; | ||
| 55 | def token = ""; | ||
| 56 | def defaultDate = ""; | ||
| 57 | Boolean removeDates; | ||
| 58 | String license; | ||
| 59 | String parentDefinitionStrategy; | ||
| 60 | String defaultLanguageDefinitionStrategy; | ||
| 61 | def defaultUser = ""; | ||
| 62 | def status = new HashMap(); | ||
| 63 | def DEFAULTAUTHOR = "xwiki:XWiki.Admin"; | ||
| 64 | |||
| 65 | public setXWiki(xwiki, context, services) { | ||
| 66 | this.xwiki = xwiki; | ||
| 67 | this.context = context; | ||
| 68 | this.services = services; | ||
| 69 | this.logger = services.logging.getLogger(this.getClass().getCanonicalName()); | ||
| 70 | } | ||
| 71 | |||
| 72 | public hasProgrammingRights() { | ||
| 73 | return xwiki.hasProgrammingRights(); | ||
| 74 | } | ||
| 75 | |||
| 76 | public setGitLabConfig(page) { | ||
| 77 | this.configdoc = xwiki.getDocument(page) | ||
| 78 | this.prjname = configdoc.getValue("name"); | ||
| 79 | this.prjdesc = configdoc.getValue("description"); | ||
| 80 | this.prjversion = configdoc.getValue("version"); | ||
| 81 | this.contribVersion = configdoc.getValue("contribVersion"); | ||
| 82 | this.xmlVersion = configdoc.getValue("xarXmlVersion"); | ||
| 83 | this.gitlaburl = configdoc.getValue("gitlab_url"); | ||
| 84 | this.repuser = configdoc.getValue("repository_user"); | ||
| 85 | this.repname = configdoc.getValue("repository_name"); | ||
| 86 | this.reppath = configdoc.getValue("repository_path"); | ||
| 87 | this.repbranch = (StringUtils.isBlank(configdoc.getValue("repository_branch"))) ? "master" : configdoc.getValue("repository_branch"); | ||
| 88 | this.defaultspace = configdoc.getValue("defaultspace"); | ||
| 89 | this.savedlist = configdoc.getValue("savedlist"); | ||
| 90 | this.defaultDate = configdoc.getValue("defaultdate"); | ||
| 91 | this.removeDates = configdoc.getValue("removeDates"); | ||
| 92 | this.license = configdoc.getValue("license"); | ||
| 93 | this.parentDefinitionStrategy = (configdoc.getValue("parentDefinitionStrategy") != null) ? configdoc.getValue("parentDefinitionStrategy") : ""; | ||
| 94 | this.defaultLanguageDefinitionStrategy = (configdoc.getValue("defaultLanguageDefinitionStrategy") != null) ? configdoc.getValue("defaultLanguageDefinitionStrategy") : ""; | ||
| 95 | this.defaultUser = configdoc.getValue("defaultuser"); | ||
| 96 | this.status = getStatus(configdoc.getValue("status")); | ||
| 97 | |||
| 98 | if (contribVersion==null || contribVersion=="") | ||
| 99 | contribVersion = "8.4" | ||
| 100 | |||
| 101 | if (xmlVersion==null || xmlVersion=="") | ||
| 102 | xmlVersion = "1.1" | ||
| 103 | |||
| 104 | def authobj = configdoc.getObject("GitLab.Code.GitLabAuthClass", "contextuser", context.user) | ||
| 105 | if (authobj && authobj.getProperty("token").property.value != '') { | ||
| 106 | this.token = authobj.getProperty("token").property.value | ||
| 107 | } else { | ||
| 108 | // Attempt to load a token defined on the user profile | ||
| 109 | def userProfileDoc = xwiki.getDocument(context.user); | ||
| 110 | userProfileDoc.use('GitLab.Code.GitLabAuthProfileClass'); | ||
| 111 | def userProfileToken = userProfileDoc.getValue('token'); | ||
| 112 | if (userProfileToken != '') { | ||
| 113 | this.token = userProfileToken; | ||
| 114 | } | ||
| 115 | } | ||
| 116 | |||
| 117 | //Basic authentication | ||
| 118 | // Create a GitLabApi instance to communicate with your GitLab server | ||
| 119 | gitlabapi = GitlabAPI.connect(gitlaburl, token) | ||
| 120 | |||
| 121 | gitlabproject = gitlabapi.getProject(repuser, repname) | ||
| 122 | logger.debug("Project: ${gitlabproject}") | ||
| 123 | return (gitlabproject==null) ? null : ""; | ||
| 124 | } | ||
| 125 | |||
| 126 | public getStatus(status) { | ||
| 127 | def smap = new HashMap(); | ||
| 128 | for (sline in StringUtils.split(status, "\r\n")) { | ||
| 129 | def items = sline.split(";"); | ||
| 130 | smap.put(items[0], [ "xwikiversion" : (items.length>1) ? items[1] : "", "xwikihash" : (items.length>2) ? items[2] : "", "gitlabversion" : (items.length>3) ? items[3] : "" ]) | ||
| 131 | } | ||
| 132 | return smap; | ||
| 133 | } | ||
| 134 | |||
| 135 | public getPageStatus(filePath) { | ||
| 136 | return status.get(filePath); | ||
| 137 | } | ||
| 138 | |||
| 139 | public saveStatus() { | ||
| 140 | def sstatus = ""; | ||
| 141 | for (key in status.keySet()) { | ||
| 142 | def stat = status.get(key); | ||
| 143 | sstatus += "${key};${stat.xwikiversion};${stat.xwikihash};${stat.gitlabversion}\n"; | ||
| 144 | } | ||
| 145 | // only save if changed | ||
| 146 | if (status!=configdoc.getValue("status")) { | ||
| 147 | configdoc.set("status", sstatus); | ||
| 148 | configdoc.save(); | ||
| 149 | } | ||
| 150 | } | ||
| 151 | |||
| 152 | public getDefaultSpace() { | ||
| 153 | return this.defaultspace; | ||
| 154 | } | ||
| 155 | |||
| 156 | public getSavedList() { | ||
| 157 | return (this.savedlist==null) ? "" : this.savedlist; | ||
| 158 | } | ||
| 159 | |||
| 160 | public getFilePath(pagedoc, nested) { | ||
| 161 | // get page | ||
| 162 | def filePath = getSourcePath() + ((nested) ? pagedoc.getSpace().replaceAll("[.]","/") : pagedoc.getSpace()) + "/" + pagedoc.documentReference.name; | ||
| 163 | def language = pagedoc.getLanguage(); | ||
| 164 | if (language!=null&&language!="") | ||
| 165 | filePath += "." + language; | ||
| 166 | filePath += ".xml" | ||
| 167 | return filePath; | ||
| 168 | } | ||
| 169 | |||
| 170 | public getStatusPath(pagedoc) { | ||
| 171 | // get page | ||
| 172 | def filePath = pagedoc.getSpace() + "/" + pagedoc.documentReference.name; | ||
| 173 | def language = pagedoc.getLanguage(); | ||
| 174 | if (language!=null&&language!="") | ||
| 175 | filePath += "." + language; | ||
| 176 | return filePath; | ||
| 177 | } | ||
| 178 | |||
| 179 | public cleanXML(gitlabcontent, withFormat) { | ||
| 180 | def newdoc = new XWikiDocument(); | ||
| 181 | newdoc.fromXML(gitlabcontent.replaceAll("\r","")); | ||
| 182 | return getXML(newdoc.newDocument(context.getContext()), withFormat); | ||
| 183 | } | ||
| 184 | |||
| 185 | public String getFileContentAsString(sha, withFormat) { | ||
| 186 | /* def dservice = new DataService(gitlabclient); | ||
| 187 | def blob = dservice.getBlob(repository, sha); | ||
| 188 | def gitlabcontent = new String(Base64.decodeBase64(blob.content.getBytes()), "UTF-8"); | ||
| 189 | if (withFormat) | ||
| 190 | return cleanXML(gitlabcontent, true); | ||
| 191 | return gitlabcontent; | ||
| 192 | */ | ||
| 193 | } | ||
| 194 | |||
| 195 | /* | ||
| 196 | public String getFileContentAsString(String space, String page, String language) { | ||
| 197 | // get page | ||
| 198 | def filePath = space + "/" + page; | ||
| 199 | if (language!=null&&language!="") | ||
| 200 | filePath += "." + language; | ||
| 201 | filePath += ".xml" | ||
| 202 | return ""; | ||
| 203 | } | ||
| 204 | */ | ||
| 205 | |||
| 206 | public getPOMFile() { | ||
| 207 | def escapetool = new EscapeTool(); | ||
| 208 | def samplePOM = xwiki.getDocument("GitLab.Code.SamplePOM").getContent(); | ||
| 209 | samplePOM = samplePOM.replaceAll("PRJNAME", prjname.toLowerCase().replaceAll(" ", "-")) | ||
| 210 | samplePOM = samplePOM.replaceAll("PRJPNAME", prjname) | ||
| 211 | samplePOM = samplePOM.replaceAll("PRJDESC", escapetool.xml(prjdesc)) | ||
| 212 | samplePOM = samplePOM.replaceAll("PRJVERSION", prjversion) | ||
| 213 | samplePOM = samplePOM.replaceAll("CONTRIBVERSION", contribVersion) | ||
| 214 | samplePOM = samplePOM.replaceAll("XMLVERSION", xmlVersion) | ||
| 215 | samplePOM = samplePOM.replaceAll("REPOSITORY_NAME", repname) | ||
| 216 | samplePOM = samplePOM.replaceAll("REPOSITORY_USER", repuser) | ||
| 217 | samplePOM = samplePOM.replaceAll("USERNAME", username) | ||
| 218 | return samplePOM; | ||
| 219 | } | ||
| 220 | |||
| 221 | public getXML(pagedoc, boolean withFormat) { | ||
| 222 | def clonedDoc = pagedoc.document.clone(); | ||
| 223 | boolean isTranslationDocument = false; | ||
| 224 | |||
| 225 | if ((this.parentDefinitionStrategy.isBlank() || this.parentDefinitionStrategy.equals("setDefault")) && clonedDoc.getParent().isBlank()) { | ||
| 226 | clonedDoc.setParent(clonedDoc.getSpace() + ".WebHome"); | ||
| 227 | } | ||
| 228 | |||
| 229 | // If the cloned document is a translation, we won't be able to know directly if it has a TranslationDocumentClass ; the quick and dirty way to check it is to get the default document. | ||
| 230 | if (xwiki.getDocument(clonedDoc.getFullName()).getObject("XWiki.TranslationDocumentClass")) { | ||
| 231 | if (clonedDoc.getSyntaxId()!="plain/1.0") { | ||
| 232 | clonedDoc.setSyntaxId("plain/1.0"); | ||
| 233 | } | ||
| 234 | if (!clonedDoc.isHidden()) { | ||
| 235 | clonedDoc.setHidden(true); | ||
| 236 | } | ||
| 237 | isTranslationDocument = true; | ||
| 238 | } | ||
| 239 | |||
| 240 | // remove Tag object | ||
| 241 | if (clonedDoc.getObject("XWiki.TagClass")) { | ||
| 242 | clonedDoc.removeObject(clonedDoc.getObject("XWiki.TagClass")); | ||
| 243 | } | ||
| 244 | |||
| 245 | // remove all gitlab authentication classes so that we don't commit passwords | ||
| 246 | for (object in clonedDoc.getObjects("GitLab.Code.GitLabAuthClass")) { | ||
| 247 | clonedDoc.removeObject(object); | ||
| 248 | } | ||
| 249 | |||
| 250 | // remove all gitlab authentication classes so that we don't commit passwords | ||
| 251 | for (object in clonedDoc.getObjects("GitLabCode.GitLabConfigClass")) { | ||
| 252 | object.set("status", "", context.getContext()) | ||
| 253 | } | ||
| 254 | |||
| 255 | if (withFormat) { | ||
| 256 | clonedDoc.setCreator(DEFAULTAUTHOR); | ||
| 257 | clonedDoc.setContentAuthor(DEFAULTAUTHOR); | ||
| 258 | clonedDoc.setAuthor(DEFAULTAUTHOR); | ||
| 259 | |||
| 260 | } else if (defaultUser && defaultUser!="") { | ||
| 261 | clonedDoc.setCreator(defaultUser); | ||
| 262 | clonedDoc.setContentAuthor(defaultUser); | ||
| 263 | clonedDoc.setAuthor(defaultUser); | ||
| 264 | } else { | ||
| 265 | clonedDoc.setCreator(clonedDoc.getAuthor()); | ||
| 266 | clonedDoc.setContentAuthor(clonedDoc.getAuthor()); | ||
| 267 | } | ||
| 268 | |||
| 269 | if (defaultDate && defaultDate!="") { | ||
| 270 | clonedDoc.setCreationDate(defaultDate); | ||
| 271 | clonedDoc.setContentUpdateDate(defaultDate); | ||
| 272 | clonedDoc.setDate(defaultDate); | ||
| 273 | clonedDoc.setVersion("1.1"); | ||
| 274 | } else { | ||
| 275 | clonedDoc.setContentUpdateDate(clonedDoc.getDate()) | ||
| 276 | clonedDoc.setCreationDate(clonedDoc.getDate()) | ||
| 277 | clonedDoc.setVersion("1.1"); | ||
| 278 | } | ||
| 279 | |||
| 280 | // Update attachment dates | ||
| 281 | for (xa in clonedDoc.getAttachmentList()) { | ||
| 282 | xa.setVersion("1.1"); | ||
| 283 | if (defaultDate && defaultDate!="") { | ||
| 284 | xa.setDate(defaultDate); | ||
| 285 | } | ||
| 286 | if (withFormat) { | ||
| 287 | xa.setAuthor(DEFAULTAUTHOR); | ||
| 288 | } else if (defaultUser && defaultUser!="") { | ||
| 289 | xa.setAuthor(defaultUser); | ||
| 290 | } | ||
| 291 | } | ||
| 292 | |||
| 293 | clonedDoc.setComment(""); | ||
| 294 | clonedDoc.setMinorEdit(false); | ||
| 295 | def c = clonedDoc.toXML(true, false, true, false, context.getContext()) | ||
| 296 | def c2 = c.trim().replaceAll("[\r]",""); | ||
| 297 | return (withFormat) ? format(c2, "", isTranslationDocument) : c2; | ||
| 298 | } | ||
| 299 | |||
| 300 | /** | ||
| 301 | * Creates a new file in the repository | ||
| 302 | * | ||
| 303 | * @param branchName The name of a repository branch | ||
| 304 | * @param commitMsg The commit message | ||
| 305 | * @param actions The actions to send | ||
| 306 | * @throws IOException on gitlab api call error | ||
| 307 | */ | ||
| 308 | public gitLabCommits(String branchName, String commitMsg, Object actions) throws IOException { | ||
| 309 | String tailUrl = gitlabproject.URL + "/" + gitlabproject.getId() + "/repository/commits"; | ||
| 310 | GitlabHTTPRequestor requestor = gitlabapi.dispatch(); | ||
| 311 | |||
| 312 | def req = requestor | ||
| 313 | .with("branch", branchName) | ||
| 314 | .with("commit_message", commitMsg) | ||
| 315 | .with("actions", actions); | ||
| 316 | logger.trace("JSON: " + JsonOutput.toJson(req.data)); | ||
| 317 | return req.to(tailUrl, GitlabCommit.class); | ||
| 318 | } | ||
| 319 | |||
| 320 | public commitFiles(pagelist, message, pom) { | ||
| 321 | def srcPath = getSourcePath(); | ||
| 322 | def newSpaces = new ArrayList(); | ||
| 323 | def newPages = new ArrayList(); | ||
| 324 | |||
| 325 | def currentPageData = new HashMap(); | ||
| 326 | def pagesBySpace = new HashMap(); | ||
| 327 | |||
| 328 | def pagename2 = ""; | ||
| 329 | |||
| 330 | // we need to list entries to see if we create or update | ||
| 331 | def entriesKeySet = getEntries("").keySet(); | ||
| 332 | |||
| 333 | for (pagename in pagelist) { | ||
| 334 | // separate the language particle at the end of the page name from the page fullname. The pagename is of form <docfullname>.<doclanguage> | ||
| 335 | def languageSeparator = pagename.lastIndexOf('.'); | ||
| 336 | if (languageSeparator < 0) { | ||
| 337 | def pagefullname = pagename; | ||
| 338 | } | ||
| 339 | def pagefullname = pagename.substring(0, languageSeparator); | ||
| 340 | def lang = pagename.substring(languageSeparator + 1); | ||
| 341 | def pagedoc = xwiki.getDocument(pagefullname); | ||
| 342 | // make sure we get the right translations | ||
| 343 | if (lang!=null && lang!="") { | ||
| 344 | pagedoc = pagedoc.getTranslatedDocument(lang); | ||
| 345 | } | ||
| 346 | def space = pagedoc.getSpace(); | ||
| 347 | def page = pagedoc.documentReference.name; | ||
| 348 | def language = pagedoc.getLanguage(); | ||
| 349 | |||
| 350 | def pageList = pagesBySpace.get(space); | ||
| 351 | if (pageList==null) { | ||
| 352 | pageList = new ArrayList(); | ||
| 353 | pagesBySpace.put(space, pageList); | ||
| 354 | } | ||
| 355 | pageList.add(pagedoc); | ||
| 356 | } | ||
| 357 | |||
| 358 | def committedPages = new ArrayList(); | ||
| 359 | def commitInfo = null; | ||
| 360 | |||
| 361 | def actions = [] | ||
| 362 | def gitlabEntries = getEntries(""); | ||
| 363 | |||
| 364 | try { | ||
| 365 | for (space in pagesBySpace.keySet()) { | ||
| 366 | // loop on each page | ||
| 367 | for (pagedoc in pagesBySpace.get(space)) { | ||
| 368 | pagename2 = pagedoc.getFullName(); | ||
| 369 | def page = pagedoc.documentReference.name; | ||
| 370 | def language = pagedoc.getLanguage(); | ||
| 371 | |||
| 372 | // find page Path | ||
| 373 | def result = findPage(gitlabEntries, pagedoc); | ||
| 374 | def entry = result.entry; | ||
| 375 | def filePath = result.filePath; | ||
| 376 | |||
| 377 | def newXML = getXML(pagedoc, true); | ||
| 378 | def newData = newXML.getBytes(); | ||
| 379 | |||
| 380 | def action = [:]; | ||
| 381 | action.action = (entry!=null) ? "update" : "create"; | ||
| 382 | action.file_path = filePath; | ||
| 383 | action.content = newXML; | ||
| 384 | |||
| 385 | // adding the page | ||
| 386 | actions.add(action); | ||
| 387 | |||
| 388 | if (!newPages.contains(filePath)) { | ||
| 389 | def oldData = currentPageData.get(filePath); | ||
| 390 | // updating status to check for changes | ||
| 391 | committedPages.add(getStatusPath(pagedoc)); | ||
| 392 | def pageStatus = status.get(getStatusPath(pagedoc)); | ||
| 393 | if (pageStatus==null) | ||
| 394 | pageStatus = [ "xwikiversion" : "", "xwikihash" : "", "gitlabversion" : ""]; | ||
| 395 | status.put(getStatusPath(pagedoc), pageStatus); | ||
| 396 | pageStatus.xwikiversion = "${pagedoc.getVersion()}"; | ||
| 397 | pageStatus.xwikihash = "${newXML.hashCode()}"; | ||
| 398 | } | ||
| 399 | } | ||
| 400 | } | ||
| 401 | } catch (Throwable e) { | ||
| 402 | e.printStackTrace(); | ||
| 403 | logger.debug("error preparing commit on page ${pagename2}: " + e.getMessage()); | ||
| 404 | return null; | ||
| 405 | } finally { | ||
| 406 | } | ||
| 407 | |||
| 408 | // Adding pom in commit | ||
| 409 | if (pom=="1") { | ||
| 410 | def action = [:]; | ||
| 411 | try { | ||
| 412 | gitlabapi.getRepositoryFile(gitlabproject, "pom.xml", repbranch) | ||
| 413 | action.action = "update" | ||
| 414 | } catch(e) { | ||
| 415 | action.action = "create" | ||
| 416 | } | ||
| 417 | action.file_path = "pom.xml"; | ||
| 418 | action.content = getPOMFile(); | ||
| 419 | actions.add(action); | ||
| 420 | logger.debug("Added pom.xml to commit"); | ||
| 421 | } | ||
| 422 | |||
| 423 | // now real commit | ||
| 424 | def newCommit = gitLabCommits(repbranch, message, actions); | ||
| 425 | logger.debug("Created commit: " + newCommit); | ||
| 426 | |||
| 427 | // we need to save the status | ||
| 428 | saveStatus(); | ||
| 429 | return "${gitlaburl}${repuser}/${repname}/tree/${newCommit.id}"; | ||
| 430 | } | ||
| 431 | |||
| 432 | public getChangedPages(String spaces) { | ||
| 433 | return getChangedPages(spaces, ""); | ||
| 434 | } | ||
| 435 | |||
| 436 | public findPage(entries, pagedoc) { | ||
| 437 | def filePath = getFilePath(pagedoc, false); | ||
| 438 | logger.debug("Check ${filePath} in entries"); | ||
| 439 | def entry = entries.get(filePath); | ||
| 440 | if (entry==null) { | ||
| 441 | filePath = getFilePath(pagedoc, true); | ||
| 442 | logger.debug("Check ${filePath} in entries"); | ||
| 443 | entry = entries.get(filePath); | ||
| 444 | } | ||
| 445 | return [ entry : entry, filePath : filePath ]; | ||
| 446 | } | ||
| 447 | |||
| 448 | protected checkPage(page, pagedoc, changedMap, samePages, gitlabEntries) { | ||
| 449 | def wikicontent = getXML(pagedoc, true); | ||
| 450 | def wikihash = (wikicontent==null) ? "" : "${wikicontent.hashCode()}"; | ||
| 451 | |||
| 452 | def wikicontent_unformatted = getXML(pagedoc, false); | ||
| 453 | |||
| 454 | def result = findPage(gitlabEntries, pagedoc); | ||
| 455 | def entry = result.entry; | ||
| 456 | def filePath = result.filePath; | ||
| 457 | logger.debug("Check ${filePath} ${wikihash}"); | ||
| 458 | |||
| 459 | if (entry==null || !wikihash.equals(entry.gitlabhash) || !wikihash.equals(entry.gitlabhash_unformatted)) { | ||
| 460 | def pageStatus = getPageStatus(getStatusPath(pagedoc)); | ||
| 461 | if (pageStatus==null) { | ||
| 462 | pageStatus = [ "xwikiversion" : pagedoc.getVersion(), "xwikihash" : "", "gitlabversion" : "", "gitlabsha" : (entry==null) ? "" : entry.gitlabsha ]; | ||
| 463 | status.put(getStatusPath(pagedoc), pageStatus); | ||
| 464 | } | ||
| 465 | pageStatus.filePath = filePath; | ||
| 466 | pageStatus.page = page; | ||
| 467 | pageStatus.fullname = pagedoc.fullName; | ||
| 468 | pageStatus.language = pagedoc.language | ||
| 469 | pageStatus.status = ""; | ||
| 470 | pageStatus.gitlabsha = (entry==null) ? "" : entry.gitlabsha; | ||
| 471 | |||
| 472 | logger.debug("Checking page ${page} version ${pagedoc.getVersion()} pageStatus ${pageStatus} ${wikihash}") | ||
| 473 | // if the documents match after formatting but the pure gitlab content is different then display it as 'F' (content is equivalent except formatting). | ||
| 474 | if (entry!=null && wikihash.equals(entry.gitlabhash)) { | ||
| 475 | pageStatus.status = "F"; | ||
| 476 | } else if (entry==null && !pagedoc.isNew()) { | ||
| 477 | pageStatus.status = "A"; | ||
| 478 | } else if (pageStatus.xwikihash=="") { | ||
| 479 | def gitlabsha = (entry==null) ? "" : entry.gitlabsha; | ||
| 480 | if (gitlabsha=="") | ||
| 481 | pageStatus.status = "A"; | ||
| 482 | else | ||
| 483 | pageStatus.status = "?"; | ||
| 484 | } else if (pagedoc.getVersion().equals(pageStatus.xwikiversion)) { | ||
| 485 | // version has not changed in the wikia | ||
| 486 | def whash = "${wikicontent.hashCode()}"; | ||
| 487 | // if the recorded hash is the same then we have a modified version in GitLab | ||
| 488 | // otherwise it's a bad state so it's a conflict | ||
| 489 | if (whash == pageStatus.xwikihash) | ||
| 490 | pageStatus.status = "U"; | ||
| 491 | else | ||
| 492 | pageStatus.status = "C"; | ||
| 493 | } else { | ||
| 494 | def gitlabsha = (entry==null) ? "" : entry.gitlabsha; | ||
| 495 | if (gitlabsha=="") | ||
| 496 | pageStatus.status = "A"; | ||
| 497 | else if(pageStatus.gitlabsha==gitlabsha) | ||
| 498 | pageStatus.status = "M"; | ||
| 499 | else | ||
| 500 | pageStatus.status = "C"; | ||
| 501 | } | ||
| 502 | |||
| 503 | // we do not check the parent anymore because we force it's value as we cannot edit it | ||
| 504 | // verify Invalid parents | ||
| 505 | // if (pagedoc.getParent()=="") | ||
| 506 | // pageStatus.status += " - Empty Parent" | ||
| 507 | |||
| 508 | // verify default language | ||
| 509 | if (pagedoc.getLanguage()=="") { | ||
| 510 | if (pagedoc.getDefaultLanguage()!="" && pagedoc.getTranslationList().size()==0) | ||
| 511 | pageStatus.status += " - Default language should be empty" | ||
| 512 | } | ||
| 513 | |||
| 514 | changedMap.put(page, pageStatus) | ||
| 515 | } else { | ||
| 516 | samePages.add(filePath); | ||
| 517 | def pageStatus = getPageStatus(getStatusPath(pagedoc)); | ||
| 518 | if (pageStatus==null) { | ||
| 519 | pageStatus = [ "xwikiversion" : "", "xwikihash" : "", "gitlabversion" : "", "gitlabsha" : "" ]; | ||
| 520 | status.put(getStatusPath(pagedoc), pageStatus); | ||
| 521 | } | ||
| 522 | pageStatus.xwikiversion = pagedoc.getVersion(); | ||
| 523 | pageStatus.xwikihash = wikicontent.hashCode(); | ||
| 524 | if (entry!=null) { | ||
| 525 | pageStatus.gitlabversion = entry.gitlabversion; | ||
| 526 | pageStatus.gitlabsha = entry.gitlabsha; | ||
| 527 | } | ||
| 528 | } | ||
| 529 | } | ||
| 530 | |||
| 531 | public containsSpace(spaceName, spaceList) { | ||
| 532 | for (item in spaceList) { | ||
| 533 | if (spaceName.startsWith(item)) | ||
| 534 | return true; | ||
| 535 | } | ||
| 536 | return false; | ||
| 537 | } | ||
| 538 | |||
| 539 | public getChangedPages(String spaces, String savedlist) { | ||
| 540 | def changedMap = new TreeMap(); | ||
| 541 | def gitlabEntries = getEntries(spaces); | ||
| 542 | def spaceList = null; | ||
| 543 | def samePages = new ArrayList(); | ||
| 544 | def list; | ||
| 545 | |||
| 546 | if (!savedlist || savedlist=="") { | ||
| 547 | spaceList = Arrays.asList(StringUtils.split(spaces," ,")); | ||
| 548 | def joinList = []; | ||
| 549 | for (space in spaceList) { | ||
| 550 | joinList.add("doc.fullName like '${space}%'") | ||
| 551 | } | ||
| 552 | def whereSpace = StringUtils.join(joinList, " or "); | ||
| 553 | def sql = "select distinct doc.fullName from XWikiDocument as doc where ${whereSpace}"; | ||
| 554 | logger.debug("Searching for ${sql}"); | ||
| 555 | list = xwiki.search(sql) | ||
| 556 | } else { | ||
| 557 | list = StringUtils.split(xwiki.getDocument(savedlist).getValue("list"), "|"); | ||
| 558 | } | ||
| 559 | |||
| 560 | for (page in list) { | ||
| 561 | def pagedoc = xwiki.getDocument(page); | ||
| 562 | checkPage(page, pagedoc, changedMap, samePages, gitlabEntries); | ||
| 563 | |||
| 564 | if (pagedoc.isNew()) { | ||
| 565 | logger.debug("GitLabApp: error reading page ${pagedoc.fullName} which should exist"); | ||
| 566 | } | ||
| 567 | |||
| 568 | def transList = null | ||
| 569 | try { | ||
| 570 | transList = pagedoc.getTranslationList(); | ||
| 571 | } catch (Exception e) { | ||
| 572 | System.out.println("Exception getting trans list of ${pagedoc.fullName} store ${pagedoc.store}: " + e.getMessage()); | ||
| 573 | e.printStackTrace(); | ||
| 574 | transList = []; | ||
| 575 | } | ||
| 576 | for (trans in transList) { | ||
| 577 | def tpagedoc = pagedoc.getTranslatedDocument(trans); | ||
| 578 | checkPage(page + "." + tpagedoc.language, tpagedoc, changedMap, samePages, gitlabEntries); | ||
| 579 | } | ||
| 580 | } | ||
| 581 | |||
| 582 | if (spaceList!=null) { | ||
| 583 | for (filePath in gitlabEntries.keySet()) { | ||
| 584 | def entry = gitlabEntries.get(filePath); | ||
| 585 | def i0 = getSourcePath().size() | ||
| 586 | def i1 = filePath.lastIndexOf("/"); | ||
| 587 | def i2 = filePath.indexOf(".xml"); | ||
| 588 | def spaceName = (i1==-1) ? filePath : filePath.substring(i0, i1); | ||
| 589 | spaceName = spaceName.replaceAll("/", ".") | ||
| 590 | def page = (i2==-1) ? filePath : filePath.substring(i1+1, i2); | ||
| 591 | def language = ""; | ||
| 592 | def pageName = page; | ||
| 593 | if (page.contains(".")) { | ||
| 594 | def i3 = page.indexOf("."); | ||
| 595 | language = page.substring(i3+1); | ||
| 596 | pageName = page.substring(0, i3); | ||
| 597 | } | ||
| 598 | logger.debug("SpaceName: ${spaceName} SpaceList: ${spaceList} SamePages: ${samePages} FilePath: ${filePath} Page: ${spaceName}.${page}") | ||
| 599 | if (containsSpace(spaceName, spaceList)&&!samePages.contains(filePath)&&!changedMap.keySet().contains(spaceName + "." + page)) { | ||
| 600 | changedMap.put(spaceName + "." + pageName, [ "fullname" : "${spaceName}.${pageName}", "language" : language, "status" : "N", "xwikiversion" : "", "gitlabsha" : entry.gitlabsha ]) | ||
| 601 | logger.debug("Adding page ${spaceName}.${page}") | ||
| 602 | } else { | ||
| 603 | if (!containsSpace(spaceName, spaceList)) | ||
| 604 | logger.debug("${spaceName} not in ${spaceList}") | ||
| 605 | if (samePages.contains(filePath)) | ||
| 606 | logger.debug("${filePath} already in list") | ||
| 607 | if (changedMap.keySet().contains(spaceName + "." + page)) | ||
| 608 | logger.debug("${spaceName}.${page} already in list") | ||
| 609 | } | ||
| 610 | } | ||
| 611 | } | ||
| 612 | // saveStatus if changed | ||
| 613 | saveStatus(); | ||
| 614 | return changedMap; | ||
| 615 | } | ||
| 616 | |||
| 617 | public updatePages(pageList, spaces) { | ||
| 618 | def changedMap = new TreeMap(); | ||
| 619 | def gitlabEntries = getEntries(spaces); | ||
| 620 | |||
| 621 | for (pageobj in pageList) { | ||
| 622 | def page = pageobj.page; | ||
| 623 | def sha = pageobj.sha; | ||
| 624 | |||
| 625 | logger.debug("Ready to update: ${page} ${sha}"); | ||
| 626 | |||
| 627 | // separate the language particle at the end of the page name from the page fullname. The pagename is of form <docfullname>.<doclanguage> | ||
| 628 | def languageSeparator = page.lastIndexOf('.'); | ||
| 629 | if (languageSeparator < 0) { | ||
| 630 | def pagefullname = page; | ||
| 631 | } | ||
| 632 | def pagefullname = page.substring(0, languageSeparator); | ||
| 633 | def lang = page.substring(languageSeparator + 1); | ||
| 634 | |||
| 635 | def pagedoc = xwiki.getDocument(pagefullname); | ||
| 636 | if (lang!=null && lang!="") { | ||
| 637 | pagedoc = pagedoc.getTranslatedDocument(lang); | ||
| 638 | } | ||
| 639 | def wikicontent = getXML(pagedoc, true); | ||
| 640 | |||
| 641 | |||
| 642 | def result = findPage(gitlabEntries, pagedoc); | ||
| 643 | def entry = result.entry; | ||
| 644 | def filePath = result.filePath; | ||
| 645 | def gitlabversion = (entry==null) ? "" : entry.gitlabversion; | ||
| 646 | def gitlabcontent = (entry==null) ? "" : entry.gitlabcontent; | ||
| 647 | |||
| 648 | logger.debug("Ready to update: ${filePath}"); | ||
| 649 | |||
| 650 | if (!wikicontent.equals(gitlabcontent) && gitlabcontent!=null && gitlabcontent!="") { | ||
| 651 | def pageStatus = getPageStatus(getStatusPath(pagedoc)); | ||
| 652 | if (pageStatus==null) { | ||
| 653 | pageStatus = [ "xwikiversion" : pagedoc.getVersion(), "xwikihash" : "", "gitlabversion" : "" ]; | ||
| 654 | } | ||
| 655 | pageStatus.filePath = filePath; | ||
| 656 | pageStatus.page = pagefullname; | ||
| 657 | pageStatus.status = ""; | ||
| 658 | |||
| 659 | // updating XWiki document from GitLab | ||
| 660 | changedMap.put(pagefullname, pageStatus); | ||
| 661 | def archive = pagedoc.document.getDocumentArchive(context.getContext()); | ||
| 662 | def version = pagedoc.document.getRCSVersion(); | ||
| 663 | |||
| 664 | def newdoc = new XWikiDocument(); | ||
| 665 | newdoc.fromXML(gitlabcontent); | ||
| 666 | |||
| 667 | // check attachments that do not exist in updated pages and delete them to recycle bin | ||
| 668 | for (xa in pagedoc.getAttachmentList()) { | ||
| 669 | if (!newdoc.getAttachment(xa.getFilename())) { | ||
| 670 | pagedoc.document.deleteAttachment(xa.attachment, true, context.getContext()); | ||
| 671 | } | ||
| 672 | } | ||
| 673 | |||
| 674 | // Make sure they are not marked dirty | ||
| 675 | for (xa in newdoc.getAttachmentList()) { | ||
| 676 | xa.setMetaDataDirty(false); | ||
| 677 | xa.getAttachment_content().setContentDirty(false); | ||
| 678 | } | ||
| 679 | |||
| 680 | // we need to make sure previous history is kept | ||
| 681 | newdoc.setDocumentArchive(archive); | ||
| 682 | |||
| 683 | // we need to keep the creator if there was already a document | ||
| 684 | if (pagedoc.getCreator()!=null) | ||
| 685 | newdoc.setCreator(pagedoc.getCreator()); | ||
| 686 | |||
| 687 | // set user and author to current user | ||
| 688 | newdoc.setContentAuthor(context.getUser()); | ||
| 689 | newdoc.setAuthor(context.getUser()); | ||
| 690 | |||
| 691 | // we need to make sure no version is added | ||
| 692 | if (pagedoc.isNew()) { | ||
| 693 | newdoc.setMetaDataDirty(true); | ||
| 694 | newdoc.setContentDirty(true); | ||
| 695 | newdoc.setRCSVersion(null); | ||
| 696 | } else { | ||
| 697 | newdoc.setMetaDataDirty(true); | ||
| 698 | newdoc.setContentDirty(true); | ||
| 699 | } | ||
| 700 | |||
| 701 | // Go through each object change between the previous and the new document in order to identify | ||
| 702 | // objects that have been removed. We will need to explicitely mark them to be removed when | ||
| 703 | // saving newdoc | ||
| 704 | newdoc.getObjectDiff(pagedoc.getDocument(), newdoc, context.getContext()).each{ diff -> | ||
| 705 | diff.each{ diffElement -> | ||
| 706 | if (diffElement.getAction().equals("object-removed")) { | ||
| 707 | def removedObject = pagedoc.getObject(diffElement.getClassName(), diffElement.getNumber()); | ||
| 708 | if (removedObject != null) { | ||
| 709 | newdoc.addXObjectToRemove(removedObject.getXWikiObject()); | ||
| 710 | } | ||
| 711 | } | ||
| 712 | } | ||
| 713 | } | ||
| 714 | |||
| 715 | // saving document | ||
| 716 | xwiki.getXWiki().saveDocument(newdoc, "Updated from GitLab", context.getContext()); | ||
| 717 | |||
| 718 | // saving attachments | ||
| 719 | newdoc.saveAllAttachments(false, true, context.getContext()); | ||
| 720 | |||
| 721 | // we need to force the saving the document archive. | ||
| 722 | if (newdoc.getDocumentArchive() != null) { | ||
| 723 | xwiki.getXWiki().getVersioningStore().saveXWikiDocArchive(newdoc.getDocumentArchive(context.getContext()), true, context.getContext()); | ||
| 724 | } | ||
| 725 | |||
| 726 | // reading the information to set the status | ||
| 727 | def newpagedoc = xwiki.getDocument(pagedoc.getFullName()); | ||
| 728 | def newwikicontent = getXML(pagedoc, true); | ||
| 729 | pageStatus.xwikiversion = pagedoc.getVersion(); | ||
| 730 | pageStatus.xwikihash = "${newwikicontent.hashCode()}"; | ||
| 731 | pageStatus.gitlabversion = gitlabversion; | ||
| 732 | } | ||
| 733 | } | ||
| 734 | // saveStatus if changed | ||
| 735 | saveStatus(); | ||
| 736 | return changedMap; | ||
| 737 | } | ||
| 738 | |||
| 739 | public exportPages(docname, pageList) { | ||
| 740 | def export = xwiki.package | ||
| 741 | export.setWithVersions(true) | ||
| 742 | export.setWithVersions(false) | ||
| 743 | export.setName(docname) | ||
| 744 | for (page in pageList) { | ||
| 745 | export.add(page, 0); | ||
| 746 | } | ||
| 747 | export.export(); | ||
| 748 | } | ||
| 749 | |||
| 750 | public getModifiedFiles(rev) { | ||
| 751 | return (getModifiedFiles("", rev, "10")); | ||
| 752 | } | ||
| 753 | |||
| 754 | public getModifiedFiles2(date, hour) { | ||
| 755 | return (getModifiedFiles("", date, hour)); | ||
| 756 | } | ||
| 757 | |||
| 758 | public getModifiedFiles2(dir, date, hour) { | ||
| 759 | } | ||
| 760 | |||
| 761 | public getModifiedFiles(dir, rev, max) { | ||
| 762 | } | ||
| 763 | |||
| 764 | public getRevisions(dir) { | ||
| 765 | } | ||
| 766 | |||
| 767 | public listFiles(dir, recursive) { | ||
| 768 | } | ||
| 769 | |||
| 770 | public getCommitStatus(prefix, sep, updatedonly) { | ||
| 771 | def str = ""; | ||
| 772 | str += "${prefix}page${sep}language${sep}version${sep}isnew${sep}hash${sep}gitlabpath${sep}gitlabversion${sep}gitlabhash${sep}isdiff\n" | ||
| 773 | |||
| 774 | while (spaceIterator.hasNext()) { | ||
| 775 | def space = entry.getName().toString(); | ||
| 776 | // Collection pageEntries = repository.getDir(space, -1, null, (Collection) null); | ||
| 777 | Iterator pageIterator = null; | ||
| 778 | while (pageIterator.hasNext()) { | ||
| 779 | def pageEntry = pageIterator.next(); | ||
| 780 | def fileName = pageEntry.getName().toString(); | ||
| 781 | def i1 = fileName.indexOf(".xml"); | ||
| 782 | def pageName = (i1==-1) ? fileName : fileName.substring(0, i1); | ||
| 783 | pageName = space + "." + pageName; | ||
| 784 | def pagedoc = xwiki.getDocument(pageName); | ||
| 785 | def pagexml = getXML(pagedoc, true); | ||
| 786 | |||
| 787 | def baos = new ByteArrayOutputStream(); | ||
| 788 | // logger.debug("reading file: ${space}/${fileName}") | ||
| 789 | // repository.getFile(space + "/" + fileName, -1, fileProperties, baos); | ||
| 790 | def gitlabcontent = new String(baos.toByteArray()) | ||
| 791 | |||
| 792 | def version = (pagedoc==null) ? "" : pagedoc.getVersion(); | ||
| 793 | def hash = (pagexml==null) ? "" : pagexml.hashCode(); | ||
| 794 | def gitlabversion = pageEntry.getRevision().toString(); | ||
| 795 | def gitlabhash = (gitlabcontent==null) ? "" : gitlabcontent.hashCode(); | ||
| 796 | |||
| 797 | def isdiff = !pagexml.equals(gitlabcontent) | ||
| 798 | if (!updatedonly || !isdiff) | ||
| 799 | str += "${prefix}${pageName}${sep}${pagedoc.getLanguage()}${sep}${version}${sep}${pagedoc.isNew()}${sep}${hash}${sep}${space}/${fileName}${sep}${gitlabversion}${sep}${gitlabhash}${sep}${isdiff}\n" | ||
| 800 | } | ||
| 801 | } | ||
| 802 | return str; | ||
| 803 | } | ||
| 804 | |||
| 805 | public getBasePath() { | ||
| 806 | def basepath = reppath; | ||
| 807 | if (basepath.startsWith("/")) | ||
| 808 | basepath = srcpath.substring(1); | ||
| 809 | if (basepath=="") | ||
| 810 | return ""; | ||
| 811 | else if (basepath.endsWith("/")) | ||
| 812 | return basepath; | ||
| 813 | else | ||
| 814 | return basepath + "/"; | ||
| 815 | } | ||
| 816 | |||
| 817 | public getSourcePath() { | ||
| 818 | def srcpath = getBasePath(); | ||
| 819 | |||
| 820 | if (repsrcpath==""||repsrcpath=="/"||repsrcpath==".") | ||
| 821 | return srcpath; | ||
| 822 | else if (repsrcpath.startsWith("/")) | ||
| 823 | srcpath = srcpath + repsrcpath.substring(0); | ||
| 824 | else | ||
| 825 | srcpath = srcpath + repsrcpath; | ||
| 826 | |||
| 827 | if (srcpath=="") | ||
| 828 | return ""; | ||
| 829 | else if (srcpath.endsWith("/")) | ||
| 830 | return srcpath | ||
| 831 | else | ||
| 832 | return srcpath + "/"; | ||
| 833 | } | ||
| 834 | |||
| 835 | public matchSpace(path, spaces) { | ||
| 836 | if (spaces==null) | ||
| 837 | return true; | ||
| 838 | |||
| 839 | for (space in spaces.split(",")) { | ||
| 840 | if (path.startsWith(space.replaceAll("[.]", "/"))) { | ||
| 841 | return true; | ||
| 842 | } | ||
| 843 | } | ||
| 844 | return false; | ||
| 845 | } | ||
| 846 | |||
| 847 | public getEntries(String spaces) { | ||
| 848 | def entries = new HashMap(); | ||
| 849 | |||
| 850 | def tree = gitlabapi.getRepositoryTree(gitlabproject, "", repbranch, true); | ||
| 851 | def srcPath = getSourcePath(); | ||
| 852 | for (file in tree) { | ||
| 853 | if (file.type=="blob") { | ||
| 854 | if (file.path.startsWith(srcPath)) { | ||
| 855 | def file2 = gitlabapi.getRepositoryFile(gitlabproject, file.path, repbranch) | ||
| 856 | def gitlabcontent = new String(Base64.decodeBase64(file2.content.getBytes()), "UTF-8"); | ||
| 857 | // calculate the hash before formatting | ||
| 858 | def gitlabhash_unformatted = (gitlabcontent==null) ? "" : gitlabcontent.hashCode(); | ||
| 859 | gitlabcontent = cleanXML(gitlabcontent, true); | ||
| 860 | def gitlabhash = (gitlabcontent==null) ? "" : gitlabcontent.hashCode(); | ||
| 861 | def gitlabversion = 0; | ||
| 862 | entries.put(file.path, [ "gitlabversion" : gitlabversion, "gitlabsha" : file.id, "gitlabhash" : "${gitlabhash}", "gitlabhash_unformatted" : "${gitlabhash_unformatted}", "gitlabcontent" : gitlabcontent ]); | ||
| 863 | logger.debug("Adding: ${file.path}"); | ||
| 864 | } | ||
| 865 | } | ||
| 866 | } | ||
| 867 | // logger.debug("Entries: ${entries}") | ||
| 868 | return entries; | ||
| 869 | } | ||
| 870 | |||
| 871 | public getXMLForDiff(pagedoc, filePath, withFormat) { | ||
| 872 | def file2 = gitlabapi.getRepositoryFile(gitlabproject, filePath, repbranch) | ||
| 873 | def gitlabxml = new String(Base64.decodeBase64(file2.content.getBytes()), "UTF-8"); | ||
| 874 | if (withFormat) | ||
| 875 | gitlabxml = cleanXML(gitlabxml, true); | ||
| 876 | logger.debug("Gitlab hash " + gitlabxml.hashCode()) | ||
| 877 | def xml = getXML(pagedoc, true); | ||
| 878 | logger.debug("XWiki hash " + xml.hashCode()) | ||
| 879 | |||
| 880 | if (pagedoc.isNew() && gitlabxml == null) | ||
| 881 | return "Document does not exist"; | ||
| 882 | |||
| 883 | if (pagedoc.isNew()) | ||
| 884 | return "Document does not exist in the wiki" | ||
| 885 | |||
| 886 | if (gitlabxml==null) | ||
| 887 | return "Document does not exist in GitLab" | ||
| 888 | |||
| 889 | // remove attachment content from xml | ||
| 890 | gitlabxml = gitlabxml.replaceAll("(?s)<attachment>(.*?)<content>(.*?)</content>(.*?)</attachment>", "<attachment>\$1<content></content>\$3</attachment>") | ||
| 891 | xml = xml.replaceAll("(?s)<attachment>(.*?)<content>(.*?)</content>(.*?)</attachment>", | ||
| 892 | "<attachment>\$1<content></content>\$3</attachment>") | ||
| 893 | return [gitlabxml, xml]; | ||
| 894 | } | ||
| 895 | |||
| 896 | public showXMLDiff(pagedoc, filePath, withFormat) { | ||
| 897 | def file2 = gitlabapi.getRepositoryFile(gitlabproject, filePath, repbranch) | ||
| 898 | def gitlabxml = new String(Base64.decodeBase64(file2.content.getBytes()), "UTF-8"); | ||
| 899 | if (withFormat) | ||
| 900 | gitlabxml = cleanXML(gitlabxml, true); | ||
| 901 | logger.debug("Gitlab hash " + gitlabxml.hashCode()) | ||
| 902 | def xml = getXML(pagedoc, true); | ||
| 903 | logger.debug("XWiki hash " + xml.hashCode()) | ||
| 904 | |||
| 905 | if (pagedoc.isNew() && gitlabxml == null) | ||
| 906 | return "Document does not exist"; | ||
| 907 | |||
| 908 | if (pagedoc.isNew()) | ||
| 909 | return "Document does not exist in the wiki" | ||
| 910 | |||
| 911 | if (gitlabxml==null) | ||
| 912 | return "Document does not exist in GitLab" | ||
| 913 | |||
| 914 | // remove attachment content from xml | ||
| 915 | gitlabxml = gitlabxml.replaceAll("(?s)<attachment>(.*?)<content>(.*?)</content>(.*?)</attachment>", "<attachment>\$1<content></content>\$3</attachment>") | ||
| 916 | xml = xml.replaceAll("(?s)<attachment>(.*?)<content>(.*?)</content>(.*?)</attachment>", | ||
| 917 | "<attachment>\$1<content></content>\$3</attachment>") | ||
| 918 | return xwiki.diff.getDifferencesAsHTML(gitlabxml, xml, false); | ||
| 919 | } | ||
| 920 | |||
| 921 | // Adding class and method to perform indentation and cleanup | ||
| 922 | public class XWikiXMLWriter extends XMLWriter | ||
| 923 | { | ||
| 924 | /** | ||
| 925 | * True if we use an output format. | ||
| 926 | */ | ||
| 927 | private boolean useFormat; | ||
| 928 | |||
| 929 | /** | ||
| 930 | * @param output the stream where to write the XML | ||
| 931 | * @throws UnsupportedEncodingException in case encoding issue | ||
| 932 | */ | ||
| 933 | public XWikiXMLWriter(OutputStream output) throws UnsupportedEncodingException | ||
| 934 | { | ||
| 935 | super(output); | ||
| 936 | } | ||
| 937 | |||
| 938 | /** | ||
| 939 | * @param output the stream where to write the XML | ||
| 940 | * @param format the style to use when outputting the XML | ||
| 941 | * @throws UnsupportedEncodingException in case encoding issue | ||
| 942 | */ | ||
| 943 | public XWikiXMLWriter(OutputStream output, OutputFormat format) throws UnsupportedEncodingException | ||
| 944 | { | ||
| 945 | super(output, format); | ||
| 946 | this.useFormat = true; | ||
| 947 | } | ||
| 948 | |||
| 949 | @Override | ||
| 950 | protected void writeComment(String text) throws IOException | ||
| 951 | { | ||
| 952 | super.writeComment(text); | ||
| 953 | |||
| 954 | // Add a new line after the license declaration | ||
| 955 | if (text.contains("See the NOTICE file distributed with this work for additional")) { | ||
| 956 | println(); | ||
| 957 | } | ||
| 958 | } | ||
| 959 | |||
| 960 | @Override | ||
| 961 | protected void writeNodeText(Node node) throws IOException | ||
| 962 | { | ||
| 963 | if (this.useFormat && node.getText().trim().length() == 0) { | ||
| 964 | // Check if parent node contains non text nodes | ||
| 965 | boolean containsNonTextNode = false; | ||
| 966 | for (Object object : node.getParent().content()) { | ||
| 967 | Node objectNode = (Node) object; | ||
| 968 | if (objectNode.getNodeType() != Node.TEXT_NODE) { | ||
| 969 | containsNonTextNode = true; | ||
| 970 | break; | ||
| 971 | } | ||
| 972 | } | ||
| 973 | if (containsNonTextNode) { | ||
| 974 | // Don't do anything, i.e. don't print the current text node | ||
| 975 | } else { | ||
| 976 | super.writeNodeText(node); | ||
| 977 | } | ||
| 978 | } else { | ||
| 979 | super.writeNodeText(node); | ||
| 980 | } | ||
| 981 | } | ||
| 982 | |||
| 983 | @Override | ||
| 984 | protected void writePrintln() throws IOException | ||
| 985 | { | ||
| 986 | // We need to reimplement this method because of a bug (bad logic) in the original writePrintln() which checks | ||
| 987 | // the last output char to decide whether to print a NL or not: | ||
| 988 | // ...3</a></b> --> ...3</a>\n</b> | ||
| 989 | // but | ||
| 990 | // ...3\n</a></b> --> ...3\n</a></b> | ||
| 991 | // and | ||
| 992 | // ...3\n</a>\n</b> --> ...3\n</a></b> | ||
| 993 | if (this.useFormat) { | ||
| 994 | println(); | ||
| 995 | // writer.write(getOutputFormat().getLineSeparator()); | ||
| 996 | } | ||
| 997 | } | ||
| 998 | } | ||
| 999 | // formats the XWiki XML including indentation | ||
| 1000 | def String format(String data, String defaultLanguage, boolean isTranslationDocument) throws Exception | ||
| 1001 | { | ||
| 1002 | def sr = new StringReader(data); | ||
| 1003 | SAXReader reader = new SAXReader(); | ||
| 1004 | Document domdoc = reader.read(sr); | ||
| 1005 | |||
| 1006 | Node rnode = domdoc.getRootElement(); | ||
| 1007 | if (rnode !=null) { | ||
| 1008 | Node node = rnode.element("author"); | ||
| 1009 | if (node != null) { | ||
| 1010 | node.setText(DEFAULTAUTHOR); | ||
| 1011 | } | ||
| 1012 | node = rnode.element("contentAuthor"); | ||
| 1013 | if (node != null) { | ||
| 1014 | node.setText(DEFAULTAUTHOR); | ||
| 1015 | } | ||
| 1016 | node = rnode.element("creator"); | ||
| 1017 | if (node != null) { | ||
| 1018 | node.setText(DEFAULTAUTHOR); | ||
| 1019 | } | ||
| 1020 | node = rnode.element("originalMetadataAuthor"); | ||
| 1021 | if (node != null) { | ||
| 1022 | node.setText(DEFAULTAUTHOR); | ||
| 1023 | } | ||
| 1024 | } | ||
| 1025 | |||
| 1026 | if (this.removeDates) { | ||
| 1027 | this.logger.debug("Removing dates from generated XML ..."); | ||
| 1028 | removeNodes("xwikidoc/creationDate", domdoc); | ||
| 1029 | removeNodes("xwikidoc/date", domdoc); | ||
| 1030 | removeNodes("xwikidoc/contentUpdateDate", domdoc); | ||
| 1031 | removeNodes("xwikidoc//attachment/date", domdoc); | ||
| 1032 | } | ||
| 1033 | |||
| 1034 | if (this.parentDefinitionStrategy.equals("remove")) { | ||
| 1035 | removeNodes("xwikidoc/parent", domdoc); | ||
| 1036 | } | ||
| 1037 | |||
| 1038 | if (this.defaultLanguageDefinitionStrategy.equals("alwaysBlank") | ||
| 1039 | || (!isTranslationDocument && this.defaultLanguageDefinitionStrategy.equals("blankIfNotTranslation"))) { | ||
| 1040 | Node node = rnode.selectSingleNode("//defaultLanguage/text()"); | ||
| 1041 | if (node != null) { | ||
| 1042 | node.detach(); | ||
| 1043 | } | ||
| 1044 | } | ||
| 1045 | |||
| 1046 | def baos = new ByteArrayOutputStream() | ||
| 1047 | XMLWriter w; | ||
| 1048 | OutputFormat format = new OutputFormat(" ", true, "UTF-8"); | ||
| 1049 | format.setExpandEmptyElements(false); | ||
| 1050 | w = new XWikiXMLWriter(baos, format); | ||
| 1051 | |||
| 1052 | w.write(domdoc); | ||
| 1053 | w.close(); | ||
| 1054 | def result = baos.toString(); | ||
| 1055 | |||
| 1056 | // Adding license header | ||
| 1057 | def xmlHeader = """<?xml version="${this.xmlVersion}" encoding="UTF-8"?>""" | ||
| 1058 | if (this.license != null && !this.license.isBlank() && xwiki.exists(this.license)) { | ||
| 1059 | String licenseContent = xwiki.getDocument(this.license).getContent(); | ||
| 1060 | result = result.replaceAll("(<.xml .*>)", xmlHeader + "\n\n" + licenseContent); | ||
| 1061 | } else { | ||
| 1062 | result = result.replaceAll("(<.xml .*>)", xmlHeader); | ||
| 1063 | } | ||
| 1064 | |||
| 1065 | return result.replaceAll("\r",""); | ||
| 1066 | } | ||
| 1067 | |||
| 1068 | /** | ||
| 1069 | * Remove the nodes found with the xpath expression. | ||
| 1070 | * Copied from FormatMojo in xwiki-commons-tool-xar-plugin | ||
| 1071 | * | ||
| 1072 | * @param xpathExpression the xpath expression of the nodes | ||
| 1073 | * @param domdoc The DOM document | ||
| 1074 | */ | ||
| 1075 | private void removeNodes(String xpathExpression, Document domdoc) | ||
| 1076 | { | ||
| 1077 | List<Node> nodes = domdoc.selectNodes(xpathExpression); | ||
| 1078 | for (Node node : nodes) { | ||
| 1079 | node.detach(); | ||
| 1080 | } | ||
| 1081 | } | ||
| 1082 | } |