Moved forum read marking code into the router.
This commit is contained in:
parent
c7f06979b2
commit
0f1b8ee6e4
20 changed files with 120 additions and 49 deletions
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
min-width: auto;
|
||||
}
|
||||
|
||||
&[disabled],
|
||||
&--busy {
|
||||
opacity: .4;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -69,6 +69,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
urlRegistryInit();
|
||||
userInit();
|
||||
userRelationsInit();
|
||||
initDataRequestMethod();
|
||||
|
||||
const changelogChangeAction: HTMLDivElement = document.querySelector('.changelog__change__action') as HTMLDivElement;
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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'))
|
||||
);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
|
@ -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);
|
|
@ -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';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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>']],
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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',
|
||||
}
|
||||
]) }}
|
||||
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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 %}
|
||||
|
|
Loading…
Add table
Reference in a new issue