Compare commits

...

4 Commits

6 changed files with 202 additions and 45 deletions

12
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,12 @@
# 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).
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.
If you open a pull request, I will review your changes and reply with a review detailing some changes I would like you to make. It may take a few back and forth comments, but then once I am happy with your changes I'll accept them and merge them into the codebase. This way we can polish your contribution to make it fit in with the existing codebase better ✨
It is also unlikely, but possible I may reject your changes. If this is the case, I will leave a comment explaining why.
If you are no longer interested in continuing to work with a pull request or will be away for a while, please leave a comment. Nobody will be offended! If you do not leave a comment or do not respond for 2 weeks, I may take over your pull request, work on it, and merge it myself.

View File

@ -76,6 +76,8 @@ If you would like to encrypt any communications with me, you can find my GPG key
## Contributing
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).
See [CONTRIBUTING.md](./CONTRIBUTING.md) for a guide on what to expect when submitting a pull request or issue to this project.
If you're feeling that way inclined, the sponsor button at the top of the page (if you're on GitHub) will take you to my [Liberapay profile](https://liberapay.com/sbrl) if you'd like to donate to say an extra thank you :-)

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

@ -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; }