diff --git a/Changelog.md b/Changelog.md index 52cd242..60e4633 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,11 +2,17 @@ This file holds the changelog for Pepperminty Wiki. This is the master list of things that have changed (second only to the commit history!) - though the information for any particular release can also be found in the description of it's page for every release made on GitHub too. -## v0.22-dev +## v0.22-beta2 + +### Fixed + - Hide the admin email address at the bottom of every page - we missed it in v0.22-beta1 (but got every other one though :P) + +## v0.22-beta1 Make sure you have PHP 7.3+ when you update past this point! It isn't the end of the world if you don't, but it will make you more secure if you do. ### Added - [Module Api] Add new `search::invindex_term_getpageids`, and `search::invindex_term_getoffsets`, and `search::index_sort_freq` methods + - [Module Api] Add new `ends_with` and `filepath_to_pagename` core functions - Added new syntax features to PeppermintParsedown, inspired by ParsedownExtreme (which we couldn't get to work, and it wasn't working before as far as I can tell) - Checkboxes: `[ ]` and `[x]` after a bullet point or at the start of a line - Marked / highlighted text: `Some text ==marked text== more text` @@ -42,12 +48,15 @@ Make sure you have PHP 7.3+ when you update past this point! It isn't the end of - A warning is generated in PHP 7.2 and below = [please upgrade](https://www.php.net/supported-versions.php) to PHP 7.3+! (#200) - [security] The `Secure` cookie flag is now automatically added when clients use HTTPS to prevent downgrade-based session stealing attacks (control this with the new `cookie_secure` setting) - Standardised prefixes to (most) `error_log()` calls to aid clarity in multi-wiki environments + - Improved pageindex rebuilder algorithm to search for and import history revisions - this helps when converting data from another wiki format + - Improved spam protection when hiding email addresses. Javascript is now required to decode email addresses - please [get in touch](https://github.com/sbrl/Pepperminty-Wiki/issues/new) if this is a problem for whatever reason. I take accessibility _very_ seriously. + - Bump weighting of title and tag matches in search results (delete the `search_title_matches_weighting` and `search_tags_matches_weighting` settings to get the new weightings) ### Fixed - Squashed a warning when using the fenced code block syntax - If a redirect page sends you to create a page that doesn't exist, a link back to the redirect page itself is now displayed - Really fix bots getting into infinite loops on the login page this time by marking all login pages as `noindex, nofollow` with a robots `` tag - - Navigating to a redirect page from a page list will no longer cause you to automatically follow the redirect + - Navigating to a redirect page from a page list or the recent changes list will no longer cause you to automatically follow the redirect - Limited sidebar size to 20% of the screen width at most - Fix the [large blank space problem](https://github.com/sbrl/Pepperminty-Wiki/blob/master/Changelog.md#fixed-3) in all themes - Squashed the text `\A` appearing before tags at the bottom of pages for some users ([ref](https://gitter.im/Pepperminty-Wiki/Lobby?at=5f0632068342f4627401f145)) @@ -58,6 +67,7 @@ Make sure you have PHP 7.3+ when you update past this point! It isn't the end of - Fixed an obscure warning when previewing PDFs (#202) - Ensure that the parent page exists when moving a page to be a child of a non-existent parent (#201) - Fixed templating (#203) + - Fixed warning from statistics engine during firstrun wizard ## v0.21.1-hotfix1 diff --git a/apidoc.json b/apidoc.json index 499d3f9..4afd7a3 100644 --- a/apidoc.json +++ b/apidoc.json @@ -1,6 +1,6 @@ { "name": "Pepperminty Wiki", - "version": "0.21.0", + "version": "0.22.0", "description": "A wiki in a box. This is the API documentation.", - "title": "Pepperminty Wiki (0.20)" + "title": "Pepperminty Wiki (0.22)" } diff --git a/core/05-functions.php b/core/05-functions.php index 6848165..dbf8b74 100644 --- a/core/05-functions.php +++ b/core/05-functions.php @@ -159,6 +159,28 @@ function path_resolve(string $path, string $basePath = null) { return implode(DIRECTORY_SEPARATOR, $components); } +/** + * Converts a filepath to a page name. + * @param string $filepath The filepath to convert. + * @return string The extracted pagename. + */ +function filepath_to_pagename(string $filepath) : string { + global $env; + // Strip the storage prefix, but only if it isn't a dot + if(starts_with($filepath, $env->storage_prefix) && $env->storage_prefix !== ".") + $filepath = mb_substr($filepath, mb_strlen($env->storage_prefix)); + + // If a revision number is detected, strip it + if(preg_match("/\.r[0-9]+$/", $filepath) > 0) + $filepath = mb_substr($filepath, 0, mb_strrpos($filepath, ".r")); + + // Strip the .md file extension + if(ends_with($filepath, ".md")) + $filepath = mb_substr($filepath, 0, -3); + + return $filepath; +} + /** * Gets the name of the parent page to the specified page. * @apiVersion 0.15.0 @@ -267,34 +289,36 @@ function makepathsafe($string) $string = preg_replace("/\.+/", ".", $string); // Don't allow slashes at the beginning $string = ltrim($string, "\\/"); + // Don't allow dots on their own + $string = preg_replace(["/^\.\\/|\\/\.$/", "/\\/\.\\//"], ["", "/"], $string); return $string; } /** - * Hides an email address from bots by adding random html entities. - * @todo Make this more clevererer :D + * Hides an email address from bots. Returns a fragment of HTML that contains the mangled email address. * @package core - * @param string $str The original email address - * @return string The mangled email address. + * @param string $str The original email address + * @param string $display_text The display text for the resulting HTML - if null then the original email address is used. + * @return string The mangled email address. */ -function hide_email($str) +function hide_email(string $email, string $display_text = null) : string { - $hidden_email = ""; - for($i = 0; $i < strlen($str); $i++) - { - if($str[$i] == "@") - { - $hidden_email .= "&#" . ord("@") . ";"; - continue; - } - if(rand(0, 1) == 0) - $hidden_email .= $str[$i]; - else - $hidden_email .= "&#" . ord($str[$i]) . ";"; + $enc = json_encode([ $email, $display_text ]); + $len = strlen($enc); + $pool = []; for($i = 0; $i < $len; $i++) $pool[] = $i; + $a = []; $b = []; + for($i = 0; $i < $len; $i++) { + $n = random_int(0, $len - $i - 1); + $j = array_splice($pool, $n, 1)[0]; $b[] = $j; + // echo("chose ".$enc[$j].", index $j, n $n\n"); + $a[] = $enc[$j]; } - - return $hidden_email; + $a = base64_encode(implode("|", $a)); + $b = base64_encode(implode("|", $b)); + $span_id = "he-".crypto_id(16); + return "[protected with javascript]"; } + /** * Checks to see if $haystack starts with $needle. * @package core @@ -303,10 +327,22 @@ function hide_email($str) * of $haystack. * @return bool Whether $needle can be found at the beginning of $haystack. */ -function starts_with($haystack, $needle) { +function starts_with(string $haystack, string $needle) : bool { $length = strlen($needle); return (substr($haystack, 0, $length) === $needle); } +/** + * Checks to see if $hackstack ends with $needle. + * The matching bookend to starts_with. + * @package core + * @param string $haystack The haystack to search.. + * @param string $needle The needle to look for. + * @return bool + */ +function ends_with(string $haystack, string $needle) : bool { + $length = strlen($needle); + return (substr($haystack, -$length) === $needle); +} /** * Case-insensitively finds all occurrences of $needle in $haystack. Handles diff --git a/core/20-pageindex-loader.php b/core/20-pageindex-loader.php index a973f67..984fea6 100644 --- a/core/20-pageindex-loader.php +++ b/core/20-pageindex-loader.php @@ -22,25 +22,21 @@ if(!file_exists($paths->pageindex)) // Create a new entry $newentry = new stdClass(); - $newentry->filename = substr( // Store the filename, whilst trimming the storage prefix + $newentry->filename = mb_substr( // Store the filename, whilst trimming the storage prefix $pagefilename, mb_strlen(preg_replace("/^\.\//iu", "", $env->storage_prefix)) // glob_recursive trim the ./ from returned filenames , so we need to as well ); // Remove the `./` from the beginning if it's still hanging around - if(substr($newentry->filename, 0, 2) == "./") - $newentry->filename = substr($newentry->filename, 2); + if(mb_substr($newentry->filename, 0, 2) == "./") + $newentry->filename = mb_substr($newentry->filename, 2); $newentry->size = filesize($pagefilename); // Store the page size $newentry->lastmodified = filemtime($pagefilename); // Store the date last modified // Todo find a way to keep the last editor independent of the page index $newentry->lasteditor = "unknown"; // Set the editor to "unknown" - - - // POTENTIAL BUG: If $env->storage_prefix is not ., then this we need to be more intelligent here - - // Extract the name of the (sub)page without the ".md" - $pagekey = mb_substr($newentry->filename, 0, -3); + $pagekey = filepath_to_pagename($newentry->filename); + error_log("pagename '$newentry->filename' → filepath '$pagekey'"); if(file_exists($env->storage_prefix . $pagekey) && // If it exists... !is_dir($env->storage_prefix . $pagekey)) // ...and isn't a directory @@ -84,6 +80,7 @@ if(!file_exists($paths->pageindex)) } } + // If the initial revision doesn't exist on disk, create it (if it does, then we handle that later) if(function_exists("history_add_revision") && !file_exists("{$pagefilename}.r0")) { // Can't use module_exists - too early copy($pagefilename, "{$pagefilename}.r0"); $newentry->history = [ (object) [ @@ -103,16 +100,46 @@ if(!file_exists($paths->pageindex)) if(function_exists("history_add_revision")) { $history_revs = glob_recursive($env->storage_prefix . "*.r*"); + // It's very important that we read the history revisions in the right order and that we don't skip any + usort($history_revs, function($a, $b) { + preg_match("/[0-9]+$/", $a, $revid_a); + $revid_a = intval($revid_a[0]); + preg_match("/[0-9]+$/", $b, $revid_b); + $revid_b = intval($revid_b[0]); + return $revid_a - $revid_b; + }); + // We can guarantee that the direcotry separator is present on the end - it's added explicitly earlier + $strlen_storageprefix = strlen($env->storage_prefix); foreach($history_revs as $filename) { - preg_match("/[0-9]+$/", "Main Page.md.r0", $revid); + preg_match("/[0-9]+$/", $filename, $revid); + error_log("raw revid | ".var_export($revid, true)); if(count($revid) === 0) continue; $revid = intval($revid[0]); - // TODO: Extract the pagename here (maybe a function is worth implementing if we haven't already?) + $pagename = filepath_to_pagename($filename); + $filepath_stripped = substr($filename, $strlen_storageprefix); - if($revid == 0 && ) { - + if(!isset($pageindex->$pagename->history)) + $pageindex->$pagename->history = []; + + if(isset($pageindex->$pagename->history[$revid])) + continue; + + error_log("pagename: $pagename, revid: $revid, pageindex entry: ".var_export($pageindex->$pagename, true)); + $newsize = filesize($filename); + $prevsize = 0; + if($revid > 0 && isset($pageindex->$pagename->history[$revid - 1])) { + $prevsize = filesize(end($pageindex->$pagename->history)->filename); } + $pageindex->$pagename->history[$revid] = (object) [ + "type" => "edit", + "rid" => $revid, + "timestamp" => filemtime($filename), + "filename" => $filepath_stripped, + "newsize" => $newsize, + "sizediff" => $newsize - $prevsize, + "editor" => "unknown" + ]; } } diff --git a/core/40-page-renderer.php b/core/40-page-renderer.php index 78710cd..76c4075 100644 --- a/core/40-page-renderer.php +++ b/core/40-page-renderer.php @@ -44,7 +44,7 @@ class page_renderer

{footer-message}

Powered by Pepperminty Wiki {version}, which was built by Starbeamrainbowlabs. Send bugs to 'bugs at starbeamrainbowlabs dot com' or open an issue.

Your local friendly moderators are {admins-name-list}.

-

This wiki is managed by {admin-details-name}.

+

This wiki is managed by {admin-details}.

{navigation-bar-bottom} {all-pages-datalist}"; @@ -128,8 +128,7 @@ class page_renderer if(!is_callable($function)) { http_response_code(500); - $admin_email = hide_email($settings->admindetails_email); - exit(page_renderer::render("$settings->sitename - Module Error", "

$settings->sitename has got a misbehaving module installed that tried to register an invalid HTML handler with the page renderer. Please contact $settings->sitename's administrator {$settings->admindetails_name} at $admin_email.")); + exit(page_renderer::render("$settings->sitename - Module Error", "

$settings->sitename has got a misbehaving module installed that tried to register an invalid HTML handler with the page renderer. Please contact $settings->sitename's administrator {$settings->admindetails_name} at ".hide_email($settings->admindetails_email).".")); } self::$part_processors[] = $function; @@ -187,8 +186,8 @@ class page_renderer "{navigation-bar}" => self::render_navigation_bar($settings->nav_links, $settings->nav_links_extra, "top"), "{navigation-bar-bottom}" => self::render_navigation_bar($settings->nav_links_bottom, [], "bottom"), + "{admin-details}" => hide_email($settings->admindetails_email, $settings->admindetails_name), "{admin-details-name}" => $settings->admindetails_name, - "{admin-details-email}" => $settings->admindetails_email, "{admins-name-list}" => implode(", ", array_map(function($username) { return page_renderer::render_username($username); }, $settings->admins)), diff --git a/core/70-parser-engine.php b/core/70-parser-engine.php index 9d003cb..293f867 100644 --- a/core/70-parser-engine.php +++ b/core/70-parser-engine.php @@ -45,7 +45,7 @@ function parse_page_source($source, $untrusted = false, $use_cache = true) { if(!$settings->parser_cache || strlen($source) < $settings->parser_cache_min_size) $use_cache = false; if(!isset($parsers[$settings->parser])) - exit(page_renderer::render_main("Parsing error - $settings->sitename", "

Parsing some page source data failed. This is most likely because $settings->sitename has the parser setting set incorrectly. Please contact " . $settings->admindetails_name . ", your $settings->sitename Administrator.")); + exit(page_renderer::render_main("Parsing error - $settings->sitename", "

Parsing some page source data failed. This is most likely because $settings->sitename has the parser setting set incorrectly. Please contact " . hide_email($settings->admindetails_email, $settings->admindetails_name) . ", $settings->sitename's Administrator.")); /* Not needed atm because escaping happens when saving, not when rendering * if($settings->clean_raw_html) diff --git a/modules/feature-comments.php b/modules/feature-comments.php index d89af84..4640848 100644 --- a/modules/feature-comments.php +++ b/modules/feature-comments.php @@ -1,7 +1,7 @@ "Page Comments", - "version" => "0.3.2", + "version" => "0.3.3", "author" => "Starbeamrainbowlabs", "description" => "Adds threaded comments to the bottom of every page.", "id" => "feature-comments", @@ -67,7 +67,7 @@ register_module([ if(!file_exists($comment_filename)) { if(file_put_contents($comment_filename, "[]\n") === false) { http_response_code(503); - exit(page_renderer::renderer_main("Error posting comment - $settings->sitename", "

$settings->sitename ran into a problem whilst creating a file to save your comment to! Please contact $settings->admindetails_name, $settings->sitename's administrator and tell them about this problem.

")); + exit(page_renderer::renderer_main("Error posting comment - $settings->sitename", "

$settings->sitename ran into a problem whilst creating a file to save your comment to! Please contact " . hide_email($settings->admindetails_email, $settings->admindetails_name) . ", $settings->sitename's administrator and tell them about this problem.

")); } } @@ -120,7 +120,7 @@ register_module([ // Save the comments back to disk if(file_put_contents($comment_filename, json_encode($comment_data, JSON_PRETTY_PRINT)) === false) { http_response_code(503); - exit(page_renderer::renderer_main("Error posting comment - $settings->sitename", "

$settings->sitename ran into a problem whilst saving your comment to disk! Please contact $settings->admindetails_name, $settings->sitename's administrator and tell them about this problem.

")); + exit(page_renderer::renderer_main("Error posting comment - $settings->sitename", "

$settings->sitename ran into a problem whilst saving your comment to disk! Please contact " . hide_email($settings->admindetails_email, $settings->admindetails_name) . ", $settings->sitename's administrator and tell them about this problem.

")); } // Add a recent change if the recent changes module is installed @@ -198,7 +198,7 @@ register_module([ if(!file_put_contents($comment_filename, json_encode($comments))) { http_response_code(503); - exit(page_renderer::render_main("Server Error - Deleting Comment - $settings->sitename", "

While $settings->sitename was able to delete the comment with the id " . htmlentities($target_id) . " on the page $env->page, it couldn't save the changes back to disk. Please contact $settings->admindetails_name, $settings->sitename's local friendly administrator about this issue.

")); + exit(page_renderer::render_main("Server Error - Deleting Comment - $settings->sitename", "

While $settings->sitename was able to delete the comment with the id " . htmlentities($target_id) . " on the page $env->page, it couldn't save the changes back to disk. Please contact " . hide_email($settings->admindetails_email, $settings->admindetails_name) . ", $settings->sitename's local friendly administrator about this issue.

")); } exit(page_renderer::render_main("Comment Deleted - $settings->sitename", "

The comment with the id " . htmlentities($target_id) . " on the page $env->page has been deleted successfully. Go back to " . htmlentities($env->page) . ".

")); diff --git a/modules/feature-readingtime.php b/modules/feature-readingtime.php index da23c87..bb01b7b 100644 --- a/modules/feature-readingtime.php +++ b/modules/feature-readingtime.php @@ -1,7 +1,7 @@ "Reading time estimator", - "version" => "0.1", + "version" => "0.2", "author" => "Starbeamrainbowlabs", "description" => "Displays the approximate reading time for a page beneath it's title.", "id" => "feature-readingtime", @@ -44,7 +44,10 @@ register_module([ * @return array An array in the form [ low_time, high_time ] in minutes */ function estimate_reading_time(string $text, string $lang = "en") : array { - $chars_count = mb_strlen($text); + $chars_count = mb_strlen(preg_replace("/\s+?/", "", strtr($text, [ + "[" => "", "]" => "", "(" => "", ")" => "", + "|" => "", "#" => "", "*" => "" + ]))); $langs = [ "en" => (object) [ "cpm" => 987, "variance" => 118 ], "ar" => (object) [ "cpm" => 612, "variance" => 88 ], diff --git a/modules/feature-recent-changes.php b/modules/feature-recent-changes.php index 817c699..4c66a64 100644 --- a/modules/feature-recent-changes.php +++ b/modules/feature-recent-changes.php @@ -305,7 +305,7 @@ function render_recent_change($rchange) if($rchange_type === "revert") $resultClasses[] = "reversion"; - $result .= "$pageDisplayHtml $editorDisplayHtml $timeDisplayHtml ($size_display)"; + $result .= "$pageDisplayHtml $editorDisplayHtml $timeDisplayHtml ($size_display)"; break; case "deletion": diff --git a/modules/feature-similarpages.php b/modules/feature-similarpages.php index fbf6ea7..bccf8f4 100644 --- a/modules/feature-similarpages.php +++ b/modules/feature-similarpages.php @@ -74,7 +74,7 @@ register_module([ if($env->action !== "view") return; - $html = "