Add support for creating pages whose name is not yet known - fixes #194

This commit is contained in:
Starbeamrainbowlabs 2020-10-25 22:50:03 +00:00
parent b78aa34972
commit 7dd9bd74c4
Signed by: sbrl
GPG Key ID: 1BE5172E637709C2
4 changed files with 51 additions and 14 deletions

View File

@ -4,10 +4,18 @@ This file holds the changelog for Pepperminty Wiki. This is the master list of t
## v0.23-dev (unreleased) ## v0.23-dev (unreleased)
## Added
- Added HTTP API support for creating pages that don't yet have a name (#194)
- This allows for having a "create new page" button in your navigation links - e.g. edit `nav_links`, `nav_links_extra`, or `nav_links_bottom` in your `peppermint.json` and add something like `[ "+", "index.php?action=edit&unknownpagename=yes" ]`.
## Changed ## Changed
- Updated the [configuration guide](https://starbeamrainbowlabs.com/labs/peppermint/peppermint-config-info.php) to include count of how many settings we have - Updated the [configuration guide](https://starbeamrainbowlabs.com/labs/peppermint/peppermint-config-info.php) to include count of how many settings we have
## Fixed
- [security] Fixed some potential XSS attacks in the page editor
## v0.22 ## v0.22
_No changes were made since the last release_ _No changes were made since the last release_

View File

@ -280,7 +280,8 @@ task_start-server() {
if [[ -z "${NO_BROWSER}" ]]; then if [[ -z "${NO_BROWSER}" ]]; then
task_begin "Opening Browser"; task_begin "Opening Browser";
sensible-browser [::]:35623; # sensible-browser isn't opening the right browser :-/
xdg-open "http://[::]:35623";
task_end $?; task_end $?;
fi fi
} }

View File

@ -1,11 +1,11 @@
<?php <?php
/* This Source Code Form is subject to the terms of the Mozilla Public /* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
register_module([ register_module([
"name" => "Page editor", "name" => "Page editor",
"version" => "0.17.9", "version" => "0.18",
"author" => "Starbeamrainbowlabs", "author" => "Starbeamrainbowlabs",
"description" => "Allows you to edit pages by adding the edit and save actions. You should probably include this one.", "description" => "Allows you to edit pages by adding the edit and save actions. You should probably include this one.",
"id" => "page-edit", "id" => "page-edit",
@ -27,7 +27,8 @@ register_module([
* @apiPermission Anonymous * @apiPermission Anonymous
* *
* @apiUse PageParameter * @apiUse PageParameter
* @apiParam {string} newpage Set to 'yes' if a new page is being created. Only affects a few bits of text here and there, and the HTTP response code recieved on success from the `save` action. * @apiParam {string} newpage Optional. Set to 'yes' if a new page is being created. Only affects a few bits of text here and there, and the HTTP response code recieved on success from the `save` action.
* @apiParam {string} unknownpagename Optional. Set to 'yes' if the name of the page to be created is currently unknown. If set, a page name box will be shown too.
*/ */
/* /*
@ -41,18 +42,23 @@ register_module([
add_action("edit", function() { add_action("edit", function() {
global $pageindex, $settings, $env, $paths; global $pageindex, $settings, $env, $paths;
$unknownpagename = isset($_GET["unknownpagename"]) && strlen(trim($_GET["unknownpagename"])) > 0;
$filename = "$env->storage_prefix$env->page.md"; $filename = "$env->storage_prefix$env->page.md";
$creatingpage = !isset($pageindex->{$env->page}); $creatingpage = !isset($pageindex->{$env->page});
if((isset($_GET["newpage"]) and $_GET["newpage"] == "true") or $creatingpage) if((isset($_GET["newpage"]) and $_GET["newpage"] == "true") or $creatingpage)
$title = "Creating $env->page"; $title = "Creating $env->page";
else if(isset($_POST['preview-edit']) && isset($_POST['content'])) else if(isset($_POST['preview-edit']) && isset($_POST['content']))
$title = "Preview Edits for $env->page"; $title = "Preview Edits for $env->page";
else if($unknownpagename)
$title = "Creating new page";
else else
$title = "Editing $env->page"; $title = "Editing $env->page";
$pagetext = ""; $pagetext = ""; $page_tags = "";
if(isset($pageindex->{$env->page})) if(isset($pageindex->{$env->page}) && !$unknownpagename)
$pagetext = file_get_contents($filename); $pagetext = file_get_contents($filename);
if(!$unknownpagename)
$page_tags = htmlentities(implode(", ", (!empty($pageindex->{$env->page}->tags)) ? $pageindex->{$env->page}->tags : []));
$isOtherUsersPage = false; $isOtherUsersPage = false;
if( if(
@ -90,7 +96,7 @@ register_module([
$sourceViewContent = "<p>$env->page is a special user page which acutally belongs to " . 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 is a special user page which acutally belongs to " . 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>";
// Append a view of the page's source // Append a view of the page's source
$sourceViewContent .= "<textarea name='content' readonly>$pagetext</textarea>"; $sourceViewContent .= "<textarea name='content' readonly>".htmlentities($pagetext)."</textarea>";
exit(page_renderer::render_main("Viewing source for $env->page", $sourceViewContent)); exit(page_renderer::render_main("Viewing source for $env->page", $sourceViewContent));
} }
@ -109,7 +115,7 @@ register_module([
} }
$content = "<h1>$title</h1>\n"; $content = "<h1>$title</h1>\n";
$page_tags = implode(", ", (!empty($pageindex->{$env->page}->tags)) ? $pageindex->{$env->page}->tags : []);
if(!$env->is_logged_in and $settings->anonedits) { if(!$env->is_logged_in and $settings->anonedits) {
$content .= "<p><strong>Warning: You are not logged in! Your IP address <em>may</em> be recorded.</strong></p>"; $content .= "<p><strong>Warning: You are not logged in! Your IP address <em>may</em> be recorded.</strong></p>";
} }
@ -136,8 +142,12 @@ register_module([
$content .= "<button class='smartsave-restore' title=\"Only works if you haven't changed the editor's content already!\">Restore Locally Saved Content</button> $content .= "<button class='smartsave-restore' title=\"Only works if you haven't changed the editor's content already!\">Restore Locally Saved Content</button>
<form method='post' name='edit-form' action='index.php?action=preview-edit&page=" . rawurlencode($env->page) . "' class='editform'> <form method='post' name='edit-form' action='index.php?action=preview-edit&page=" . rawurlencode($env->page) . "' class='editform'>
<input type='hidden' name='prev-content-hash' value='" . generate_page_hash(isset($old_pagetext) ? $old_pagetext : $pagetext) . "' /> <input type='hidden' name='prev-content-hash' value='" . generate_page_hash(isset($old_pagetext) ? $old_pagetext : $pagetext) . "' />";
<textarea name='content' autofocus tabindex='1'>$pagetext</textarea> if($unknownpagename)
$content .= "<div><label for='page'>Page Name:</label>
<input type='text' id='page' name='page' value='' placeholder='Enter the name of the page here.' title='Enter the name of the page here.' />
<input type='hidden' name='prevent_save_if_exists' value='yes' />";
$content .= "<textarea name='content' autofocus tabindex='1'>$pagetext</textarea>
<pre class='fit-text-mirror'></pre> <pre class='fit-text-mirror'></pre>
<input type='text' id='tags' name='tags' value='" . htmlentities($page_tags, ENT_HTML5 | ENT_QUOTES) . "' placeholder='Enter some tags for the page here. Separate them with commas.' title='Enter some tags for the page here. Separate them with commas.' tabindex='2' /> <input type='text' id='tags' name='tags' value='" . htmlentities($page_tags, ENT_HTML5 | ENT_QUOTES) . "' placeholder='Enter some tags for the page here. Separate them with commas.' title='Enter some tags for the page here. Separate them with commas.' tabindex='2' />
<p class='editing-message'>$settings->editing_message</p> <p class='editing-message'>$settings->editing_message</p>
@ -289,7 +299,9 @@ window.addEventListener("load", function(event) {
* @apiPermission Anonymous * @apiPermission Anonymous
* *
* @apiUse PageParameter * @apiUse PageParameter
* @apiParam {string} format The format ot return the edit key in. Possible values: text, json. Default: text. * @apiParam {string} format The format to return the edit key in. Possible values: text, json. Default: text.
* @apiParam {string} prevent_save_if_exists Optional. If set to 'yes', then if a page exists with the specified page name the save is aborted and an error page returned instead.
* @apiParam {string} page The name of the page to save the edit to. Note that in this specific instance *only*, the page name can be specified over GET or POST (POST will override GET if both are present).
*/ */
/* /*
@ -366,7 +378,10 @@ window.addEventListener("load", function(event) {
* %save% * %save%
*/ */
add_action("save", function() { add_action("save", function() {
global $pageindex, $settings, $env, $save_preprocessors, $paths; global $pageindex, $settings, $env, $save_preprocessors, $paths;
// Update the page name in the main environment, since the page name may be submitted via the POST form
if(isset($_POST["page"]))
$env->page = $_POST["page"];
if(!$settings->editing) if(!$settings->editing)
{ {
@ -397,6 +412,16 @@ window.addEventListener("load", function(event) {
header("refresh: 5; url=index.php?page=" . rawurlencode($env->page)); header("refresh: 5; url=index.php?page=" . rawurlencode($env->page));
exit("Bad request: No content specified."); exit("Bad request: No content specified.");
} }
if(isset($_POST["prevent_save_if_exists"]) && isset($pageindex->{$env->page})) {
http_response_code(409);
exit(page_renderer::render_main("Error saving new page - ".htmlentities($env->page)." - $settings->sitename", "<p>Error: A page with that name already exists. Things you can do:</p>
<ul>
<li>View the existing page here: <a target='_blank' href='?action={$settings->defaultaction}&page=".rawurlencode($env->page)."'>".htmlentities($env->page)."</a></li>
<li><a href='javascript:history.back();'>Go back to continu editing and change the page name</a></li>
</ul>
<p>For reference, the page content you attempted to submit is shown below:</p>
<textarea name='content'>".htmlentities($_POST["content"])."</textarea>"));
}
// Make sure that the directory in which the page needs to be saved exists // Make sure that the directory in which the page needs to be saved exists
if(!is_dir(dirname("$env->storage_prefix$env->page.md"))) if(!is_dir(dirname("$env->storage_prefix$env->page.md")))

View File

@ -123,7 +123,7 @@ nav:not(.nav-more-menu):not(.mega-menu) > span > a { font-weight: bolder; }
.nav-divider { color: transparent; } .nav-divider { color: transparent; }
.nav-more { position: relative; background-color: var(--accent-a3); min-width: 10em; } .nav-more { position: relative; background-color: var(--accent-a3); min-width: 10em; }
.nav-more > label { font-weight: bold; } label { font-weight: bold; }
label { cursor: pointer; } label { cursor: pointer; }
.nav-more-menu { z-index: 10000; position: absolute; flex-direction: column; top: 2.6rem; right: 100000px; text-align: left; background-color: var(--accent-a2); border-top: 3px solid var(--accent-a3); border-bottom: 3px solid var(--accent-a3); } .nav-more-menu { z-index: 10000; position: absolute; flex-direction: column; top: 2.6rem; right: 100000px; text-align: left; background-color: var(--accent-a2); border-top: 3px solid var(--accent-a3); border-bottom: 3px solid var(--accent-a3); }
input[type=checkbox]:checked ~ .nav-more-menu { right: -0.2rem; box-shadow: 0.4rem 0.4rem 1rem 0 var(--shadow); } input[type=checkbox]:checked ~ .nav-more-menu { right: -0.2rem; box-shadow: 0.4rem 0.4rem 1rem 0 var(--shadow); }
@ -225,11 +225,14 @@ input[type=text], input[type=password], input[type=url], input[type=email], inpu
textarea, .fit-text-mirror { min-height: 10em; line-height: 1.3em; font-size: 1.25rem; } textarea, .fit-text-mirror { min-height: 10em; line-height: 1.3em; font-size: 1.25rem; }
textarea, textarea[name=content] + pre, textarea ~ input[type=submit], #search-box { width: calc(100% - 0.3rem); box-sizing: border-box; } textarea, textarea[name=content] + pre, textarea ~ input[type=submit], #search-box { width: calc(100% - 0.3rem); box-sizing: border-box; }
textarea ~ input[type=submit] { margin: 0.5rem 0; padding: 0.5rem; font-weight: bolder; } textarea ~ input[type=submit] { margin: 0.5rem 0; padding: 0.5rem; font-weight: bolder; }
.editform input[type=text] { width: calc(100% - 0.3rem); box-sizing: border-box; } .editform input[type=text] { width: calc(100% - 0.3rem); box-sizing: border-box; }
.editform label { margin-left: 1em; }
input.edit-page-button[type='submit'] { width: 49.5%; box-sizing: border-box; } input.edit-page-button[type='submit'] { width: 49.5%; box-sizing: border-box; }
input[type=radio] { transform: scale(2); } input[type=radio] { transform: scale(2); }
input[type=submit].large { width: 100%; box-sizing: border-box; padding: 0.5em; font-size: 1.25em; font-weight: bolder; } input[type=submit].large { width: 100%; box-sizing: border-box; padding: 0.5em; font-size: 1.25em; font-weight: bolder; }
.smartsave-restore { margin-bottom: 1em; }
.preview-message { text-align: center; } .preview-message { text-align: center; }
@media (min-width: 800px) { @media (min-width: 800px) {
.jump-to-comments { position: absolute; top: 3.5em; right: 2em; display: block; text-align: right; pointer-events: none; } .jump-to-comments { position: absolute; top: 3.5em; right: 2em; display: block; text-align: right; pointer-events: none; }