MediaWiki:Gadget-metadata.js

MediaWiki:Gadget-metadata.js

/** _____________________________________________________________________________

* |                                                                             |
* |                    === WARNING: GLOBAL GADGET FILE ===                      |
* |                  Changes to this page affect many users.                    |
* | Please discuss changes on the talk page or on WT:Gadget before editing. |
* |_____________________________________________________________________________|
*
* Imported from revision 185704269 as of January 20, 2008 18:40 from
* User:Pyrospirit/metadata.js, itself a modified version of
* User:Outriggr/metadata.js.
* Metadata assessment script
* Finds the WP 1.0/WikiProject assessment of every article you go to, then
* displays that information in the article header. See (new script homepage
* pending).
* @author Outriggr - created the script and used to maintain it
* @author Pyrospirit - used to maintain and update the script
* @author Nihiltres - Overhauled the script, current maintainer
*/

window.assessment = (function () {

   var assessmentObj = {
       props: {},
       methods: {}
   },
       //internal shortcuts
       ap = assessmentObj.props,
       am = assessmentObj.methods;
   /**
    * The main function of the script. If the checkArticle() function can find
    * the assessment, it parses and displays that assessment for the page.
    * Otherwise, it tries to retrieve an assessment via AJAX.
    */
   assessmentObj.init = function () {
       if (!$("#siteSub").length || //incompatible skin
               mw.config.get("wgNamespaceNumber") !== 0 || //non-mainspace page
               (mw.config.get("wgAction") !== "view" && mw.config.get("wgAction") !== "purge") || //non-read action
               mw.util.getParamValue("printable") || //printable page
               mw.config.get("wgArticleId") === 0 || //nonexistent page
               mw.config.get("wgIsMainPage") === true //Main Page
               ) {
           return; //Don't run the script under any of these conditions.
       }
       ap.foundAssessment = am.checkArticle(); //checks for types visible from article page
       if (!ap.foundAssessment.exists) { // no type visible on article, proceed to check the talk page
           $.ajax({
               url: mw.config.get("wgScript") + "?title=Talk:" + mw.util.wikiUrlencode(mw.config.get("wgPageName")) + "&action=raw&section=0",
               async: true,
               dataType: "text",
               success: function (responseText) {
                   ap.text = responseText;
                   ap.foundAssessment = am.getAssessment(ap.text);
                   ap.updata = am.renderAssessment(ap.foundAssessment);
                   am.update();
               }
           });
       } else {
           ap.updata = am.renderAssessment(ap.foundAssessment);
           am.update();
       }
   };
   /**
    * Checks for various objects on the article page that indicate a certain
    * assessment, such as a disambiguation page notice. If this function can
    * find the assessment, AJAX is not needed for this page.
    * @returns {Object} checkResult - the assessment found
    */
   am.checkArticle = function () {
       var checkResult = {
           extra: [],
           exists: false
       }, checksList = [
           [$("#disambig, #disambig_disambigbox, #disambigbox").length, "dab"],
           [$("#setindexbox").length, "setindex"],
           [$("#contentSub").text() === "Redirect page", "redir"],
           /* [$("table.stub").length, "stub"], */
           [$("#ca-talk").hasClass("new"), "none"] //no talk page
       ];
       $.each(checksList, function (i, e) {
           if (e[0]) {
               checkResult.rating = e[1];
               checkResult.exists = true;
               return false;
           }
       });
       return checkResult;
   };
   /**
    * Searches the provided wikicode for the rating part of an assessment and
    * returns it as a string.
    * Note that higher assessments take priority, and less-used assessments
    * such as "list", "current", or "future" are used only if nothing else can
    * be found.
    * @param {String} text - some wikitext to be searched for assessment info
    */
   am.getRating = function (text) {
       var rating = "none",
           standardChecks = [
               [/\|\s*(class|currentstatus)\s*=\s*fa\b/i, "fa"],
               [/\|\s*(class|currentstatus)\s*=\s*fl\b/i, "fl"],
               [/\|\s*class\s*=\s*a\b/i, "a"],
               [/\|\s*class\s*=\s*b\b/i, "b"],
               [/\|\s*class\s*=\s*bplus\b/i, "bplus"], //used by WP Math
               [/\|\s*class\s*=\s*c\b/i, "c"],
               [/\|\s*class\s*=\s*start/i, "start"],
               [/\|\s*class\s*=\s*stub/i, "stub"],
               [/\|\s*class\s*=\s*al\b/i, "al"], // used by WP Military history & WP Highways
               [/\|\s*class\s*=\s*bl\b/i, "bl"], // used by WP Military history
               [/\|\s*class\s*=\s*cl\b/i, "cl"], // used by WP Military history
               [/\|\s*class\s*=\s*list/i, "list"],
               [/\|\s*class\s*=\s*sl\b/i, "sl"], // used by WP Plants
               [/\|\s*class\s*=\s*(dab|disambig)/i, "dab"],
               [/\|\s*class\s*=\s*cur(rent)?/i, "cur"],
               [/\|\s*class\s*=\s*future/i, "future"]
           ];
       //evaluate the standard checks
       $.each(standardChecks, function (i, e) {
           if (text.match(e[0])) {
               rating = e[1];
               return false;
           }
       });
       //and then the nonstandard ones. These override earlier ratings if applicable.
       if (rating === "a" && text.match(/\|\s*class\s*=\s*ga\b|\|\s*currentstatus\s*=\s*(ffa\/)?ga\b/i)) {
           rating = "a/ga"; // A-class articles that are also GA's
       } else if (text.match(/\|\s*class\s*=\s*ga\b|\|\s*currentstatus\s*=\s*(ffa\/)?ga\b|\{\{\s*ga\s*\|/i) &&
               !text.match(/\|\s*currentstatus\s*=\s*dga\b/i)) {
           rating = "ga";
       }
       return rating;
   };
   /**
    * Searches the provided wikicode for data on the article's current and past
    * featured or good status and returns an object that contains this data
    * along with some miscellaneous other bits of information.
    * @param {String} text - some wikitext to be searched for assessment info
    * @return {Object} gottenAssessment - the assessment data for the page
    */
   am.getAssessment = function (text) {
       var gottenAssessment = {
           rating: am.getRating(text),
           pageLink: [null, null],
           extra: [],
           activeReview: null,
           exists: true
       },
           actionNumber = 0,
           pageLinkFlag = false,
           peerReview, linkPattern, linkMatch, currentList, formerList;
       currentList = [
           // Current nominations (FAC, FLC, or GAN)
           {
               reg: /\{\{\s*featured[ _]article[ _]candidates\s*(?:[\|\}]\s*([^\|\}]*))?[^\}]*?\}\}/i,
               extraName: "fac",
               addArticleNameTo: "Wikipedia:Featured_article_candidates\/"
           },
           {
               reg: /\{\{\s*featured[ _]list[ _]candidates\s*(?:[\|\}]\s*([^\|\}]*))?[^\}]*?\}\}/i,
               extraName: "flc",
               addArticleNameTo: "Wikipedia:Featured_list_candidates\/"
           },
           {
               reg: /\{\{\s*ga ?nominee\s*[\|\}][^\}]*\}\}/i,
               extraName: "gan",
               addMatchReg: /\|\s*page\s*=\s*(\d+).*\|\s*status\s*=\s*\w+\b/i,
               addMatchTo: "Talk:" + mw.config.get("wgPageName") + "\/GA"
           },
           // Current reviews of a status (FAR, FLRC, or GAR)
           {
               reg: /\{\{\s*featured[ _]article[ _]review\s*(?:[\|\}]\s*([^\|\}]*))?[^\}]*?\}\}/i,
               extraName: "far",
               addArticleNameTo: "Wikipedia:Featured_article_review\/"
           },
           {
               reg: /\{\{\s*featured[ _]list[ _]removal[ _]candidates\s*(?:[\|\}]\s*([^\|\}]*))?[^\}]*?\}\}/i,
               extraName: "flrc",
               addArticleNameTo: "Wikipedia:Featured_list_removal_candidates\/"
           },
           {
               reg: /\{\{\s*gar\/link\s*[\|\}][^\}]*\}\}/i,
               extraName: "gar",
               addMatchReg: /\|\s*GARpage\s*=\s*(\d+).*\|/i,
               addMatchTo: mw.config.get("wgPageName")
           }
       ];
       $.each(currentList, function (i, e) {
           var reg = text.match(e.reg),
               articleName,
               tempMatch;
           if (reg) {
               gottenAssessment.extra.push(e.extraName);
               if (e.hasOwnProperty("addArticleNameTo") && reg[1]) {
                   articleName = am.decodeEntities($.trim(reg[1]));
                   if (articleName) {
                       gottenAssessment.pageLink[0] = e.addArticleNameTo + articleName;
                   }
               }
               if (e.hasOwnProperty("addMatchReg")) {
                   tempMatch = reg[0].match(e.addMatchReg);
                   if (tempMatch) {
                       gottenAssessment.pageLink[0] = (e.addMatchTo || "") + (tempMatch[1] || "");
                   }
                   if (e.extraName === "gar") { //Can't get around this special case easily
                       gottenAssessment.pageLink[0] = am.getGARLink(e.addMatchTo, tempMatch[1]);
                   }
               }
               return false;
           }
       });
       formerList = [
           // Former statuses (FFA, FFL, or DGA)
           {
               name: "ffa",
               reg: /\|\s*currentstatus\s*=\s*ffa\b/i,
               getActionNumber: true,
               getActionNumberReg: /\|\s*action(\d+)\s*=\s*far\b/gi
           },
           {
               name: "ffa",
               reg: /\|\s*action(\d+)\s*=\s*far\b/gi,
               extraCondition: function (ec_reg) {
               //Checks if the last FAR entry in ArticleHistory resulted in removal.
                   var match, ratingSearch;
                   if (!ec_reg) {
                       return false;
                   }
                   match = text.match(new RegExp(
                       "\\|\\s*action" + ec_reg[ec_reg.length - 1].match(/\d+/) + "result\\s*=\\s*removed\\b",
                       "i"
                   ));
                   ratingSearch = (gottenAssessment.rating.search(/f[al]/i) === -1);
                   return (match && ratingSearch);
               },
               getActionNumber: true
           },
           {
               name: "ffa",
               reg: /\{\{\s*formerfa2?\b/i
           },
           {
               name: "ffl",
               reg: /\|\s*currentstatus\s*=\s*ffl\b/i
           },
           {
               name: "ffl",
               reg: /\{\{\s*ffl\s*[\|\}]/i
           },
           {
               name: "dga",
               reg: /\|\s*currentstatus\s*=\s*dga\b/i,
               getActionNumber: true,
               getActionNumberReg: /\|\s*action(\d+)\s*=\s*gar\b/gi
           },
           {
               name: "dga",
               reg: /\{\{\s*d(elisted)?ga\s*[\|\}]/i
           },
           // Former nominations (former FAC, FLC, or GAN)
           {
               name: "ffac",
               reg: /\|\s*action(\d+)\s*=\s*fac\b/gi,
               extraCondition: function () {
                   return (gottenAssessment.rating.search(/f[al]/i) === -1);
               },
               getActionNumber: true
           },
           {
               name: "ffac",
               reg: /\|\s*currentstatus\s*=\s*ffac\b/i
           },
           {
               name: "ffac",
               reg: /\{\{\s*fac?(failed|(\-|[ _]\()?contested\)?)\s*[\|\}]/i
           },
           {
               name: "fflc",
               reg: /\|\s*action(\d+)\s*=\s*flc\b/gi,
               extraCondition: function () {
                   return (gottenAssessment.rating.search(/f[al]/i) === -1);
               },
               getActionNumber: true
           },
           {
               name: "fflc",
               reg: /\|\s*currentstatus\s*=\s*fflc\b/i
           },
           {
               name: "fgan",
               reg: /\|\s*action(\d+)\s*=\s*gan\b/gi,
               extraCondition: function () {
                   return (gottenAssessment.rating.search(/f[al]|(a\/)?ga/i) === -1);
               },
               getActionNumber: true
           },
           {
               name: "fgan",
               reg: /\|\s*currentstatus\s*=\s*fgan\b/i
           },
           {
               name: "fgan",
               reg: /\{\{\s*f(ailed ?)?ga\s*[\|\}]/i
           }
       ];
       $.each(formerList, function (i, e) {
           var reg = text.match(e.reg),
               extraCondition = !e.hasOwnProperty("extraCondition") ||
                   typeof e.extraCondition !== "function" ||
                   e.extraCondition(reg), //either true (ignored) or result of function
               tempMatch;
           if (reg && extraCondition) {
               gottenAssessment.extra.push(e.name);
               if (e.getActionNumber) {
                   tempMatch = (e.getActionNumberReg ? text.match(e.getActionNumberReg) : reg);
                   actionNumber = tempMatch[tempMatch.length - 1].match(/\d+/);
                   pageLinkFlag = true;
               }
               return false;
           }
       });
       // Looks for currently active peer reviews
       ap.showOldPeerReviews = false; //see TODO below
       peerReview = text.match(/\{\{\s*peer[_ ]?review\s*\|\s*archive\s*=\s*(\d+)\b/i);
       if (peerReview) {
           gottenAssessment.review = "Wikipedia:Peer_review/" +
               mw.config.get("wgPageName") + "/archive" + peerReview[1];
           gottenAssessment.activeReview = true;
       } else if (ap.showOldPeerReviews) {
           $.noop(); // TODO: Add code for old peer reviews
       } else {
           gottenAssessment.review = null;
       }
       // Scans for the link associated with an action in ArticleHistory
       if (pageLinkFlag) {
           linkPattern = new RegExp("\\|\\s*action" + actionNumber + "link\\s*=\\s*([^\\n\\|]+)\\s*\\|");
           linkMatch = text.match(linkPattern);
           gottenAssessment.pageLink[1] = linkMatch ? am.decodeEntities(linkMatch[1]) : null;
       }
       return gottenAssessment;
   };
   /**
    * Parses an assessment object into the HTML and CSS code needed to update
    * the article header. If it doesn't recognize a part of the information
    * given, it will simply ignore it and mark as unassessed.
    * @param {Object} assess - assessment information for this article
    * @return {String} newClass - the CSS class corresponding to its assessment
    * @return {String} slogan - HTML giving (with a link) the main assessment
    * @return {String} info - HTML giving (with a link) additional information
    */
   am.renderAssessment = function (assess) {
       var assessLink = mw.util.getUrl("Wikipedia:Content_assessment"),
           peerReviewText = am.addPeerReview(assess.review, assess.activeReview),
           pageLink = assess.pageLink || [null, null],
           info = am.getExtraInfo((assess.extra || []), pageLink),
           newClass,
           slogan,
           ratingList;
       if (peerReviewText) {
           info.push(peerReviewText);
       }
       ratingList = [
           {
               name: "a",
               className: "assess-a-text",
               text: "An <a>A-class<\/a> article"
           },
           {
               name: "a/ga",
               className: "assess-a-text",
               text: "An <a>A-class<\/a> article",
               info: "Also a <a href=\"" + mw.util.getUrl("Wikipedia:Good_articles") + "\">good article<\/a>."
           },
           {
               name: "ga",
               className: "assess-ga-text",
               text: "A <a>good article<\/a>",
               url: mw.util.getUrl("Wikipedia:Good_articles")
           },
           {
               name: "b",
               className: "assess-b-text",
               text: "A <a>B-class<\/a> article"
           },
           {
               name: "bplus",
               className: "assess-bplus-text",
               text: "A <a>B-plus-class<\/a> article",
               url: mw.util.getUrl("Wikipedia:WikiProject_Mathematics/Wikipedia_1.0/Grading_scheme")
           },
           {
               name: "c",
               className: "assess-c-text",
               text: "A <a>C-class<\/a> article"
           },
           {
               name: "start",
               className: "assess-start-text",
               text: "A <a>start-class<\/a> article"
           },
           {
               name: "stub",
               className: "assess-stub-text",
               text: "A <a>stub-class<\/a> article"
           },
           {
           	name: "al",
           	className: "assess-al-text",
           	text: "An <a>A-class<\/a> list",
           	url: mw.util.getUrl("Wikipedia:WikiProject_Military_history/Assessment") + "#SCALE" //Could use a more general link if one is available
           },
           {
           	name: "bl",
           	className: "assess-bl-text",
           	text: "A <a>B-class<\/a> list",
           	url: mw.util.getUrl("Wikipedia:WikiProject_Military_history/Assessment") + "#SCALE"
           },
           {
           	name: "cl",
           	className: "assess-cl-text",
           	text: "A <a>C-class<\/a> list",
           	url: mw.util.getUrl("Wikipedia:WikiProject_Military_history/Assessment") + "#SCALE"
           },
           {
               name: "sl",
               className: "assess-sl-text",
               text: "A <a>stub-class<\/a> list"
           },
           {
               name: "list",
               className: "assess-list-text",
               text: "A <a>list-class<\/a> article",
               url: mw.util.getUrl("Wikipedia:Stand-alone lists")
           },
           {
               name: "dab",
               className: "assess-dab-text",
               text: "A <a>disambiguation page<\/a>",
               url: mw.util.getUrl("Wikipedia:Disambiguation")
           },
           {
               name: "setindex",
               className: "assess-setindex-text",
               text: "A <a>set index article<\/a>",
               url: mw.util.getUrl("Wikipedia:Set_index_articles")
           },
           {
               name: "redir",
               className: "assess-redir-text",
               text: "A <a>redirect page<\/a>",
               url: mw.util.getUrl("Help:Redirect")
           },
           {
               name: "fl",
               className: "assess-fl-text",
               text: "A <a>featured list<\/a>",
               url: mw.util.getUrl("Wikipedia:Featured_lists")
           },
           {
               name: "fa",
               className: "assess-fa-text",
               text: "A <a>featured article<\/a>",
               url: mw.util.getUrl("Wikipedia:Featured_articles")
           },
           {
               name: "cur",
               className: "assess-cur-text",
               text: "A <a>current-class<\/a> article",
               url: mw.util.getUrl("Portal:Current_events")
           },
           {
               name: "future",
               className: "assess-future-text",
               text: "A <a>future-class<\/a> article",
               url: mw.util.getUrl("Category:Future-Class_articles")
           }
       ];
       $.each(ratingList, function (i, e) {
           if (assess.rating === e.name) {
               newClass = e.className;
               slogan = $("").doc(e.text).children().attr({href: (e.url || assessLink)}).parent().doc();
               if (e.info) {
                   info.push(e.info);
               }
               return false;
           }
       });
       if (!newClass) {
           newClass = "assess-unassessed-text";
           slogan = "An <a href=\"" + assessLink + "\">unassessed<\/a> article";
       }
       return {newClass: newClass, slogan: slogan, info: info};
   };
   /**
    * Creates an info string based on the assessment info and a page link.
    */
   am.getExtraInfo = function (extra, pageLink) {
       var info = [], page = mw.config.get("wgPageName"), typeList;
       typeList = [
           // Current nominations and reviews
           {
               name: "fac",
               doc: "Currently a <a>featured article candidate<\/a>.",
               url: pageLink[0] || ("Wikipedia:Featured_article_candidates/" + page)
           },
           {
               name: "flc",
               doc: "Currently a <a>featured list candidate<\/a>.",
               url: pageLink[0] || ("Wikipedia:Featured_list_candidates/" + page)
           },
           {
               name: "gan",
               doc: "Currently a <a>good article nominee<\/a>.",
               url: pageLink[0] || "Wikipedia:Good_article_nominations"
           },
           {
               name: "far",
               doc: "Currently undergoing <a>review<\/a> of its featured status.",
               url: pageLink[0] || ("Wikipedia:Featured_article_review/" + page)
           },
           {
               name: "flrc",
               doc: "Currently a <a>candidate<\/a> for removal as a featured list.",
               url: pageLink[0] || ("Wikipedia:Featured_list_removal_candidates/" + page)
           },
           {
               name: "gar",
               doc: "Currently undergoing a <a>good article reassessment<\/a>.",
               url: pageLink[0] || "Wikipedia:Good_article_reassessment",
               wrapper: "<\/span>"
           },
           // Past statuses and nominations
           {
               name: "ffa",
               doc: "A <a>former<\/a> featured article.",
               url: pageLink[1] || ("Wikipedia:Featured_article_review/" + page)
           },
           {
               name: "ffl",
               doc: "A <a>former<\/a> featured list.",
               url: pageLink[1] || ("Wikipedia:Featured_list_removal_candidates/" + page)
           },
           {
               name: "dga",
               doc: "A <a>delisted<\/a> good article.",
               url: pageLink[1] || "Wikipedia:Good_article_reassessment"
           },
           {
               name: "ffac",
               doc: "A former <a>featured article candidate<\/a>.",
               url: pageLink[1] || ("Wikipedia:Featured_article_candidates/" + page)
           },
           {
               name: "fflc",
               doc: "A former <a>featured list candidate<\/a>.",
               url: pageLink[1] || ("Wikipedia:Featured_list_candidates/" + page)
           },
           {
               name: "fgan",
               doc: "A former <a>good article nominee<\/a>.",
               url: pageLink[1] || "Wikipedia:Good_article_nominations"
           }
       ];
       $.each(typeList, function (i, e) {
           if (extra.indexOf(e.name) !== -1) {
               info.push($("").doc(e.doc).children().attr({href: mw.util.getUrl(e.url)})
                   .parent().wrapInner(e.wrapper || null).doc());
           }
       });
       return info;
   };
   /**
    * Get the correct link for Good Article reassessments. These things require an
    * additional AJAX request to determine whether it's a community or individual
    * reassessment. The trick is to assume it's a community reassessment, then
    * switch the link once the request returns if it's actually not.
    * @param {String} articleName - the name of the article to use
    * @param {String} reviewNumber - the number of the GAR to look for
    */
   am.getGARLink = function (articleName, reviewNumber) {
       var communityTitle = "Wikipedia:Good_article_reassessment\/" + articleName + "\/" + reviewNumber,
           individualTitle = "Talk:" + articleName + "\/GA" + reviewNumber,
           titlesList = [communityTitle, individualTitle],
           api = new mw.Api();
       api.get({
           action: "query",
           titles: titlesList.join("|"),
           prop: "info",
           format: "json"
       }).done(function (data) {
           var i, j, noCommunityAssessment,
               query = data.query,
               communityTitleNorm = titlesList[0],
               individualTitleNorm = titlesList[1],
               len = query.normalized.length,
               garLink = $("#assess-gar-link a:first");
           for (j = 0; j < len; j++) {
               switch (query.normalized[j].from) {
               case titlesList[0]:
                   communityTitleNorm = query.normalized[j].to;
                   break;
               case titlesList[1]:
                   individualTitleNorm = query.normalized[j].to;
                   break;
               }
           }
           noCommunityAssessment = false;
           for (i = -1; i >= -2; i--) {
               if (query.pages[i] && typeof query.pages[i].missing === "string") {
                   if (query.pages[i].title === individualTitleNorm) {
                       // No individual assessment, no need to change anything.
                       return;
                   }
                   if (query.pages[i].title === communityTitleNorm) {
                       // There's no community assessment, so flag it.
                       noCommunityAssessment = true;
                   }
               }
           }
           if (noCommunityAssessment && garLink.length) {
               // There's an individual assessment but no community assessment. Switch the link.
               garLink.attr("href", mw.util.getUrl(titlesList[1]));
           }
       });
       return communityTitle;
   };
   /**
    * Creates the peer review text from an info string, if a peer review was detected earlier.
    */
   am.addPeerReview = function (peerReview, activeReview) {
       var reviewText = null;
       if (peerReview) {

reviewText = $("

" +
               (activeReview ? "Currently being" : "Previously") + " <a>peer reviewed<\/a>.<\/span><\/div>");
           reviewText.find("a").attr({href: mw.util.getUrl(peerReview)});
           reviewText = reviewText.doc(); //Note div wrapper above.
       }
       return reviewText;
   };
   /**
    * Updates article header with new assessment information by giving it a new
    * class (for style information such as color) and altering the tagline below
    * it to state the assessment found.
    */
   am.update = function () {
       var info = ap.updata.info,
           infoSpan = $("<\/span>"),
siteSub = $("
<\/span> from Wikipedia, the free encyclopedia<\/div>");
       siteSub.children().doc(ap.updata.slogan);
       if (info && info.length > 0) {
           infoSpan.doc(".");
           $.each(info, function (i, e) {
               infoSpan.append(" ").append(e);
           });
           siteSub.append(infoSpan);
       }
       $("h1:first").addClass(ap.updata.newClass || null);
       $("#siteSub").doc(siteSub.doc());
   };
   /**
    * Decodes all HTML entities in the string provided.
    */
   am.decodeEntities = function (str) {
       var t = document.createElement("textarea");
       t.innerHTML = str;
       return t.value;
   };
   return assessmentObj;

}());

/**

* Initializes the script on page load
*/

$(assessment.init);


Share this article:

This article uses material from the Wikipedia article MediaWiki:Gadget-metadata.js, and is written by contributors. Text is available under a CC BY-SA 4.0 International License; additional terms may apply. Images, videos and audio are available under their respective licenses.