Get Started

Version 7.1 by Veronika Kocher on Tuesday, 01 Sep 2026 13:10
Warning
For security reasons, the document is displayed in restricted mode as it is not the current version. There may be differences and errors due to this.

 GET STARTED — BILINGUAL REPAIR / PILOT
XWiki 18.x — Firefox Web Console

WHAT THIS DOES:
 1. Verifies that the current original Get Started page is exactly the German
   version found in the V3 export.
 2. Refuses to continue if de_AT already exists.
3. Creates de_AT using the current German content.
 4. Verifies the de_AT translation.
5. Restores the English original content and title.
 6. Verifies BOTH versions afterwards.

 It does NOT touch any other page.

 USAGE:
- Open https://xwiki.uni-ak.ac.at/bin/view/Service%20Navigator/Art%20%26%20Research%20Services%20Navigator/Enable/Get%20Started/
 - Firefox: Ctrl+Shift+K
- Paste this entire file and run it.

(async function () {
  "use strict";

  var WIKI = "xwiki";
  var SPACES = [
    "Service Navigator",
    "Art & Research Services Navigator",
    "Enable",
    "Get Started"
  ];
  var PAGE = "WebHome";
  var TARGET = "de_AT";

  var EXPECTED_CURRENT_DE_HASH = "7deec51cd0b625e3049cca99a5a69d0834db07fbc317f498f765f56cdc4463a9";
  var EXPECTED_RESTORED_EN_HASH = "86ff3e7227841109c46a8e1fbdc2dac7b5ff684d8e8e86da7dbc0c5341d5848b";

  var DE_TITLE = "Erste Schritte";
  var EN_TITLE = "Get Started";

  var DE_CONTENT = "Failed to execute the [html] macro. Cause: [When using HTML content inline, you can only use inline HTML content. Block HTML content (such as tables) cannot be displayed. Try leaving an empty line before and after the macro.]. Click on this message for details.
";
  var EN_CONTENT = "Failed to execute the [html] macro. Cause: [When using HTML content inline, you can only use inline HTML content. Block HTML content (such as tables) cannot be displayed. Try leaving an empty line before and after the macro.]. Click on this message for details.
";

  var origin = window.location.origin;
  var restWiki = origin + "/rest/wikis/" + encodeURIComponent(WIKI);

  function enc(value) {
    return encodeURIComponent(value);
  }

  function pageURL() {
    var parts = SPACES.map(function (space) {
      return "spaces/" + enc(space);
    });

    return restWiki + "/" + parts.join("/") + "/pages/" + enc(PAGE);
  }

  function translationURL(language) {
    return pageURL() + "/translations/" + enc(language);
  }

  function textNS(node, localName) {
    var list = node.getElementsByTagNameNS("*", localName);
    return list.length ? (list[0].textContent || "") : "";
  }

  function parsePage(xmlText) {
    var xml = new DOMParser().parseFromString(xmlText, "application/xml");
    var error = xml.getElementsByTagName("parsererror");

    if (error.length) {
      throw new Error("XWiki returned invalid XML.");
    }

    return {
      title: textNS(xml, "title"),
      content: textNS(xml, "content"),
      syntax: textNS(xml, "syntax"),
      language: textNS(xml, "language"),
      defaultLanguage: textNS(xml, "defaultLanguage"),
      version: textNS(xml, "version")
    };
  }

  async function sha256(text) {
    var bytes = new TextEncoder().encode(text);
    var digest = await crypto.subtle.digest("SHA-256", bytes);
    var array = new Uint8Array(digest);
    var out = "";

    for (var i = 0; i < array.length; i++) {
      out += array[i].toString(16).padStart(2, "0");
    }

    return out;
  }

  async function getPage(url) {
    var response = await fetch(url, {
      method: "GET",
      credentials: "same-origin",
      headers: {
        "Accept": "application/xml"
      },
      cache: "no-store"
    });

    return {
      ok: response.ok,
      status: response.status,
      text: await response.text()
    };
  }

  async function putForm(url, fields) {
    var body = new URLSearchParams();

    Object.keys(fields).forEach(function (key) {
      body.set(key, fields[key]);
    });

    var response = await fetch(url, {
      method: "PUT",
      credentials: "same-origin",
      headers: {
        "Accept": "application/xml",
        "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8"
      },
      body: body.toString()
    });

    return {
      ok: response.ok || response.status === 201 || response.status === 202 || response.status === 304,
      status: response.status,
      text: await response.text()
    };
  }

  function fail(message) {
    console.error("STOPPED:", message);
    throw new Error(message);
  }

  console.clear();
  console.log("=== GET STARTED BILINGUAL REPAIR PILOT ===");
  console.log("Only Get Started will be changed.");

  
  
PRE-FLIGHT
  

  var originalResponse = await getPage(pageURL());

  if (!originalResponse.ok) {
    fail("Could not read original page. HTTP " + originalResponse.status);
  }

  var original = parsePage(originalResponse.text);
  var currentHash = await sha256(original.content);

  console.log("Current original title:", original.title);
  console.log("Current original version:", original.version || "(not reported)");
  console.log("Current original language:", original.language || "(empty)");
  console.log("Current default language:", original.defaultLanguage || "(empty)");
  console.log("Current content SHA-256:", currentHash);

  if (currentHash !== EXPECTED_CURRENT_DE_HASH) {
    fail(
      "The current page has changed since the V3 export. " +
      "Expected German hash " + EXPECTED_CURRENT_DE_HASH +
      ", got " + currentHash +
      ". Nothing was changed."
    );
  }

  var existingDE = await getPage(translationURL(TARGET));

  if (existingDE.ok) {
    fail(
      "A de_AT translation already exists. " +
      "This pilot will not overwrite it. Nothing was changed."
    );
  }

  if (existingDE.status !== 404) {
    console.warn(
      "de_AT lookup returned HTTP " + existingDE.status +
      " rather than 404. Continuing only if you explicitly confirm."
    );
  }

  console.log("");
  console.log("PRE-FLIGHT OK");
  console.log("Planned operations:");
  console.log("1. Create de_AT title: Erste Schritte");
  console.log("2. Store the current German source in de_AT");
  console.log("3. Restore the English source as the original Get Started page");
  console.log("4. Verify both sources by SHA-256");

  var confirmed = window.confirm(
    "GET STARTED — bilingual repair pilot\n\n" +
    "This will ONLY change the Get Started page.\n\n" +
    "1. Save its current German content as de_AT\n" +
    "2. Restore the English original\n" +
    "3. Verify both copies\n\n" +
    "Continue?"
  );

  if (!confirmed) {
    console.warn("Cancelled. Nothing was changed.");
    return;
  }

  
  
STEP 1 — CREATE de_AT
  

  console.log("");
  console.log("STEP 1/4 — creating de_AT translation...");

  var createDE = await putForm(
    translationURL(TARGET) + "?minorRevision=true",
    {
      title: DE_TITLE,
      content: DE_CONTENT
    }
  );

  if (!createDE.ok) {
    fail("Creating de_AT failed. HTTP " + createDE.status);
  }

  console.log("de_AT PUT returned HTTP", createDE.status);

  
  
STEP 2 — VERIFY de_AT BEFORE TOUCHING ORIGINAL
  

  console.log("STEP 2/4 — verifying de_AT...");

  var verifyDEResponse = await getPage(translationURL(TARGET));

  if (!verifyDEResponse.ok) {
    fail(
      "de_AT was written but could not be read back. HTTP " +
      verifyDEResponse.status +
      ". English original was NOT changed."
    );
  }

  var verifyDE = parsePage(verifyDEResponse.text);
  var verifyDEHash = await sha256(verifyDE.content);

  if (verifyDEHash !== EXPECTED_CURRENT_DE_HASH) {
    fail(
      "de_AT verification failed. Expected hash " +
      EXPECTED_CURRENT_DE_HASH +
      ", got " + verifyDEHash +
      ". English original was NOT changed."
    );
  }

  console.log("de_AT verified:", verifyDE.title, verifyDEHash);

  
  
STEP 3 — RESTORE EN ORIGINAL
  

  console.log("STEP 3/4 — restoring English original...");

  var restoreEN = await putForm(
    pageURL() + "?minorRevision=true",
    {
      title: EN_TITLE,
      content: EN_CONTENT
    }
  );

  if (!restoreEN.ok) {
    fail(
      "de_AT is safely stored, but restoring English failed. HTTP " +
      restoreEN.status +
      ". The German translation is preserved."
    );
  }

  console.log("English original PUT returned HTTP", restoreEN.status);

  
  
STEP 4 — VERIFY BOTH
  

  console.log("STEP 4/4 — final verification...");

  var finalENResponse = await getPage(pageURL());
  var finalDEResponse = await getPage(translationURL(TARGET));

  if (!finalENResponse.ok || !finalDEResponse.ok) {
    fail(
      "Final verification GET failed. EN HTTP " +
      finalENResponse.status +
      ", de_AT HTTP " +
      finalDEResponse.status
    );
  }

  var finalEN = parsePage(finalENResponse.text);
  var finalDE = parsePage(finalDEResponse.text);

  var finalENHash = await sha256(finalEN.content);
  var finalDEHash = await sha256(finalDE.content);

  var enOK = finalENHash === EXPECTED_RESTORED_EN_HASH;
  var deOK = finalDEHash === EXPECTED_CURRENT_DE_HASH;

  console.table([
    {
      Version: "English original",
      Title: finalEN.title,
      Language: finalEN.language || finalEN.defaultLanguage || "(empty/default)",
      HashOK: enOK ? "YES" : "NO",
      SHA256: finalENHash
    },
    {
      Version: "de_AT translation",
      Title: finalDE.title,
      Language: finalDE.language || TARGET,
      HashOK: deOK ? "YES" : "NO",
      SHA256: finalDEHash
    }
  ]);

  if (!enOK || !deOK) {
    fail(
      "Final hashes do not match. EN=" + enOK + ", de_AT=" + deOK
    );
  }

  console.log("");
  console.log("=== PILOT COMPLETE ===");
  console.log("English original restored: YES");
  console.log("de_AT translation created: YES");
  console.log("Both contents verified byte-for-byte: YES");
  console.log("");
  console.log(
    "Now reload Get Started and test the language switch / Information tab."
  );
})().catch(function (error) {
  console.error("GET STARTED PILOT STOPPED:", error);
});