From a5563bb4587692a2d875a523d2782304423f3989 Mon Sep 17 00:00:00 2001
From: Starbeamrainbowlabs <sbrl@starbeamrainbowlabs.com>
Date: Sun, 10 Feb 2019 23:01:01 +0000
Subject: [PATCH] Add title to internal links, and refactor page cache id
 generation

---
 Changelog.md                   |  1 +
 build/index.php                | 96 +++++++++++++++++++++++++++-------
 core.php                       | 22 +++++---
 module_index.json              | 12 ++---
 modules/parser-default-old.php | 13 +++--
 modules/parser-parsedown.php   | 68 ++++++++++++++++++++----
 6 files changed, 166 insertions(+), 46 deletions(-)

diff --git a/Changelog.md b/Changelog.md
index cc332a2..1d4e8a1 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -19,6 +19,7 @@ This file holds the changelog for Pepperminty Wiki. This is the master list of t
      - Useful for longer pages
      - `parser_cache_min_size` may need tuning for your specific installation (lower it if you regularly use features that are slow to parse; raise if it's the opposite)
      - You may need to turn this off if it interferes with complex dynamic includes
+ - Internal links now show the page name on hover (inter-wiki links are also supported here)
 
 ### Changed
  - Completely reworked the README to refactor out the documentation to its [own static site](https://starbeamrainbowlabs.com/labs/peppermint/_docpress/)
diff --git a/build/index.php b/build/index.php
index a22e8b7..8bd100a 100644
--- a/build/index.php
+++ b/build/index.php
@@ -413,7 +413,7 @@ if($settings->sessionprefix == "auto")
 /////////////////////////////////////////////////////////////////////////////
 /** The version of Pepperminty Wiki currently running. */
 $version = "v0.18-dev";
-$commit = "d51c3f163fbaeed63e8b0109e684a810d2a4604a";
+$commit = "1938bfd5b985a4a21872010d963553d10cab25e1";
 /// Environment ///
 /** Holds information about the current request environment. */
 $env = new stdClass();
@@ -2123,9 +2123,14 @@ function has_action($action_name)
 }
 
 $parsers = [
-	"none" => function() {
-		throw new Exception("No parser registered!");
-	}
+	"none" => [
+		"parser" => function() {
+			throw new Exception("No parser registered!");
+		},
+		"hash_generator" => function() {
+			throw new Exception("No parser registered!");
+		}
+	]
 ];
 /**
  * Registers a new parser.
@@ -2133,13 +2138,16 @@ $parsers = [
  * @param string	$name       	The name of the new parser to register.
  * @param function	$parser_code	The function to register as a new parser.
  */
-function add_parser($name, $parser_code)
+function add_parser($name, $parser_code, $hash_generator)
 {
 	global $parsers;
 	if(isset($parsers[$name]))
 		throw new Exception("Can't register parser with name '$name' because a parser with that name already exists.");
 
-	$parsers[$name] = $parser_code;
+	$parsers[$name] = [
+		"parser" => $parser_code,
+		"hash_generator" => $hash_generator
+	];
 }
 /**
  * Parses the specified page source using the parser specified in the settings
@@ -2164,7 +2172,7 @@ function parse_page_source($source, $use_cache = true) {
 		$source = htmlentities($source, ENT_QUOTES | ENT_HTML5);
 */
 	
-	$cache_id = str_replace(["+","/"], ["-","_"], base64_encode(hash("sha256", "$version|$settings->parser|$source", true)));
+	$cache_id = $parsers[$settings->parser]["hash_generator"]($source);
 	$cache_file = "{$paths->cache_directory}/{$cache_id}.html";
 	
 	$result = null;
@@ -2173,7 +2181,7 @@ function parse_page_source($source, $use_cache = true) {
 		$result .= "\n<!-- cache: hit, id: $cache_id, took: " . round((microtime(true) - $start_time)*1000, 5) . "ms -->\n";
 	}
 	if($result == null) {
-		$result = $parsers[$settings->parser]($source);
+		$result = $parsers[$settings->parser]["parser"]($source);
 		// If we should use the cache and we failed to write to it, warn the admin.
 		// It's not terribible if we can't write to the cache directory (so we shouldn't stop dead & refuse service), but it's still of concern.
 		if($use_cache && !file_put_contents($cache_file, $result))
@@ -3723,7 +3731,7 @@ function is_interwiki_link($pagename) {
 
 register_module([
 	"name" => "Recent Changes",
-	"version" => "0.3.5",
+	"version" => "0.4",
 	"author" => "Starbeamrainbowlabs",
 	"description" => "Adds recent changes. Access through the 'recent-changes' action.",
 	"id" => "feature-recent-changes",
@@ -4173,7 +4181,7 @@ function render_recent_change_atom($recent_changes) {
 		
 		$xml->startElement("author");
 		$xml->writeElement("name", $recent_change->user);
-		$xml->writeElement("uri", "$full_url_stem?page=".rawurlencode("$settings->user_page_prefix/$recent_change->page"));
+		$xml->writeElement("uri", "$full_url_stem?page=".rawurlencode("$settings->user_page_prefix/$recent_change->user"));
 		$xml->endElement();
 		
 		$xml->endElement();
@@ -9022,7 +9030,7 @@ register_module([
 
 register_module([
 	"name" => "Parsedown",
-	"version" => "0.9.12",
+	"version" => "0.9.13",
 	"author" => "Emanuil Rusev & Starbeamrainbowlabs",
 	"description" => "An upgraded (now default!) parser based on Emanuil Rusev's Parsedown Extra PHP library (https://github.com/erusev/parsedown-extra), which is licensed MIT. Please be careful, as this module adds some weight to your installation, and also *requires* write access to the disk on first load.",
 	"id" => "parser-parsedown",
@@ -9037,6 +9045,28 @@ register_module([
 			$result = $parser->text($source);
 			
 			return $result;
+		}, function($source) {
+			global $version, $settings, $pageindex;
+			$id_text = "$version|$settings->parser|$source";
+			
+			// Find template includes
+			preg_match_all(
+				'/\{\{\s*([^|]+)\s*(?:\|[^}]*)?\}\}/',
+				$source, $includes
+			);
+			
+			foreach($includes[1] as $include_pagename) {
+				if(empty($pageindex->$include_pagename))
+				$id_text .= "|$include_pagename:" . parsedown_pagename_resolve(
+					$pageindex->$include_pagename->lastmodified
+				);
+			}
+			
+			return str_replace(["+","/"], ["-","_"], base64_encode(hash(
+				"sha256",
+				$id_text,
+				true
+			)));
 		});
 		
 		/*
@@ -9224,6 +9254,28 @@ if(!file_exists("./ParsedownExtra.php") || filesize("./ParsedownExtra.php") ===
 require_once("./Parsedown.php");
 require_once("./ParsedownExtra.php");
 
+/**
+ * Attempts to 'auto-correct' a page name by trying different capitalisation
+ * combinations.
+ * @param	string	$pagename	The page name to auto-correct.
+ * @return	string	The auto-corrected page name.
+ */
+function parsedown_pagename_resolve($pagename) {
+	global $pageindex;
+	
+	// If the page doesn't exist, check varying different
+	// capitalisations to see if it exists under some variant.
+	if(!empty($pageindex->$pagename))
+		return $pagename;
+	
+	$pagename = ucfirst($pagename);
+	if(!empty($pageindex->$pagename))
+		return $pagename;
+	
+	$pagename = ucwords($pagename);
+	return $pagename;
+}
+
 /*
  * ██████   █████  ██████  ███████ ███████ ██████   ██████  ██     ██ ███    ██
  * ██   ██ ██   ██ ██   ██ ██      ██      ██   ██ ██    ██ ██     ██ ████   ██
@@ -9501,14 +9553,10 @@ class PeppermintParsedown extends ParsedownExtra
 			// 3: Page name auto-correction
 			// -------------------------------
 			$is_interwiki_link = module_exists("feature-interwiki-links") && is_interwiki_link($link_page);
-			if(!$is_interwiki_link && empty($pageindex->$link_page)) {
-				// If the page doesn't exist, check varying different
-				// capitalisations to see if it exists under some variant.
-				if(!empty($pageindex->{ucfirst($link_page)}))
-					$link_page = ucfirst($link_page);
-				else if(!empty($pageindex->{ucwords($link_page)}))
-					$link_page = ucwords($link_page);
-			}
+			// Try different variants on the pagename to try and get it to 
+			// match something automagically
+			if(!$is_interwiki_link && empty($pageindex->$link_page))
+				$link_page = parsedown_pagename_resolve($link_page);
 			
 			
 			// 4: Construct the full url
@@ -9529,6 +9577,12 @@ class PeppermintParsedown extends ParsedownExtra
 				$is_interwiki_link = false;
 			}
 			
+			// 5: Construct the title
+			// -------------------------------
+			$title = $link_page;
+			if($is_interwiki_link)
+				$title = interwiki_pagename_resolve($link_page)->name . ": " . interwiki_pagename_parse($link_page)[1] . " (Interwiki)";
+			
 			if(strlen($hash_code) > 0)
 				$link_url .= "#$hash_code";
 			
@@ -9540,8 +9594,10 @@ class PeppermintParsedown extends ParsedownExtra
 				"element" => [
 					"name" => "a",
 					"text" => $display,
+					
 					"attributes" => [
-						"href" => $link_url
+						"href" => $link_url,
+						"title" => $title
 					]
 				]
 			];
diff --git a/core.php b/core.php
index f9cd5b3..ad41ede 100644
--- a/core.php
+++ b/core.php
@@ -1720,9 +1720,14 @@ function has_action($action_name)
 }
 
 $parsers = [
-	"none" => function() {
-		throw new Exception("No parser registered!");
-	}
+	"none" => [
+		"parser" => function() {
+			throw new Exception("No parser registered!");
+		},
+		"hash_generator" => function() {
+			throw new Exception("No parser registered!");
+		}
+	]
 ];
 /**
  * Registers a new parser.
@@ -1730,13 +1735,16 @@ $parsers = [
  * @param string	$name       	The name of the new parser to register.
  * @param function	$parser_code	The function to register as a new parser.
  */
-function add_parser($name, $parser_code)
+function add_parser($name, $parser_code, $hash_generator)
 {
 	global $parsers;
 	if(isset($parsers[$name]))
 		throw new Exception("Can't register parser with name '$name' because a parser with that name already exists.");
 
-	$parsers[$name] = $parser_code;
+	$parsers[$name] = [
+		"parser" => $parser_code,
+		"hash_generator" => $hash_generator
+	];
 }
 /**
  * Parses the specified page source using the parser specified in the settings
@@ -1761,7 +1769,7 @@ function parse_page_source($source, $use_cache = true) {
 		$source = htmlentities($source, ENT_QUOTES | ENT_HTML5);
 */
 	
-	$cache_id = str_replace(["+","/"], ["-","_"], base64_encode(hash("sha256", "$version|$settings->parser|$source", true)));
+	$cache_id = $parsers[$settings->parser]["hash_generator"]($source);
 	$cache_file = "{$paths->cache_directory}/{$cache_id}.html";
 	
 	$result = null;
@@ -1770,7 +1778,7 @@ function parse_page_source($source, $use_cache = true) {
 		$result .= "\n<!-- cache: hit, id: $cache_id, took: " . round((microtime(true) - $start_time)*1000, 5) . "ms -->\n";
 	}
 	if($result == null) {
-		$result = $parsers[$settings->parser]($source);
+		$result = $parsers[$settings->parser]["parser"]($source);
 		// If we should use the cache and we failed to write to it, warn the admin.
 		// It's not terribible if we can't write to the cache directory (so we shouldn't stop dead & refuse service), but it's still of concern.
 		if($use_cache && !file_put_contents($cache_file, $result))
diff --git a/module_index.json b/module_index.json
index fd538f1..e265183 100755
--- a/module_index.json
+++ b/module_index.json
@@ -91,11 +91,11 @@
     },
     {
         "name": "Recent Changes",
-        "version": "0.3.5",
+        "version": "0.4",
         "author": "Starbeamrainbowlabs",
         "description": "Adds recent changes. Access through the 'recent-changes' action.",
         "id": "feature-recent-changes",
-        "lastupdate": 1548630687,
+        "lastupdate": 1549211706,
         "optional": false
     },
     {
@@ -271,20 +271,20 @@
     },
     {
         "name": "Old Default Parser",
-        "version": "0.10",
+        "version": "0.10.1",
         "author": "Johnny Broadway & Starbeamrainbowlabs",
         "description": "The *old* default parser for Pepperminty Wiki. Based on Johnny Broadway's Slimdown (with more than a few modifications). This parser's features are documented in the help page. Superceded by a customised extension of parsedown extra.",
         "id": "parser-default-old",
-        "lastupdate": 1458824880,
+        "lastupdate": 1549839049,
         "optional": true
     },
     {
         "name": "Parsedown",
-        "version": "0.9.12",
+        "version": "0.9.13",
         "author": "Emanuil Rusev & Starbeamrainbowlabs",
         "description": "An upgraded (now default!) parser based on Emanuil Rusev's Parsedown Extra PHP library (https:\/\/github.com\/erusev\/parsedown-extra), which is licensed MIT. Please be careful, as this module adds some weight to your installation, and also *requires* write access to the disk on first load.",
         "id": "parser-parsedown",
-        "lastupdate": 1546724653,
+        "lastupdate": 1549839523,
         "optional": false
     }
 ]
\ No newline at end of file
diff --git a/modules/parser-default-old.php b/modules/parser-default-old.php
index 23e06dd..3222b29 100644
--- a/modules/parser-default-old.php
+++ b/modules/parser-default-old.php
@@ -1,7 +1,7 @@
 <?php
 register_module([
 	"name" => "Old Default Parser",
-	"version" => "0.10",
+	"version" => "0.10.1",
 	"author" => "Johnny Broadway & Starbeamrainbowlabs",
 	"description" => "The *old* default parser for Pepperminty Wiki. Based on Johnny Broadway's Slimdown (with more than a few modifications). This parser's features are documented in the help page. Superceded by a customised extension of parsedown extra.",
 	"id" => "parser-default-old",
@@ -11,6 +11,13 @@ register_module([
 		
 		add_parser("default", function($markdown) {
 			return Slimdown::render($markdown);
+		}, function($source) {
+			global $version, $settings;
+			return str_replace(["+","/"], ["-","_"], base64_encode(hash(
+				"sha256",
+				"$version|$settings->parser|$source",
+				true
+			)));
 		});
 		
 		// Register the help section
@@ -159,7 +166,7 @@ class Slimdown {
 		return trim ($text);
 	}
 }
-////////////////////////////////////////////////////////////////////////////////////////////
-////////////////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
 
 ?>
diff --git a/modules/parser-parsedown.php b/modules/parser-parsedown.php
index 35fb965..f5c1b74 100644
--- a/modules/parser-parsedown.php
+++ b/modules/parser-parsedown.php
@@ -1,7 +1,7 @@
 <?php
 register_module([
 	"name" => "Parsedown",
-	"version" => "0.9.12",
+	"version" => "0.10",
 	"author" => "Emanuil Rusev & Starbeamrainbowlabs",
 	"description" => "An upgraded (now default!) parser based on Emanuil Rusev's Parsedown Extra PHP library (https://github.com/erusev/parsedown-extra), which is licensed MIT. Please be careful, as this module adds some weight to your installation, and also *requires* write access to the disk on first load.",
 	"id" => "parser-parsedown",
@@ -16,6 +16,28 @@ register_module([
 			$result = $parser->text($source);
 			
 			return $result;
+		}, function($source) {
+			global $version, $settings, $pageindex;
+			$id_text = "$version|$settings->parser|$source";
+			
+			// Find template includes
+			preg_match_all(
+				'/\{\{\s*([^|]+)\s*(?:\|[^}]*)?\}\}/',
+				$source, $includes
+			);
+			
+			foreach($includes[1] as $include_pagename) {
+				if(empty($pageindex->$include_pagename))
+				$id_text .= "|$include_pagename:" . parsedown_pagename_resolve(
+					$pageindex->$include_pagename->lastmodified
+				);
+			}
+			
+			return str_replace(["+","/"], ["-","_"], base64_encode(hash(
+				"sha256",
+				$id_text,
+				true
+			)));
 		});
 		
 		/*
@@ -203,6 +225,28 @@ if(!file_exists("./ParsedownExtra.php") || filesize("./ParsedownExtra.php") ===
 require_once("./Parsedown.php");
 require_once("./ParsedownExtra.php");
 
+/**
+ * Attempts to 'auto-correct' a page name by trying different capitalisation
+ * combinations.
+ * @param	string	$pagename	The page name to auto-correct.
+ * @return	string	The auto-corrected page name.
+ */
+function parsedown_pagename_resolve($pagename) {
+	global $pageindex;
+	
+	// If the page doesn't exist, check varying different
+	// capitalisations to see if it exists under some variant.
+	if(!empty($pageindex->$pagename))
+		return $pagename;
+	
+	$pagename = ucfirst($pagename);
+	if(!empty($pageindex->$pagename))
+		return $pagename;
+	
+	$pagename = ucwords($pagename);
+	return $pagename;
+}
+
 /*
  * ██████   █████  ██████  ███████ ███████ ██████   ██████  ██     ██ ███    ██
  * ██   ██ ██   ██ ██   ██ ██      ██      ██   ██ ██    ██ ██     ██ ████   ██
@@ -480,14 +524,10 @@ class PeppermintParsedown extends ParsedownExtra
 			// 3: Page name auto-correction
 			// -------------------------------
 			$is_interwiki_link = module_exists("feature-interwiki-links") && is_interwiki_link($link_page);
-			if(!$is_interwiki_link && empty($pageindex->$link_page)) {
-				// If the page doesn't exist, check varying different
-				// capitalisations to see if it exists under some variant.
-				if(!empty($pageindex->{ucfirst($link_page)}))
-					$link_page = ucfirst($link_page);
-				else if(!empty($pageindex->{ucwords($link_page)}))
-					$link_page = ucwords($link_page);
-			}
+			// Try different variants on the pagename to try and get it to 
+			// match something automagically
+			if(!$is_interwiki_link && empty($pageindex->$link_page))
+				$link_page = parsedown_pagename_resolve($link_page);
 			
 			
 			// 4: Construct the full url
@@ -508,6 +548,12 @@ class PeppermintParsedown extends ParsedownExtra
 				$is_interwiki_link = false;
 			}
 			
+			// 5: Construct the title
+			// -------------------------------
+			$title = $link_page;
+			if($is_interwiki_link)
+				$title = interwiki_pagename_resolve($link_page)->name . ": " . interwiki_pagename_parse($link_page)[1] . " (Interwiki)";
+			
 			if(strlen($hash_code) > 0)
 				$link_url .= "#$hash_code";
 			
@@ -519,8 +565,10 @@ class PeppermintParsedown extends ParsedownExtra
 				"element" => [
 					"name" => "a",
 					"text" => $display,
+					
 					"attributes" => [
-						"href" => $link_url
+						"href" => $link_url,
+						"title" => $title
 					]
 				]
 			];
