2020-09-23 22:22:39 +00:00
< ? php
/* This Source Code Form is subject to the terms of the Mozilla Public
2020-10-25 22:50:03 +00:00
* License , v2 . 0. If a copy of the MPL was not distributed with this
2020-09-23 22:22:39 +00:00
* file , You can obtain one at https :// mozilla . org / MPL / 2.0 /. */
2015-09-19 09:19:56 +00:00
register_module ([
" name " => " Page editor " ,
2021-09-03 00:23:42 +00:00
" version " => " 0.18.1 " ,
2015-09-19 09:19:56 +00:00
" author " => " Starbeamrainbowlabs " ,
" description " => " Allows you to edit pages by adding the edit and save actions. You should probably include this one. " ,
" id " => " page-edit " ,
2019-03-02 21:13:41 +00:00
" extra_data " => [
2020-01-26 21:04:39 +00:00
" diff.min.js " => " https://cdnjs.cloudflare.com/ajax/libs/jsdiff/2.2.2/diff.min.js " ,
" awesomplete.min.js " => " https://cdnjs.cloudflare.com/ajax/libs/awesomplete/1.1.5/awesomplete.min.js " ,
" awesomplete.min.css " => " https://cdnjs.cloudflare.com/ajax/libs/awesomplete/1.1.5/awesomplete.min.css "
2019-03-02 21:13:41 +00:00
],
2015-09-19 09:19:56 +00:00
" code " => function () {
2017-02-18 20:55:25 +00:00
global $settings , $env ;
2017-11-13 23:12:13 +00:00
2018-04-22 14:55:14 +00:00
2016-06-15 18:20:03 +00:00
/**
* @ api { get } ? action = edit & page = { pageName }[ & newpage = yes ] Get an editing page
* @ apiDescription Gets an editing page for a given page . If you don ' t have permission to edit the page in question , a view source pagee is returned instead .
* @ apiName EditPage
2018-04-22 14:55:14 +00:00
* @ apiGroup Editing
2016-06-15 18:20:03 +00:00
* @ apiPermission Anonymous
*
* @ apiUse PageParameter
2020-10-25 22:50:03 +00:00
* @ 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 .
2016-06-15 18:20:03 +00:00
*/
2015-09-19 09:19:56 +00:00
/*
* _ _ _
* ___ __ | ( _ ) | _
* / _ \ / _ ` | | __ |
* | __ / ( _ | | | | _
* \___ | \__ , _ | _ | \__ |
* % edit %
*/
add_action ( " edit " , function () {
2020-01-26 21:04:39 +00:00
global $pageindex , $settings , $env , $paths ;
2015-09-19 09:19:56 +00:00
2020-10-25 22:50:03 +00:00
$unknownpagename = isset ( $_GET [ " unknownpagename " ]) && strlen ( trim ( $_GET [ " unknownpagename " ])) > 0 ;
2015-11-08 21:15:08 +00:00
$filename = " $env->storage_prefix $env->page .md " ;
2017-12-10 15:17:13 +00:00
$creatingpage = ! isset ( $pageindex -> { $env -> page });
2015-09-19 09:19:56 +00:00
if (( isset ( $_GET [ " newpage " ]) and $_GET [ " newpage " ] == " true " ) or $creatingpage )
2015-09-22 13:34:18 +00:00
$title = " Creating $env->page " ;
2017-06-29 05:33:57 +00:00
else if ( isset ( $_POST [ 'preview-edit' ]) && isset ( $_POST [ 'content' ]))
$title = " Preview Edits for $env->page " ;
2020-10-25 22:50:03 +00:00
else if ( $unknownpagename )
$title = " Creating new page " ;
2015-09-19 09:19:56 +00:00
else
2015-09-22 13:34:18 +00:00
$title = " Editing $env->page " ;
2015-09-19 09:19:56 +00:00
2020-10-25 22:50:03 +00:00
$pagetext = " " ; $page_tags = " " ;
if ( isset ( $pageindex -> { $env -> page }) && ! $unknownpagename )
2015-09-19 09:19:56 +00:00
$pagetext = file_get_contents ( $filename );
2020-10-25 22:50:03 +00:00
if ( ! $unknownpagename )
$page_tags = htmlentities ( implode ( " , " , ( ! empty ( $pageindex -> { $env -> page } -> tags )) ? $pageindex -> { $env -> page } -> tags : []));
2015-09-19 09:19:56 +00:00
2017-02-11 15:22:04 +00:00
$isOtherUsersPage = false ;
if (
$settings -> user_page_prefix == mb_substr ( $env -> page , 0 , mb_strlen ( $settings -> user_page_prefix )) and // The current page is a user page of some sort
(
! $env -> is_logged_in or // the user isn't logged in.....
extract_user_from_userpage ( $env -> page ) !== $env -> user // ...or it's not under this user's own name
)
) {
$isOtherUsersPage = true ;
}
2016-04-09 09:55:44 +00:00
if (( ! $env -> is_logged_in and ! $settings -> anonedits ) or // if we aren't logged in and anonymous edits are disabled
2017-02-11 15:22:04 +00:00
! $settings -> editing or // or editing is disabled
(
2017-12-10 15:17:13 +00:00
isset ( $pageindex -> { $env -> page }) and // or if the page exists
isset ( $pageindex -> { $env -> page } -> protect ) and // the protect property exists
$pageindex -> { $env -> page } -> protect and // the protect property is true
2017-02-11 15:22:04 +00:00
! $env -> is_admin // the user isn't an admin
) or
$isOtherUsersPage // this page actually belongs to another user
2015-10-03 11:26:32 +00:00
)
2015-09-19 09:19:56 +00:00
{
2020-06-04 18:11:29 +00:00
if ( ! $creatingpage ) {
2015-09-19 09:19:56 +00:00
// The page already exists - let the user view the page source
2021-09-03 00:23:42 +00:00
$sourceViewContent = " <p> $settings->sitename does not allow anonymous users to make edits. You can view the source of $env->page_safe below, but you can't edit it. You could, however, try <a href='index.php?action=login&returnto= " . rawurlencode ( $_SERVER [ " REQUEST_URI " ]) . " '>logging in</a>.</p> \n " ;
2017-02-11 15:22:04 +00:00
2016-04-09 09:55:44 +00:00
if ( $env -> is_logged_in )
2021-09-03 00:23:42 +00:00
$sourceViewContent = " <p> $env->page_safe is protected, and you aren't an administrator or moderator. You can view the source of $env->page_safe below, but you can't edit it.</p> \n " ;
2017-02-11 15:22:04 +00:00
2017-12-31 23:17:25 +00:00
if ( ! $settings -> editing )
2021-09-03 00:23:42 +00:00
$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> " ;
2017-12-31 23:17:25 +00:00
2017-02-11 15:22:04 +00:00
if ( $isOtherUsersPage )
2021-09-03 00:23:42 +00:00
$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> " ;
2017-02-11 15:22:04 +00:00
// Append a view of the page's source
2020-10-25 22:50:03 +00:00
$sourceViewContent .= " <textarea name='content' readonly> " . htmlentities ( $pagetext ) . " </textarea> " ;
2017-02-11 15:22:04 +00:00
exit ( page_renderer :: render_main ( " Viewing source for $env->page " , $sourceViewContent ));
2015-09-19 09:19:56 +00:00
}
2020-06-04 18:11:29 +00:00
else {
2021-09-03 00:23:42 +00:00
$errorMessage = " <p>The page <code> $env->page_safe </code> does not exist, but you do not have permission to create it.</p><p>If you haven't already, perhaps you should try <a href='index.php?action=login&returnto= " . rawurlencode ( $_SERVER [ " REQUEST_URI " ]) . " '>logging in</a>.</p> \n " ;
2017-02-11 15:22:04 +00:00
if ( $isOtherUsersPage ) {
2021-09-03 00:23:42 +00:00
$errorMessage = " <p>The page <code> $env->page_safe </code> doesn't exist, but you can't create it because it's a page belonging to another user.</p> \n " ;
2017-02-11 15:22:04 +00:00
if ( ! $env -> is_logged_in )
2021-09-03 00:23:42 +00:00
$errorMessage .= " <p>You could try <a href='?action=login&returnto= " . rawurlencode ( $_SERVER [ " REQUEST_URI " ]) . " '>logging in</a>.</p> \n " ;
2017-02-11 15:22:04 +00:00
}
2015-09-19 09:19:56 +00:00
http_response_code ( 404 );
2017-02-11 15:22:04 +00:00
exit ( page_renderer :: render_main ( " 404 - $env->page " , $errorMessage ));
2015-09-19 09:19:56 +00:00
}
}
2021-09-03 00:23:42 +00:00
$content = " <h1> " . htmlentities ( $title ) . " </h1> \n " ;
2020-10-25 22:50:03 +00:00
2020-02-04 01:45:14 +00:00
if ( ! $env -> is_logged_in and $settings -> anonedits ) {
2015-09-19 09:19:56 +00:00
$content .= " <p><strong>Warning: You are not logged in! Your IP address <em>may</em> be recorded.</strong></p> " ;
}
2020-06-04 18:11:29 +00:00
if ( isset ( $_GET [ " redirected_from " ])) {
$content .= " <p><em>Redirected from <a href='?page= " . rawurlencode ( $_GET [ " redirected_from " ]) . " &redirect=no'> " . htmlentities ( $_GET [ " redirected_from " ]) . " </a></em></p> \n " ;
}
2017-06-28 05:44:05 +00:00
// Include preview, if set
if ( isset ( $_POST [ 'preview-edit' ]) && isset ( $_POST [ 'content' ])) {
// Need this for the prev-content-hash to prevent the conflict page from appearing
$old_pagetext = $pagetext ;
// set the page content to the newly edited content
$pagetext = $_POST [ 'content' ];
// Set the tags to the new tags, if needed
if ( isset ( $_POST [ 'tags' ]))
$page_tags = $_POST [ 'tags' ];
// Insert the "view" part of the page we're editing
2017-07-02 21:15:20 +00:00
$content .= " <p class='preview-message'><strong>This is only a preview, so your edits haven't been saved! Scroll down to continue editing.</strong></p> " . parse_page_source ( $pagetext );
2017-06-28 05:44:05 +00:00
}
2018-09-30 10:35:41 +00:00
$content .= " <button class='smartsave-restore' title= \" Only works if you haven't changed the editor's content already! \" >Restore Locally Saved Content</button>
2021-09-03 00:23:42 +00:00
< form method = 'post' name = 'edit-form' action = 'index.php?action=preview-edit&page=" . rawurlencode($env->page) . "' class = 'editform' >
2020-10-25 22:50:03 +00:00
< input type = 'hidden' name = 'prev-content-hash' value = '" . generate_page_hash(isset($old_pagetext) ? $old_pagetext : $pagetext) . "' /> " ;
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' /> " ;
2021-09-03 00:23:42 +00:00
$content .= " <textarea name='content' autofocus tabindex='1'> " . htmlentities ( $pagetext ) . " </textarea>
2017-06-28 05:44:05 +00:00
< pre class = 'fit-text-mirror' ></ pre >
2019-11-10 15:44:21 +00:00
< 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' />
2017-06-28 05:44:05 +00:00
< p class = 'editing-message' > $settings -> editing_message </ p >
< input name = 'preview-edit' class = 'edit-page-button' type = 'submit' value = 'Preview Changes' tabindex = '4' />
2017-07-02 20:14:50 +00:00
< input name = 'submit-edit' class = 'edit-page-button' type = 'submit' value = 'Save Page' tabindex = '3' />
2017-06-28 05:44:05 +00:00
</ form > " ;
2016-11-05 14:08:50 +00:00
// Allow tab characters in the page editor
2019-01-27 22:56:51 +00:00
page_renderer :: add_js_snippet ( " window.addEventListener('load', function(event) {
2016-11-05 14:08:50 +00:00
// Adapted from https://jsfiddle.net/2wAzx/13/
document . querySelector ( \ " [name=content] \" ).addEventListener( \" keydown \" , (event) => {
if ( event . keyCode !== 9 ) return true ;
var currentValue = event . target . value , startPos = event . target . selectionStart , endPos = event . target . selectionEnd ;
event . target . value = currentValue . substring ( 0 , startPos ) + \ " \\ t \" + currentValue.substring(endPos);
event . target . selectionStart = event . target . selectionEnd = startPos + 1 ;
event . stopPropagation (); event . preventDefault ();
return false ;
});
2016-10-30 10:06:46 +00:00
}); " );
2016-10-22 15:43:53 +00:00
2017-03-05 16:15:50 +00:00
// Utilise the mirror to automatically resize the textarea to fit it's content
2019-01-27 22:56:51 +00:00
page_renderer :: add_js_snippet ( ' function updateTextSize ( textarea , mirror , event ) {
2017-03-05 16:15:50 +00:00
let textareaFontSize = parseFloat ( getComputedStyle ( textarea ) . fontSize );
let textareaWidth = textarea . getBoundingClientRect () . width ; // - parseInt(textarea.style.padding);
mirror . style . width = `${textareaWidth}px` ;
mirror . innerText = textarea . value ;
textarea . style . height = `${mirror.offsetHeight + (textareaFontSize * 5)}px` ;
}
function trackTextSize ( textarea ) {
let mirror = textarea . nextElementSibling ;
textarea . addEventListener ( " input " , updateTextSize . bind ( null , textarea , mirror ));
updateTextSize ( textarea , mirror , null );
}
window . addEventListener ( " load " , function ( event ) {
trackTextSize ( document . querySelector ( " textarea[name=content] " ));
});
' );
2016-10-22 15:43:53 +00:00
// ~
/// ~~~ Smart saving ~~~ ///
2019-01-27 22:56:51 +00:00
page_renderer :: add_js_snippet ( ' window . addEventListener ( " load " , function ( event ) {
2017-11-21 20:07:50 +00:00
" use strict " ;
2016-11-05 14:08:50 +00:00
// Smart saving
2017-11-21 20:07:50 +00:00
let getSmartSaveKey = function () { return document . querySelector ( " main h1 " ) . innerHTML . replace ( " Creating " , " " ) . replace ( " Editing " , " " ) . trim (); }
2016-11-05 14:08:50 +00:00
// Saving
document . querySelector ( " textarea[name=content] " ) . addEventListener ( " keyup " , function ( event ) { window . localStorage . setItem ( getSmartSaveKey (), event . target . value ) });
// Loading
2017-11-21 20:07:50 +00:00
var editor = document . querySelector ( " textarea[name=content] " );
let smartsave_restore = function () {
2016-11-05 14:08:50 +00:00
editor . value = localStorage . getItem ( getSmartSaveKey ());
2017-11-21 20:07:50 +00:00
}
if ( editor . value . length === 0 ) // Don\'t restore if there\'s data in the editor already
smartsave_restore ();
document . querySelector ( " .smartsave-restore " ) . addEventListener ( " click " , function ( event ) {
event . stopPropagation ();
event . preventDefault ();
smartsave_restore ();
2016-11-05 14:08:50 +00:00
});
2016-10-30 10:06:46 +00:00
}); ' );
2020-01-26 21:04:39 +00:00
// Why would anyone want this disabled?
if ( $settings -> editing_tags_autocomplete ) {
page_renderer :: add_js_link ( " $paths->extra_data_directory /page-edit/awesomplete.min.js " );
page_renderer :: add_js_snippet ( ' window . addEventListener ( " load " , async ( event ) => {
// FUTURE: Optionally cache this?
let response = await fetch ( " ?action=list-tags&format=text " );
if ( ! response . ok ) {
console . warn ( `Warning: Failed to fetch tags list with status code ${response.status} ${response.statusText}` );
return ;
}
let tags = ( await response . text ()) . split ( " \n " );
console . log ( tags );
window . input_tags_completer = new Awesomplete (
document . querySelector ( " #tags " ), {
list : tags ,
filter : function ( text , input ) {
console . log ( arguments );
// Avoid suggesting tags that are already present
if ( input . split ( / , \s */ ) . includes ( text . value )) return false ;
return Awesomplete . FILTER_CONTAINS ( text , input . match ( / [ ^ ,] * $ / )[ 0 ]);
},
item : function ( text , input ) {
return Awesomplete . ITEM ( text , input . match ( / [ ^ ,] * $ / )[ 0 ]);
},
replace : function ( text ) {
var before = this . input . value . match ( /^.+ , \s *|/ )[ 0 ];
this . input . value = before + text + " , " ;
}
}
);
});
' );
$content .= " <link rel= \" stylesheet \" href= \" $paths->extra_data_directory /page-edit/awesomplete.min.css \" /> " ;
}
2016-10-22 15:43:53 +00:00
2015-09-19 09:19:56 +00:00
exit ( page_renderer :: render_main ( " $title - $settings->sitename " , $content ));
});
2017-07-03 17:33:25 +00:00
/**
2020-08-18 12:49:16 +00:00
* @ api { post } ? action = preview - edit & page = { pageName }[ & newpage = yes ] Get a preview of an edit to a page
2017-07-03 17:33:25 +00:00
* @ apiDescription Gets a preview of the current edit state of a given page
* @ apiName PreviewPage
2018-02-14 22:19:11 +00:00
* @ apiGroup Editing
2017-07-03 17:33:25 +00:00
* @ apiPermission Anonymous
*
* @ apiUse PageParameter
* @ apiParam { string } newpage Set to 'yes' if a new page is being created .
* @ apiParam { string } preview - edit Set to a value to preview an edit of a page .
*/
/*
*
* ██████ ██████ ███████ ██ ██ ██ ███████ ██ ██
* ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
* ██████ ██████ █████ ██ ██ ██ █████ ██ █ ██
* ██ ██ ██ ██ ██ ██ ██ ██ ██ ███ ██
* ██ ██ ██ ███████ ████ ██ ███████ ███ ███
*
* ███████ ██████ ██ ████████
* ██ ██ ██ ██ ██
* █████ ██ ██ ██ ██
* ██ ██ ██ ██ ██
* ███████ ██████ ██ ██
*
*/
add_action ( " preview-edit " , function () {
global $pageindex , $settings , $env , $actions ;
if ( isset ( $_POST [ 'preview-edit' ]) && isset ( $_POST [ 'content' ])) {
// preview changes
get_object_vars ( $actions )[ 'edit' ]();
}
else {
// save page
get_object_vars ( $actions )[ 'save' ]();
}
});
2018-04-22 14:55:14 +00:00
/**
* @ api { post } ? action = acquire - edit - key & page = { pageName } Acquire an edit key for a page
* @ apiDescription Returns an edit key that can be used to programmatically save an edit to a page . It does _not_ necessarily mean that such an edit will be saved . For example , editing might be disabled , or you might not have permission to save an edit on a particular page .
* @ apiName AcquireEditKey
* @ apiGroup Editing
* @ apiPermission Anonymous
*
2019-08-08 17:29:49 +00:00
* @ apiUse PageParameter
2020-10-25 22:50:03 +00:00
* @ 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 ) .
2018-04-22 14:55:14 +00:00
*/
/*
* █████ ██████ ██████ ██ ██ ██ ██████ ███████
* ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
* ███████ ██ ██ ██ ██ ██ ██ ██████ █████ █████
* ██ ██ ██ ██ ▄▄ ██ ██ ██ ██ ██ ██ ██
* ██ ██ ██████ ██████ ██████ ██ ██ ██ ███████
* ▀▀
*
* ███████ ██████ ██ ████████
* ██ ██ ██ ██ ██
* █████ ██ ██ ██ ██ █████
* ██ ██ ██ ██ ██
* ███████ ██████ ██ ██
*
* ██ ██ ███████ ██ ██
* ██ ██ ██ ██ ██
* █████ █████ ████
* ██ ██ ██ ██
* ██ ██ ███████ ██
*/
add_action ( " acquire-edit-key " , function () {
global $env ;
if ( ! file_exists ( $env -> page_filename )) {
http_response_code ( 404 );
header ( " content-type: text/plain " );
exit ( " Error: The page ' $env->page ' couldn't be found. " );
}
$format = $_GET [ " format " ] ? ? " text " ;
$page_hash = generate_page_hash ( file_get_contents ( $env -> page_filename ));
switch ( $format ) {
case " text " :
header ( " content-type: text/plain " );
exit ( " $env->page\t $page_hash " );
case " json " :
$result = new stdClass ();
$result -> page = $env -> page ;
$result -> key = $page_hash ;
header ( " content-type: application/json " );
exit ( json_encode ( $result ));
default :
http_response_code ( 406 );
header ( " content-type: text/plain " );
exit ( " Error: The format $format is not currently known. Supported formats: text, json. Default: text. \n Think this is a bug? Open an issue at https://github.com/sbrl/Pepperminty-Wiki/issues/new " );
}
});
2016-06-15 18:20:03 +00:00
/**
2018-04-22 14:57:11 +00:00
* @ api { post } ? action = save & page = { pageName } Save an edit to a page
2016-06-15 18:20:03 +00:00
* @ apiDescription Saves an edit to a page . If an edit conflict is encountered , then a conflict resolution page is returned instead .
2018-04-22 14:57:11 +00:00
* @ apiName SavePage
2018-04-22 14:55:14 +00:00
* @ apiGroup Editing
2016-06-15 18:20:03 +00:00
* @ apiPermission Anonymous
*
* @ apiUse PageParameter
* @ apiParam { string } newpage GET only . Set to 'yes' to indicate that this is a new page that is being saved . Only affects the HTTP response code you recieve upon success .
* @ apiParam { string } content POST only . The new content to save to the given filename .
* @ apiParam { string } tags POST only . A comma - separated list of tags to assign to the current page . Will replace the existing list of tags , if any are present .
* @ apiParam { string } prev - content - hash POST only . The hash of the original content before editing . If this hash is found to be different to a hash computed of the currentl saved content , a conflict resolution page will be returned instead of saving the provided content .
*
* @ apiError UnsufficientPermissionError You don ' t currently have sufficient permissions to save an edit .
*/
2016-08-18 13:07:00 +00:00
2015-09-19 09:19:56 +00:00
/*
*
* ___ __ ___ _____
* / __ |/ _ ` \ \ / / _ \
* \__ \ ( _ | | \ V / __ /
* | ___ / \__ , _ | \_ / \___ |
* % save %
*/
add_action ( " save " , function () {
2020-10-25 22:50:03 +00:00
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
2021-09-02 20:34:40 +00:00
if ( isset ( $_POST [ " page " ])) {
2020-10-25 22:50:03 +00:00
$env -> page = $_POST [ " page " ];
2021-09-02 20:34:40 +00:00
$env -> page_safe = htmlentities ( $env -> page );
}
2016-04-09 16:04:43 +00:00
2015-09-19 09:19:56 +00:00
if ( ! $settings -> editing )
{
2018-04-22 16:58:48 +00:00
header ( " x-failure-reason: editing-disabled " );
2017-12-10 15:17:13 +00:00
header ( " location: index.php?page= " . rawurlencode ( $env -> page ));
2015-09-19 09:19:56 +00:00
exit ( page_renderer :: render_main ( " Error saving edit " , " <p>Editing is currently disabled on this wiki.</p> " ));
}
2015-09-22 13:34:18 +00:00
if ( ! $env -> is_logged_in and ! $settings -> anonedits )
2015-09-19 09:19:56 +00:00
{
http_response_code ( 403 );
2017-12-10 15:17:13 +00:00
header ( " refresh: 5; url=index.php?page= " . rawurlencode ( $env -> page ));
2018-04-22 16:58:48 +00:00
header ( " x-login-required: yes " );
2015-09-19 09:19:56 +00:00
exit ( " You are not logged in, so you are not allowed to save pages on $settings->sitename . Redirecting in 5 seconds.... " );
}
2015-10-03 11:41:35 +00:00
if ((
2017-12-10 15:17:13 +00:00
isset ( $pageindex -> { $env -> page }) and
isset ( $pageindex -> { $env -> page } -> protect ) and
$pageindex -> { $env -> page } -> protect
2015-10-03 11:41:35 +00:00
) and ! $env -> is_admin )
2015-09-30 10:26:22 +00:00
{
http_response_code ( 403 );
2017-12-10 15:17:13 +00:00
header ( " refresh: 5; url=index.php?page= " . rawurlencode ( $env -> page ));
exit ( htmlentities ( $env -> page ) . " is protected, and you aren't logged in as an administrator or moderator. Your edit was not saved. Redirecting in 5 seconds... " );
2015-09-30 10:26:22 +00:00
}
2015-09-19 09:19:56 +00:00
if ( ! isset ( $_POST [ " content " ]))
{
http_response_code ( 400 );
2017-12-10 15:17:13 +00:00
header ( " refresh: 5; url=index.php?page= " . rawurlencode ( $env -> page ));
2015-09-19 09:19:56 +00:00
exit ( " Bad request: No content specified. " );
}
2020-10-25 22:50:03 +00:00
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 > " ));
}
2015-09-19 09:19:56 +00:00
// Make sure that the directory in which the page needs to be saved exists
2015-11-08 21:15:08 +00:00
if ( ! is_dir ( dirname ( " $env->storage_prefix $env->page .md " )))
2015-09-19 09:19:56 +00:00
{
// Recursively create the directory if needed
2016-08-18 13:07:00 +00:00
mkdir ( dirname ( " $env->storage_prefix $env->page .md " ), 0775 , true );
2015-09-19 09:19:56 +00:00
}
2015-10-25 11:26:35 +00:00
// Read in the new page content
2015-10-04 11:42:53 +00:00
$pagedata = $_POST [ " content " ];
2016-06-03 07:45:17 +00:00
// We don't need to santise the input here as Parsedown has an
// option that does this for us, and is _way_ more intelligent about
// it.
2015-09-19 09:19:56 +00:00
2016-06-03 07:45:17 +00:00
// Read in the new page tags, so long as there are actually some
// tags to read in
2015-11-01 14:31:51 +00:00
$page_tags = [];
2020-02-04 01:45:14 +00:00
if ( strlen ( trim ( $_POST [ " tags " ])) > 0 ) {
2015-11-01 14:31:51 +00:00
$page_tags = explode ( " , " , $_POST [ " tags " ]);
// Trim off all the whitespace
2020-02-04 01:45:14 +00:00
foreach ( $page_tags as & $tag ) {
2015-11-01 14:31:51 +00:00
$tag = trim ( $tag );
2020-02-04 01:45:14 +00:00
}
// Ignore empty tags
$page_tags = array_filter ( $page_tags , function ( $value ) {
return ! is_null ( $value ) && $value !== '' ;
});
2015-11-01 14:31:51 +00:00
}
2015-10-25 11:26:35 +00:00
2016-04-09 16:04:43 +00:00
// Check for edit conflicts
2016-08-28 12:41:18 +00:00
if ( ! empty ( $pageindex -> { $env -> page }) && file_exists ( $env -> storage_prefix . $pageindex -> { $env -> page } -> filename ))
2016-04-09 16:04:43 +00:00
{
2016-08-28 12:41:18 +00:00
$existing_content_hash = sha1_file ( $env -> storage_prefix . $pageindex -> { $env -> page } -> filename );
if ( isset ( $_POST [ " prev-content-hash " ]) and
$existing_content_hash != $_POST [ " prev-content-hash " ])
2016-04-09 16:04:43 +00:00
{
2016-08-28 12:41:18 +00:00
$existingPageData = htmlentities ( file_get_contents ( $env -> storage_prefix . $env -> storage_prefix . $pageindex -> { $env -> page } -> filename ));
// An edit conflict has occurred! We should get the user to fix it.
2021-09-03 00:23:42 +00:00
$content = " <h1>Resolving edit conflict - $env->page_safe </h1> " ;
2016-08-28 12:41:18 +00:00
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> " ;
}
2021-09-03 00:23:42 +00:00
$content .= " <p>An edit conflict has arisen because someone else has saved an edit to $env->page_safe since you started editing it. Both texts are shown below, along the differences between the 2 conflicting revisions. To continue, please merge your changes with the existing content. Note that only the text in the existing content box will be kept when you press the \" Resolve Conflict \" button at the bottom of the page.</p>
2016-08-28 12:41:18 +00:00
2021-09-03 00:23:42 +00:00
< form method = 'post' action = 'index.php?action=save&page=" . rawurlencode($env->page) . "&action=save' class = 'editform' >
2016-08-28 12:41:18 +00:00
< h2 > Existing content </ h2 >
< textarea id = 'original-content' name = 'content' autofocus tabindex = '1' > $existingPageData </ textarea >
< h2 > Differences </ h2 >
< div id = 'highlighted-diff' class = 'highlighted-diff' ></ div >
<!--< pre class = 'highlighted-diff-wrapper' >< code id = 'highlighted-diff' ></ code ></ pre >-->
< h2 > Your content </ h2 >
2021-09-03 00:23:42 +00:00
< textarea id = 'new-content' > " .htmlentities( $pagedata ). " </ textarea >
2017-07-29 09:50:42 +00:00
< input type = 'text' name = 'tags' value = '" . htmlentities($_POST["tags"]) . "' 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' />
2016-08-28 12:41:18 +00:00
< p class = 'editing_message' > $settings -> editing_message </ p >
< input name = 'submit-edit' type = 'submit' value = 'Resolve Conflict' tabindex = '3' />
</ form > " ;
// Insert a reference to jsdiff to generate the diffs
2020-01-26 21:04:39 +00:00
$diff_script = <<< 'DIFFSCRIPT'
2016-05-01 14:06:27 +00:00
window . addEventListener ( " load " , function ( event ) {
var destination = document . getElementById ( " highlighted-diff " ),
2016-08-28 12:41:18 +00:00
diff = JsDiff . diffWords ( document . getElementById ( " original-content " ) . value , document . getElementById ( " new-content " ) . value ),
output = " " ;
2016-05-01 14:06:27 +00:00
diff . forEach ( function ( change ) {
var classes = " token " ;
if ( change . added ) classes += " diff-added " ;
if ( change . removed ) classes += " diff-removed " ;
output += `<span class='${classes}'>${change.value}</span>` ;
});
destination . innerHTML = output ;
});
DIFFSCRIPT ;
2020-01-26 21:04:39 +00:00
page_renderer :: add_js_link ( " $paths->extra_data_directory /page-edit/diff.min.js " );
page_renderer :: add_js_snippet ( $diff_script );
2016-08-28 12:41:18 +00:00
2018-04-22 16:58:48 +00:00
header ( " x-failure-reason: edit-conflict " );
2016-08-28 12:41:18 +00:00
exit ( page_renderer :: render_main ( " Edit Conflict - $env->page - $settings->sitename " , $content ));
}
2016-04-09 16:04:43 +00:00
}
2016-08-19 09:00:03 +00:00
// -----~~~==~~~-----
2015-11-01 09:30:29 +00:00
// Update the inverted search index
2019-08-22 20:38:17 +00:00
if ( module_exists ( " feature-search " )) {
// Construct an index for the old and new page content
$oldindex = [];
$oldpagedata = " " ; // We need the old page data in order to pass it to the preprocessor
if ( file_exists ( " $env->storage_prefix $env->page .md " )) {
$oldpagedata = file_get_contents ( " $env->storage_prefix $env->page .md " );
$oldindex = search :: index_generate ( $oldpagedata );
}
$newindex = search :: index_generate ( $pagedata );
// Compare the indexes of the old and new content
$additions = [];
$removals = [];
search :: index_compare ( $oldindex , $newindex , $additions , $removals );
// Load in the inverted index
search :: invindex_load ( $paths -> searchindex );
// Merge the changes into the inverted index
search :: invindex_merge ( ids :: getid ( $env -> page ), $additions , $removals );
// Save the inverted index back to disk
search :: invindex_close ();
2016-01-16 13:24:13 +00:00
}
2021-09-03 00:23:42 +00:00
2016-08-19 09:00:03 +00:00
// -----~~~==~~~-----
2015-11-01 09:30:29 +00:00
2015-11-08 21:15:08 +00:00
if ( file_put_contents ( " $env->storage_prefix $env->page .md " , $pagedata ) !== false )
2015-09-19 09:19:56 +00:00
{
// Make sure that this page's parents exist
2017-12-10 15:17:13 +00:00
check_subpage_parents ( $env -> page );
2015-09-19 09:19:56 +00:00
2015-10-04 12:08:59 +00:00
// Update the page index
2017-12-10 15:17:13 +00:00
if ( ! isset ( $pageindex -> { $env -> page }))
2015-09-19 09:19:56 +00:00
{
2017-12-10 15:17:13 +00:00
$pageindex -> { $env -> page } = new stdClass ();
$pageindex -> { $env -> page } -> filename = " $env->page .md " ;
2015-09-19 09:19:56 +00:00
}
2017-12-10 15:17:13 +00:00
$pageindex -> { $env -> page } -> size = strlen ( $_POST [ " content " ]);
$pageindex -> { $env -> page } -> lastmodified = time ();
2015-09-30 10:26:22 +00:00
if ( $env -> is_logged_in )
2018-03-18 16:37:25 +00:00
$pageindex -> { $env -> page } -> lasteditor = $env -> user ;
2016-08-21 20:02:36 +00:00
else // TODO: Add an option to record the user's IP here instead
2018-03-18 16:37:25 +00:00
$pageindex -> { $env -> page } -> lasteditor = " anonymous " ;
2017-12-10 15:17:13 +00:00
$pageindex -> { $env -> page } -> tags = $page_tags ;
2015-09-19 09:19:56 +00:00
2015-10-04 07:59:35 +00:00
// A hack to resave the pagedata if the preprocessors have
// changed it. We need this because the preprocessors *must*
// run _after_ the pageindex has been updated.
$pagedata_orig = $pagedata ;
2015-10-03 09:18:26 +00:00
// Execute all the preprocessors
foreach ( $save_preprocessors as $func )
2017-12-10 15:17:13 +00:00
$func ( $pageindex -> { $env -> page }, $pagedata , $oldpagedata );
2015-10-03 09:18:26 +00:00
2015-10-04 07:59:35 +00:00
if ( $pagedata !== $pagedata_orig )
2015-11-08 21:15:08 +00:00
file_put_contents ( " $env->storage_prefix $env->page .md " , $pagedata );
2015-10-04 07:59:35 +00:00
2019-06-01 14:55:48 +00:00
save_pageindex ();
2015-09-19 09:19:56 +00:00
if ( isset ( $_GET [ " newpage " ]))
http_response_code ( 201 );
else
http_response_code ( 200 );
2015-11-01 14:26:13 +00:00
// header("content-type: text/plain");
2017-12-10 15:17:13 +00:00
header ( " location: index.php?page= " . rawurlencode ( $env -> page ) . " &edit_status=success&redirect=no " );
2015-09-19 09:19:56 +00:00
exit ();
}
else
{
2018-04-22 16:58:48 +00:00
header ( " x-failure-reason: server-error " );
2015-09-19 09:19:56 +00:00
http_response_code ( 507 );
2015-11-01 14:26:13 +00:00
exit ( page_renderer :: render_main ( " Error saving page - $settings->sitename " , " <p> $settings->sitename failed to write your changes to the server's disk. Your changes have not been saved, but you might be able to recover your edit by pressing the back button in your browser.</p>
2021-09-03 00:23:42 +00:00
< p > Please tell the administrator of this wiki ( " . htmlentities( $settings->admindetails_name ) . " ) about this problem .</ p > " ));
2015-09-19 09:19:56 +00:00
}
});
2015-12-21 08:45:38 +00:00
add_help_section ( " 15-editing " , " Editing " , " <p>To edit a page on $settings->sitename , click the edit button on the top bar. Note that you will probably need to be logged in. If you do not already have an account you will need to ask $settings->sitename 's administrator for an account since there is no registration form. Note that the $settings->sitename 's administrator may have changed these settings to allow anonymous edits.</p>
< p > Editing is simple . The edit page has a sizeable box that contains a page ' s current contents . Once you are done altering it , add or change the comma separated list of tags in the field below the editor and then click save page .</ p >
< p > A reference to the syntax that $settings -> sitename supports can be found below .</ p > " );
2017-02-18 20:55:25 +00:00
2018-05-11 22:29:43 +00:00
add_help_section ( " 17-user-pages " , " User Pages " , " <p>If you are logged in, $settings->sitename allocates you your own user page that only you can edit. On $settings->sitename , user pages are sub-pages of the <a href='?page= " . rawurlencode ( $settings -> user_page_prefix ) . " '> " . htmlentities ( $settings -> user_page_prefix ) . " </a> page, and each user page can have a nested structure of pages underneath it, just like a normal page. Your user page is located at <a href='?page= " . rawurlencode ( get_user_pagename ( $env -> user )) . " '> " . htmlentities ( get_user_pagename ( $env -> user )) . " </a>. " .
( module_exists ( " page-user-list " ) ? " You can see a list of all the users on $settings->sitename and visit their user pages on the <a href='?action=user-list'>user list</a>. " : " " )
. " </p> " );
2015-09-19 09:19:56 +00:00
}
]);
2018-05-25 11:07:51 +00:00
/**
* Generates a unique hash of a page ' s content for edit conflict detection
* purposes .
* @ param string $page_data The page text to hash .
* @ return string A hash of the given page text .
*/
2018-04-22 14:55:14 +00:00
function generate_page_hash ( $page_data ) {
return sha1 ( $page_data );
}
2015-09-19 09:19:56 +00:00
?>