Moved forum read marking code into the router.

This commit is contained in:
flash 2019-12-15 02:59:19 +01:00
parent c7f06979b2
commit 0f1b8ee6e4
20 changed files with 120 additions and 49 deletions

View file

@ -66,6 +66,7 @@
text-decoration: none;
transition: color .2s;
padding: 2px 5px;
transition: opacity .2s;
&:hover,
&:focus {
@ -76,5 +77,9 @@
&:not(:last-child) {
margin-right: 5px;
}
&[disabled] {
opacity: .4;
}
}
}

View file

@ -34,6 +34,7 @@
min-width: auto;
}
&[disabled],
&--busy {
opacity: .4;
}

View file

@ -68,3 +68,51 @@ function updateCSRF(token: string): void {
elements[i].value = token;
}
}
function handleDataRequestMethod(elem: HTMLAnchorElement, method: string, url: string): void {
const split: string[] = url.split('?', 2),
target: string = split[0],
query: string = split[1] || null;
if(elem.getAttribute('disabled'))
return;
elem.setAttribute('disabled', 'disabled');
const xhr: XMLHttpRequest = new XMLHttpRequest;
xhr.onreadystatechange = function(ev) {
if(xhr.readyState !== 4)
return;
elem.removeAttribute('disabled');
if(xhr.status === 301 || xhr.status === 302 || xhr.status === 307 || xhr.status === 308) {
location.assign(xhr.getResponseHeader('X-Misuzu-Location'));
return;
}
if(xhr.status >= 400 && xhr.status <= 599) {
alert(xhr.responseText);
return;
}
};
xhr.open(method, target);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.setRequestHeader('X-Misuzu-CSRF', getCSRFToken());
xhr.setRequestHeader('X-Misuzu-XHR', '1');
xhr.send(query);
}
function initDataRequestMethod(): void {
const links: HTMLCollection = document.links;
for(let i = 0; i < links.length; i++) {
let elem: HTMLAnchorElement = links[i] as HTMLAnchorElement;
if(!elem.href || !elem.dataset || !elem.dataset.mszMethod)
continue;
elem.onclick = function(ev) {
ev.preventDefault();
handleDataRequestMethod(elem, elem.dataset.mszMethod, elem.href);
};
}
}

View file

@ -69,6 +69,7 @@ document.addEventListener('DOMContentLoaded', () => {
urlRegistryInit();
userInit();
userRelationsInit();
initDataRequestMethod();
const changelogChangeAction: HTMLDivElement = document.querySelector('.changelog__change__action') as HTMLDivElement;

View file

@ -8,13 +8,7 @@ $forumId = !empty($_GET['f']) && is_string($_GET['f']) ? (int)$_GET['f'] : 0;
switch($indexMode) {
case 'mark':
$markEntireForum = $forumId === 0;
if(user_session_active() && CSRF::validateRequest()) {
forum_mark_read($markEntireForum ? null : $forumId, user_session_current('user_id', 0));
}
url_redirect($markEntireForum ? 'forum-index' : 'forum-category', ['forum' => $forumId]);
url_redirect($forumId < 1 ? 'forum-mark-global' : 'forum-mark-single', ['forum' => $forumId]);
break;
default:

View file

@ -15,27 +15,28 @@ $router = new Router;
$router->setInstance();
$router->addRoutes(
// Home
Route::get('/', Handler::call('index@HomeHandler')),
Route::get('/', Handler::call('index@Home')),
// Info
Route::get('/info', Handler::call('index@InfoHandler')),
Route::get('/info/([A-Za-z0-9_/]+)', true, Handler::call('page@InfoHandler')),
Route::get('/info', Handler::call('index@Info')),
Route::get('/info/([A-Za-z0-9_/]+)', true, Handler::call('page@Info')),
// Forum
Route::create(['GET', 'POST'], '/forum/mark-as-read', Handler::call('markAsRead@ForumHandler')),
Route::get('/forum/mark-as-read', Handler::call('markAsReadGET@Forum'))->addFilters(Filter::call('EnforceLogIn')),
Route::post('/forum/mark-as-read', Handler::call('markAsReadPOST@Forum'))->addFilters(Filter::call('EnforceLogIn'), Filter::call('ValidateCsrf')),
// Sock Chat
Route::create(['GET', 'POST'], '/_sockchat.php', Handler::call('phpFile@SockChatHandler')),
Route::get('/_sockchat/emotes', Handler::call('emotes@SockChatHandler')),
Route::get('/_sockchat/bans', Handler::call('bans@SockChatHandler')),
Route::get('/_sockchat/login', Handler::call('login@SockChatHandler')),
Route::post('/_sockchat/bump', Handler::call('bump@SockChatHandler')),
Route::post('/_sockchat/verify', Handler::call('verify@SockChatHandler')),
Route::create(['GET', 'POST'], '/_sockchat.php', Handler::call('phpFile@SockChat')),
Route::get('/_sockchat/emotes', Handler::call('emotes@SockChat')),
Route::get('/_sockchat/bans', Handler::call('bans@SockChat')),
Route::get('/_sockchat/login', Handler::call('login@SockChat')),
Route::post('/_sockchat/bump', Handler::call('bump@SockChat')),
Route::post('/_sockchat/verify', Handler::call('verify@SockChat')),
// Redirects
Route::get('/index.php', Handler::redirect(url('index'), true)),
Route::get('/info.php', Handler::redirect(url('info'), true)),
Route::get('/info.php/([A-Za-z0-9_/]+)', true, Handler::call('redir@InfoHandler')),
Route::get('/info.php/([A-Za-z0-9_/]+)', true, Handler::call('redir@Info')),
Route::get('/auth.php', Handler::call('legacy@AuthHandler'))
);

View file

@ -45,7 +45,7 @@ final class CSRF {
public static function header(...$args): string {
return 'X-Misuzu-CSRF: ' . self::token(...$args);
}
public static function validateRequest(?string $identity = null, ?string $secretKey = null): bool {
public static function validateRequest($identity = null, ?string $secretKey = null): bool {
if(isset($_SERVER['HTTP_X_MISUZU_CSRF'])) {
$token = $_SERVER['HTTP_X_MISUZU_CSRF'];
} elseif(isset($_REQUEST['_csrf']) && is_string($_REQUEST['_csrf'])) { // Change this to $_POST later, it should never appear in urls

View file

@ -5,7 +5,7 @@ use Misuzu\Http\HttpResponseMessage;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
class EnforceLoggedInFilter implements FilterInterface {
class EnforceLogInFilter implements FilterInterface {
public function process(ServerRequestInterface $request): ?ResponseInterface {
if(!user_session_active())
return new HttpResponseMessage(403);

View file

@ -5,7 +5,7 @@ use Misuzu\Http\HttpResponseMessage;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
class EnforceLoggedOutFilter implements FilterInterface {
class EnforceLogOutFilter implements FilterInterface {
public function process(ServerRequestInterface $request): ?ResponseInterface {
if(user_session_active())
return new HttpResponseMessage(404);

View file

@ -2,8 +2,7 @@
namespace Misuzu\Http\Filters;
final class Filter {
public static function call(string $name): array {
[$funcName, $className] = explode('@', $name, 2);
return [__NAMESPACE__ . '\\' . $className, $funcName];
public static function call(string $className): string {
return __NAMESPACE__ . '\\' . $className . 'Filter';
}
}

View file

@ -1,5 +1,5 @@
<?php
namespace Hanyuu\Filters;
namespace Misuzu\Http\Filters;
use Misuzu\CSRF;
use Misuzu\Http\HttpResponseMessage;
@ -9,7 +9,12 @@ use Psr\Http\Message\ServerRequestInterface;
class ValidateCsrfFilter implements FilterInterface {
public function process(ServerRequestInterface $request): ?ResponseInterface {
if($request->getMethod() !== 'GET' && $request->getMethod() !== 'DELETE') {
$token = $request->getBodyParam('_csrf');
$token = $request->getHeaderLine('X-Misuzu-CSRF');
if(empty($token)) {
$body = $request->getParsedBody();
$token = isset($body['_csrf']) && is_string($body['_csrf']) ? $body['_csrf'] : null;
}
if(empty($token) || !CSRF::validate($token))
return new HttpResponseMessage(400);

View file

@ -1,17 +1,31 @@
<?php
namespace Misuzu\Http\Handlers;
use Misuzu\CSRF;
final class ForumHandler extends Handler {
public function markAsRead(Response $response, Request $request) {
public function markAsReadGET(Response $response, Request $request): void {
$query = $request->getQueryParams();
$forumId = isset($query['forum']) && is_string($query['forum']) ? (int)$query['forum'] : null;
$response->setTemplate('confirm', [
'title' => 'Mark forum as read',
'message' => 'Are you sure you want to mark ' . ($forumId === null ? 'the entire' : 'this') . ' forum as read?',
'return' => url($forumId ? 'forum-category' : 'forum-index', ['forum' => $forumId]),
'params' => [
'forum' => $forumId,
]
]);
}
if($request->getMethod() === 'GET') {
$response->setTemplate('confirm', [
'message' => 'Are you sure you want to mark the entire forum as read?',
'return' => url('forum-index'),
]);
return;
}
public function markAsReadPOST(Response $response, Request $request) {
$body = $request->getParsedBody();
$forumId = isset($body['forum']) && is_string($body['forum']) ? (int)$body['forum'] : null;
forum_mark_read($forumId, user_session_current('user_id'));
return 'now POSTing';
$response->redirect(
url($forumId ? 'forum-category' : 'forum-index', ['forum' => $forumId]),
false,
$request->hasHeader('X-Misuzu-XHR')
);
}
}

View file

@ -4,7 +4,7 @@ namespace Misuzu\Http\Handlers;
abstract class Handler {
public static function call(string $name): array {
[$funcName, $className] = explode('@', $name, 2);
return [__NAMESPACE__ . '\\' . $className, $funcName];
return [__NAMESPACE__ . '\\' . $className . 'Handler', $funcName];
}
public static function redirect(string $location, bool $permanent = false): callable {

View file

@ -46,9 +46,9 @@ class RouterResponseMessage extends HttpResponseMessage {
return $this;
}
public function redirect(string $path, bool $permanent = false): self {
public function redirect(string $path, bool $permanent = false, bool $xhr = false): self {
$this->setStatusCode($permanent ? 301 : 302);
$this->setHeader('Location', $path);
$this->setHeader($xhr ? 'X-Misuzu-Location' : 'Location', $path);
return $this;
}

View file

@ -40,8 +40,8 @@ define('MSZ_URLS', [
'forum-index' => ['/forum'],
'forum-leaderboard' => ['/forum/leaderboard.php', ['id' => '<id>', 'mode' => '<mode>']],
'forum-mark-global' => ['/forum/index.php', ['m' => 'mark', 'csrf' => '{csrf}']],
'forum-mark-single' => ['/forum/index.php', ['m' => 'mark', 'csrf' => '{csrf}', 'f' => '<forum>']],
'forum-mark-global' => ['/forum/mark-as-read'],
'forum-mark-single' => ['/forum/mark-as-read', ['forum' => '<forum>']],
'forum-topic-new' => ['/forum/posting.php', ['f' => '<forum>']],
'forum-reply-new' => ['/forum/posting.php', ['t' => '<topic>']],
'forum-category' => ['/forum/forum.php', ['f' => '<forum>', 'p' => '<page>']],

View file

@ -1,6 +1,6 @@
{% macro input_hidden(name, value) %}
{% spaceless %}
<input type="hidden" name="{{ name }}" value="{{ value }}">
<input type="hidden" name="{{ name }}" value="{{ value }}"/>
{% endspaceless %}
{% endmacro %}
@ -20,7 +20,7 @@
{% if tabindex > 0 %}tabindex="{{ tabindex }}"{% endif %} {% if autofocus|default(false) %}autofocus{% endif %}
{% for name, value in attributes|default([]) %}
{{ name }}{% if value|length > 0 %}="{{ value }}"{% endif %}
{% endfor %}>
{% endfor %}/>
{% endspaceless %}
{% endmacro %}
@ -33,7 +33,7 @@
{% if value|length > 0 %}value="{{ value }}"{% endif %}
{% for name, value in attributes|default([]) %}
{{ name }}{% if value|length > 0 %}="{{ value }}"{% endif %}
{% endfor %}>
{% endfor %}/>
{% endspaceless %}
{% endmacro %}
@ -61,7 +61,7 @@
{% if accepts|length > 0 %}accept="{{ accepts|join(',') }}"{% endif %}
{% for name, value in attributes|default([]) %}
{{ name }}{% if value|length > 0 %}="{{ value }}"{% endif %}
{% endfor %}>
{% endfor %}/>
{% endspaceless %}
{% endmacro %}
@ -113,7 +113,7 @@
<label class="input__colour{% if class %} {{ class }}{% endif %}">
<div class="input__colour__overlay"></div>
<input type="color" {% if name|length > 0 %}name="{{ name }}"{% else %}readonly onclick="return false"{% endif %}
value="{{ value }}" class="input__colour__control">
value="{{ value }}" class="input__colour__control"/>
</label>
{% endspaceless %}
{% endmacro %}

View file

@ -9,13 +9,15 @@
{{ container_title('<i class="' ~ class|default('fas fa-exclamation-circle') ~ ' fa-fw"></i> ' ~ title) }}
{{ input_csrf() }}
{% for name, value in params|default([]) %}
<input type="hidden" name="{{ name }}" value="{{ value }}">
{% if value is not empty %}
<input type="hidden" name="{{ name }}" value="{{ value }}"/>
{% endif %}
{% endfor %}
<div class="confirm__message">
{{ message|default('Are you sure you w') }}
</div>
<div class="confirm__buttons">
<button class="input__button confirm__button">Yes</button>
<input type="submit" class="input__button confirm__button" value="Yes"/>
<a href="{{ return|default('/') }}" class="input__button confirm__button">No</a>
</div>
</form>

View file

@ -13,6 +13,7 @@
'html': '<i class="far fa-check-circle"></i> Mark as Read',
'url': url('forum-mark-single', {'forum': forum_info.forum_id}),
'display': current_user is defined,
'method': 'POST',
}
]) }}

View file

@ -23,7 +23,7 @@
{% if current_user is defined %}
<div class="container forum__actions">
<a href="{{ url('forum-mark-global') }}" class="input__button forum__actions__button">Mark All Read</a>
<a href="{{ url('forum-mark-global') }}" class="input__button forum__actions__button" data-msz-method="POST">Mark All Read</a>
</div>
{% endif %}
{% else %}

View file

@ -55,7 +55,7 @@
<div class="forum__header__actions">
{% for action in actions %}
{% if action.display is not defined or action.display %}
<a class="forum__header__action{% if action.class is defined %}{{ action.class }}{% endif %}" href="{{ action.url }}">
<a class="forum__header__action{% if action.class is defined %}{{ action.class }}{% endif %}" href="{{ action.url }}"{% if action.method is defined %} data-msz-method="{{ action.method }}"{% endif %}>
{{ action.html|raw }}
</a>
{% endif %}