Compare commits

...

5 Commits

7 changed files with 197 additions and 52 deletions

View File

@ -1,7 +1,7 @@
# Contributors guide
Contributions are very welcome - both issues and pull requests! Please mention in your pull request that you release your work under the MPL-2.0 (see below).
While Pepperminty Wiki is a huge long-time passion of mine, at times it may take up to 2 weeks at times for me to respond to issues and pull requests. If it has been a few days and you have not received a reply, consider joining [the Gitter/Matrix chat](https://app.gitter.im/#/room/#Pepperminty-Wiki_Lobby:gitter.im) and poking me on there.
While Pepperminty Wiki is a huge long-time passion of mine, due to life stuff at times it may take up to 2 weeks at times for me to respond to issues and pull requests. If it has been a few days and you have not received a reply, consider joining [the Gitter/Matrix chat](https://app.gitter.im/#/room/#Pepperminty-Wiki_Lobby:gitter.im) and poking me on there.
I recommend checking on your pull request or issue every few days for a reply.

View File

@ -6,8 +6,10 @@ This file holds the changelog for Pepperminty Wiki. This is the master list of t
This is the next release of Pepperminty Wiki, that hasn't been released yet.
- **Fixed:** Fixed link to the interwiki links documentation on the help page if interwiki links have not yet been setup.
- **Fixed:** Fixed typos in system text
- **Changed:** Catch and deal with more unpacking issues on first run (thanks, @daveschroeter in [#249](https://github.com/sbrl/Pepperminty-Wiki/issues/249))
## v0.24
- **Added:** `filter` GET parameter to the `list` action, which filters the list of pages to contain only those containing the specified substring.
- **Fixed:** [Rest API] Documented `redirect` and `redirected_from` GET params to the `view` action.

View File

@ -130,6 +130,31 @@ function glob_recursive($pattern, $flags = 0)
return $files;
}
/**
* Normalize file name & path.
* Used to convert filenames returned by glob_recursive() to a format used in pageindex.
*
* @package core
* @author Alx84
* @param string $filename A filename with storage prefix as retuned by glob_recursive()
* @return string Normalized filename
*/
function normalize_filename($filename)
{
global $env;
// glob_recursive() returns values like "./storage_prefix/folder/filename.md"
// in the pageindex we save them as "folder/filename.md"
$result = mb_substr( // Store the filename, whilst trimming the storage prefix
$filename,
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(mb_substr($result, 0, 2) == "./")
$result = mb_substr($result, 2);
return $result;
}
/**
* Resolves a relative path against a given base directory.
* @since 0.20.0

View File

@ -3,68 +3,73 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
/**
* Rebuilds the page index based on what files are found
* @param bool $output Whether to send progress information to the user's browser.
*/
function pageindex_rebuild(bool $output = true) : void {
global $env, $pageindex;
if($output && !is_cli()) {
header("content-type: text/event-stream");
ob_end_flush();
}
/*
* Sort out the pageindex. Create it if it doesn't exist, and load + parse it
* if it does.
*/
if(!file_exists($paths->pageindex))
{
$glob_str = $env->storage_prefix . "*.md";
$existingpages = glob_recursive($glob_str);
$existingpages_count = count($existingpages);
// Debug statements. Uncomment when debugging the pageindex regenerator.
// var_dump($env->storage_prefix);
// var_dump($glob_str);
// var_dump($existingpages);
// save our existing pageindex, if it is available at this point
// we will use it to salvage some data out of it, like tags and authors
if (is_a($pageindex, 'stdClass')) $old_pageindex = $pageindex;
else $old_pageindex = new stdClass();
// compose a new pageindex into a global variable
$pageindex = new stdClass();
// We use a for loop here because foreach doesn't loop over new values inserted
// while we were looping
for($i = 0; $i < $existingpages_count; $i++)
{
$pagefilename = $existingpages[$i];
// Create a new entry
// Create a new entry for each md file we found
$newentry = new stdClass();
$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(mb_substr($newentry->filename, 0, 2) == "./")
$newentry->filename = mb_substr($newentry->filename, 2);
// glob_recursive() returns values like "./storage_prefix/folder/filename.md"
// in the pageindex we save them as "folder/filename.md"
$newentry->filename = normalize_filename($pagefilename);
$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"
// Extract the name of the (sub)page without the ".md"
$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
{
// This page (potentially) has an associated file!
// Let's investigate.
// Blindly add the file to the pageindex for now.
// Future We might want to do a security check on the file later on.
// File a bug if you think we should do this.
$newentry->uploadedfile = true; // Yes this page does have an uploaded file associated with it
$newentry->uploadedfilepath = $pagekey; // It's stored here
// Work out what kind of file it really is
$mimechecker = finfo_open(FILEINFO_MIME_TYPE);
$newentry->uploadedfilemime = finfo_file($mimechecker, $env->storage_prefix . $pagekey);
}
// Debug statements. Uncomment when debugging the pageindex regenerator.
// echo("pagekey: ");
// var_dump($pagekey);
// echo("newentry: ");
// var_dump($newentry);
// Subpage parent checker
if(strpos($pagekey, "/") !== false)
{
@ -83,7 +88,15 @@ if(!file_exists($paths->pageindex))
$existingpages[] = $subpage_parent_filename;
}
}
// Attempt to salvage tags and lasteditor from the previous pageindex
if (@$old_pageindex->$pagekey->tags)
$newentry->tags = $old_pageindex->$pagekey->tags;
$newentry->lasteditor = "unknown";
if (@$old_pageindex->$pagekey->lasteditor)
$newentry->lasteditor = $old_pageindex->$pagekey->lasteditor;
// 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");
@ -91,20 +104,31 @@ if(!file_exists($paths->pageindex))
"type" => "edit",
"rid" => 0,
"timestamp" => $newentry->lastmodified,
"filename" => "{$pagefilename}.r0",
"filename" => normalize_filename("{$pagefilename}.r0"),
"newsize" => $newentry->size,
"sizediff" => $newentry->size,
"editor" => "unknown"
"editor" => $newentry->lasteditor
] ];
}
// Store the new entry in the new page index
$pageindex->$pagekey = $newentry;
if($output) {
$message = "[" . ($i + 1) . " / $existingpages_count] Added $pagefilename to the pageindex.";
if(!is_cli()) $message = "data: $message\n\n";
else $message = "$message\r";
echo($message);
flush();
}
}
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
// collect from the filesystem what revision files we have
$history_revs = glob_recursive($env->storage_prefix . "*.md.r*");
// sort them in the ascending order of their revision numbers - it's very important for further processing
usort($history_revs, function($a, $b) {
preg_match("/[0-9]+$/", $a, $revid_a);
$revid_a = intval($revid_a[0]);
@ -112,29 +136,40 @@ if(!file_exists($paths->pageindex))
$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]+$/", $filename, $revid);
error_log("raw revid | ".var_export($revid, true));
if(count($revid) === 0) continue;
$revid = intval($revid[0]);
$pagename = filepath_to_pagename($filename);
$filepath_stripped = substr($filename, $strlen_storageprefix);
$filepath_stripped = normalize_filename($filename);
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);
}
// Let's attempt to salvage the editor for this revision from the old pageindex
// For that we walk through history of edits from old pageindex to find what editor was set for this specific file
$revision_editor = "unknown";
if ($old_pageindex->$pagename->history) {
foreach ($old_pageindex->$pagename->history as $revision)
if ($revision->filename == $filepath_stripped && isset($revision->editor))
$revision_editor = $revision->editor;
}
// save the revision into history
$pageindex->$pagename->history[$revid] = (object) [
"type" => "edit",
"rid" => $revid,
@ -142,13 +177,30 @@ if(!file_exists($paths->pageindex))
"filename" => $filepath_stripped,
"newsize" => $newsize,
"sizediff" => $newsize - $prevsize,
"editor" => "unknown"
"editor" => $revision_editor
];
}
}
save_pageindex();
unset($existingpages);
if($output && !is_cli()) {
echo("data: Done! \n\n");
flush();
}
}
/*
* Sort out the pageindex. Create it if it doesn't exist, and load + parse it
* if it does.
*/
if(!file_exists($paths->pageindex))
{
pageindex_rebuild(false);
}
else
{

View File

@ -47,10 +47,10 @@ register_module([
$content .= "<p>You're currently running Pepperminty Wiki $version+" . substr($commit, 0, 7) . ".</p>\n";
$content .= "<h2>Actions</h2>";
// rebuild search index button
$content .= "<button class='action-invindex-rebuild' title='Rebuilds the index that is consulted when searching the wiki. Hit this button if some pages are not showing up.'>Rebuild Search Index</button>\n";
$content .= "<progress class='action-invindex-rebuild-progress' min='0' max='100' value='0' style='display: none;'></progress><br />\n";
$content .= "<output class='action-invindex-rebuild-latestmessage'></output><br />\n";
$invindex_rebuild_script = <<<SCRIPT
window.addEventListener("load", function(event) {
document.querySelector(".action-invindex-rebuild").addEventListener("click", function(event) {
@ -76,8 +76,38 @@ window.addEventListener("load", function(event) {
});
});
SCRIPT;
page_renderer::add_js_snippet($invindex_rebuild_script);
// rebuild page index button
$content .= "<button class='action-pageindex-rebuild' title='Rebuilds the page index that contains information (tags, author, dates, filename) about all wiki pages. Hit this button if MD files were changed externally.'>Rebuild Page Index</button>\n";
$content .= "<progress class='action-pageindex-rebuild-progress' min='0' max='100' value='0' style='display: none;'></progress><br />\n";
$content .= "<output class='action-pageindex-rebuild-latestmessage'></output><br />\n";
$pageindex_rebuild_script = <<<SCRIPT
window.addEventListener("load", function(event) {
document.querySelector(".action-pageindex-rebuild").addEventListener("click", function(event) {
var rebuildActionEvents = new EventSource("?action=pageindex-rebuild");
var latestMessageElement = document.querySelector(".action-pageindex-rebuild-latestmessage");
var progressElement = document.querySelector(".action-pageindex-rebuild-progress");
rebuildActionEvents.addEventListener("message", function(event) {
console.log(event);
let message = event.data;
latestMessageElement.value = event.data;
let parts = message.match(/^\[\s*(\d+)\s+\/\s+(\d+)\s*\]/);
if(parts != null) {
progressElement.style.display = "";
progressElement.min = 0;
progressElement.max = parseInt(parts[2]);
progressElement.value = parseInt(parts[1]);
}
if(message.startsWith("Done!"))
rebuildActionEvents.close();
});
// Close the connection on error & don't try again
rebuildActionEvents.addEventListener("error", (_event) => rebuildActionEvents.close());
});
});
SCRIPT;
page_renderer::add_js_snippet($pageindex_rebuild_script);
$content .= "<h2>Settings</h2>";
$content .= "<p>Mouse over the name of each setting to see a description of what it does.</p>\n";
@ -206,6 +236,41 @@ SCRIPT;
$content .= "</textarea>\n";
exit(page_renderer::render_main("Master Settings Updated - $settings->sitename", $content));
});
/**
* @api {get} ?action=pageindex-rebuild[&format=json] Rebuilds the page index
* @apiName UserList
* @apiGroup Utility
* @apiPermission Anonymous
*/
/* _ _ _ _ _ _
* (_) | | | | (_) | | | |
* _ __ __ _ __ _ ___ _ _ __ __| | ___ __ __ ______ _ __ ___ | |__ _ _ _ | | __| |
* | '_ \ / _` | / _` | / _ \ | | | '_ \ / _` | / _ \ \ \/ / |______| | '__| / _ \ | '_ \ | | | | | | | | / _` |
* | |_) | | (_| | | (_| | | __/ | | | | | | | (_| | | __/ > < | | | __/ | |_) | | |_| | | | | | | (_| |
* | .__/ \__,_| \__, | \___| |_| |_| |_| \__,_| \___| /_/\_\ |_| \___| |_.__/ \__,_| |_| |_| \__,_|
* | | __/ |
* |_| |___/
*
*/
add_action("pageindex-rebuild", function() {
global $env, $settings;
if($env->is_admin ||
(
!empty($_POST["secret"]) &&
$_POST["secret"] === $settings->secret
)
)
pageindex_rebuild();
else
{
http_response_code(401);
exit(page_renderer::render_main("Error - Page index regenerator - $settings->sitename", "<p>Error: You aren't allowed to regenerate the page index. Try logging in as an admin, or setting the <code>secret</code> POST parameter to $settings->sitename's secret - which can be found in $settings->sitename's <code>peppermint.json</code> file.</p>"));
}
});
add_help_section("800-raw-page-content", "Viewing Raw Page Content", "<p>Although you can use the edit page to view a page's source, you can also ask $settings->sitename to send you the raw page source and nothing else. This feature is intented for those who want to automate their interaction with $settings->sitename.</p>
<p>To use this feature, navigate to the page for which you want to see the source, and then alter the <code>action</code> parameter in the url's query string to be <code>raw</code>. If the <code>action</code> parameter doesn't exist, add it. Note that when used on an file's page this action will return the source of the description and not the file itself.</p>");

View File

@ -1,11 +1,11 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
register_module([
"name" => "Page editor",
"version" => "0.19",
"version" => "0.19.1",
"author" => "Starbeamrainbowlabs",
"description" => "Allows you to edit pages by adding the edit and save actions. You should probably include this one.",
"id" => "page-edit",
@ -88,7 +88,7 @@ register_module([
$sourceViewContent = "<p>$settings->sitename currently has editing disabled, so you can't make changes to this page at this time. Please contact ".htmlentities($settings->admindetails_name).", $settings->sitename's administrator for more information - their contact details can be found at the bottom of this page. Even so, you can still view the source of this page. It's disabled below:</p>";
if($isOtherUsersPage)
$sourceViewContent = "<p>$env->page_safe is a special user page which acutally belongs to " . htmlentities(extract_user_from_userpage($env->page)) . ", another user on $settings->sitename. Because of this, you are not allowed to edit it (though you can always edit your own page and any pages under it if you're logged in). You can, however, vieww it's source below.</p>";
$sourceViewContent = "<p>$env->page_safe is a special user page which actually belongs to " . htmlentities(extract_user_from_userpage($env->page)) . ", another user on $settings->sitename. Because of this, you are not allowed to edit it (though you can always edit your own page and any pages under it if you're logged in). You can, however, view it's source below.</p>";
// Append a view of the page's source
$sourceViewContent .= "<textarea name='content' readonly>".htmlentities($pagetext)."</textarea>";

View File

@ -174,6 +174,7 @@ blockquote { padding-left: 1em; border-left: 0.2em solid var(--accent-a3); borde
pre { white-space: pre-wrap; padding: 0.3em 0.5em; background: var(--bg-page-inset); border-radius: 0.25em; box-shadow: inset 0 0 0.5em var(--shadow); }
code { font-size: 1.1em; }
code:not(:has(> pre)) { background: var(--bg-page-inset); }
a { cursor: pointer; }
a:focus { outline-width: 0.1em; }