Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix to #242 #243

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
25 changes: 25 additions & 0 deletions core/05-functions.php
Expand Up @@ -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
Expand Down
138 changes: 95 additions & 43 deletions core/20-pageindex-loader.php
Expand Up @@ -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)
{
Expand All @@ -83,72 +88,119 @@
$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");
$newentry->history = [ (object) [
"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]);
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]+$/", $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,
"timestamp" => filemtime($filename),
"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
{
Expand Down
69 changes: 67 additions & 2 deletions modules/feature-guiconfig.php
Expand Up @@ -47,10 +47,10 @@
$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) {
Expand All @@ -76,8 +76,38 @@
});
});
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";
Expand Down Expand Up @@ -206,6 +236,41 @@
$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>");
Expand Down
1 change: 1 addition & 0 deletions themes/default/theme.css
Expand Up @@ -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; }
Expand Down