Massive overhauls.
This commit is contained in:
parent
51f3c47a31
commit
b4a581087a
50 changed files with 1080 additions and 614 deletions
13
misuzu.php
13
misuzu.php
|
@ -1,8 +1,11 @@
|
|||
<?php
|
||||
namespace Misuzu;
|
||||
|
||||
use Misuzu\Database\{ Database, DatabaseMigrationManager };
|
||||
use PDO;
|
||||
use Misuzu\Database\Database;
|
||||
use Misuzu\Database\DatabaseMigrationManager;
|
||||
use Misuzu\Net\GeoIP;
|
||||
use Misuzu\Net\IPAddress;
|
||||
|
||||
define('MSZ_STARTUP', microtime(true));
|
||||
define('MSZ_ROOT', __DIR__);
|
||||
|
@ -37,7 +40,6 @@ $errorHandler->register();
|
|||
|
||||
require_once 'src/audit_log.php';
|
||||
require_once 'src/changelog.php';
|
||||
require_once 'src/colour.php';
|
||||
require_once 'src/comments.php';
|
||||
require_once 'src/manage.php';
|
||||
require_once 'src/news.php';
|
||||
|
@ -50,9 +52,6 @@ require_once 'src/Forum/poll.php';
|
|||
require_once 'src/Forum/post.php';
|
||||
require_once 'src/Forum/topic.php';
|
||||
require_once 'src/Forum/validate.php';
|
||||
require_once 'src/Net/geoip.php';
|
||||
require_once 'src/Net/ip.php';
|
||||
require_once 'src/Parsers/parse.php';
|
||||
require_once 'src/Users/auth.php';
|
||||
require_once 'src/Users/avatar.php';
|
||||
require_once 'src/Users/background.php';
|
||||
|
@ -384,7 +383,7 @@ MIG;
|
|||
exit;
|
||||
}
|
||||
|
||||
geoip_init(Config::get('geoip.database', Config::TYPE_STR, '/var/lib/GeoIP/GeoLite2-Country.mmdb'));
|
||||
GeoIP::init(Config::get('geoip.database', Config::TYPE_STR, '/var/lib/GeoIP/GeoLite2-Country.mmdb'));
|
||||
|
||||
if(!MSZ_DEBUG) {
|
||||
$twigCache = sys_get_temp_dir() . '/msz-tpl-cache-' . md5(MSZ_ROOT);
|
||||
|
@ -456,7 +455,7 @@ MIG;
|
|||
}
|
||||
|
||||
CSRF::setGlobalSecretKey(Config::get('csrf.secret', Config::TYPE_STR, 'soup'));
|
||||
CSRF::setGlobalIdentity(empty($userDisplayInfo) ? ip_remote_address() : $cookieData['session_token']);
|
||||
CSRF::setGlobalIdentity(empty($userDisplayInfo) ? IPAddress::remote() : $cookieData['session_token']);
|
||||
|
||||
if(Config::get('private.enabled', Config::TYPE_BOOL)) {
|
||||
$onLoginPage = $_SERVER['PHP_SELF'] === url('auth-login');
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<?php
|
||||
namespace Misuzu;
|
||||
|
||||
use Misuzu\Net\IPAddress;
|
||||
use Misuzu\Users\User;
|
||||
|
||||
require_once '../../misuzu.php';
|
||||
|
@ -20,7 +21,7 @@ $notices = [];
|
|||
$siteIsPrivate = Config::get('private.enable', Config::TYPE_BOOL);
|
||||
$loginPermCat = $siteIsPrivate ? Config::get('private.perm.cat', Config::TYPE_STR) : '';
|
||||
$loginPermVal = $siteIsPrivate ? Config::get('private.perm.val', Config::TYPE_INT) : 0;
|
||||
$ipAddress = ip_remote_address();
|
||||
$ipAddress = IPAddress::remote();
|
||||
$remainingAttempts = user_login_attempts_remaining($ipAddress);
|
||||
|
||||
while(!empty($_POST['login']) && is_array($_POST['login'])) {
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
namespace Misuzu;
|
||||
|
||||
use UnexpectedValueException;
|
||||
use Misuzu\Net\IPAddress;
|
||||
|
||||
require_once '../../misuzu.php';
|
||||
|
||||
|
@ -25,7 +26,7 @@ if($userId > 0 && empty($username)) {
|
|||
$notices = [];
|
||||
$siteIsPrivate = Config::get('private.enable', Config::TYPE_BOOL);
|
||||
$canResetPassword = $siteIsPrivate ? Config::get('private.allow_password_reset', Config::TYPE_BOOL, true) : true;
|
||||
$ipAddress = ip_remote_address();
|
||||
$ipAddress = IPAddress::remote();
|
||||
$remainingAttempts = user_login_attempts_remaining($ipAddress);
|
||||
|
||||
while($canResetPassword) {
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
<?php
|
||||
namespace Misuzu;
|
||||
|
||||
use Misuzu\Net\IPAddress;
|
||||
use Misuzu\Net\IPAddressBlacklist;
|
||||
use Misuzu\Users\User;
|
||||
|
||||
require_once '../../misuzu.php';
|
||||
|
@ -12,10 +14,10 @@ if(user_session_active()) {
|
|||
|
||||
$register = !empty($_POST['register']) && is_array($_POST['register']) ? $_POST['register'] : [];
|
||||
$notices = [];
|
||||
$ipAddress = ip_remote_address();
|
||||
$ipAddress = IPAddress::remote();
|
||||
$remainingAttempts = user_login_attempts_remaining($ipAddress);
|
||||
$restricted = ip_blacklist_check(ip_remote_address()) ? 'blacklist'
|
||||
: (user_warning_check_ip(ip_remote_address()) ? 'ban' : '');
|
||||
$restricted = IPAddressBlacklist::check($ipAddress) ? 'blacklist'
|
||||
: (user_warning_check_ip($ipAddress) ? 'ban' : '');
|
||||
|
||||
while(!$restricted && !empty($register)) {
|
||||
if(!CSRF::validateRequest()) {
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
<?php
|
||||
namespace Misuzu;
|
||||
|
||||
use Misuzu\Net\IPAddress;
|
||||
|
||||
require_once '../../misuzu.php';
|
||||
|
||||
if(user_session_active()) {
|
||||
|
@ -10,7 +12,7 @@ if(user_session_active()) {
|
|||
|
||||
$twofactor = !empty($_POST['twofactor']) && is_array($_POST['twofactor']) ? $_POST['twofactor'] : [];
|
||||
$notices = [];
|
||||
$ipAddress = ip_remote_address();
|
||||
$ipAddress = IPAddress::remote();
|
||||
$remainingAttempts = user_login_attempts_remaining($ipAddress);
|
||||
$tokenInfo = user_auth_tfa_token_info(
|
||||
!empty($_GET['token']) && is_string($_GET['token']) ? $_GET['token'] : (
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
<?php
|
||||
namespace Misuzu;
|
||||
|
||||
use Misuzu\Net\IPAddress;
|
||||
use Misuzu\Parsers\Parser;
|
||||
|
||||
require_once '../../misuzu.php';
|
||||
|
||||
if(!user_session_active()) {
|
||||
|
@ -40,13 +43,13 @@ if($mode === 'preview') {
|
|||
$postText = (string)($_POST['post']['text']);
|
||||
$postParser = (int)($_POST['post']['parser']);
|
||||
|
||||
if(!parser_is_valid($postParser)) {
|
||||
if(!Parser::isValid($postParser)) {
|
||||
http_response_code(400);
|
||||
return;
|
||||
}
|
||||
|
||||
http_response_code(200);
|
||||
echo parse_text(htmlspecialchars($postText), $postParser);
|
||||
echo Parser::instance($postParser)->parseText(htmlspecialchars($postText));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -129,7 +132,7 @@ $notices = [];
|
|||
if(!empty($_POST)) {
|
||||
$topicTitle = $_POST['post']['title'] ?? '';
|
||||
$postText = $_POST['post']['text'] ?? '';
|
||||
$postParser = (int)($_POST['post']['parser'] ?? MSZ_PARSER_BBCODE);
|
||||
$postParser = (int)($_POST['post']['parser'] ?? Parser::BBCODE);
|
||||
$topicType = isset($_POST['post']['type']) ? (int)$_POST['post']['type'] : null;
|
||||
$postSignature = isset($_POST['post']['signature']);
|
||||
|
||||
|
@ -170,7 +173,7 @@ if(!empty($_POST)) {
|
|||
}
|
||||
}
|
||||
|
||||
if(!parser_is_valid($postParser)) {
|
||||
if(!Parser::isValid($postParser)) {
|
||||
$notices[] = 'Invalid parser selected.';
|
||||
}
|
||||
|
||||
|
@ -202,7 +205,7 @@ if(!empty($_POST)) {
|
|||
$topicId,
|
||||
$forum['forum_id'],
|
||||
user_session_current('user_id', 0),
|
||||
ip_remote_address(),
|
||||
IPAddress::remote(),
|
||||
$postText,
|
||||
$postParser,
|
||||
$postSignature
|
||||
|
@ -212,7 +215,7 @@ if(!empty($_POST)) {
|
|||
break;
|
||||
|
||||
case 'edit':
|
||||
if(!forum_post_update($postId, ip_remote_address(), $postText, $postParser, $postSignature, $postText !== $post['post_text'])) {
|
||||
if(!forum_post_update($postId, IPAddress::remote(), $postText, $postParser, $postSignature, $postText !== $post['post_text'])) {
|
||||
$notices[] = 'Post edit failed.';
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
<?php
|
||||
namespace Misuzu;
|
||||
|
||||
use Misuzu\Net\IPAddressBlacklist;
|
||||
|
||||
require_once '../../../misuzu.php';
|
||||
|
||||
if(!perms_check_user(MSZ_PERMS_GENERAL, user_session_current('user_id'), General::PERM_MANAGE_BLACKLIST)) {
|
||||
|
@ -18,7 +20,7 @@ if(!empty($_POST)) {
|
|||
|
||||
if(!empty($_POST['blacklist']['remove']) && is_array($_POST['blacklist']['remove'])) {
|
||||
foreach($_POST['blacklist']['remove'] as $cidr) {
|
||||
if(!ip_blacklist_remove($cidr)) {
|
||||
if(!IPAddressBlacklist::remove($cidr)) {
|
||||
$notices[] = sprintf('Failed to remove "%s" from the blacklist.', $cidr);
|
||||
}
|
||||
}
|
||||
|
@ -34,7 +36,7 @@ if(!empty($_POST)) {
|
|||
continue;
|
||||
}
|
||||
|
||||
if(!ip_blacklist_add($cidr)) {
|
||||
if(!IPAddressBlacklist::add($cidr)) {
|
||||
$notices[] = sprintf('Failed to add "%s" to the blacklist.', $cidr);
|
||||
}
|
||||
}
|
||||
|
@ -44,5 +46,5 @@ if(!empty($_POST)) {
|
|||
|
||||
Template::render('manage.general.blacklist', [
|
||||
'notices' => $notices,
|
||||
'blacklist' => ip_blacklist_list(),
|
||||
'blacklist' => IPAddressBlacklist::list(),
|
||||
]);
|
||||
|
|
|
@ -45,21 +45,20 @@ if(!empty($_POST['role']) && is_array($_POST['role']) && CSRF::validateRequest()
|
|||
return;
|
||||
}
|
||||
|
||||
$roleColour = colour_create();
|
||||
$roleColour = new Colour;
|
||||
|
||||
if(!empty($_POST['role']['colour']['inherit'])) {
|
||||
colour_set_inherit($roleColour);
|
||||
$roleColour->setInherit(true);
|
||||
} else {
|
||||
foreach(['red', 'green', 'blue'] as $key) {
|
||||
$value = (int)($_POST['role']['colour'][$key] ?? -1);
|
||||
$func = 'colour_set_' . ucfirst($key);
|
||||
|
||||
if($value < 0 || $value > 0xFF) {
|
||||
echo 'invalid colour value';
|
||||
try {
|
||||
$roleColour->{'set' . ucfirst($key)}($value);
|
||||
} catch(\Exception $ex){
|
||||
echo $ex->getMessage();
|
||||
return;
|
||||
}
|
||||
|
||||
$func($roleColour, $value);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -118,7 +117,7 @@ if(!empty($_POST['role']) && is_array($_POST['role']) && CSRF::validateRequest()
|
|||
$updateRole->bind('role_name', $roleName);
|
||||
$updateRole->bind('role_hierarchy', $roleHierarchy);
|
||||
$updateRole->bind('role_hidden', $roleSecret ? 1 : 0);
|
||||
$updateRole->bind('role_colour', $roleColour);
|
||||
$updateRole->bind('role_colour', $roleColour->getRaw());
|
||||
$updateRole->bind('role_description', $roleDescription);
|
||||
$updateRole->bind('role_title', $roleTitle);
|
||||
$updateRole->execute();
|
||||
|
@ -179,7 +178,10 @@ if($roleId !== null) {
|
|||
return;
|
||||
}
|
||||
|
||||
Template::set(['edit_role' => $editRole]);
|
||||
Template::set([
|
||||
'edit_role' => $editRole,
|
||||
'role_colour' => new Colour($editRole['role_colour']),
|
||||
]);
|
||||
}
|
||||
|
||||
Template::render('manage.users.role', [
|
||||
|
|
|
@ -139,17 +139,19 @@ if(CSRF::validateRequest() && $canEdit) {
|
|||
}
|
||||
|
||||
if(!empty($_POST['colour']) && is_array($_POST['colour'])) {
|
||||
$userColour = null;
|
||||
$setUserInfo['user_colour'] = null;
|
||||
|
||||
if(!empty($_POST['colour']['enable'])) {
|
||||
$userColour = colour_create();
|
||||
$userColour = new Colour;
|
||||
|
||||
if(!colour_from_hex($userColour, (string)($_POST['colour']['hex'] ?? ''))) {
|
||||
$notices[] = 'An invalid colour was supplied.';
|
||||
try {
|
||||
$userColour->setHex((string)($_POST['colour']['hex'] ?? ''));
|
||||
} catch(\Exception $ex) {
|
||||
$notices[] = $ex->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
$setUserInfo['user_colour'] = $userColour;
|
||||
$setUserInfo['user_colour'] = $userColour->getRaw();
|
||||
}
|
||||
}
|
||||
|
||||
if(!empty($_POST['password']) && is_array($_POST['password'])) {
|
||||
|
@ -241,6 +243,7 @@ $roles = $getRoles->fetchAll();
|
|||
|
||||
Template::render('manage.users.user', [
|
||||
'manage_user' => $manageUser,
|
||||
'user_colour' => empty($manageUser['user_colour']) ? Colour::none() : new Colour($manageUser['user_colour']),
|
||||
'manage_notices' => $notices,
|
||||
'manage_roles' => $roles,
|
||||
'can_edit_user' => $canEdit,
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
<?php
|
||||
namespace Misuzu;
|
||||
|
||||
use Misuzu\Net\IPAddress;
|
||||
|
||||
require_once '../../../misuzu.php';
|
||||
|
||||
if(!perms_check_user(MSZ_PERMS_USER, user_session_current('user_id'), MSZ_PERM_USER_MANAGE_WARNINGS)) {
|
||||
|
@ -77,7 +79,7 @@ if(!empty($_POST['warning']) && is_array($_POST['warning'])) {
|
|||
$warningsUser,
|
||||
user_get_last_ip($warningsUser),
|
||||
$currentUserId,
|
||||
ip_remote_address(),
|
||||
IPAddress::remote(),
|
||||
$warningType,
|
||||
$_POST['warning']['note'],
|
||||
$_POST['warning']['private'],
|
||||
|
|
|
@ -16,7 +16,7 @@ $feedMode = trim($_SERVER['PATH_INFO'] ?? '', '/');
|
|||
$categoryId = !empty($_GET['c']) && is_string($_GET['c']) ? (int)$_GET['c'] : 0;
|
||||
$postId = !empty($_GET['p']) && is_string($_GET['p']) ? (int)$_GET['p'] : 0;
|
||||
|
||||
if(!empty($feedMode) && news_feed_supported($feedMode)) {
|
||||
if(!empty($feedMode) && in_array($feedMode, ['rss', 'atom'])) {
|
||||
$location = empty($categoryId) ? url("news-feed-{$feedMode}") : url("news-category-feed-{$feedMode}", ['category' => $categoryId]);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,11 +1,26 @@
|
|||
<?php
|
||||
namespace Misuzu;
|
||||
|
||||
use Misuzu\Feeds\Feed;
|
||||
use Misuzu\Feeds\FeedItem;
|
||||
use Misuzu\Feeds\AtomFeedSerializer;
|
||||
use Misuzu\Feeds\RssFeedSerializer;
|
||||
use Misuzu\Parsers\Parser;
|
||||
|
||||
require_once '../../misuzu.php';
|
||||
|
||||
$feedMode = trim($_SERVER['PATH_INFO'] ?? '', '/');
|
||||
|
||||
if(!news_feed_supported($feedMode)) {
|
||||
switch($feedMode) {
|
||||
case 'rss':
|
||||
$feedSerializer = new RssFeedSerializer;
|
||||
break;
|
||||
case 'atom':
|
||||
$feedSerializer = new AtomFeedSerializer;
|
||||
break;
|
||||
}
|
||||
|
||||
if(!isset($feedSerializer)) {
|
||||
echo render_error(400);
|
||||
return;
|
||||
}
|
||||
|
@ -28,11 +43,34 @@ if(!$posts) {
|
|||
return;
|
||||
}
|
||||
|
||||
$feed = (new Feed)
|
||||
->setTitle(Config::get('site.name', Config::TYPE_STR, 'Misuzu') . ' » ' . ($category['category_name'] ?? 'Featured News'))
|
||||
->setDescription($category['category_description'] ?? 'A live featured news feed.')
|
||||
->setContentUrl(url_prefix(false) . (empty($category) ? url('news-index') : url('news-category', ['category' => $category['category_id']])))
|
||||
->setFeedUrl(url_prefix(false) . (empty($category) ? url("news-feed-{$feedMode}") : url("news-category-feed-{$feedMode}", ['category' => $category['category_id']])));
|
||||
|
||||
foreach($posts as $post) {
|
||||
$postUrl = url_prefix(false) . url('news-post', ['post' => $post['post_id']]);
|
||||
$commentsUrl = url_prefix(false) . url('news-post-comments', ['post' => $post['post_id']]);
|
||||
$authorUrl = url_prefix(false) . url('user-profile', ['user' => $post['user_id']]);
|
||||
|
||||
$feedItem = (new FeedItem)
|
||||
->setTitle($post['post_title'])
|
||||
->setSummary(first_paragraph($post['post_text']))
|
||||
->setContent(Parser::instance(Parser::MARKDOWN)->parseText($post['post_text']))
|
||||
->setCreationDate(strtotime($post['post_created']))
|
||||
->setUniqueId($postUrl)
|
||||
->setContentUrl($postUrl)
|
||||
->setCommentsUrl($commentsUrl)
|
||||
->setAuthorName($post['username'])
|
||||
->setAuthorUrl($authorUrl);
|
||||
|
||||
if(!$feed->hasLastUpdate() || $feed->getLastUpdate() < $feedItem->getCreationDate())
|
||||
$feed->setLastUpdate($feedItem->getCreationDate());
|
||||
|
||||
$feed->addItem($feedItem);
|
||||
}
|
||||
|
||||
header("Content-Type: application/{$feedMode}+xml; charset=utf-8");
|
||||
|
||||
echo news_feed($feedMode, $posts, [
|
||||
'title' => Config::get('site.name', Config::TYPE_STR, 'Misuzu') . ' » ' . ($category['category_name'] ?? 'Featured News'),
|
||||
'subtitle' => $category['category_description'] ?? 'A live featured news feed.',
|
||||
'html-url' => empty($category) ? url('news-index') : url('news-category', ['category' => $category['category_id']]),
|
||||
'feed-url' => empty($category) ? url("news-feed-{$feedMode}") : url("news-category-feed-{$feedMode}", ['category' => $category['category_id']]),
|
||||
]);
|
||||
echo $feedSerializer->serializeFeed($feed);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<?php
|
||||
namespace Misuzu;
|
||||
|
||||
use Misuzu\Parsers\Parser;
|
||||
use Misuzu\Users\User;
|
||||
|
||||
require_once '../misuzu.php';
|
||||
|
@ -88,7 +89,7 @@ if($isEditing) {
|
|||
$setAboutError = user_set_about_page(
|
||||
$profileUser->user_id,
|
||||
$_POST['about']['text'] ?? '',
|
||||
(int)($_POST['about']['parser'] ?? MSZ_PARSER_PLAIN)
|
||||
(int)($_POST['about']['parser'] ?? Parser::PLAIN)
|
||||
);
|
||||
|
||||
if($setAboutError !== MSZ_E_USER_ABOUT_OK) {
|
||||
|
@ -107,7 +108,7 @@ if($isEditing) {
|
|||
$setSignatureError = user_set_signature(
|
||||
$profileUser->user_id,
|
||||
$_POST['signature']['text'] ?? '',
|
||||
(int)($_POST['signature']['parser'] ?? MSZ_PARSER_PLAIN)
|
||||
(int)($_POST['signature']['parser'] ?? Parser::PLAIN)
|
||||
);
|
||||
|
||||
if($setSignatureError !== MSZ_E_USER_SIGNATURE_OK) {
|
||||
|
|
|
@ -48,8 +48,8 @@ final class CSRF {
|
|||
public static function validateRequest(?string $identity = null, ?string $secretKey = null): bool {
|
||||
if(isset($_SERVER['HTTP_X_MISUZU_CSRF'])) {
|
||||
$token = $_SERVER['HTTP_X_MISUZU_CSRF'];
|
||||
} elseif(isset($_POST['_csrf']) && is_string($_POST['_csrf'])) {
|
||||
$token = $_POST['_csrf'];
|
||||
} elseif(isset($_REQUEST['_csrf']) && is_string($_REQUEST['_csrf'])) { // Change this to $_POST later, it should never appear in urls
|
||||
$token = $_REQUEST['_csrf'];
|
||||
} elseif(isset($_REQUEST['csrf']) && is_string($_REQUEST['csrf'])) {
|
||||
$token = $_REQUEST['csrf'];
|
||||
} else {
|
||||
|
|
144
src/Colour.php
Normal file
144
src/Colour.php
Normal file
|
@ -0,0 +1,144 @@
|
|||
<?php
|
||||
namespace Misuzu;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
class Colour {
|
||||
private const FLAG_INHERIT = 0x40000000;
|
||||
|
||||
private const READABILITY_THRESHOLD = 186;
|
||||
private const LUMINANCE_WEIGHT_RED = .299;
|
||||
private const LUMINANCE_WEIGHT_GREEN = .587;
|
||||
private const LUMINANCE_WEIGHT_BLUE = .114;
|
||||
|
||||
private int $raw = 0;
|
||||
|
||||
public function __construct(?int $raw = 0) {
|
||||
$this->setRaw($raw);
|
||||
}
|
||||
|
||||
public static function none(): self {
|
||||
return new static(self::FLAG_INHERIT);
|
||||
}
|
||||
|
||||
public static function fromRgb(int $red, int $green, int $blue): self {
|
||||
return (new static)->setRed($red)->getGreen($green)->setBlue($blue);
|
||||
}
|
||||
public static function fromHex(string $hex): self {
|
||||
return (new static)->setHex($hex);
|
||||
}
|
||||
|
||||
public function getRaw(): int {
|
||||
return $this->raw;
|
||||
}
|
||||
public function setRaw(int $raw): self {
|
||||
if($raw < 0 || $raw > 0x7FFFFFFF)
|
||||
throw new InvalidArgumentException('Invalid raw colour.');
|
||||
$this->raw = $raw;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getInherit(): bool {
|
||||
return ($this->getRaw() & self::FLAG_INHERIT) > 0;
|
||||
}
|
||||
public function setInherit(bool $inherit): self {
|
||||
$raw = $this->getRaw();
|
||||
|
||||
if($inherit)
|
||||
$raw |= self::FLAG_INHERIT;
|
||||
else
|
||||
$raw &= ~self::FLAG_INHERIT;
|
||||
|
||||
$this->setRaw($raw);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getRed(): int {
|
||||
return ($this->getRaw() & 0xFF0000) >> 16;
|
||||
}
|
||||
public function setRed(int $red): self {
|
||||
if($red < 0 || $red > 0xFF)
|
||||
throw new InvalidArgumentException('Invalid red value.');
|
||||
|
||||
$raw = $this->getRaw();
|
||||
$raw &= ~0xFF0000;
|
||||
$raw |= $red << 16;
|
||||
$this->setRaw($raw);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getGreen(): int {
|
||||
return ($this->getRaw() & 0xFF00) >> 8;
|
||||
}
|
||||
public function setGreen(int $green): self {
|
||||
if($green < 0 || $green > 0xFF)
|
||||
throw new InvalidArgumentException('Invalid green value.');
|
||||
|
||||
$raw = $this->getRaw();
|
||||
$raw &= ~0xFF00;
|
||||
$raw |= $green << 8;
|
||||
$this->setRaw($raw);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getBlue(): int {
|
||||
return ($this->getRaw() & 0xFF);
|
||||
}
|
||||
public function setBlue(int $blue): self {
|
||||
if($blue < 0 || $blue > 0xFF)
|
||||
throw new InvalidArgumentException('Invalid blue value.');
|
||||
|
||||
$raw = $this->getRaw();
|
||||
$raw &= ~0xFF;
|
||||
$raw |= $blue;
|
||||
$this->setRaw($raw);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getLuminance(): int {
|
||||
return self::LUMINANCE_WEIGHT_RED * $this->getRed()
|
||||
+ self::LUMINANCE_WEIGHT_GREEN * $this->getGreen()
|
||||
+ self::LUMINANCE_WEIGHT_BLUE * $this->getBlue();
|
||||
}
|
||||
|
||||
public function getHex(): string {
|
||||
return str_pad(dechex($this->getRaw() & 0xFFFFFF), 6, '0', STR_PAD_LEFT);
|
||||
}
|
||||
public function setHex(string $hex): self {
|
||||
if($hex[0] === '#')
|
||||
$hex = mb_substr($hex, 1);
|
||||
|
||||
if(!ctype_xdigit($hex))
|
||||
throw new InvalidArgumentException('Argument contains invalid characters.');
|
||||
|
||||
$length = mb_strlen($hex);
|
||||
|
||||
if($length === 3) {
|
||||
$hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2];
|
||||
} elseif($length !== 6) {
|
||||
throw new InvalidArgumentException('Argument is not a hex string.');
|
||||
}
|
||||
|
||||
return $this->setRaw(hexdec($hex));
|
||||
}
|
||||
|
||||
public function getCSS(): string {
|
||||
if($this->getInherit())
|
||||
return 'inherit';
|
||||
|
||||
return '#' . $this->getHex();
|
||||
}
|
||||
|
||||
public static function extractCSSContract(
|
||||
string $dark = 'dark', string $light = 'light', bool $inheritIsDark = true
|
||||
): string {
|
||||
if($this->getInherit())
|
||||
return $inheritIsDark ? $dark : $light;
|
||||
|
||||
return $this->getLuminance($colour) > self::READABILITY_THRESHOLD ? $dark : $light;
|
||||
}
|
||||
}
|
54
src/Debug/Stopwatch.php
Normal file
54
src/Debug/Stopwatch.php
Normal file
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
namespace Misuzu\Debug;
|
||||
|
||||
class Stopwatch {
|
||||
private $startTime = 0;
|
||||
private $stopTime = 0;
|
||||
private $laps = [];
|
||||
|
||||
private static $instance = null;
|
||||
|
||||
public static function __callStatic(string $name, array $args) {
|
||||
if(self::$instance === null)
|
||||
self::$instance = new static;
|
||||
return self::$instance->{substr($name, 1)}(...$args);
|
||||
}
|
||||
|
||||
public function __construct() {}
|
||||
|
||||
private static function time() {
|
||||
return microtime(true);
|
||||
}
|
||||
|
||||
public function start(): void {
|
||||
$this->startTime = self::time();
|
||||
}
|
||||
|
||||
public function lap(string $text): void {
|
||||
$this->laps[$text] = self::time();
|
||||
}
|
||||
|
||||
public function stop(): void {
|
||||
$this->stopTime = self::time();
|
||||
}
|
||||
|
||||
public function reset(): void {
|
||||
$this->laps = [];
|
||||
$this->startTime = 0;
|
||||
$this->stopTime = 0;
|
||||
}
|
||||
|
||||
public function elapsed(): float {
|
||||
return $this->stopTime - $this->startTime;
|
||||
}
|
||||
|
||||
public function laps(): array {
|
||||
$laps = [];
|
||||
|
||||
foreach($this->laps as $name => $time) {
|
||||
$laps[$name] = $time - $this->startTime;
|
||||
}
|
||||
|
||||
return $laps;
|
||||
}
|
||||
}
|
115
src/Feeds/AtomFeedSerializer.php
Normal file
115
src/Feeds/AtomFeedSerializer.php
Normal file
|
@ -0,0 +1,115 @@
|
|||
<?php
|
||||
namespace Misuzu\Feeds;
|
||||
|
||||
use DOMDocument;
|
||||
use DOMElement;
|
||||
|
||||
class AtomFeedSerializer extends XmlFeedSerializer {
|
||||
protected function formatTime(int $time): string {
|
||||
return date('c', $time);
|
||||
}
|
||||
|
||||
protected function createRoot(DOMDocument $document, Feed $feed): DOMElement {
|
||||
$atom = $document->appendChild($document->createElement('feed'));
|
||||
$atom->setAttribute('xmlns', 'http://www.w3.org/2005/Atom');
|
||||
|
||||
$atom->appendChild(
|
||||
$document->createElement(
|
||||
'id',
|
||||
$feed->hasContentUrl()
|
||||
? $this->cleanString($feed->getContentUrl())
|
||||
: time()
|
||||
)
|
||||
);
|
||||
|
||||
return $atom;
|
||||
}
|
||||
|
||||
protected function createTitle(DOMDocument $document, string $title): DOMElement {
|
||||
return $document->createElement('title', $this->cleanString($title));
|
||||
}
|
||||
|
||||
protected function createDescription(DOMDocument $document, string $description): ?DOMElement {
|
||||
return $document->createElement('subtitle', $this->cleanString($description));
|
||||
}
|
||||
|
||||
protected function createLastUpdate(DOMDocument $document, int $lastUpdate): ?DOMElement {
|
||||
return $document->createElement('updated', $this->formatTime($lastUpdate));
|
||||
}
|
||||
|
||||
protected function createContentUrl(DOMDocument $document, string $contentUrl): ?DOMElement {
|
||||
$link = $document->createElement('link');
|
||||
$link->setAttribute('href', $this->cleanString($contentUrl));
|
||||
return $link;
|
||||
}
|
||||
|
||||
protected function createFeedUrl(DOMDocument $document, string $feedUrl): ?DOMElement {
|
||||
$link = $document->createElement('link');
|
||||
$link->setAttribute('href', $this->cleanString($feedUrl));
|
||||
$link->setAttribute('ref', 'self');
|
||||
return $link;
|
||||
}
|
||||
|
||||
protected function createItem(DOMDocument $document, FeedItem $feedItem): DOMElement {
|
||||
$elem = $document->createElement('entry');
|
||||
|
||||
$elem->appendChild(
|
||||
$document->createElement(
|
||||
'id',
|
||||
$feedItem->hasContentUrl()
|
||||
? $this->cleanString($feedItem->getContentUrl())
|
||||
: time()
|
||||
)
|
||||
);
|
||||
|
||||
return $elem;
|
||||
}
|
||||
|
||||
protected function createItemTitle(DOMDocument $document, string $title): DOMElement {
|
||||
return $document->createElement('title', $this->cleanString($title));
|
||||
}
|
||||
|
||||
protected function createItemSummary(DOMDocument $document, string $summary): ?DOMElement {
|
||||
return $document->createElement('summary', $this->cleanString($summary));
|
||||
}
|
||||
|
||||
protected function createItemContent(DOMDocument $document, string $content): ?DOMElement {
|
||||
$elem = $document->createElement('content', $this->cleanString($content));
|
||||
$elem->setAttribute('type', 'html');
|
||||
return $elem;
|
||||
}
|
||||
|
||||
protected function createItemCreationDate(DOMDocument $document, int $creationDate): ?DOMElement {
|
||||
return $document->createElement('updated', $this->formatTime($creationDate));
|
||||
}
|
||||
|
||||
protected function createItemUniqueId(DOMDocument $document, string $uniqueId): ?DOMElement {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function createItemContentUrl(DOMDocument $document, string $contentUrl): ?DOMElement {
|
||||
$elem = $document->createElement('link');
|
||||
$elem->setAttribute('href', $this->cleanString($contentUrl));
|
||||
$elem->setAttribute('type', 'text/html');
|
||||
return $elem;
|
||||
}
|
||||
|
||||
protected function createItemCommentsUrl(DOMDocument $document, string $commentsUrl): ?DOMElement {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function createItemAuthor(DOMDocument $document, ?string $authorName, ?string $authorUrl): ?DOMElement {
|
||||
if(empty($authorName) && empty($authorUrl))
|
||||
return null;
|
||||
|
||||
$elem = $document->createElement('author');
|
||||
|
||||
if(!empty($authorName))
|
||||
$elem->appendChild($document->createElement('name', $this->cleanString($authorName)));
|
||||
|
||||
if(!empty($authorUrl))
|
||||
$elem->appendChild($document->createElement('uri', $this->cleanString($authorUrl)));
|
||||
|
||||
return $elem;
|
||||
}
|
||||
}
|
78
src/Feeds/Feed.php
Normal file
78
src/Feeds/Feed.php
Normal file
|
@ -0,0 +1,78 @@
|
|||
<?php
|
||||
namespace Misuzu\Feeds;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
class Feed {
|
||||
private string $title = '';
|
||||
private ?string $description = null;
|
||||
private ?int $lastUpdate = null;
|
||||
private ?string $contentUrl = null;
|
||||
private ?string $feedUrl = null;
|
||||
private array $feedItems = [];
|
||||
|
||||
public function getTitle(): string {
|
||||
return $this->title;
|
||||
}
|
||||
public function setTitle(string $title): self {
|
||||
$this->title = $title;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDescription(): string {
|
||||
return $this->description ?? '';
|
||||
}
|
||||
public function hasDescription(): bool {
|
||||
return isset($this->description);
|
||||
}
|
||||
public function setDescription(?string $description): self {
|
||||
$this->description = $description;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getLastUpdate(): int {
|
||||
return $this->lastUpdate ?? 0;
|
||||
}
|
||||
public function hasLastUpdate(): bool {
|
||||
return isset($this->lastUpdate);
|
||||
}
|
||||
public function setLastUpdate(?int $lastUpdate): self {
|
||||
$this->lastUpdate = $lastUpdate;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getContentUrl(): string {
|
||||
return $this->contentUrl ?? '';
|
||||
}
|
||||
public function hasContentUrl(): bool {
|
||||
return isset($this->contentUrl);
|
||||
}
|
||||
public function setContentUrl(?string $contentUrl): self {
|
||||
$this->contentUrl = $contentUrl;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getFeedUrl(): string {
|
||||
return $this->feedUrl ?? '';
|
||||
}
|
||||
public function hasFeedUrl(): bool {
|
||||
return isset($this->feedUrl);
|
||||
}
|
||||
public function setFeedUrl(?string $feedUrl): self {
|
||||
$this->feedUrl = $feedUrl;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getItems(): array {
|
||||
return $this->feedItems;
|
||||
}
|
||||
public function hasItems(): bool {
|
||||
return count($this->feedItems) > 0;
|
||||
}
|
||||
public function addItem(FeedItem $item): self {
|
||||
if($item === null)
|
||||
throw new InvalidArgumentException('item may not be null');
|
||||
$this->feedItems[] = $item;
|
||||
return $this;
|
||||
}
|
||||
}
|
110
src/Feeds/FeedItem.php
Normal file
110
src/Feeds/FeedItem.php
Normal file
|
@ -0,0 +1,110 @@
|
|||
<?php
|
||||
namespace Misuzu\Feeds;
|
||||
|
||||
class FeedItem {
|
||||
private string $title = '';
|
||||
private ?string $summary = null;
|
||||
private ?string $content = null;
|
||||
private ?int $creationDate = null;
|
||||
private ?string $uniqueId = null;
|
||||
private ?string $contentUrl = null;
|
||||
private ?string $commentsUrl = null;
|
||||
private ?string $authorName = null;
|
||||
private ?string $authorUrl = null;
|
||||
|
||||
public function getTitle(): string {
|
||||
return $this->title;
|
||||
}
|
||||
public function setTitle(string $title): self {
|
||||
$this->title = $title;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getSummary(): string {
|
||||
return $this->summary ?? '';
|
||||
}
|
||||
public function hasSummary(): bool {
|
||||
return isset($this->summary);
|
||||
}
|
||||
public function setSummary(?string $summary): self {
|
||||
$this->summary = $summary;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getContent(): string {
|
||||
return $this->content ?? '';
|
||||
}
|
||||
public function hasContent(): bool {
|
||||
return isset($this->content);
|
||||
}
|
||||
public function setContent(?string $content): self {
|
||||
$this->content = $content;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCreationDate(): int {
|
||||
return $this->creationDate;
|
||||
}
|
||||
public function hasCreationDate(): bool {
|
||||
return isset($this->creationDate);
|
||||
}
|
||||
public function setCreationDate(?int $creationDate): self {
|
||||
$this->creationDate = $creationDate;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getUniqueId(): string {
|
||||
return $this->uniqueId ?? '';
|
||||
}
|
||||
public function hasUniqueId(): bool {
|
||||
return isset($this->uniqueId);
|
||||
}
|
||||
public function setUniqueId(?string $uniqueId): self {
|
||||
$this->uniqueId = $uniqueId;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getContentUrl(): string {
|
||||
return $this->contentUrl ?? '';
|
||||
}
|
||||
public function hasContentUrl(): bool {
|
||||
return isset($this->contentUrl);
|
||||
}
|
||||
public function setContentUrl(?string $contentUrl): self {
|
||||
$this->contentUrl = $contentUrl;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCommentsUrl(): string {
|
||||
return $this->commentsUrl ?? '';
|
||||
}
|
||||
public function hasCommentsUrl(): bool {
|
||||
return isset($this->commentsUrl);
|
||||
}
|
||||
public function setCommentsUrl(?string $commentsUrl): self {
|
||||
$this->commentsUrl = $commentsUrl;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getAuthorName(): string {
|
||||
return $this->authorName ?? '';
|
||||
}
|
||||
public function hasAuthorName(): bool {
|
||||
return isset($this->authorName);
|
||||
}
|
||||
public function setAuthorName(?string $authorName): self {
|
||||
$this->authorName = $authorName;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getAuthorUrl(): string {
|
||||
return $this->authorUrl ?? '';
|
||||
}
|
||||
public function hasAuthorUrl(): bool {
|
||||
return isset($this->authorUrl);
|
||||
}
|
||||
public function setAuthorUrl(?string $authorUrl): self {
|
||||
$this->authorUrl = $authorUrl;
|
||||
return $this;
|
||||
}
|
||||
}
|
6
src/Feeds/FeedSerializer.php
Normal file
6
src/Feeds/FeedSerializer.php
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?php
|
||||
namespace Misuzu\Feeds;
|
||||
|
||||
abstract class FeedSerializer {
|
||||
abstract public function serializeFeed(Feed $feed): string;
|
||||
}
|
82
src/Feeds/RssFeedSerializer.php
Normal file
82
src/Feeds/RssFeedSerializer.php
Normal file
|
@ -0,0 +1,82 @@
|
|||
<?php
|
||||
namespace Misuzu\Feeds;
|
||||
|
||||
use DOMDocument;
|
||||
use DOMElement;
|
||||
|
||||
class RssFeedSerializer extends XmlFeedSerializer {
|
||||
protected function formatTime(int $time): string {
|
||||
return date('r', $time);
|
||||
}
|
||||
|
||||
protected function createRoot(DOMDocument $document, Feed $feed): DOMElement {
|
||||
$rss = $document->appendChild($document->createElement('rss'));
|
||||
$rss->setAttribute('version', '2.0');
|
||||
$rss->setAttribute('xmlns:atom', 'http://www.w3.org/2005/Atom');
|
||||
|
||||
$channel = $rss->appendChild($document->createElement('channel'));
|
||||
$channel->appendChild($document->createElement('ttl', '900'));
|
||||
return $channel;
|
||||
}
|
||||
|
||||
protected function createTitle(DOMDocument $document, string $title): DOMElement {
|
||||
return $document->createElement('title', $this->cleanString($title));
|
||||
}
|
||||
|
||||
protected function createDescription(DOMDocument $document, string $description): ?DOMElement {
|
||||
return $document->createElement('description', $this->cleanString($description));
|
||||
}
|
||||
|
||||
protected function createLastUpdate(DOMDocument $document, int $lastUpdate): ?DOMElement {
|
||||
return $document->createElement('pubDate', $this->formatTime($lastUpdate));
|
||||
}
|
||||
|
||||
protected function createContentUrl(DOMDocument $document, string $contentUrl): ?DOMElement {
|
||||
return $document->createElement('link', $this->cleanString($contentUrl));
|
||||
}
|
||||
|
||||
protected function createFeedUrl(DOMDocument $document, string $feedUrl): ?DOMElement {
|
||||
$link = $document->createElement('atom:link');
|
||||
$link->setAttribute('href', $this->cleanString($feedUrl));
|
||||
$link->setAttribute('ref', 'self');
|
||||
return $link;
|
||||
}
|
||||
|
||||
protected function createItem(DOMDocument $document, FeedItem $feedItem): DOMElement {
|
||||
return $document->createElement('item');
|
||||
}
|
||||
|
||||
protected function createItemTitle(DOMDocument $document, string $title): DOMElement {
|
||||
return $document->createElement('title', $this->cleanString($title));
|
||||
}
|
||||
|
||||
protected function createItemSummary(DOMDocument $document, string $summary): ?DOMElement {
|
||||
return $document->createElement('description', $this->cleanString($summary));
|
||||
}
|
||||
|
||||
protected function createItemContent(DOMDocument $document, string $content): ?DOMElement {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function createItemCreationDate(DOMDocument $document, int $creationDate): ?DOMElement {
|
||||
return $document->createElement('pubDate', $this->formatTime($creationDate));
|
||||
}
|
||||
|
||||
protected function createItemUniqueId(DOMDocument $document, string $uniqueId): ?DOMElement {
|
||||
$elem = $document->createElement('guid', $uniqueId);
|
||||
$elem->setAttribute('isPermaLink', 'true');
|
||||
return $elem;
|
||||
}
|
||||
|
||||
protected function createItemContentUrl(DOMDocument $document, string $contentUrl): ?DOMElement {
|
||||
return $document->createElement('link', $contentUrl);
|
||||
}
|
||||
|
||||
protected function createItemCommentsUrl(DOMDocument $document, string $commentsUrl): ?DOMElement {
|
||||
return $document->createElement('comments', $commentsUrl);
|
||||
}
|
||||
|
||||
protected function createItemAuthor(DOMDocument $document, ?string $authorName, ?string $authorUrl): ?DOMElement {
|
||||
return null;
|
||||
}
|
||||
}
|
79
src/Feeds/XmlFeedSerializer.php
Normal file
79
src/Feeds/XmlFeedSerializer.php
Normal file
|
@ -0,0 +1,79 @@
|
|||
<?php
|
||||
namespace Misuzu\Feeds;
|
||||
|
||||
use DOMDocument;
|
||||
use DOMElement;
|
||||
|
||||
abstract class XmlFeedSerializer extends FeedSerializer {
|
||||
public function serializeFeed(Feed $feed): string {
|
||||
$document = new DOMDocument('1.0', 'utf-8');
|
||||
$root = $this->createRoot($document, $feed);
|
||||
$root->appendChild($this->createTitle($document, $feed->getTitle()));
|
||||
|
||||
if($feed->hasDescription())
|
||||
self::appendChild($root, $this->createDescription($document, $feed->getDescription()));
|
||||
if($feed->hasLastUpdate())
|
||||
self::appendChild($root, $this->createLastUpdate($document, $feed->getLastUpdate()));
|
||||
if($feed->hasContentUrl())
|
||||
self::appendChild($root, $this->createContentUrl($document, $feed->getContentUrl()));
|
||||
if($feed->hasFeedUrl())
|
||||
self::appendChild($root, $this->createFeedUrl($document, $feed->getFeedUrl()));
|
||||
|
||||
if($feed->hasItems()) {
|
||||
foreach($feed->getItems() as $item) {
|
||||
$root->appendChild($this->serializeFeedItem($document, $item));
|
||||
}
|
||||
}
|
||||
|
||||
return $document->saveXML();
|
||||
}
|
||||
|
||||
private function serializeFeedItem(DOMDocument $document, FeedItem $feedItem): DOMElement {
|
||||
$elem = $this->createItem($document, $feedItem);
|
||||
$elem->appendChild($this->createItemTitle($document, $feedItem->getTitle()));
|
||||
|
||||
if($feedItem->hasSummary())
|
||||
self::appendChild($elem, $this->createItemSummary($document, $feedItem->getSummary()));
|
||||
if($feedItem->hasContent())
|
||||
self::appendChild($elem, $this->createItemContent($document, $feedItem->getContent()));
|
||||
if($feedItem->hasCreationDate())
|
||||
self::appendChild($elem, $this->createItemCreationDate($document, $feedItem->getCreationDate()));
|
||||
if($feedItem->hasUniqueId())
|
||||
self::appendChild($elem, $this->createItemUniqueId($document, $feedItem->getUniqueId()));
|
||||
if($feedItem->hasContentUrl())
|
||||
self::appendChild($elem, $this->createItemContentUrl($document, $feedItem->getContentUrl()));
|
||||
if($feedItem->hasCommentsUrl())
|
||||
self::appendChild($elem, $this->createItemCommentsUrl($document, $feedItem->getCommentsUrl()));
|
||||
if($feedItem->hasAuthorName() || $feedItem->hasAuthorUrl())
|
||||
self::appendChild($elem, $this->createItemAuthor($document, $feedItem->getAuthorName(), $feedItem->getAuthorUrl()));
|
||||
|
||||
return $elem;
|
||||
}
|
||||
|
||||
protected function cleanString(string $string): string {
|
||||
return htmlspecialchars($string, ENT_XML1 | ENT_COMPAT | ENT_SUBSTITUTE);
|
||||
}
|
||||
|
||||
protected static function appendChild(DOMElement $parent, ?DOMElement $elem): ?DOMElement {
|
||||
if($elem !== null)
|
||||
return $parent->appendChild($elem);
|
||||
return $elem;
|
||||
}
|
||||
|
||||
abstract protected function formatTime(int $time): string;
|
||||
abstract protected function createRoot(DOMDocument $document, Feed $feed): DOMElement;
|
||||
abstract protected function createTitle(DOMDocument $document, string $title): DOMElement;
|
||||
abstract protected function createDescription(DOMDocument $document, string $description): ?DOMElement;
|
||||
abstract protected function createLastUpdate(DOMDocument $document, int $lastUpdate): ?DOMElement;
|
||||
abstract protected function createContentUrl(DOMDocument $document, string $contentUrl): ?DOMElement;
|
||||
abstract protected function createFeedUrl(DOMDocument $document, string $feedUrl): ?DOMElement;
|
||||
abstract protected function createItem(DOMDocument $document, FeedItem $feedItem): DOMElement;
|
||||
abstract protected function createItemTitle(DOMDocument $document, string $title): DOMElement;
|
||||
abstract protected function createItemSummary(DOMDocument $document, string $summary): ?DOMElement;
|
||||
abstract protected function createItemContent(DOMDocument $document, string $content): ?DOMElement;
|
||||
abstract protected function createItemCreationDate(DOMDocument $document, int $creationDate): ?DOMElement;
|
||||
abstract protected function createItemUniqueId(DOMDocument $document, string $uniqueId): ?DOMElement;
|
||||
abstract protected function createItemContentUrl(DOMDocument $document, string $contentUrl): ?DOMElement;
|
||||
abstract protected function createItemCommentsUrl(DOMDocument $document, string $commentsUrl): ?DOMElement;
|
||||
abstract protected function createItemAuthor(DOMDocument $document, ?string $authorName, ?string $authorUrl): ?DOMElement;
|
||||
}
|
|
@ -226,7 +226,7 @@ function forum_get_colour(int $forumId): int {
|
|||
$forumId = $colourInfo['forum_parent'];
|
||||
}
|
||||
|
||||
return colour_none();
|
||||
return 0x40000000;
|
||||
}
|
||||
|
||||
function forum_increment_clicks(int $forumId): void {
|
||||
|
|
|
@ -7,7 +7,7 @@ function forum_post_create(
|
|||
int $userId,
|
||||
string $ipAddress,
|
||||
string $text,
|
||||
int $parser = MSZ_PARSER_PLAIN,
|
||||
int $parser = \Misuzu\Parsers\Parser::PLAIN,
|
||||
bool $displaySignature = true
|
||||
): int {
|
||||
$createPost = \Misuzu\DB::prepare('
|
||||
|
@ -31,7 +31,7 @@ function forum_post_update(
|
|||
int $postId,
|
||||
string $ipAddress,
|
||||
string $text,
|
||||
int $parser = MSZ_PARSER_PLAIN,
|
||||
int $parser = \Misuzu\Parsers\Parser::PLAIN,
|
||||
bool $displaySignature = true,
|
||||
bool $bumpUpdate = true
|
||||
): bool {
|
||||
|
|
23
src/Net/GeoIP.php
Normal file
23
src/Net/GeoIP.php
Normal file
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
namespace Misuzu\Net;
|
||||
|
||||
use GeoIp2\Database\Reader as GeoIPDBReader;
|
||||
|
||||
final class GeoIP {
|
||||
private static $geoip = null;
|
||||
private static $geoipDbPath = null;
|
||||
|
||||
public static function init(string $dbPath): void {
|
||||
self::$geoipDbPath = $dbPath;
|
||||
}
|
||||
|
||||
public static function getReader(): GeoIPDBReader {
|
||||
if(self::$geoip === null)
|
||||
self::$geoip = new GeoIPDBReader(self::$geoipDbPath);
|
||||
return self::$geoip;
|
||||
}
|
||||
|
||||
public static function resolveCountry(string $ipAddress) {
|
||||
return self::getReader()->country($ipAddress);
|
||||
}
|
||||
}
|
108
src/Net/IPAddress.php
Normal file
108
src/Net/IPAddress.php
Normal file
|
@ -0,0 +1,108 @@
|
|||
<?php
|
||||
namespace Misuzu\Net;
|
||||
|
||||
use Exception;
|
||||
use InvalidArgumentException;
|
||||
|
||||
final class IPAddress {
|
||||
public const VERSION_UNKNOWN = 0;
|
||||
public const VERSION_4 = 4;
|
||||
public const VERSION_6 = 6;
|
||||
|
||||
private const SIZES = [
|
||||
self::VERSION_4 => 4,
|
||||
self::VERSION_6 => 16,
|
||||
];
|
||||
|
||||
public const DEFAULT_V4 = '127.1';
|
||||
public const DEFAULT_V6 = '::1';
|
||||
|
||||
public static function remote(string $fallback = self::DEFAULT_V6): string {
|
||||
return $_SERVER['REMOTE_ADDR'] ?? $fallback;
|
||||
}
|
||||
|
||||
public static function country(string $address, string $fallback = 'XX'): string {
|
||||
try {
|
||||
return GeoIP::resolveCountry($address)->country->isoCode ?? $fallback;
|
||||
} catch(Exception $e) {}
|
||||
|
||||
return $fallback;
|
||||
}
|
||||
|
||||
public static function rawWidth(int $version): int {
|
||||
return isset(self::SIZES[$version]) ? self::SIZES[$version] : 0;
|
||||
}
|
||||
|
||||
public static function detectStringVersion(string $address): int {
|
||||
if(filter_var($address, FILTER_VALIDATE_IP) !== false) {
|
||||
if(filter_var($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== false)
|
||||
return self::VERSION_6;
|
||||
|
||||
if(filter_var($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false)
|
||||
return self::VERSION_4;
|
||||
}
|
||||
|
||||
return self::VERSION_UNKNOWN;
|
||||
}
|
||||
|
||||
public static function detectRawVersion(string $address): int {
|
||||
$addressLength = strlen($address);
|
||||
|
||||
foreach(self::SIZES as $version => $length) {
|
||||
if($length === $addressLength)
|
||||
return $version;
|
||||
}
|
||||
|
||||
return self::VERSION_UNKNOWN;
|
||||
}
|
||||
|
||||
public static function cidrToRaw(string $cidr): ?array {
|
||||
if(strpos($cidr, '/') !== false) {
|
||||
[$subnet, $mask] = explode('/', $cidr, 2);
|
||||
} else {
|
||||
$subnet = $cidr;
|
||||
}
|
||||
|
||||
try {
|
||||
$subnet = inet_pton($subnet);
|
||||
} catch(Exception $ex) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$mask = empty($mask) ? null : (int)$mask;
|
||||
|
||||
return compact('subnet', 'mask');
|
||||
}
|
||||
|
||||
public static function matchCidr(string $address, string $cidr): bool {
|
||||
$address = inet_pton($address);
|
||||
$cidr = self::cidrToRaw($cidr);
|
||||
return self::matchCidrRaw($address, $cidr['subnet'], $cidr['mask']);
|
||||
}
|
||||
|
||||
public static function matchCidrRaw(string $address, string $subnet, ?int $mask = null): bool {
|
||||
$version = self::detectRawVersion($subnet);
|
||||
|
||||
if($version === self::VERSION_UNKNOWN)
|
||||
return false;
|
||||
|
||||
$bits = self::SIZES[$version] * 8;
|
||||
|
||||
if(empty($mask))
|
||||
$mask = $bits;
|
||||
|
||||
if($mask < 1 || $mask > $bits || $version !== self::detectRawVersion($subnet))
|
||||
return false;
|
||||
|
||||
for($i = 0; $i < ceil($mask / 8); $i++) {
|
||||
$byteMask = (0xFF00 >> max(0, min(8, $mask - ($i * 8)))) & 0xFF;
|
||||
$addressByte = ord($address[$i]) & $byteMask;
|
||||
$subnetByte = ord($subnet[$i]) & $byteMask;
|
||||
|
||||
if($addressByte !== $subnetByte)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
80
src/Net/IPAddressBlacklist.php
Normal file
80
src/Net/IPAddressBlacklist.php
Normal file
|
@ -0,0 +1,80 @@
|
|||
<?php
|
||||
namespace Misuzu\Net;
|
||||
|
||||
use Misuzu\DB;
|
||||
|
||||
final class IPAddressBlacklist {
|
||||
public static function check(string $address): bool {
|
||||
return (bool)DB::prepare("
|
||||
SELECT INET6_ATON(:address) AS `target`, (
|
||||
SELECT COUNT(*) > 0
|
||||
FROM `msz_ip_blacklist`
|
||||
WHERE LENGTH(`ip_subnet`) = LENGTH(`target`)
|
||||
AND `ip_subnet` & LPAD('', LENGTH(`ip_subnet`), X'FF') << LENGTH(`ip_subnet`) * 8 - `ip_mask`
|
||||
= `target` & LPAD('', LENGTH(`ip_subnet`), X'FF') << LENGTH(`ip_subnet`) * 8 - `ip_mask`
|
||||
)
|
||||
")->bind('address', $address)
|
||||
->fetchColumn(1, false);
|
||||
}
|
||||
|
||||
public static function add(string $cidr): bool {
|
||||
$raw = IPAddress::cidrToRaw($cidr);
|
||||
|
||||
if(empty($raw))
|
||||
return false;
|
||||
|
||||
return self::addRaw($raw['subnet'], $raw['mask']);
|
||||
}
|
||||
|
||||
public static function addRaw(string $subnet, ?int $mask = null): bool {
|
||||
$version = IPAddress::detectRawVersion($subnet);
|
||||
|
||||
if($version === IPAddress::VERSION_UNKNOWN)
|
||||
return false;
|
||||
|
||||
$bits = IPAddress::rawWidth($version) * 8;
|
||||
|
||||
if(empty($mask)) {
|
||||
$mask = $bits;
|
||||
} elseif($mask < 1 || $mask > $bits) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return DB::prepare('
|
||||
REPLACE INTO `msz_ip_blacklist` (`ip_subnet`, `ip_mask`)
|
||||
VALUES (:subnet, :mask)
|
||||
')->bind('subnet', $subnet)
|
||||
->bind('mask', $mask)
|
||||
->execute();
|
||||
}
|
||||
|
||||
public static function remove(string $cidr): bool {
|
||||
$raw = IPAddress::cidrToRaw($cidr);
|
||||
|
||||
if(empty($raw))
|
||||
return false;
|
||||
|
||||
return self::removeRaw($raw['subnet'], $raw['mask']);
|
||||
}
|
||||
|
||||
public static function removeRaw(string $subnet, ?int $mask = null): bool {
|
||||
return DB::prepare('
|
||||
DELETE FROM `msz_ip_blacklist`
|
||||
WHERE `ip_subnet` = :subnet
|
||||
AND `ip_mask` = :mask
|
||||
')->bind('subnet', $subnet)
|
||||
->bind('mask', $mask)
|
||||
->execute();
|
||||
}
|
||||
|
||||
public static function list(): array {
|
||||
return DB::query("
|
||||
SELECT
|
||||
INET6_NTOA(`ip_subnet`) AS `ip_subnet`,
|
||||
`ip_mask`,
|
||||
LENGTH(`ip_subnet`) AS `ip_bytes`,
|
||||
CONCAT(INET6_NTOA(`ip_subnet`), '/', `ip_mask`) as `ip_cidr`
|
||||
FROM `msz_ip_blacklist`
|
||||
")->fetchAll();
|
||||
}
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
<?php
|
||||
use GeoIp2\Database\Reader as GeoIPDBReader;
|
||||
|
||||
function geoip_init(?string $database = null): void {
|
||||
$existing = geoip_instance();
|
||||
|
||||
if(!empty($existing)) {
|
||||
$existing->close();
|
||||
}
|
||||
|
||||
geoip_instance(new GeoIPDBReader($database ?? \Misuzu\Config::get('geoip.database')));
|
||||
}
|
||||
|
||||
function geoip_instance(?GeoIPDBReader $newInstance = null): ?GeoIPDBReader {
|
||||
static $instance = null;
|
||||
|
||||
if(!empty($newInstance)) {
|
||||
$instance = $newInstance;
|
||||
}
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
function geoip_cache(string $section, string $ipAddress, callable $value) {
|
||||
static $memo = [];
|
||||
|
||||
if(empty($meme[$ipAddress][$section])) {
|
||||
$memo[$ipAddress][$section] = $value();
|
||||
}
|
||||
|
||||
return $memo[$ipAddress][$section] ?? null;
|
||||
}
|
||||
|
||||
function geoip_country(string $ipAddress) {
|
||||
return geoip_cache('country', $ipAddress, function () use ($ipAddress) {
|
||||
return geoip_instance()->country($ipAddress);
|
||||
});
|
||||
}
|
184
src/Net/ip.php
184
src/Net/ip.php
|
@ -1,184 +0,0 @@
|
|||
<?php
|
||||
define('MSZ_IP_UNKNOWN', 0);
|
||||
define('MSZ_IP_V4', 4);
|
||||
define('MSZ_IP_V6', 6);
|
||||
|
||||
define('MSZ_IP_SIZES', [
|
||||
MSZ_IP_V4 => 4,
|
||||
MSZ_IP_V6 => 16,
|
||||
]);
|
||||
|
||||
function ip_remote_address(string $fallback = '::1'): string {
|
||||
return $_SERVER['REMOTE_ADDR'] ?? $fallback;
|
||||
}
|
||||
|
||||
function ip_country_code(string $address, string $fallback = 'XX'): string {
|
||||
try {
|
||||
return geoip_country($address)->country->isoCode ?? $fallback;
|
||||
} catch(Exception $e) {}
|
||||
|
||||
return $fallback;
|
||||
}
|
||||
|
||||
function ip_get_string_version(string $address): int {
|
||||
if(filter_var($address, FILTER_VALIDATE_IP) === false) {
|
||||
return MSZ_IP_UNKNOWN;
|
||||
}
|
||||
|
||||
if(filter_var($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== false) {
|
||||
return MSZ_IP_V6;
|
||||
}
|
||||
|
||||
if(filter_var($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false) {
|
||||
return MSZ_IP_V4;
|
||||
}
|
||||
|
||||
return MSZ_IP_UNKNOWN;
|
||||
}
|
||||
|
||||
function ip_get_raw_version(string $raw): int {
|
||||
$rawLength = strlen($raw);
|
||||
|
||||
foreach(MSZ_IP_SIZES as $version => $length) {
|
||||
if($rawLength === $length) {
|
||||
return $version;
|
||||
}
|
||||
}
|
||||
|
||||
return MSZ_IP_UNKNOWN;
|
||||
}
|
||||
|
||||
function ip_get_raw_width(int $version): int {
|
||||
return MSZ_IP_SIZES[$version] ?? 0;
|
||||
}
|
||||
|
||||
function ip_match_cidr_raw(string $address, string $subnet, ?int $mask = null): bool {
|
||||
$version = ip_get_raw_version($subnet);
|
||||
$bits = ip_get_raw_width($version) * 8;
|
||||
|
||||
if(empty($mask)) {
|
||||
$mask = $bits;
|
||||
}
|
||||
|
||||
if($mask < 1 || $mask > $bits || $version !== ip_get_raw_version($subnet)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for($i = 0; $i < ceil($mask / 8); $i++) {
|
||||
$byteMask = (0xFF00 >> max(0, min(8, $mask - ($i * 8)))) & 0xFF;
|
||||
$addressByte = ord($address[$i]) & $byteMask;
|
||||
$subnetByte = ord($subnet[$i]) & $byteMask;
|
||||
|
||||
if($addressByte !== $subnetByte) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function ip_match_cidr(string $address, string $cidr): bool {
|
||||
$address = inet_pton($address);
|
||||
[$subnet, $mask] = ['', 0];
|
||||
extract(ip_cidr_to_raw($cidr));
|
||||
return ip_match_cidr_raw($address, $subnet, $mask);
|
||||
}
|
||||
|
||||
function ip_cidr_to_raw(string $cidr): ?array {
|
||||
if(strpos($cidr, '/') !== false) {
|
||||
[$subnet, $mask] = explode('/', $cidr, 2);
|
||||
} else {
|
||||
$subnet = $cidr;
|
||||
}
|
||||
|
||||
try {
|
||||
$subnet = inet_pton($subnet);
|
||||
} catch(Exception $ex) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$mask = empty($mask) ? null : (int)$mask;
|
||||
|
||||
return compact('subnet', 'mask');
|
||||
}
|
||||
|
||||
function ip_blacklist_check(string $address): bool {
|
||||
$checkBlacklist = \Misuzu\DB::prepare("
|
||||
SELECT COUNT(*) > 0
|
||||
FROM `msz_ip_blacklist`
|
||||
WHERE LENGTH(`ip_subnet`) = LENGTH(INET6_ATON(:ip1))
|
||||
AND `ip_subnet` & LPAD('', LENGTH(`ip_subnet`), X'FF') << LENGTH(`ip_subnet`) * 8 - `ip_mask`
|
||||
= INET6_ATON(:ip2) & LPAD('', LENGTH(`ip_subnet`), X'FF') << LENGTH(`ip_subnet`) * 8 - `ip_mask`
|
||||
");
|
||||
$checkBlacklist->bind('ip1', $address);
|
||||
$checkBlacklist->bind('ip2', $address);
|
||||
return (bool)$checkBlacklist->fetchColumn();
|
||||
}
|
||||
|
||||
function ip_blacklist_add_raw(string $subnet, ?int $mask = null): bool {
|
||||
$version = ip_get_raw_version($subnet);
|
||||
|
||||
if($version === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$bits = ip_get_raw_width($version) * 8;
|
||||
|
||||
if(empty($mask)) {
|
||||
$mask = $bits;
|
||||
} elseif($mask < 1 || $mask > $bits) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$addBlacklist = \Misuzu\DB::prepare('
|
||||
REPLACE INTO `msz_ip_blacklist`
|
||||
(`ip_subnet`, `ip_mask`)
|
||||
VALUES
|
||||
(:subnet, :mask)
|
||||
');
|
||||
$addBlacklist->bind('subnet', $subnet);
|
||||
$addBlacklist->bind('mask', $mask);
|
||||
return $addBlacklist->execute();
|
||||
}
|
||||
|
||||
function ip_blacklist_add(string $cidr): bool {
|
||||
$raw = ip_cidr_to_raw($cidr);
|
||||
|
||||
if(empty($raw)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ip_blacklist_add_raw($raw['subnet'], $raw['mask']);
|
||||
}
|
||||
|
||||
function ip_blacklist_remove_raw(string $subnet, ?int $mask = null): bool {
|
||||
$removeBlacklist = \Misuzu\DB::prepare('
|
||||
DELETE FROM `msz_ip_blacklist`
|
||||
WHERE `ip_subnet` = :subnet
|
||||
AND `ip_mask` = :mask
|
||||
');
|
||||
$removeBlacklist->bind('subnet', $subnet);
|
||||
$removeBlacklist->bind('mask', $mask);
|
||||
return $removeBlacklist->execute();
|
||||
}
|
||||
|
||||
function ip_blacklist_remove(string $cidr): bool {
|
||||
$raw = ip_cidr_to_raw($cidr);
|
||||
|
||||
if(empty($raw)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ip_blacklist_remove_raw($raw['subnet'], $raw['mask']);
|
||||
}
|
||||
|
||||
function ip_blacklist_list(): array {
|
||||
return \Misuzu\DB::query("
|
||||
SELECT
|
||||
INET6_NTOA(`ip_subnet`) AS `ip_subnet`,
|
||||
`ip_mask`,
|
||||
LENGTH(`ip_subnet`) AS `ip_bytes`,
|
||||
CONCAT(INET6_NTOA(`ip_subnet`), '/', `ip_mask`) as `ip_cidr`
|
||||
FROM `msz_ip_blacklist`
|
||||
")->fetchAll();
|
||||
}
|
|
@ -4,12 +4,6 @@ namespace Misuzu\Parsers\BBCode;
|
|||
use Misuzu\Parsers\ParserInterface;
|
||||
|
||||
class BBCodeParser implements ParserInterface {
|
||||
private static $instance;
|
||||
|
||||
public static function instance(): BBCodeParser {
|
||||
return is_null(static::$instance) ? (static::$instance = new BBCodeParser()) : static::$instance;
|
||||
}
|
||||
|
||||
private $tags = [];
|
||||
|
||||
public function __construct(array $tags = []) {
|
||||
|
|
44
src/Parsers/Parser.php
Normal file
44
src/Parsers/Parser.php
Normal file
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
namespace Misuzu\Parsers;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use Misuzu\Parsers\BBCode\BBCodeParser;
|
||||
|
||||
final class Parser {
|
||||
public const PLAIN = 0;
|
||||
public const BBCODE = 1;
|
||||
public const MARKDOWN = 2;
|
||||
|
||||
private const PARSERS = [
|
||||
self::PLAIN => PlainParser::class,
|
||||
self::BBCODE => BBCodeParser::class,
|
||||
self::MARKDOWN => MarkdownParser::class,
|
||||
];
|
||||
public const NAMES = [
|
||||
self::PLAIN => 'Plain text',
|
||||
self::BBCODE => 'BB Code',
|
||||
self::MARKDOWN => 'Markdown',
|
||||
];
|
||||
|
||||
private static $instances = [];
|
||||
|
||||
public static function isValid(int $parser): bool {
|
||||
return array_key_exists($parser, self::PARSERS);
|
||||
}
|
||||
|
||||
public static function name(int $parser): string {
|
||||
return self::isValid($parser) ? self::NAMES[$parser] : '';
|
||||
}
|
||||
|
||||
public static function instance(int $parser): ParserInterface {
|
||||
if(!self::isValid($parser))
|
||||
throw new InvalidArgumentException('Invalid parser.');
|
||||
|
||||
if(!isset(self::$instances[$parser])) {
|
||||
$className = self::PARSERS[$parser];
|
||||
self::$instances[$parser] = new $className;
|
||||
}
|
||||
|
||||
return self::$instances[$parser];
|
||||
}
|
||||
}
|
12
src/Parsers/PlainParser.php
Normal file
12
src/Parsers/PlainParser.php
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
namespace Misuzu\Parsers;
|
||||
|
||||
class PlainParser implements ParserInterface {
|
||||
public function parseText(string $text): string {
|
||||
return nl2br($text);
|
||||
}
|
||||
|
||||
public function parseLine(string $line): string {
|
||||
return $line;
|
||||
}
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
<?php
|
||||
use Misuzu\Parsers\MarkdownParser;
|
||||
use Misuzu\Parsers\BBCode\BBCodeParser;
|
||||
|
||||
define('MSZ_PARSER_PLAIN', 0);
|
||||
define('MSZ_PARSER_BBCODE', 1);
|
||||
define('MSZ_PARSER_MARKDOWN', 2);
|
||||
define('MSZ_PARSERS', [
|
||||
MSZ_PARSER_PLAIN,
|
||||
MSZ_PARSER_BBCODE,
|
||||
MSZ_PARSER_MARKDOWN,
|
||||
]);
|
||||
|
||||
define('MSZ_PARSERS_NAMES', [
|
||||
MSZ_PARSER_PLAIN => 'Plain text',
|
||||
MSZ_PARSER_BBCODE => 'BB Code',
|
||||
MSZ_PARSER_MARKDOWN => 'Markdown',
|
||||
]);
|
||||
|
||||
function parser_is_valid(int $parser): bool {
|
||||
return in_array($parser, MSZ_PARSERS, true);
|
||||
}
|
||||
|
||||
function parser_name(int $parser): string {
|
||||
return parser_is_valid($parser) ? MSZ_PARSERS_NAMES[$parser] : '';
|
||||
}
|
||||
|
||||
function parse_text(string $text, int $parser): string {
|
||||
if(!parser_is_valid($parser)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
switch($parser) {
|
||||
case MSZ_PARSER_MARKDOWN:
|
||||
return MarkdownParser::instance()->parseText($text);
|
||||
|
||||
case MSZ_PARSER_BBCODE:
|
||||
return BBCodeParser::instance()->parseText($text);
|
||||
|
||||
case MSZ_PARSER_PLAIN:
|
||||
return nl2br($text);
|
||||
}
|
||||
}
|
||||
|
||||
function parse_line(string $line, int $parser): string {
|
||||
if(!parser_is_valid($parser)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
switch($parser) {
|
||||
case MSZ_PARSER_MARKDOWN:
|
||||
return MarkdownParser::instance()->parseLine($line);
|
||||
|
||||
case MSZ_PARSER_BBCODE:
|
||||
return BBCodeParser::instance()->parseLine($line);
|
||||
|
||||
case MSZ_PARSER_PLAIN:
|
||||
return $line;
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ namespace Misuzu;
|
|||
use Twig_Extension;
|
||||
use Twig_Filter;
|
||||
use Twig_Function;
|
||||
use Misuzu\Parsers\Parser;
|
||||
|
||||
final class TwigMisuzu extends Twig_Extension {
|
||||
public function getFilters() {
|
||||
|
@ -13,19 +14,13 @@ final class TwigMisuzu extends Twig_Extension {
|
|||
new Twig_Filter('first_paragraph', 'first_paragraph'),
|
||||
new Twig_Filter('byte_symbol', 'byte_symbol'),
|
||||
new Twig_Filter('html_link', 'html_link'),
|
||||
new Twig_Filter('parse_line', 'parse_line'),
|
||||
new Twig_Filter('parse_text', 'parse_text'),
|
||||
// deprecate this call, convert to html in php
|
||||
new Twig_Filter('parse_text', fn(string $text, int $parser): string => Parser::instance($parser)->parseText($text)),
|
||||
new Twig_Filter('asset_url', [static::class, 'assetUrl']),
|
||||
new Twig_Filter('perms_check', 'perms_check'),
|
||||
new Twig_Filter('bg_settings', 'user_background_settings_strings'),
|
||||
new Twig_Filter('colour_contrast', 'colour_get_css_contrast'),
|
||||
new Twig_Filter('colour_props', 'colour_get_properties'),
|
||||
new Twig_Filter('colour_hex', 'colour_get_hex'),
|
||||
new Twig_Filter('colour_inherit', 'colour_get_inherit'),
|
||||
new Twig_Filter('clamp', 'clamp'),
|
||||
new Twig_Filter('log_format', function (string $text, string $json): string {
|
||||
return vsprintf($text, json_decode($json));
|
||||
}),
|
||||
new Twig_Filter('log_format', fn(string $text, string $json) => vsprintf($text, json_decode($json))),
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ function user_login_attempt_record(bool $success, ?int $userId, string $ipAddres
|
|||
|
||||
$storeAttempt->bind('attempt_success', $success ? 1 : 0);
|
||||
$storeAttempt->bind('attempt_ip', $ipAddress);
|
||||
$storeAttempt->bind('attempt_country', ip_country_code($ipAddress));
|
||||
$storeAttempt->bind('attempt_country', \Misuzu\Net\IPAddress::country($ipAddress));
|
||||
$storeAttempt->bind('attempt_user_agent', $userAgent);
|
||||
$storeAttempt->bind('user_id', $userId, $userId === null ? PDO::PARAM_NULL : PDO::PARAM_INT);
|
||||
$storeAttempt->execute();
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
namespace Misuzu\Users;
|
||||
|
||||
use Misuzu\DB;
|
||||
use Misuzu\Net\IPAddress;
|
||||
|
||||
class User {
|
||||
private const USER_SELECT = '
|
||||
|
@ -42,7 +43,7 @@ class User {
|
|||
') ->bind('username', $username)->bind('email', $email)
|
||||
->bind('register_ip', $ipAddress)->bind('last_ip', $ipAddress)
|
||||
->bind('password', user_password_hash($password))
|
||||
->bind('user_country', ip_country_code($ipAddress))
|
||||
->bind('user_country', IPAddress::country($ipAddress))
|
||||
->executeGetId();
|
||||
|
||||
if($createUser < 1)
|
||||
|
|
|
@ -22,7 +22,7 @@ function user_session_create(
|
|||
');
|
||||
$createSession->bind('user_id', $userId);
|
||||
$createSession->bind('session_ip', $ipAddress);
|
||||
$createSession->bind('session_country', ip_country_code($ipAddress));
|
||||
$createSession->bind('session_country', \Misuzu\Net\IPAddress::country($ipAddress));
|
||||
$createSession->bind('session_user_agent', $userAgent);
|
||||
$createSession->bind('session_key', $sessionKey);
|
||||
|
||||
|
@ -123,7 +123,7 @@ function user_session_bump_active(int $sessionId, string $ipAddress = null): voi
|
|||
WHERE `session_id` = :session_id
|
||||
');
|
||||
$bump->bind('session_id', $sessionId);
|
||||
$bump->bind('last_ip', $ipAddress ?? ip_remote_address());
|
||||
$bump->bind('last_ip', $ipAddress ?? \Misuzu\Net\IPAddress::remote());
|
||||
$bump->execute();
|
||||
}
|
||||
|
||||
|
|
|
@ -171,7 +171,7 @@ function user_bump_last_active(int $userId, string $ipAddress = null): void {
|
|||
`last_ip` = INET6_ATON(:last_ip)
|
||||
WHERE `user_id` = :user_id
|
||||
');
|
||||
$bumpUserLast->bind('last_ip', $ipAddress ?? ip_remote_address());
|
||||
$bumpUserLast->bind('last_ip', $ipAddress ?? \Misuzu\Net\IPAddress::remote());
|
||||
$bumpUserLast->bind('user_id', $userId);
|
||||
$bumpUserLast->execute();
|
||||
}
|
||||
|
@ -306,12 +306,12 @@ define('MSZ_E_USER_ABOUT_INVALID_PARSER', 2);
|
|||
define('MSZ_E_USER_ABOUT_TOO_LONG', 3);
|
||||
define('MSZ_E_USER_ABOUT_UPDATE_FAILED', 4);
|
||||
|
||||
function user_set_about_page(int $userId, string $content, int $parser = MSZ_PARSER_PLAIN): int {
|
||||
function user_set_about_page(int $userId, string $content, int $parser = \Misuzu\Parsers\Parser::PLAIN): int {
|
||||
if($userId < 1) {
|
||||
return MSZ_E_USER_ABOUT_INVALID_USER;
|
||||
}
|
||||
|
||||
if(!parser_is_valid($parser)) {
|
||||
if(!\Misuzu\Parsers\Parser::isValid($parser)) {
|
||||
return MSZ_E_USER_ABOUT_INVALID_PARSER;
|
||||
}
|
||||
|
||||
|
@ -344,12 +344,12 @@ define('MSZ_E_USER_SIGNATURE_INVALID_PARSER', 2);
|
|||
define('MSZ_E_USER_SIGNATURE_TOO_LONG', 3);
|
||||
define('MSZ_E_USER_SIGNATURE_UPDATE_FAILED', 4);
|
||||
|
||||
function user_set_signature(int $userId, string $content, int $parser = MSZ_PARSER_PLAIN): int {
|
||||
function user_set_signature(int $userId, string $content, int $parser = \Misuzu\Parsers\Parser::PLAIN): int {
|
||||
if($userId < 1) {
|
||||
return MSZ_E_USER_SIGNATURE_INVALID_USER;
|
||||
}
|
||||
|
||||
if(!parser_is_valid($parser)) {
|
||||
if(!\Misuzu\Parsers\Parser::isValid($parser)) {
|
||||
return MSZ_E_USER_SIGNATURE_INVALID_PARSER;
|
||||
}
|
||||
|
||||
|
|
|
@ -76,7 +76,7 @@ function audit_log(
|
|||
array $params = [],
|
||||
?string $ipAddress = null
|
||||
): void {
|
||||
$ipAddress = $ipAddress ?? ip_remote_address();
|
||||
$ipAddress = $ipAddress ?? \Misuzu\Net\IPAddress::remote();
|
||||
|
||||
for($i = 0; $i < count($params); $i++) {
|
||||
if(preg_match('#^(-?[0-9]+)$#', $params[$i])) {
|
||||
|
@ -94,7 +94,7 @@ function audit_log(
|
|||
$addLog->bind('user', $userId < 1 ? null : $userId);
|
||||
$addLog->bind('params', json_encode($params));
|
||||
$addLog->bind('ip', $ipAddress);
|
||||
$addLog->bind('country', ip_country_code($ipAddress));
|
||||
$addLog->bind('country', \Misuzu\Net\IPAddress::country($ipAddress));
|
||||
$addLog->execute();
|
||||
}
|
||||
|
||||
|
|
136
src/colour.php
136
src/colour.php
|
@ -1,136 +0,0 @@
|
|||
<?php
|
||||
define('MSZ_COLOUR_INHERIT', 0x40000000);
|
||||
define('MSZ_COLOUR_READABILITY_THRESHOLD', 186);
|
||||
define('MSZ_COLOUR_LUMINANCE_WEIGHT_RED', 0.299);
|
||||
define('MSZ_COLOUR_LUMINANCE_WEIGHT_GREEN', 0.587);
|
||||
define('MSZ_COLOUR_LUMINANCE_WEIGHT_BLUE', 0.114);
|
||||
|
||||
function colour_create(): int {
|
||||
return 0;
|
||||
}
|
||||
|
||||
function colour_none(): int {
|
||||
return MSZ_COLOUR_INHERIT;
|
||||
}
|
||||
|
||||
function colour_set_inherit(int &$colour, bool $enabled = true): void {
|
||||
if($enabled) {
|
||||
$colour |= MSZ_COLOUR_INHERIT;
|
||||
} else {
|
||||
$colour &= ~MSZ_COLOUR_INHERIT;
|
||||
}
|
||||
}
|
||||
|
||||
function colour_get_inherit(int $colour): bool {
|
||||
return ($colour & MSZ_COLOUR_INHERIT) > 0;
|
||||
}
|
||||
|
||||
function colour_get_red(int $colour): int {
|
||||
return ($colour >> 16) & 0xFF;
|
||||
}
|
||||
|
||||
function colour_set_red(int &$colour, int $red): void {
|
||||
$red = $red & 0xFF;
|
||||
$colour &= ~0xFF0000;
|
||||
$colour |= $red << 16;
|
||||
}
|
||||
|
||||
function colour_get_green(int $colour): int {
|
||||
return ($colour >> 8) & 0xFF;
|
||||
}
|
||||
|
||||
function colour_set_green(int &$colour, int $green): void {
|
||||
$green = $green & 0xFF;
|
||||
$colour &= ~0xFF00;
|
||||
$colour |= $green << 8;
|
||||
}
|
||||
|
||||
function colour_get_blue(int $colour): int {
|
||||
return $colour & 0xFF;
|
||||
}
|
||||
|
||||
function colour_set_blue(int &$colour, int $blue): void {
|
||||
$blue = $blue & 0xFF;
|
||||
$colour &= ~0xFF;
|
||||
$colour |= $blue;
|
||||
}
|
||||
|
||||
function colour_get_luminance(int $colour): int {
|
||||
return MSZ_COLOUR_LUMINANCE_WEIGHT_RED * colour_get_red($colour)
|
||||
+ MSZ_COLOUR_LUMINANCE_WEIGHT_GREEN * colour_get_green($colour)
|
||||
+ MSZ_COLOUR_LUMINANCE_WEIGHT_BLUE * colour_get_blue($colour);
|
||||
}
|
||||
|
||||
function colour_get_hex(int $colour, string $format = '#%s'): string {
|
||||
return sprintf(
|
||||
$format,
|
||||
str_pad(dechex($colour & 0xFFFFFF), 6, '0', STR_PAD_LEFT)
|
||||
);
|
||||
}
|
||||
|
||||
function colour_get_css(int $colour): string {
|
||||
if(colour_get_inherit($colour)) {
|
||||
return 'inherit';
|
||||
}
|
||||
|
||||
return colour_get_hex($colour);
|
||||
}
|
||||
|
||||
function colour_get_css_contrast(
|
||||
int $colour,
|
||||
string $dark = 'dark',
|
||||
string $light = 'light',
|
||||
bool $inheritIsDark = true
|
||||
): string {
|
||||
if(colour_get_inherit($colour)) {
|
||||
return $inheritIsDark ? $dark : $light;
|
||||
}
|
||||
|
||||
return colour_get_luminance($colour) > MSZ_COLOUR_READABILITY_THRESHOLD
|
||||
? $dark
|
||||
: $light;
|
||||
}
|
||||
|
||||
function colour_from_rgb(int &$colour, int $red, int $green, int $blue): bool {
|
||||
colour_set_red($colour, $red);
|
||||
colour_set_green($colour, $green);
|
||||
colour_set_blue($colour, $blue);
|
||||
return true;
|
||||
}
|
||||
|
||||
function colour_from_hex(int &$colour, string $hex): bool {
|
||||
if($hex[0] === '#') {
|
||||
$hex = mb_substr($hex, 1);
|
||||
}
|
||||
|
||||
$length = mb_strlen($hex);
|
||||
|
||||
if($length === 3) {
|
||||
$hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2];
|
||||
} elseif($length !== 6) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!ctype_xdigit($hex)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
colour_from_rgb(
|
||||
$colour,
|
||||
hexdec(mb_substr($hex, 0, 2)),
|
||||
hexdec(mb_substr($hex, 2, 2)),
|
||||
hexdec(mb_substr($hex, 4, 2))
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function colour_get_properties(int $colour): array {
|
||||
return [
|
||||
'red' => colour_get_red($colour),
|
||||
'green' => colour_get_green($colour),
|
||||
'blue' => colour_get_blue($colour),
|
||||
'inherit' => colour_get_inherit($colour),
|
||||
'luminance' => colour_get_luminance($colour),
|
||||
];
|
||||
}
|
95
src/news.php
95
src/news.php
|
@ -325,98 +325,3 @@ function news_post_get(int $postId): array {
|
|||
$getPost->bind(':post_id', $postId);
|
||||
return $getPost->fetch();
|
||||
}
|
||||
|
||||
define('MSZ_NEWS_FEED_ATOM', 'atom');
|
||||
define('MSZ_NEWS_FEED_RSS', 'rss');
|
||||
define('MSZ_NEWS_SUPPORTED_FEEDS', [
|
||||
MSZ_NEWS_FEED_ATOM, MSZ_NEWS_FEED_RSS,
|
||||
]);
|
||||
|
||||
function news_feed_supported(string $type): string {
|
||||
return in_array($type, MSZ_NEWS_SUPPORTED_FEEDS);
|
||||
}
|
||||
|
||||
function news_feed(string $type, array $posts, array $info): string {
|
||||
if(!news_feed_supported($type)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$document = new DOMDocument('1.0', 'utf-8');
|
||||
$urlPrefix = url_prefix(false);
|
||||
$htmlUrl = $urlPrefix . $info['html-url'];
|
||||
$feedUrl = $urlPrefix . $info['feed-url'];
|
||||
|
||||
if($type === 'rss') {
|
||||
$dateFormat = 'r';
|
||||
$rss = $document->appendChild($document->createElement('rss'));
|
||||
$rss->setAttribute('version', '2.0');
|
||||
$rss->setAttribute('xmlns:atom', 'http://www.w3.org/2005/Atom');
|
||||
$feed = $rss->appendChild($document->createElement('channel'));
|
||||
$feed->appendChild($document->createElement('ttl', '900'));
|
||||
} else {
|
||||
$dateFormat = 'c';
|
||||
$feed = $document->appendChild($document->createElement('feed'));
|
||||
$feed->setAttribute('xmlns', 'http://www.w3.org/2005/Atom');
|
||||
$link = $feed->appendChild($document->createElement('link'));
|
||||
$link->setAttribute('href', $htmlUrl);
|
||||
}
|
||||
|
||||
$feed->appendChild($document->createElement('title', $info['title']));
|
||||
$feed->appendChild($document->createElement(
|
||||
$type === 'rss' ? 'description' : 'subtitle', $info['subtitle']
|
||||
));
|
||||
$feed->appendChild($document->createElement(
|
||||
$type === 'rss' ? 'link' : 'id', $htmlUrl
|
||||
));
|
||||
$feed->appendChild($document->createElement(
|
||||
$type === 'rss' ? 'pubDate' : 'updated',
|
||||
date($dateFormat, strtotime($posts[0]['post_created']))
|
||||
));
|
||||
$link = $feed->appendChild($document->createElement($type === 'rss' ? 'atom:link' : 'link'));
|
||||
$link->setAttribute('rel', 'self');
|
||||
$link->setAttribute('href', $feedUrl);
|
||||
|
||||
foreach($posts as $post) {
|
||||
$entry = $feed->appendChild($document->createElement($type === 'rss' ? 'item' : 'entry'));
|
||||
$entry->appendChild($document->createElement('title', $post['post_title']));
|
||||
$entry->appendChild($document->createElement(
|
||||
$type === 'rss' ? 'link' : 'id',
|
||||
$urlPrefix . url('news-post', ['post' => $post['post_id']])
|
||||
));
|
||||
$entry->appendChild($document->createElement(
|
||||
$type === 'rss' ? 'pubDate' : 'updated',
|
||||
date($dateFormat, strtotime($post['post_created']))
|
||||
));
|
||||
|
||||
$entry->appendChild($document->createElement(
|
||||
$type === 'rss' ? 'description' : 'summary',
|
||||
first_paragraph($post['post_text'])
|
||||
));
|
||||
|
||||
if($type === 'rss') {
|
||||
$entry->appendChild($document->createElement(
|
||||
'comments',
|
||||
$urlPrefix . url('news-post-comments', ['post' => $post['post_id']])
|
||||
));
|
||||
$guid = $entry->appendChild($document->createElement(
|
||||
'guid',
|
||||
$urlPrefix . url('news-post', ['post' => $post['post_id']])
|
||||
));
|
||||
$guid->setAttribute('isPermaLink', 'true');
|
||||
} else {
|
||||
$link = $entry->appendChild($document->createElement('link'));
|
||||
$link->setAttribute('href', $urlPrefix . url('news-post', ['post' => $post['post_id']]));
|
||||
$link->setAttribute('type', 'text/html');
|
||||
$html = $entry->appendChild($document->createElement(
|
||||
'content',
|
||||
htmlentities(parse_text($post['post_text'], MSZ_PARSER_MARKDOWN))
|
||||
));
|
||||
$html->setAttribute('type', 'html');
|
||||
$author = $entry->appendChild($document->createElement('author'));
|
||||
$author->appendChild($document->createElement('name', $post['username']));
|
||||
$author->appendChild($document->createElement('uri', $urlPrefix . url('user-profile', ['user' => $post['user_id']])));
|
||||
}
|
||||
}
|
||||
|
||||
return $document->saveXML();
|
||||
}
|
||||
|
|
|
@ -76,7 +76,7 @@
|
|||
<h1>{{ title }}</h1>
|
||||
|
||||
{% if change.change_text|length >= 1 %}
|
||||
{{ change.change_text|parse_text(constant('MSZ_PARSER_MARKDOWN'))|raw }}
|
||||
{{ change.change_text|parse_text(constant('\\Misuzu\\Parsers\\Parser::MARKDOWN'))|raw }}
|
||||
{% else %}
|
||||
<p>This change has no additional notes.</p>
|
||||
{% endif %}
|
||||
|
|
|
@ -440,7 +440,7 @@
|
|||
</a>
|
||||
</div>
|
||||
|
||||
<div class="forum__post__text{% if post.post_parse == constant('MSZ_PARSER_MARKDOWN') %} markdown{% endif %}">
|
||||
<div class="forum__post__text{% if post.post_parse == constant('\\Misuzu\\Parsers\\Parser::MARKDOWN') %} markdown{% endif %}">
|
||||
{{ post.post_text|escape|parse_text(post.post_parse)|raw }}
|
||||
</div>
|
||||
|
||||
|
@ -464,7 +464,7 @@
|
|||
{% endif %}
|
||||
|
||||
{% if post.post_display_signature and post.poster_signature_content|length > 0 %}
|
||||
<div class="forum__post__signature{% if post.poster_signature_parser == constant('MSZ_PARSER_MARKDOWN') %} markdown{% endif %}">
|
||||
<div class="forum__post__signature{% if post.poster_signature_parser == constant('\\Misuzu\\Parsers\\Parser::MARKDOWN') %} markdown{% endif %}">
|
||||
{{ post.poster_signature_content|escape|parse_text(post.poster_signature_parser)|raw }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
|
|
@ -139,8 +139,8 @@
|
|||
<div class="forum__post__settings">
|
||||
{{ input_select(
|
||||
'post[parser]',
|
||||
constant('MSZ_PARSERS_NAMES'),
|
||||
posting_defaults.parser|default(posting_post.post_parse|default(posting_info.user_post_parse|default(constant('MSZ_PARSER_BBCODE')))),
|
||||
constant('\\Misuzu\\Parsers\\Parser::NAMES'),
|
||||
posting_defaults.parser|default(posting_post.post_parse|default(posting_info.user_post_parse|default(constant('\\Misuzu\\Parsers\\Parser::BBCODE')))),
|
||||
null, null, false, 'forum__post__dropdown js-forum-posting-parser'
|
||||
) }}
|
||||
{% if is_opening and posting_types|length > 1 %}
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
{{ container_title(document.title) }}
|
||||
|
||||
<div class="container__content markdown">
|
||||
{{ document.content|parse_text(constant('MSZ_PARSER_MARKDOWN'))|raw }}
|
||||
{{ document.content|parse_text(constant('\\Misuzu\\Parsers\\Parser::MARKDOWN'))|raw }}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
@ -43,33 +43,31 @@
|
|||
<div class="container">
|
||||
{{ container_title('Colour') }}
|
||||
|
||||
{% set colour_props = edit_role.role_colour|default(constant('MSZ_COLOUR_INHERIT'))|colour_props %}
|
||||
|
||||
<label class="form__label">
|
||||
<div class="form__label__text">Inherit Colour</div>
|
||||
<div class="form__label__input">
|
||||
{{ input_checkbox('role[colour][inherit]', '', colour_props.inherit) }}
|
||||
{{ input_checkbox('role[colour][inherit]', '', role_colour is defined ? role_colour.inherit : true) }}
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label class="form__label">
|
||||
<div class="form__label__text">Red</div>
|
||||
<div class="form__label__input">
|
||||
{{ input_text('role[colour][red]', '', colour_props.red, 'number', '', false, {'min':0,'max':255}) }}
|
||||
{{ input_text('role[colour][red]', '', role_colour.red|default(0), 'number', '', false, {'min':0,'max':255}) }}
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label class="form__label">
|
||||
<div class="form__label__text">Green</div>
|
||||
<div class="form__label__input">
|
||||
{{ input_text('role[colour][green]', '', colour_props.green, 'number', '', false, {'min':0,'max':255}) }}
|
||||
{{ input_text('role[colour][green]', '', role_colour.green|default(0), 'number', '', false, {'min':0,'max':255}) }}
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label class="form__label">
|
||||
<div class="form__label__text">Blue</div>
|
||||
<div class="form__label__input">
|
||||
{{ input_text('role[colour][blue]', '', colour_props.blue, 'number', '', false, {'min':0,'max':255}) }}
|
||||
{{ input_text('role[colour][blue]', '', role_colour.blue|default(0), 'number', '', false, {'min':0,'max':255}) }}
|
||||
</div>
|
||||
</label>
|
||||
|
||||
|
|
|
@ -99,11 +99,11 @@
|
|||
<label class="form__label">
|
||||
<div class="form__label__text">Custom Colour</div>
|
||||
<div class="form__label__input">
|
||||
{{ input_checkbox('colour[enable]', '', manage_user.user_colour is not null and not manage_user.user_colour|colour_inherit, '', '', false, null, not can_edit_user) }}
|
||||
{{ input_checkbox('colour[enable]', '', not user_colour.inherit, '', '', false, null, not can_edit_user) }}
|
||||
</div>
|
||||
</label>
|
||||
|
||||
{{ input_colour(can_edit_user ? 'colour[hex]' : '', '', manage_user.user_colour|default(0)|colour_hex) }}
|
||||
{{ input_colour(can_edit_user ? 'colour[hex]' : '', '', '#%s'|format(user_colour.hex)) }}
|
||||
</div>
|
||||
|
||||
{# TODO: if the hierarchy of the current user is too low to touch the role then opacity should be lowered and input disabled #}
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
<div class="news__preview__content markdown">
|
||||
<h1>{{ post.post_title }}</h1>
|
||||
<div class="news__preview__text">
|
||||
{{ post.post_text|first_paragraph|parse_text(constant('MSZ_PARSER_MARKDOWN'))|raw }}
|
||||
{{ post.post_text|first_paragraph|parse_text(constant('\\Misuzu\\Parsers\\Parser::MARKDOWN'))|raw }}
|
||||
</div>
|
||||
<div class="news__preview__links">
|
||||
<a href="{{ url('news-post', {'post': post.post_id}) }}" class="news__preview__link">Continue reading</a>
|
||||
|
@ -88,7 +88,7 @@
|
|||
|
||||
<div class="news__post__text markdown">
|
||||
<h1>{{ post.post_title }}</h1>
|
||||
{{ post.post_text|parse_text(constant('MSZ_PARSER_MARKDOWN'))|raw }}
|
||||
{{ post.post_text|parse_text(constant('\\Misuzu\\Parsers\\Parser::MARKDOWN'))|raw }}
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
|
|
@ -181,11 +181,11 @@
|
|||
|
||||
{% if profile_is_editing %}
|
||||
<div class="profile__signature__editor">
|
||||
{{ input_select('about[parser]', constant('MSZ_PARSERS_NAMES'), profile_user.user_about_parser, '', '', false, 'profile__about__select') }}
|
||||
{{ input_select('about[parser]', constant('\\Misuzu\\Parsers\\Parser::NAMES'), profile_user.user_about_parser, '', '', false, 'profile__about__select') }}
|
||||
<textarea name="about[text]" class="input__textarea profile__about__text" id="about-textarea">{{ profile_user.user_about_content|escape }}</textarea>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="profile__about__content{% if profile_is_editing %} profile__about__content--edit{% elseif profile_user.user_about_parser == constant('MSZ_PARSER_MARKDOWN') %} markdown{% endif %}">
|
||||
<div class="profile__about__content{% if profile_is_editing %} profile__about__content--edit{% elseif profile_user.user_about_parser == constant('\\Misuzu\\Parsers\\Parser::MARKDOWN') %} markdown{% endif %}">
|
||||
{{ profile_user.user_about_content|escape|parse_text(profile_user.user_about_parser)|raw }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
@ -198,11 +198,11 @@
|
|||
|
||||
{% if profile_is_editing %}
|
||||
<div class="profile__signature__editor">
|
||||
{{ input_select('signature[parser]', constant('MSZ_PARSERS_NAMES'), profile_user.user_signature_parser, '', '', false, 'profile__signature__select') }}
|
||||
{{ input_select('signature[parser]', constant('\\Misuzu\\Parsers\\Parser::NAMES'), profile_user.user_signature_parser, '', '', false, 'profile__signature__select') }}
|
||||
<textarea name="signature[text]" class="input__textarea profile__signature__text" id="signature-textarea">{{ profile_user.user_signature_content|escape }}</textarea>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="profile__signature__content{% if profile_is_editing %} profile__signature__content--edit{% elseif profile_user.user_signature_parser == constant('MSZ_PARSER_MARKDOWN') %} markdown{% endif %}">
|
||||
<div class="profile__signature__content{% if profile_is_editing %} profile__signature__content--edit{% elseif profile_user.user_signature_parser == constant('\\Misuzu\\Parsers\\Parser::MARKDOWN') %} markdown{% endif %}">
|
||||
{{ profile_user.user_signature_content|escape|parse_text(profile_user.user_signature_parser)|raw }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
|
|
@ -245,7 +245,7 @@ function html_link(string $url, ?string $content = null, $attributes = []): stri
|
|||
}
|
||||
|
||||
function html_colour(?int $colour, $attribs = '--user-colour'): string {
|
||||
$colour = $colour ?? colour_none();
|
||||
$colour = $colour == null ? \Misuzu\Colour::none() : new \Misuzu\Colour($colour);
|
||||
|
||||
if(is_string($attribs)) {
|
||||
$attribs = [
|
||||
|
@ -261,7 +261,7 @@ function html_colour(?int $colour, $attribs = '--user-colour'): string {
|
|||
}
|
||||
|
||||
$css = '';
|
||||
$value = colour_get_css($colour);
|
||||
$value = $colour->getCSS();
|
||||
|
||||
foreach($attribs as $name => $format) {
|
||||
$css .= $name . ':' . sprintf($format, $value) . ';';
|
||||
|
|
Loading…
Add table
Reference in a new issue