Emoticon restructure.

This commit is contained in:
flash 2019-12-09 03:41:34 +01:00
parent b9d0c721ad
commit 1a40c11a54
10 changed files with 337 additions and 194 deletions

View file

@ -0,0 +1,86 @@
<?php
namespace Misuzu\DatabaseMigrations\EmoticonRestructure;
use PDO;
function migrate_up(PDO $conn): void {
$emotes = $conn->query('SELECT * FROM `msz_emoticons`')->fetchAll(PDO::FETCH_ASSOC);
$pruneDupes = $conn->prepare('DELETE FROM `msz_emoticons` WHERE `emote_id` = :id');
// int order, int hierarchy, string url, array(string string, int order) strings
$images = [];
$delete = [];
foreach($emotes as $emote) {
if(!isset($images[$emote['emote_url']])) {
$images[$emote['emote_url']] = [
'id' => $emote['emote_id'],
'order' => $emote['emote_order'],
'hierarchy' => $emote['emote_hierarchy'],
'url' => $emote['emote_url'],
'strings' => [],
];
} else {
$delete[] = $emote['emote_id'];
}
$images[$emote['emote_url']]['strings'][] = [
'string' => $emote['emote_string'],
'order' => count($images[$emote['emote_url']]['strings']) + 1,
];
}
foreach($delete as $id) {
$pruneDupes->bindValue('id', $id);
$pruneDupes->execute();
}
$conn->exec('
ALTER TABLE `msz_emoticons`
DROP COLUMN `emote_string`,
DROP INDEX `emotes_string`,
DROP INDEX `emotes_url`,
ADD UNIQUE INDEX `emotes_url` (`emote_url`);
');
$conn->exec("
CREATE TABLE `msz_emoticons_strings` (
`emote_id` INT UNSIGNED NOT NULL,
`emote_string_order` MEDIUMINT NOT NULL DEFAULT 0,
`emote_string` VARCHAR(50) NOT NULL COLLATE 'ascii_general_nopad_ci',
INDEX `string_emote_foreign` (`emote_id`),
INDEX `string_order_key` (`emote_string_order`),
UNIQUE INDEX `string_unique` (`emote_string`),
CONSTRAINT `string_emote_foreign`
FOREIGN KEY (`emote_id`)
REFERENCES `msz_emoticons` (`emote_id`)
ON UPDATE CASCADE
ON DELETE CASCADE
) COLLATE='utf8mb4_bin';
");
$insertString = $conn->prepare('
INSERT INTO `msz_emoticons_strings` (`emote_id`, `emote_string_order`, `emote_string`)
VALUES (:id, :order, :string)
');
foreach($images as $image) {
$insertString->bindValue('id', $image['id']);
foreach($image['strings'] as $string) {
$insertString->bindValue('order', $string['order']);
$insertString->bindValue('string', $string['string']);
$insertString->execute();
}
}
}
function migrate_down(PDO $conn): void {
$conn->exec('DROP TABLE `msz_emoticons_strings`');
$conn->exec("
ALTER TABLE `msz_emoticons`
ADD COLUMN `emote_string` VARCHAR(50) NOT NULL COLLATE 'ascii_general_nopad_ci' AFTER `emote_hierarchy`,
DROP INDEX `emotes_url`,
ADD INDEX `emotes_url` (`emote_url`),
ADD UNIQUE INDEX `emote_string` (`emote_url`);
");
}

View file

@ -40,7 +40,6 @@ require_once 'src/changelog.php';
require_once 'src/colour.php';
require_once 'src/comments.php';
require_once 'src/csrf.php';
require_once 'src/emotes.php';
require_once 'src/manage.php';
require_once 'src/news.php';
require_once 'src/otp.php';

View file

@ -9,31 +9,42 @@ if(!perms_check_user(MSZ_PERMS_GENERAL, user_session_current('user_id'), General
}
$emoteId = !empty($_GET['e']) && is_string($_GET['e']) ? (int)$_GET['e'] : 0;
$isNew = $emoteId <= 0;
$emoteInfo = !$isNew ? Emoticon::byId($emoteId) : new Emoticon;
if($emoteId > 0) {
$emoteInfo = emotes_get_by_id($emoteId);
}
if(csrf_verify_request() && isset($_POST['emote_order']) && isset($_POST['emote_hierarchy']) && !empty($_POST['emote_url']) && !empty($_POST['emote_strings'])) {
$emoteInfo->setUrl($_POST['emote_url'])
->setHierarchy($_POST['emote_hierarchy'])
->setOrder($_POST['emote_order'])
->save();
if(csrf_verify_request()
&& isset($_POST['emote_order']) && isset($_POST['emote_hierarchy'])
&& !empty($_POST['emote_string']) && !empty($_POST['emote_url'])) {
if(empty($emoteInfo)) {
$emoteId = emotes_add($_POST['emote_string'], $_POST['emote_url'], $_POST['emote_hierarchy'], $_POST['emote_order']);
if($isNew && !$emoteInfo->hasId())
throw new \Exception("SOMETHING HAPPENED");
if($emoteId > 0) {
// seems like an odd decision to redirect back to the emoticons index, but it'll probably be nicer in the long run
url_redirect('manage-general-emoticons');
return;
} else {
echo "SOMETHING HAPPENED {$emoteId}";
$setStrings = array_column($emoteInfo->getStrings(), 'emote_string');
$applyStrings = explode(' ', mb_strtolower($_POST['emote_strings']));
$removeStrings = [];
foreach($setStrings as $string) {
if(!in_array($string, $applyStrings)) {
$removeStrings[] = $string;
}
} else {
emotes_update_url($emoteInfo['emote_url'], $_POST['emote_url'], $_POST['emote_hierarchy'], $_POST['emote_order']);
emotes_update_string($emoteId, $_POST['emote_string']);
$emoteInfo = emotes_get_by_id($emoteId);
}
$setStrings = array_diff($setStrings, $removeStrings);
foreach($applyStrings as $string) {
if(!in_array($string, $setStrings)) {
$setStrings[] = $string;
}
}
foreach($removeStrings as $string)
$emoteInfo->removeString($string);
foreach($setStrings as $string)
$emoteInfo->addString($string);
}
Template::render('manage.general.emoticon', [
'emote_info' => $emoteInfo ?? null,
'emote_info' => $emoteInfo,
]);

View file

@ -10,14 +10,20 @@ if(!perms_check_user(MSZ_PERMS_GENERAL, user_session_current('user_id'), General
if(csrf_verify_request() && !empty($_GET['emote']) && is_string($_GET['emote'])) {
$emoteId = (int)$_GET['emote'];
$emoteInfo = Emoticon::byId($emoteId);
if(empty($emoteInfo)) {
echo render_error(404);
return;
}
if(!empty($_GET['order']) && is_string($_GET['order'])) {
emotes_order_change($emoteId, $_GET['order'] === 'i');
} elseif(!empty($_GET['alias']) && is_string($_GET['alias'])) {
emotes_add_alias($emoteId, $_GET['alias']);
$emoteInfo->changeOrder($_GET['order'] === 'i' ? 1 : -1);
} elseif(!empty($_GET['alias']) && is_string($_GET['alias']) && ctype_alnum($_GET['alias'])) {
$emoteInfo->addString(mb_strtolower($_GET['alias']));
return;
} elseif(!empty($_GET['delete'])) {
emotes_remove_id($emoteId);
$emoteInfo->delete();
}
url_redirect('manage-general-emoticons');
@ -25,5 +31,5 @@ if(csrf_verify_request() && !empty($_GET['emote']) && is_string($_GET['emote']))
}
Template::render('manage.general.emoticons', [
'emotes' => emotes_list(PHP_INT_MAX),
'emotes' => Emoticon::all(PHP_INT_MAX),
]);

172
src/Emoticon.php Normal file
View file

@ -0,0 +1,172 @@
<?php
namespace Misuzu;
final class Emoticon {
public const ALL = PHP_INT_MAX;
public function __construct() {
}
public function getId(): int {
return $this->emote_id ?? 0;
}
public function hasId(): bool {
return isset($this->emote_id) && $this->emote_id > 0;
}
public function getOrder(): int {
return $this->emote_order ?? 0;
}
public function setOrder(int $order): self {
$this->emote_order = $order;
return $this;
}
public function changeOrder(int $difference): self {
if(!$this->hasId())
return $this;
DB::prepare('
UPDATE `msz_emoticons`
SET `emote_order` = `emote_order` + :diff
WHERE `emote_id` = :id
')->bind('id', $this->getId())
->bind('diff', $difference)
->execute();
return $this;
}
public function getHierarchy(): int {
return $this->emote_hierarchy ?? 0;
}
public function setHierarchy(int $hierarchy): self {
$this->emote_hierarchy = $hierarchy;
return $this;
}
public function getUrl(): string {
return $this->emote_url ?? '';
}
public function setUrl(string $url): self {
$this->emote_url = $url;
return $this;
}
public function addString(string $string, ?int $order = null): bool {
if(!$this->hasId())
return false;
if($order === null) {
$order = DB::prepare('
SELECT MAX(`emote_string_order`) + 1
FROM `msz_emoticons_strings`
WHERE `emote_id` = :id
')->bind('id', $this->getId())->fetchColumn();
}
return DB::prepare('
REPLACE INTO `msz_emoticons_strings` (`emote_id`, `emote_string_order`, `emote_string`)
VALUES (:id, :order, :string)
')->bind('id', $this->getId())
->bind('order', $order)
->bind('string', $string)
->execute();
}
public function removeString(string $string): bool {
if(!$this->hasId())
return false;
return DB::prepare('
DELETE FROM `msz_emoticons_strings`
WHERE `emote_string` = :string
')->bind('string', $string)
->execute();
}
public function getStrings(): array {
if(!$this->hasId())
return [];
return DB::prepare('
SELECT `emote_string_order`, `emote_string`
FROM `msz_emoticons_strings`
WHERE `emote_id` = :id
ORDER BY `emote_string_order`
')->bind('id', $this->getId())->fetchObjects();
}
public function save(): bool {
if($this->hasId()) {
$save = DB::prepare('
UPDATE `msz_emoticons`
SET `emote_order` = :order,
`emote_hierarchy` = :hierarchy,
`emote_url` = :url
WHERE `emote_id` = :id
')->bind('id', $this->getId());
} else {
$save = DB::prepare('
INSERT INTO `msz_emoticons` (`emote_order`, `emote_hierarchy`, `emote_url`)
VALUES (:order, :hierarchy, :url)
');
}
$saved = $save->bind('order', $this->getOrder())
->bind('hierarchy', $this->getHierarchy())
->bind('url', $this->getUrl())
->execute();
if(!$this->hasId() && $saved)
$this->emote_id = DB::lastId();
return $saved;
}
public function delete(): void {
if(!$this->hasId())
return;
DB::prepare('DELETE FROM `msz_emoticons` WHERE `emote_id` = :id')
->bind('id', $this->getId())
->execute();
}
public static function byId(int $emoteId): self {
if($emoteId < 1)
return [];
$getEmote = DB::prepare('
SELECT `emote_id`, `emote_order`, `emote_hierarchy`, `emote_url`
FROM `msz_emoticons`
WHERE `emote_id` = :id
');
$getEmote->bind('id', $emoteId);
return $getEmote->fetchObject(self::class);
}
public static function all(int $hierarchy = self::ALL, bool $unique = false, bool $order = true): array {
$getEmotes = DB::prepare('
SELECT `emote_id`, `emote_order`, `emote_hierarchy`, `emote_url`
FROM `msz_emoticons`
WHERE `emote_hierarchy` <= :hierarchy
ORDER BY IF(:order, `emote_order`, `emote_id`)
');
$getEmotes->bind('hierarchy', $hierarchy);
$getEmotes->bind('order', $order);
$emotes = $getEmotes->fetchObjects(self::class);
// Removes aliases, emote with lowest ordering is considered the main
if($unique) {
$existing = [];
for($i = 0; $i < count($emotes); $i++) {
if(in_array($emotes[$i]->emote_url, $existing)) {
unset($emotes[$i]);
} else {
$existing[] = $emotes[$i]->emote_url;
}
}
}
return $emotes;
}
}

View file

@ -1,156 +0,0 @@
<?php
function emotes_list(int $hierarchy = PHP_INT_MAX, bool $unique = false, bool $order = true): array {
$getEmotes = \Misuzu\DB::prepare('
SELECT `emote_id`, `emote_order`, `emote_hierarchy`,
`emote_string`, `emote_url`
FROM `msz_emoticons`
WHERE `emote_hierarchy` <= :hierarchy
ORDER BY IF(:order, `emote_order`, `emote_id`)
');
$getEmotes->bind('hierarchy', $hierarchy);
$getEmotes->bind('order', $order);
$emotes = $getEmotes->fetchAll();
// Removes aliases, emote with lowest ordering is considered the main
if($unique) {
$existing = [];
for($i = 0; $i < count($emotes); $i++) {
if(in_array($emotes[$i]['emote_url'], $existing)) {
unset($emotes[$i]);
} else {
$existing[] = $emotes[$i]['emote_url'];
}
}
}
return $emotes;
}
function emotes_get_by_id(int $emoteId): array {
if($emoteId < 1) {
return [];
}
$getEmote = \Misuzu\DB::prepare('
SELECT `emote_id`, `emote_order`, `emote_hierarchy`,
`emote_string`, `emote_url`
FROM `msz_emoticons`
WHERE `emote_id` = :id
');
$getEmote->bind('id', $emoteId);
return $getEmote->fetch();
}
function emotes_add(string $string, string $url, int $hierarchy = 0, int $order = 0): int {
if(empty($string) || empty($url)) {
return -1;
}
$insertEmote = \Misuzu\DB::prepare('
INSERT INTO `msz_emoticons` (
`emote_order`, `emote_hierarchy`, `emote_string`, `emote_url`
)
VALUES (
:order, :hierarchy, :string, :url
)
');
$insertEmote->bind('order', $order);
$insertEmote->bind('hierarchy', $hierarchy);
$insertEmote->bind('string', $string);
$insertEmote->bind('url', $url);
if(!$insertEmote->execute()) {
return -2;
}
return \Misuzu\DB::lastId();
}
function emotes_add_alias(int $emoteId, string $alias): int {
if($emoteId < 1 || empty($alias)) {
return -1;
}
$createAlias = \Misuzu\DB::prepare('
INSERT INTO `msz_emoticons` (
`emote_order`, `emote_hierarchy`, `emote_string`, `emote_url`
)
SELECT `emote_order`, `emote_hierarchy`, :alias, `emote_url`
FROM `msz_emoticons`
WHERE `emote_id` = :id
');
$createAlias->bind('id', $emoteId);
$createAlias->bind('alias', $alias);
if(!$createAlias->execute()) {
return -2;
}
return \Misuzu\DB::lastId();
}
function emotes_update_url(string $existingUrl, string $url, int $hierarchy = 0, int $order = 0): void {
if(empty($existingUrl) || empty($url)) {
return;
}
$updateByUrl = \Misuzu\DB::prepare('
UPDATE `msz_emoticons`
SET `emote_url` = :url,
`emote_hierarchy` = :hierarchy,
`emote_order` = :order
WHERE `emote_url` = :existing_url
');
$updateByUrl->bind('existing_url', $existingUrl);
$updateByUrl->bind('url', $url);
$updateByUrl->bind('hierarchy', $hierarchy);
$updateByUrl->bind('order', $order);
$updateByUrl->execute();
}
function emotes_update_string(string $id, string $string): void {
if($id < 1 || empty($string)) {
return;
}
$updateString = \Misuzu\DB::prepare('
UPDATE `msz_emoticons`
SET `emote_string` = :string
WHERE `emote_id` = :id
');
$updateString->bind('id', $id);
$updateString->bind('string', $string);
$updateString->execute();
}
// use this for actually removing emoticons
function emotes_remove_url(string $url): void {
$removeByUrl = \Misuzu\DB::prepare('
DELETE FROM `msz_emoticons`
WHERE `emote_url` = :url
');
$removeByUrl->bind('url', $url);
$removeByUrl->execute();
}
// use this for removing single aliases
function emotes_remove_id(int $emoteId): void {
$removeById = \Misuzu\DB::prepare('
DELETE FROM `msz_emoticons`
WHERE `emote_id` = :id
');
$removeById->bind('id', $emoteId);
$removeById->execute();
}
function emotes_order_change(int $id, bool $increase): void {
$increaseOrder = \Misuzu\DB::prepare('
UPDATE `msz_emoticons`
SET `emote_order` = IF(:increase, `emote_order` + 1, `emote_order` - 1)
WHERE `emote_id` = :id
');
$increaseOrder->bind('id', $id);
$increaseOrder->bind('increase', $increase ? 1 : 0);
$increaseOrder->execute();
}

View file

@ -2,7 +2,7 @@
{% from 'macros.twig' import container_title %}
{% from '_layout/input.twig' import input_csrf, input_text, input_checkbox, input_file, input_select, input_colour %}
{% set title = emote_info is null ? 'Adding a new emoticon' : 'Editing ' ~ emote_info.emote_string %}
{% set title = emote_info is null ? 'Adding a new emoticon' : 'Editing #' ~ emote_info.id %}
{% block manage_content %}
<div class="container manage__emotes">
@ -22,13 +22,13 @@
</label>
<label class="manage__emote__field">
<div class="manage__emote__field__name">String</div>
{{ input_text('emote_string', 'manage__emote__field__value', emote_info.emote_string|default(), 'text', '', true) }}
<div class="manage__emote__field__name">URL</div>
{{ input_text('emote_url', 'manage__emote__field__value', emote_info.emote_url|default(), 'text', '', true) }}
</label>
<label class="manage__emote__field">
<div class="manage__emote__field__name">URL</div>
{{ input_text('emote_url', 'manage__emote__field__value', emote_info.emote_url|default(), 'text', '', true) }}
<div class="manage__emote__field__name">Strings</div>
{{ input_text('emote_strings', 'manage__emote__field__value', emote_info.strings|column('emote_string')|join(' '), 'text', '', true) }}
</label>
<div class="manage__emote__actions">

View file

@ -24,9 +24,6 @@
<div class="manage__emotes__entry__hierarchy">
Hier.
</div>
<div class="manage__emotes__entry__string">
String
</div>
<div class="manage__emotes__entry__image">
Image
</div>
@ -46,11 +43,8 @@
<div class="manage__emotes__entry__hierarchy">
{{ emote.emote_hierarchy }}
</div>
<div class="manage__emotes__entry__string">
{{ emote.emote_string }}
</div>
<div class="manage__emotes__entry__image">
<img src="{{ emote.emote_url }}" alt="{{ emote.emote_string }}" class="emoticon manage__emotes__emoticon">
<img src="{{ emote.emote_url }}" alt="{{ emote.emote_url }}" class="emoticon manage__emotes__emoticon">
</div>
<div class="manage__emotes__entry__actions">
<button class="input__button input__button--autosize" title="Create Alias" onclick="createEmoteAlias({{ emote.emote_id }}, prompt('Enter an alias for this emoticon...'))"><i class="fas fa-copy fa-fw"></i></button>

View file

@ -26,7 +26,7 @@
</head>
<body class="main{% if site_background is defined %} {{ site_background.settings|bg_settings('main--bg-%s')|join(' ') }}{% endif %}"
style="{% if global_accent_colour is defined %}{{ global_accent_colour|html_colour('--accent-colour') }}{% endif %}">
style="{% if global_accent_colour is defined %}{{ global_accent_colour|html_colour('--accent-colour') }}{% endif %}" id="container">
{% include '_layout/header.twig' %}
<div class="main__wrapper">

View file

@ -88,6 +88,37 @@ function byte_symbol(int $bytes, bool $decimal = false, array $symbols = ['', 'K
return sprintf("%.2f %s%sB", $bytes, $symbol, $symbol !== '' && !$decimal ? 'i' : '');
}
// For chat emote list, nuke this when Sharp Chat comms are in this project
function emotes_list(int $hierarchy = PHP_INT_MAX, bool $unique = false, bool $order = true): array {
$getEmotes = \Misuzu\DB::prepare('
SELECT e.`emote_id`, e.`emote_order`, e.`emote_hierarchy`, e.`emote_url`,
s.`emote_string_order`, s.`emote_string`
FROM `msz_emoticons_strings` AS s
LEFT JOIN `msz_emoticons` AS e
ON e.`emote_id` = s.`emote_id`
WHERE `emote_hierarchy` <= :hierarchy
ORDER BY IF(:order, e.`emote_order`, e.`emote_id`), s.`emote_string_order`
');
$getEmotes->bind('hierarchy', $hierarchy);
$getEmotes->bind('order', $order);
$emotes = $getEmotes->fetchAll();
// Removes aliases, emote with lowest ordering is considered the main
if($unique) {
$existing = [];
for($i = 0; $i < count($emotes); $i++) {
if(in_array($emotes[$i]['emote_url'], $existing)) {
unset($emotes[$i]);
} else {
$existing[] = $emotes[$i]['emote_url'];
}
}
}
return $emotes;
}
function safe_delete(string $path): void {
$path = realpath($path);