Wiki-Quellcode von GitLabGroovy

Zuletzt geändert von René Vögeli am 2026/01/23 08:43

Verstecke letzte Bearbeiter
René Vögeli 1.1 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 }