diff --git a/404.php b/404.php index 04366812..855cd0ad 100644 --- a/404.php +++ b/404.php @@ -4,9 +4,41 @@ include "header.php"; -echo new \OOUI\MessageWidget( [ - 'type' => 'error', - 'label' => 'Page not found. The wiki you are looking for may have been deleted.' -] ); +$redirect = false; + +// Check for redirect +$uri = $_SERVER['REQUEST_URI']; +if ( preg_match( '`/wikis/([0-9a-f]{10,32})/`', $uri, $matches, PREG_OFFSET_CAPTURE ) !== false ) { + $wiki = $matches[1][0]; + $offset = $matches[1][1]; + $wikiData = get_wiki_data( $wiki ); + // Follow up to 10 redirect steps + $i = 0; + while ( $wikiData['redirect'] && $i < 10 ) { + $redirect = $wikiData['redirect']; + $wikiData = get_wiki_data( $redirect ); + $i++; + } + $redirectUri = + substr( $uri, 0, $offset ) . + $redirect . + substr( $uri, $offset + strlen( $wiki ) ); +} + +if ( $redirect ) { + echo new \OOUI\MessageWidget( [ + 'type' => 'info', + 'icon' => 'articleRedirect', + 'label' => new \OOUI\HtmlSnippet( + 'This wiki has been deleted and the following wiki was selected as a direct replacement: ' . + '' . substr( $redirect, 0, 10 ) . '' + ) + ] ); +} else { + echo new \OOUI\MessageWidget( [ + 'type' => 'error', + 'label' => 'Page not found. The wiki you are looking for may have been deleted.' + ] ); +} include "footer.html"; diff --git a/delete.php b/delete.php index 906ac1ac..50a25681 100644 --- a/delete.php +++ b/delete.php @@ -11,57 +11,140 @@ die( '

You are not allowed to delete this wiki.

' ); } -if ( !isset( $_POST['confirm' ] ) ) { - $patches = format_patch_list( $wikiData['patchList'], $wikiData['branch'] ); - $linkedTasks = format_linked_tasks( $wikiData['linkedTaskList'] ); - $creator = $wikiData[ 'creator' ] ?? ''; +if ( !$wikiData['deleted'] ) { + if ( !isset( $_POST['confirm' ] ) ) { + $patches = format_patch_list( $wikiData['patchList'], $wikiData['branch'] ); + $linkedTasks = format_linked_tasks( $wikiData['linkedTaskList'] ); + $creator = $wikiData[ 'creator' ] ?? ''; - echo '' . - '' . - '' . - '' . - '' . - '' . - ( $useOAuth ? '' : '' ) . - '' . - '' . - '' . - '' . - '' . - '' . - ( $useOAuth ? '' : '' ) . - '' . - '
WikiPatches
✓=Merged ✗=Abandoned
Linked tasks
✓=Resolved ✗=Declined/Invalid
TimeCreator
' . substr( $wiki, 0, 10 ) . '' . $patches . '' . $linkedTasks . '' . date( 'Y-m-d H:i:s', $wikiData[ 'created' ] ) . '' . ( $creator ? user_link( $creator ) : '?' ) . '
'; + echo '' . + '' . + '' . + '' . + '' . + '' . + ( $useOAuth ? '' : '' ) . + '' . + '' . + '' . + '' . + '' . + '' . + ( $useOAuth ? '' : '' ) . + '' . + '
WikiPatches
✓=Merged ✗=Abandoned
Linked tasks
✓=Resolved ✗=Declined/Invalid
TimeCreator
' . substr( $wiki, 0, 10 ) . '' . $patches . '' . $linkedTasks . '' . date( 'Y-m-d H:i:s', $wikiData[ 'created' ] ) . '' . ( $creator ? user_link( $creator ) : '?' ) . '
'; - echo '
' . - '

Are you sure you want to delete this wiki?

' . - '

This cannot be undone.

' . - new OOUI\ButtonInputWidget( [ - 'type' => 'submit', - 'name' => 'confirm', - 'label' => 'Delete', - 'flags' => [ 'primary', 'destructive' ] - ] ) . - new OOUI\HiddenInputWidget( [ - 'name' => 'csrf_token', - 'value' => get_csrf_token(), - ] ) . - '
'; - die(); -} + $username = $user ? $user->username : null; + $wikilist = [ + [ + 'data' => '', + 'label' => 'None', + ] + ]; + $stmt = $mysqli->prepare( ' + SELECT wiki, creator, UNIX_TIMESTAMP( created ) created + FROM wikis + WHERE !deleted + ORDER BY IF( creator = ?, 1, 0 ) DESC, created DESC + ' ); + if ( !$stmt ) { + die( $mysqli->error ); + } + $stmt->bind_param( 's', $username ); + $stmt->execute(); + $results = $stmt->get_result(); + if ( !$results ) { + die( $mysqli->error ); + } + $shownMyWikis = false; + $shownOtherWikis = false; + while ( $data = $results->fetch_assoc() ) { + if ( $data[ 'wiki' ] === $wiki ) { + continue; + } + $creator = $data[ 'creator' ] ?? ''; + if ( !$shownMyWikis && $creator === $username ) { + $wikilist[] = [ 'optgroup' => 'My wikis' ]; + $shownMyWikis = true; + } + if ( $shownMyWikis && !$shownOtherWikis && $creator !== $username ) { + $wikilist[] = [ 'optgroup' => 'Other wikis' ]; + $shownOtherWikis = true; + } + $wikilist[] = [ + 'data' => $data[ 'wiki' ], + 'label' => substr( $data[ 'wiki' ], 0, 10 ) . ' - ' . $data[ 'creator' ] . ' (' . date( 'Y-m-d H:i:s', $data[ 'created' ] ) . ')', + ]; + } + echo new OOUI\FormLayout( [ + 'method' => 'POST', + 'items' => [ + new OOUI\FieldsetLayout( [ + 'label' => new OOUI\HtmlSnippet( + '
Are you sure you want to delete this wiki? This cannot be undone.' + ), + 'items' => array_filter( [ + count( $wikilist ) > 1 ? + new OOUI\FieldLayout( + new OOUI\DropdownInputWidget( [ + 'name' => 'redirect', + 'options' => $wikilist, + ] ), + [ + 'label' => 'Leave a redirect to another wiki (optional):', + 'align' => 'left', + ] + ) : + null, + new OOUI\FieldLayout( + new OOUI\ButtonInputWidget( [ + 'type' => 'submit', + 'name' => 'confirm', + 'label' => 'Delete', + 'flags' => [ 'primary', 'destructive' ] + ] ), + [ + 'label' => ' ', + 'align' => 'left', + ] + ), + new OOUI\FieldLayout( + new OOUI\HiddenInputWidget( [ + 'name' => 'csrf_token', + 'value' => get_csrf_token(), + ] ) + ), + ] ) + ] ) + ] + ] ); -if ( !isset( $_POST['csrf_token'] ) || !check_csrf_token( $_POST['csrf_token'] ) ) { - die( "Invalid session." ); -} + } else { + if ( !isset( $_POST['csrf_token'] ) || !check_csrf_token( $_POST['csrf_token'] ) ) { + die( "Invalid session." ); + } + + $redirect = $_POST['redirect'] ?: null; + + ob_implicit_flush( true ); -ob_implicit_flush( true ); + echo '
'; + $error = delete_wiki( $wiki, $redirect ); + echo '
'; -echo '
'; -$error = delete_wiki( $wiki ); -echo '
'; + if ( $error ) { + die( '

Error deleting wiki:
' . htmlentities( $error ) . '

' ); + } -if ( $error ) { - die( '

Error deleting wiki:
' . htmlentities( $error ) . '

' ); -} else { + // Refresh wiki data + $wikiData = get_wiki_data( $wiki ); + } +} + +if ( $wikiData['deleted'] ) { echo '

Wiki deleted.

'; } + +if ( $wikiData['redirect'] ) { + echo '

Redirected to ' . $wikiData['redirect'] . '.

'; +} diff --git a/includes.php b/includes.php index 9ef3eba3..682b4ffe 100644 --- a/includes.php +++ b/includes.php @@ -77,7 +77,7 @@ function get_wiki_data( string $wiki ): array { global $mysqli; $stmt = $mysqli->prepare( ' - SELECT wiki, creator, UNIX_TIMESTAMP( created ) created, patches, branch, announcedTasks, timeToCreate, deleted + SELECT wiki, creator, UNIX_TIMESTAMP( created ) created, patches, branch, announcedTasks, timeToCreate, deleted, redirect FROM wikis WHERE wiki = ? ' ); if ( !$stmt ) { @@ -316,7 +316,11 @@ function shell( $cmd, array $env = [] ): ?string { return $error ? null : $process->getOutput(); } -function delete_wiki( string $wiki ): int { +function is_valid_hash( string $hash ): bool { + return preg_match( '/^[0-9a-f]{10,32}$/', $hash ) !== false; +} + +function delete_wiki( string $wiki, ?string $redirect = null ): ?string { global $mysqli; $wikiData = get_wiki_data( $wiki ); @@ -325,7 +329,7 @@ function delete_wiki( string $wiki ): int { return 'Wiki already deleted.'; } - $error = shell_echo( __DIR__ . '/deletewiki.sh', + $errorCode = shell_echo( __DIR__ . '/deletewiki.sh', [ 'PATCHDEMO' => __DIR__, 'WIKI' => $wiki @@ -346,16 +350,20 @@ function delete_wiki( string $wiki ): int { ); } + if ( $redirect && !is_valid_hash( $redirect ) ) { + $redirect = null; + } + $stmt = $mysqli->prepare( ' UPDATE wikis - SET deleted = 1 + SET deleted = 1, redirect = ? WHERE wiki = ? ' ); - $stmt->bind_param( 's', $wiki ); + $stmt->bind_param( 'ss', $redirect, $wiki ); $stmt->execute(); $stmt->close(); - return $error; + return $errorCode ? 'Delete script failed.' : null; } $requestCache = []; diff --git a/sql/patchdemo.sql b/sql/patchdemo.sql index c78762b9..1d213cc8 100644 --- a/sql/patchdemo.sql +++ b/sql/patchdemo.sql @@ -42,3 +42,6 @@ ALTER TABLE `tasks` ALTER TABLE `wikis` ADD COLUMN IF NOT EXISTS `branch` VARCHAR(64) NOT NULL AFTER `patches`; + +ALTER TABLE `wikis` + ADD COLUMN IF NOT EXISTS `redirect` VARCHAR(32) NULL AFTER `deleted`;