Compare commits
23 commits
20240124.1
...
master
Author | SHA1 | Date | |
---|---|---|---|
flash | 5d3e1d4960 | ||
flash | 9bb943bacf | ||
flash | 107d16cf46 | ||
flash | 0afc5186a7 | ||
flash | 0300bae994 | ||
flash | cb0c64f8ed | ||
flash | 89ef9d9ad1 | ||
flash | c02d922dc6 | ||
flash | 80cd6222c4 | ||
flash | 344a3c9160 | ||
flash | df5dbdf3ad | ||
flash | c0caceed7b | ||
flash | be54ce2c22 | ||
flash | 070dc5e782 | ||
flash | b89621cb1a | ||
flash | 760cca0e5d | ||
flash | fe77f1616c | ||
flash | eb81ed7a82 | ||
flash | 8ef11afe02 | ||
flash | cca016ba10 | ||
flash | b80151583e | ||
flash | d8cc208a85 | ||
flash | 4b2f9a2fec |
2
LICENSE
2
LICENSE
|
@ -1,4 +1,4 @@
|
|||
Copyright (c) 2017-2023, flashwave <me@flash.moe>
|
||||
Copyright (c) 2017-2024, flashwave <me@flash.moe>
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
|
|
|
@ -154,6 +154,9 @@
|
|||
}
|
||||
|
||||
.forum__post__action {
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
display: block;
|
||||
padding: 5px 10px;
|
||||
margin: 1px;
|
||||
color: inherit;
|
||||
|
|
|
@ -146,9 +146,14 @@
|
|||
}
|
||||
.header__desktop__user__button__count {
|
||||
position: absolute;
|
||||
bottom: 1px;
|
||||
right: 1px;
|
||||
font-size: 10px;
|
||||
top: -5px;
|
||||
right: -3px;
|
||||
z-index: 1;
|
||||
font-size: .5em;
|
||||
line-height: 1.4em;
|
||||
text-align: right;
|
||||
padding: 2px 2px 0;
|
||||
border-radius: 4px;
|
||||
background-color: var(--header-accent-colour);
|
||||
opacity: .9;
|
||||
border-radius: 4px;
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
outline-style: none;
|
||||
}
|
||||
|
||||
html,
|
||||
|
@ -57,6 +56,8 @@ body {
|
|||
|
||||
html {
|
||||
scrollbar-color: var(--accent-colour) var(--background-colour);
|
||||
accent-color: var(--accent-colour);
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
||||
.main {
|
||||
|
@ -163,6 +164,8 @@ html {
|
|||
|
||||
@include manage/_manage.css;
|
||||
|
||||
@include messages/messages.css;
|
||||
|
||||
@include news/container.css;
|
||||
@include news/feeds.css;
|
||||
@include news/list.css;
|
||||
|
|
|
@ -17,4 +17,5 @@
|
|||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 5px;
|
||||
gap: 5px;
|
||||
}
|
||||
|
|
37
assets/misuzu.css/messages/actions.css
Normal file
37
assets/misuzu.css/messages/actions.css
Normal file
|
@ -0,0 +1,37 @@
|
|||
.messages-actions-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 30px;
|
||||
margin: 1px;
|
||||
font-size: 1.3em;
|
||||
line-height: 1.4em;
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
transition: background-color .1s;
|
||||
width: 100%;
|
||||
border: 0;
|
||||
background-color: inherit;
|
||||
text-align: left;
|
||||
}
|
||||
.messages-actions-item:hover,
|
||||
.messages-actions-item:focus {
|
||||
background-color: #444f;
|
||||
}
|
||||
.messages-actions-item:active,
|
||||
.messages-actions-item-current {
|
||||
background-color: var(--accent-colour) !important;
|
||||
}
|
||||
.messages-actions-item[disabled] {
|
||||
background-color: inherit !important;
|
||||
opacity: .4;
|
||||
}
|
||||
.messages-actions-item-icon {
|
||||
text-align: center;
|
||||
width: 30px;
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.messages-actions-item-label {
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
}
|
26
assets/misuzu.css/messages/columns.css
Normal file
26
assets/misuzu.css/messages/columns.css
Normal file
|
@ -0,0 +1,26 @@
|
|||
.messages-columns {
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.messages-columns-sidebar {
|
||||
width: 200px;
|
||||
flex-shrink: 0;
|
||||
flex-grow: 0;
|
||||
}
|
||||
|
||||
.messages-columns-content {
|
||||
flex-shrink: 1;
|
||||
flex-grow: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@media (max-width: 800px) {
|
||||
.messages-columns {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.messages-columns-sidebar {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
80
assets/misuzu.css/messages/entry.css
Normal file
80
assets/misuzu.css/messages/entry.css
Normal file
|
@ -0,0 +1,80 @@
|
|||
.messages-entry {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 2px 4px;
|
||||
gap: 4px;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
}
|
||||
.messages-entry-header {
|
||||
display: flex;
|
||||
font-size: 1.1em;
|
||||
line-height: 1.6em;
|
||||
border-bottom: 2px solid #9999;
|
||||
gap: 2px;
|
||||
}
|
||||
.messages-entry-check {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 20px;
|
||||
}
|
||||
.messages-entry-check input {
|
||||
display: block;
|
||||
}
|
||||
.messages-entry-unread {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 20px;
|
||||
}
|
||||
.messages-entry-unread-orb {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background-color: var(--accent-colour);
|
||||
border-radius: 100%;
|
||||
}
|
||||
.messages-entry-author {
|
||||
font-weight: bold;
|
||||
border-bottom: 2px solid var(--user-colour, currentColor);
|
||||
margin: 0 0 -2px;
|
||||
flex-grow: 0;
|
||||
flex-shrink: 1;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.messages-entry-spacing {
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
}
|
||||
.messages-entry-datetime {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
color: #aaa;
|
||||
align-self: flex-end;
|
||||
}
|
||||
.messages-entry-subject {
|
||||
line-height: 1.4em;
|
||||
color: #fff;
|
||||
overflow: hidden;
|
||||
}
|
||||
.messages-entry-preview {
|
||||
line-height: 1.4em;
|
||||
color: #888;
|
||||
overflow: hidden;
|
||||
}
|
||||
.messages-entry-preview .messages-entry-overflow {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
.messages-entry-overflow {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
33
assets/misuzu.css/messages/folder.css
Normal file
33
assets/misuzu.css/messages/folder.css
Normal file
|
@ -0,0 +1,33 @@
|
|||
.messages-folder {
|
||||
margin: 1px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1px;
|
||||
padding: 1px;
|
||||
}
|
||||
.messages-folder-item {
|
||||
background-color: #161616;
|
||||
transition: background-color .1s;
|
||||
}
|
||||
.messages-folder-item:nth-child(2n) {
|
||||
background-color: #1f1f1f;
|
||||
}
|
||||
.messages-folder-item:hover,
|
||||
.messages-folder-item:focus {
|
||||
background-color: #262626;
|
||||
}
|
||||
.messages-folder-item:active,
|
||||
.messages-folder-item-current {
|
||||
background-color: var(--accent-colour) !important;
|
||||
}
|
||||
.messages-folder-notice {
|
||||
text-align: center;
|
||||
margin: 10px;
|
||||
}
|
||||
.messages-folder-notice-text {
|
||||
font-size: 1.4em;
|
||||
line-height: 1.5em;
|
||||
}
|
||||
.messages-folder .pagination {
|
||||
margin-top: 2px;
|
||||
}
|
135
assets/misuzu.css/messages/message.css
Normal file
135
assets/misuzu.css/messages/message.css
Normal file
|
@ -0,0 +1,135 @@
|
|||
.messages-message {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
padding: 10px;
|
||||
}
|
||||
.messages-message-snippet {
|
||||
cursor: pointer;
|
||||
font-size: .9em;
|
||||
line-height: 1.5em;
|
||||
color: #888;
|
||||
gap: 5px;
|
||||
opacity: .8;
|
||||
transition: opacity .1s;
|
||||
}
|
||||
.messages-message-snippet:hover,
|
||||
.messages-message-snippet:focus,
|
||||
.messages-message-snippet:focus-within {
|
||||
opacity: 1;
|
||||
}
|
||||
.messages-message-draft {
|
||||
border-top: 2px solid var(--accent-colour) !important;
|
||||
border-left: 2px solid var(--accent-colour) !important;
|
||||
border-right: 2px solid var(--accent-colour);
|
||||
border-bottom: 2px solid var(--accent-colour);
|
||||
}
|
||||
.messages-message-deleted {
|
||||
border-top: 2px solid red;
|
||||
border-left: 2px solid red;
|
||||
border-right: 2px solid red !important;
|
||||
border-bottom: 2px solid red !important;
|
||||
}
|
||||
|
||||
.messages-message-overflow {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.messages-message-header {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
border-bottom: 1px #444 solid;
|
||||
padding-bottom: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.messages-message-sender-avatar {
|
||||
flex-shrink: 0;
|
||||
flex-grow: 0;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.messages-message-sender-avatar img {
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.messages-message-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-shrink: 1;
|
||||
flex-grow: 1;
|
||||
overflow: hidden;
|
||||
gap: 2px;
|
||||
}
|
||||
.messages-message-details-spacing {
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
}
|
||||
|
||||
.messages-message-header-columns {
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
}
|
||||
.messages-message-sender-name {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 1;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.messages-message-sender-name a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
font-weight: 700;
|
||||
border-bottom: 2px solid var(--user-colour, currentColor);
|
||||
}
|
||||
.messages-message-datetime {
|
||||
flex-shrink: 0;
|
||||
flex-grow: 0;
|
||||
align-self: flex-end;
|
||||
padding-bottom: 2px;
|
||||
}
|
||||
|
||||
.messages-message-addressee {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
.messages-message-addressee-to {
|
||||
flex-shrink: 0;
|
||||
flex-grow: 0;
|
||||
}
|
||||
.messages-message-addressee-user {
|
||||
flex-shrink: 1;
|
||||
flex-grow: 0;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.messages-message-addressee-user a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
font-weight: 700;
|
||||
border-bottom: 2px solid var(--user-colour, currentColor);
|
||||
}
|
||||
|
||||
.messages-message-subject {
|
||||
line-height: 2em;
|
||||
}
|
||||
|
||||
.messages-message-body {
|
||||
line-height: 1.4em;
|
||||
}
|
||||
.messages-message-body p:first-child {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
.messages-message-body p:last-child {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.messages-message-snippet-body {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
line-height: 1.4em;
|
||||
}
|
9
assets/misuzu.css/messages/messages.css
Normal file
9
assets/misuzu.css/messages/messages.css
Normal file
|
@ -0,0 +1,9 @@
|
|||
@include messages/actions.css;
|
||||
@include messages/columns.css;
|
||||
@include messages/entry.css;
|
||||
@include messages/folder.css;
|
||||
@include messages/message.css;
|
||||
@include messages/recipient.css;
|
||||
@include messages/reply.css;
|
||||
@include messages/sidebar.css;
|
||||
@include messages/thread.css;
|
17
assets/misuzu.css/messages/recipient.css
Normal file
17
assets/misuzu.css/messages/recipient.css
Normal file
|
@ -0,0 +1,17 @@
|
|||
.messages-recipient {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.messages-recipient-avatar {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.messages-recipient-name {
|
||||
padding: 5px;
|
||||
}
|
||||
.messages-recipient-name-input {
|
||||
width: 100%;
|
||||
}
|
52
assets/misuzu.css/messages/reply.css
Normal file
52
assets/misuzu.css/messages/reply.css
Normal file
|
@ -0,0 +1,52 @@
|
|||
.messages-reply-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
gap: 5px;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.messages-reply-subject-input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.messages-reply-body-input {
|
||||
min-width: 100%;
|
||||
max-width: 100%;
|
||||
min-height: 100px;
|
||||
}
|
||||
.messages-reply-compose .messages-reply-body-input {
|
||||
min-height: 300px;
|
||||
}
|
||||
|
||||
.messages-reply-actions {
|
||||
display: flex;
|
||||
padding: 1px;
|
||||
gap: 1px;
|
||||
}
|
||||
.messages-reply-action {
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
display: block;
|
||||
padding: 5px 10px;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
transition: background-color .2s;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.messages-reply-action:hover,
|
||||
.messages-reply-action:focus {
|
||||
background-color: rgba(0, 0, 0, .2);
|
||||
}
|
||||
|
||||
.messages-reply-options {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.messages-reply-settings {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
11
assets/misuzu.css/messages/sidebar.css
Normal file
11
assets/misuzu.css/messages/sidebar.css
Normal file
|
@ -0,0 +1,11 @@
|
|||
.messages-sidebar {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
.messages-sidebar-button {
|
||||
text-align: center;
|
||||
padding: 10px;
|
||||
}
|
5
assets/misuzu.css/messages/thread.css
Normal file
5
assets/misuzu.css/messages/thread.css
Normal file
|
@ -0,0 +1,5 @@
|
|||
.messages-thread {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1px;
|
||||
}
|
40
assets/misuzu.js/csrfp.js
Normal file
40
assets/misuzu.js/csrfp.js
Normal file
|
@ -0,0 +1,40 @@
|
|||
#include utility.js
|
||||
|
||||
const MszCSRFP = (() => {
|
||||
let elem;
|
||||
const getElement = () => {
|
||||
if(elem === undefined)
|
||||
elem = $q('meta[name="csrfp-token"]');
|
||||
return elem;
|
||||
};
|
||||
|
||||
const getToken = () => {
|
||||
const elem = getElement();
|
||||
return typeof elem.content === 'string' ? elem.content : '';
|
||||
};
|
||||
|
||||
const setToken = token => {
|
||||
if(typeof token !== 'string')
|
||||
throw 'token must be a string';
|
||||
|
||||
const elem = getElement();
|
||||
if(typeof elem.content === 'string')
|
||||
elem.content = token;
|
||||
};
|
||||
|
||||
return {
|
||||
getToken: getToken,
|
||||
setToken: setToken,
|
||||
setFromHeaders: result => {
|
||||
if(typeof result.headers !== 'function')
|
||||
throw 'result.headers is not a function';
|
||||
|
||||
const headers = result.headers();
|
||||
if(!(headers instanceof Map))
|
||||
throw 'result of result.headers does not return a map';
|
||||
|
||||
if(headers.has('x-csrfp-token'))
|
||||
setToken(headers.get('x-csrfp-token'));
|
||||
},
|
||||
};
|
||||
})();
|
|
@ -56,7 +56,7 @@ const MszAudioEmbedPlayer = function(metadata, options) {
|
|||
if(haveNativeControls)
|
||||
playerAttrs.controls = 'controls';
|
||||
|
||||
const watchers = new MszWatcherCollection;
|
||||
const watchers = new MszWatchers;
|
||||
watchers.define(MszAudioEmbedPlayerEvents());
|
||||
|
||||
const player = $e({
|
||||
|
|
|
@ -229,7 +229,7 @@ const MszVideoEmbedPlayer = function(metadata, options) {
|
|||
videoAttrs.style.width = initialSize[0].toString() + 'px';
|
||||
videoAttrs.style.height = initialSize[1].toString() + 'px';
|
||||
|
||||
const watchers = new MszWatcherCollection;
|
||||
const watchers = new MszWatchers;
|
||||
watchers.define(MszVideoEmbedPlayerEvents());
|
||||
|
||||
const player = $e({
|
||||
|
@ -375,7 +375,7 @@ const MszVideoEmbedYouTube = function(metadata, options) {
|
|||
currentTime = undefined,
|
||||
isPlaying = undefined;
|
||||
|
||||
const watchers = new MszWatcherCollection;
|
||||
const watchers = new MszWatchers;
|
||||
watchers.define(MszVideoEmbedPlayerEvents());
|
||||
|
||||
const player = $e({
|
||||
|
@ -576,7 +576,7 @@ const MszVideoEmbedNicoNico = function(metadata, options) {
|
|||
currentTime = undefined,
|
||||
isPlaying = false;
|
||||
|
||||
const watchers = new MszWatcherCollection;
|
||||
const watchers = new MszWatchers;
|
||||
watchers.define(MszVideoEmbedPlayerEvents());
|
||||
|
||||
const player = $e({
|
||||
|
|
|
@ -19,7 +19,7 @@ const MszEEPROM = (() => {
|
|||
const scriptElem = $e({
|
||||
tag: 'script',
|
||||
attrs: {
|
||||
src: `${peepPath}/eeprom.js`,
|
||||
src: `${peepPath}/scripts/eepromv1a.js`,
|
||||
charset: 'utf-8',
|
||||
type: 'text/javascript',
|
||||
onerror: () => reject(),
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#include msgbox.jsx
|
||||
#include parsing.js
|
||||
#include utility.js
|
||||
#include ext/eeprom.js
|
||||
|
||||
|
@ -13,10 +14,7 @@ const MszForumEditor = function(form) {
|
|||
parserElem = form.querySelector('.js-forum-posting-parser'),
|
||||
previewElem = form.querySelector('.js-forum-posting-preview'),
|
||||
modeElem = form.querySelector('.js-forum-posting-mode'),
|
||||
markupBtns = form.querySelectorAll('.js-forum-posting-markup');
|
||||
|
||||
const bbBtns = $q('.forum__post__actions--bbcode'),
|
||||
mdBtns = $q('.forum__post__actions--markdown');
|
||||
markupActs = form.querySelector('.js-forum-posting-actions');
|
||||
|
||||
let lastPostText = '',
|
||||
lastPostParser;
|
||||
|
@ -24,10 +22,10 @@ const MszForumEditor = function(form) {
|
|||
MszEEPROM.init()
|
||||
.catch(() => console.error('Failed to initialise EEPROM'))
|
||||
.then(() => {
|
||||
const eepromClient = new EEPROM(peepApp, `${peepPath}/uploads`, '');
|
||||
const eepromClient = new EEPROM(peepApp, peepPath);
|
||||
const eepromHistory = <div class="eeprom-widget-history-items"/>;
|
||||
|
||||
const eepromHandleFileUpload = file => {
|
||||
const eepromHandleFileUpload = async file => {
|
||||
const uploadElemNameValue = <div class="eeprom-widget-file-name-value" title={file.name}>{file.name}</div>;
|
||||
const uploadElemName = <a class="eeprom-widget-file-name" target="_blank">{uploadElemNameValue}</a>;
|
||||
const uploadElemProgressText = <div class="eeprom-widget-file-progress">Please wait...</div>;
|
||||
|
@ -48,62 +46,21 @@ const MszForumEditor = function(form) {
|
|||
eepromHistory.appendChild(uploadElem);
|
||||
|
||||
const explodeUploadElem = () => $r(uploadElem);
|
||||
const uploadTask = eepromClient.createUpload(file);
|
||||
const uploadTask = eepromClient.create(file);
|
||||
|
||||
uploadTask.onProgress = function(progressInfo) {
|
||||
const progressValue = `${progressInfo.progress}%`;
|
||||
uploadElemProgressBarValue.style.width = progressValue;
|
||||
uploadElemProgressText.textContent = `${progressValue} (${progressInfo.total - progressInfo.loaded} bytes remaining)`;
|
||||
};
|
||||
uploadTask.onProgress(prog => {
|
||||
uploadElemProgressBarValue.style.width = `${Math.ceil(prog.progress * 100)}%`;
|
||||
uploadElemProgressText.textContent = `${prog.progress.toLocaleString(undefined, { style: 'percent' })} (${prog.total - prog.loaded} bytes remaining)`;
|
||||
});
|
||||
|
||||
uploadTask.onFailure = function(errorInfo) {
|
||||
if(!errorInfo.userAborted) {
|
||||
let errorText = 'Was unable to upload file.';
|
||||
try {
|
||||
const fileInfo = await uploadTask.start();
|
||||
|
||||
switch(errorInfo.error) {
|
||||
case EEPROM.ERR_INVALID:
|
||||
errorText = 'Upload request was invalid.';
|
||||
break;
|
||||
case EEPROM.ERR_AUTH:
|
||||
errorText = 'Upload authentication failed, refresh and try again.';
|
||||
break;
|
||||
case EEPROM.ERR_ACCESS:
|
||||
errorText = "You're not allowed to upload files.";
|
||||
break;
|
||||
case EEPROM.ERR_GONE:
|
||||
errorText = 'Upload client has a configuration error or the server is gone.';
|
||||
break;
|
||||
case EEPROM.ERR_DMCA:
|
||||
errorText = 'This file has been uploaded before and was removed for copyright reasons, you cannot upload this file.';
|
||||
break;
|
||||
case EEPROM.ERR_SERVER:
|
||||
errorText = 'Upload server returned a critical error, try again later.';
|
||||
break;
|
||||
case EEPROM.ERR_SIZE:
|
||||
if(errorInfo.maxSize < 1)
|
||||
errorText = 'Selected file is too large.';
|
||||
else {
|
||||
const types = ['bytes', 'KB', 'MB', 'GB', 'TB'],
|
||||
typeIndex = parseInt(Math.floor(Math.log(errorInfo.maxSize) / Math.log(1024))),
|
||||
number = Math.round(errorInfo.maxSize / Math.pow(1024, _i), 2);
|
||||
|
||||
errorText = `Upload may not be larger than ${number} ${types[typeIndex]}.`;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
uploadElem.classList.add('eeprom-widget-file-fail');
|
||||
uploadElemProgressText.textContent = errorText;
|
||||
MszShowMessageBox(errorText, 'Upload Error');
|
||||
}
|
||||
};
|
||||
|
||||
uploadTask.onComplete = function(fileInfo) {
|
||||
uploadElem.classList.add('eeprom-widget-file-done');
|
||||
uploadElemName.href = fileInfo.url;
|
||||
uploadElemProgressText.textContent = '';
|
||||
|
||||
const insertTheLinkIntoTheBoxEx2 = function() {
|
||||
const insertTheLinkIntoTheBoxEx2 = () => {
|
||||
const parserMode = parseInt(parserElem.value);
|
||||
let insertText = location.protocol + fileInfo.url;
|
||||
|
||||
|
@ -126,14 +83,27 @@ const MszForumEditor = function(form) {
|
|||
uploadElemProgressText.appendChild(<a href="javascript:void(0)" onclick={() => insertTheLinkIntoTheBoxEx2()}>Insert</a>);
|
||||
uploadElemProgressText.appendChild($t(' '));
|
||||
uploadElemProgressText.appendChild(<a href="javascript:void(0)" onclick={() => {
|
||||
eepromClient.deleteUpload(fileInfo).start();
|
||||
explodeUploadElem();
|
||||
eepromClient.delete(fileInfo)
|
||||
.then(() => explodeUploadElem())
|
||||
.catch(ex => {
|
||||
console.error(ex);
|
||||
MszShowMessageBox(ex, 'Upload Error');
|
||||
});
|
||||
}}>Delete</a>);
|
||||
|
||||
insertTheLinkIntoTheBoxEx2();
|
||||
};
|
||||
} catch(ex) {
|
||||
let errorText = 'Upload aborted.';
|
||||
|
||||
uploadTask.start();
|
||||
if(!ex.aborted) {
|
||||
console.error(ex);
|
||||
errorText = ex.toString();
|
||||
}
|
||||
|
||||
uploadElem.classList.add('eeprom-widget-file-fail');
|
||||
uploadElemProgressText.textContent = errorText;
|
||||
await MszShowMessageBox(errorText, 'Upload Error');
|
||||
}
|
||||
};
|
||||
|
||||
const eepromFormInput = <input type="file" multiple={true} class="eeprom-widget-form-input"
|
||||
|
@ -204,13 +174,15 @@ const MszForumEditor = function(form) {
|
|||
}
|
||||
});
|
||||
|
||||
for(const button of markupBtns)
|
||||
button.addEventListener('click', () => $insertTags(textElem, button.dataset.tagOpen, button.dataset.tagClose));
|
||||
|
||||
const switchButtons = parser => {
|
||||
parser = parseInt(parser);
|
||||
bbBtns.hidden = parser !== 1;
|
||||
mdBtns.hidden = parser !== 2;
|
||||
$rc(markupActs);
|
||||
|
||||
const tags = MszParsing.getTagsFor(parser);
|
||||
for(const tag of tags)
|
||||
markupActs.appendChild(<button class={['forum__post__action', 'forum__post__action--tag', `forum__post__action--${tag.name}`]}
|
||||
type="button" title={tag.summary} onclick={() => $insertTags(textElem, tag.open, tag.close)}>
|
||||
<i class={tag.icon}/>
|
||||
</button>);
|
||||
};
|
||||
|
||||
const renderPreview = async (parser, text) => {
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include events/events.js
|
||||
#include ext/sakuya.js
|
||||
#include forum/editor.jsx
|
||||
#include messages/messages.js
|
||||
|
||||
(async () => {
|
||||
const initLoginPage = async () => {
|
||||
|
@ -56,7 +57,7 @@
|
|||
// can probably be done in a less stupid manner
|
||||
MszForumEditorAllowClose = true;
|
||||
|
||||
elem.submit();
|
||||
elem.form.submit();
|
||||
ev.preventDefault();
|
||||
}
|
||||
});
|
||||
|
@ -80,6 +81,8 @@
|
|||
|
||||
await initLoginPage();
|
||||
|
||||
MszMessages();
|
||||
|
||||
MszEmbed.handle($qa('.js-msz-embed-media'));
|
||||
} catch(ex) {
|
||||
console.error(ex);
|
||||
|
|
89
assets/misuzu.js/messages/actbtn.js
Normal file
89
assets/misuzu.js/messages/actbtn.js
Normal file
|
@ -0,0 +1,89 @@
|
|||
#include watcher.js
|
||||
|
||||
const MszMessagesActionButton = function(button, stateless) {
|
||||
if(!(button instanceof Element))
|
||||
throw 'button must be an element';
|
||||
|
||||
const stateful = !stateless;
|
||||
const pub = {};
|
||||
|
||||
const icon = button.querySelector('.js-messages-button-icon i');
|
||||
const label = button.querySelector('.js-messages-button-label');
|
||||
|
||||
const update = () => {
|
||||
if(stateful) {
|
||||
icon.className = button.dataset[`${button.dataset.state}Ico`];
|
||||
label.textContent = button.dataset[`${button.dataset.state}Str`];
|
||||
}
|
||||
};
|
||||
pub.update = update;
|
||||
|
||||
const stateWatcher = new MszWatcher;
|
||||
const getState = () => button.dataset.state !== 'inactive';
|
||||
const setState = state => {
|
||||
button.dataset.state = state ? 'active' : 'inactive';
|
||||
update();
|
||||
stateWatcher.call(getState());
|
||||
};
|
||||
|
||||
if(stateful) {
|
||||
pub.getState = getState;
|
||||
pub.setState = setState;
|
||||
pub.watchState = handler => { stateWatcher.watch(handler, getState()); };
|
||||
pub.unwatchState = handler => { stateWatcher.unwatch(handler); };
|
||||
}
|
||||
|
||||
let clickAction;
|
||||
const click = async () => {
|
||||
if(clickAction !== undefined) {
|
||||
if(stateful) {
|
||||
const result = await clickAction(getState());
|
||||
if(typeof result === 'boolean')
|
||||
setState(result);
|
||||
} else
|
||||
await clickAction();
|
||||
}
|
||||
};
|
||||
pub.click = click;
|
||||
|
||||
button.addEventListener('click', () => click());
|
||||
|
||||
update();
|
||||
|
||||
pub.setAction = action => {
|
||||
if(typeof action !== 'function')
|
||||
throw 'action must be a function';
|
||||
clickAction = action;
|
||||
};
|
||||
|
||||
let preventEnable = false;
|
||||
|
||||
pub.getEnabled = () => !button.disabled;
|
||||
pub.setEnabled = state => {
|
||||
if(!preventEnable)
|
||||
button.disabled = !state;
|
||||
};
|
||||
pub.disableWith = async callback => {
|
||||
if(typeof callback !== 'function')
|
||||
throw 'callback must be a function';
|
||||
if(preventEnable)
|
||||
throw 'preventEnable is true';
|
||||
|
||||
preventEnable = true;
|
||||
const wasDisabled = button.disabled;
|
||||
button.disabled = true;
|
||||
|
||||
try {
|
||||
return await callback();
|
||||
} finally {
|
||||
button.disabled = wasDisabled;
|
||||
preventEnable = false;
|
||||
}
|
||||
};
|
||||
|
||||
pub.setHidden = state => {
|
||||
button.hidden = state;
|
||||
};
|
||||
|
||||
return pub;
|
||||
};
|
167
assets/misuzu.js/messages/list.js
Normal file
167
assets/misuzu.js/messages/list.js
Normal file
|
@ -0,0 +1,167 @@
|
|||
#include utility.js
|
||||
#include watcher.js
|
||||
|
||||
const MsgMessagesList = function(list) {
|
||||
if(!(list instanceof Element))
|
||||
throw 'list must be an element';
|
||||
|
||||
const watchers = new MszWatchers;
|
||||
watchers.define(['select']);
|
||||
|
||||
let selectedCount = 0;
|
||||
|
||||
const items = Array.from(list.querySelectorAll('.js-messages-entry')).map(elem => {
|
||||
const item = new MsgMessagesEntry(elem);
|
||||
item.onSelectedChange((state, initial) => {
|
||||
if(state)
|
||||
++selectedCount;
|
||||
else if(!initial)
|
||||
--selectedCount;
|
||||
|
||||
if(!initial)
|
||||
watchers.call('select', selectedCount, items.length);
|
||||
});
|
||||
return item;
|
||||
});
|
||||
|
||||
const recountSelected = () => {
|
||||
selectedCount = 0;
|
||||
|
||||
for(const item of items)
|
||||
if(item.getSelected())
|
||||
++selectedCount;
|
||||
};
|
||||
|
||||
const onSelectedChange = handler => {
|
||||
watchers.watch('select', handler, selectedCount, items.length);
|
||||
};
|
||||
|
||||
onSelectedChange(selectedCount => {
|
||||
const state = selectedCount > 0;
|
||||
for(const item of items)
|
||||
item.setClickIsSelect(state);
|
||||
});
|
||||
|
||||
return {
|
||||
getItems: () => items,
|
||||
getItemsCount: () => items.length,
|
||||
getSelectedItems: () => {
|
||||
const selected = [];
|
||||
|
||||
for(const item of items)
|
||||
if(item.getSelected())
|
||||
selected.push(item);
|
||||
|
||||
return selected;
|
||||
},
|
||||
removeItem: item => {
|
||||
$ari(items, item);
|
||||
$r(item.getElement());
|
||||
recountSelected();
|
||||
watchers.call('select', selectedCount, items.length);
|
||||
},
|
||||
getAllSelected: () => {
|
||||
if(items.length < 1)
|
||||
return false;
|
||||
|
||||
for(const item of items)
|
||||
if(!item.getSelected())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
},
|
||||
setAllSelected: state => {
|
||||
for(const item of items)
|
||||
item.setSelected(state);
|
||||
selectedCount = state ? items.length : 0;
|
||||
watchers.call('select', selectedCount, items.length);
|
||||
},
|
||||
onSelectedChange: onSelectedChange,
|
||||
};
|
||||
};
|
||||
|
||||
const MsgMessagesEntry = function(entry) {
|
||||
if(!(entry instanceof Element))
|
||||
throw 'entry must be an element';
|
||||
|
||||
const msgId = entry.dataset.msgId;
|
||||
|
||||
const unreadElem = entry.querySelector('.js-messages-entry-unread');
|
||||
const isRead = () => entry.dataset.msgRead === 'read';
|
||||
const setRead = state => {
|
||||
if(state) {
|
||||
entry.dataset.msgRead = 'read';
|
||||
unreadElem.hidden = true;
|
||||
} else {
|
||||
entry.dataset.msgRead = 'unread';
|
||||
unreadElem.hidden = false;
|
||||
}
|
||||
};
|
||||
|
||||
const isSent = () => entry.dataset.msgSent === 'sent';
|
||||
const setSent = state => {
|
||||
entry.dataset.msgRead = state ? 'sent' : 'draft';
|
||||
};
|
||||
|
||||
const checkbox = entry.querySelector('.js-entry-checkbox');
|
||||
const getSelected = () => checkbox.checked;
|
||||
const setSelected = state => checkbox.checked = state;
|
||||
const toggleSelected = () => checkbox.checked = !checkbox.checked;
|
||||
|
||||
let clickIsSelect = false;
|
||||
|
||||
const watchers = new MszWatchers;
|
||||
watchers.define(['select']);
|
||||
|
||||
checkbox.addEventListener('click', ev => ev.stopPropagation());
|
||||
checkbox.addEventListener('keydown', ev => ev.stopPropagation());
|
||||
|
||||
checkbox.addEventListener('change', () => {
|
||||
watchers.call('select', getSelected());
|
||||
});
|
||||
|
||||
const navigateToMessage = () => {
|
||||
const url = entry.dataset.msgUrl;
|
||||
if(url !== undefined && url.startsWith('/') && !url.startsWith('//'))
|
||||
location.assign(url);
|
||||
};
|
||||
|
||||
entry.addEventListener('keydown', ev => {
|
||||
if(ev.key === 'Enter' || ev.key === 'NumpadEnter') {
|
||||
ev.preventDefault();
|
||||
entry.click();
|
||||
}
|
||||
});
|
||||
|
||||
entry.addEventListener('click', ev => {
|
||||
ev.preventDefault();
|
||||
|
||||
if(clickIsSelect)
|
||||
checkbox.click();
|
||||
else
|
||||
navigateToMessage();
|
||||
});
|
||||
|
||||
entry.addEventListener('dblclick', ev => {
|
||||
ev.preventDefault();
|
||||
|
||||
if(clickIsSelect)
|
||||
navigateToMessage();
|
||||
});
|
||||
|
||||
return {
|
||||
getId: () => msgId,
|
||||
getElement: () => entry,
|
||||
isRead: isRead,
|
||||
setRead: setRead,
|
||||
isSent: isSent,
|
||||
setSent: setSent,
|
||||
getSelected: getSelected,
|
||||
setSelected: setSelected,
|
||||
toggleSelected: toggleSelected,
|
||||
setClickIsSelect: state => clickIsSelect = state,
|
||||
onSelectedChange: handler => {
|
||||
watchers.watch('select', handler, getSelected());
|
||||
},
|
||||
};
|
||||
};
|
386
assets/misuzu.js/messages/messages.js
Normal file
386
assets/misuzu.js/messages/messages.js
Normal file
|
@ -0,0 +1,386 @@
|
|||
#include csrfp.js
|
||||
#include msgbox.js
|
||||
#include utility.js
|
||||
#include messages/actbtn.js
|
||||
#include messages/list.js
|
||||
#include messages/recipient.js
|
||||
#include messages/reply.jsx
|
||||
#include messages/thread.js
|
||||
|
||||
const MszMessages = () => {
|
||||
const extractMsgIds = msg => {
|
||||
if(typeof msg.getId === 'function')
|
||||
return msg.getId();
|
||||
if(typeof msg.toString === 'function')
|
||||
return msg.toString();
|
||||
throw 'unsupported message type';
|
||||
};
|
||||
|
||||
const displayErrorMessage = async error => {
|
||||
let text;
|
||||
if(typeof error === 'string')
|
||||
text = error;
|
||||
else if(typeof error.text === 'string')
|
||||
text = error.text;
|
||||
else if(typeof error.toString === 'function')
|
||||
text = error.toString();
|
||||
else
|
||||
text = 'Something indescribable happened.';
|
||||
|
||||
await MszShowMessageBox(text, 'Error');
|
||||
return false;
|
||||
};
|
||||
|
||||
const msgsCreate = async (title, text, parser, draft, recipient, replyTo) => {
|
||||
const formData = new FormData;
|
||||
formData.append('_csrfp', MszCSRFP.getToken());
|
||||
formData.append('title', title);
|
||||
formData.append('body', text);
|
||||
formData.append('parser', parser);
|
||||
formData.append('draft', draft);
|
||||
formData.append('recipient', recipient);
|
||||
formData.append('reply', replyTo);
|
||||
|
||||
const result = await $x.post('/messages/create', { type: 'json' }, formData);
|
||||
|
||||
MszCSRFP.setFromHeaders(result);
|
||||
|
||||
const body = result.body();
|
||||
if(body.error !== undefined)
|
||||
throw body.error;
|
||||
|
||||
return body;
|
||||
};
|
||||
|
||||
const msgsUpdate = async (messageId, title, text, parser, draft) => {
|
||||
const formData = new FormData;
|
||||
formData.append('_csrfp', MszCSRFP.getToken());
|
||||
formData.append('title', title);
|
||||
formData.append('body', text);
|
||||
formData.append('parser', parser);
|
||||
formData.append('draft', draft);
|
||||
|
||||
const result = await $x.post(`/messages/${encodeURIComponent(messageId)}`, { type: 'json' }, formData);
|
||||
|
||||
MszCSRFP.setFromHeaders(result);
|
||||
|
||||
const body = result.body();
|
||||
if(body.error !== undefined)
|
||||
throw body.error;
|
||||
|
||||
return body;
|
||||
};
|
||||
|
||||
const msgsMark = async (msgs, state) => {
|
||||
const result = await $x.post('/messages/mark', { type: 'json' }, {
|
||||
_csrfp: MszCSRFP.getToken(),
|
||||
type: state,
|
||||
messages: msgs.map(extractMsgIds).join(','),
|
||||
});
|
||||
|
||||
MszCSRFP.setFromHeaders(result);
|
||||
|
||||
const body = result.body();
|
||||
if(body.error !== undefined)
|
||||
throw body.error;
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const msgsDelete = async msgs => {
|
||||
const result = await $x.post('/messages/delete', { type: 'json' }, {
|
||||
_csrfp: MszCSRFP.getToken(),
|
||||
messages: msgs.map(extractMsgIds).join(','),
|
||||
});
|
||||
|
||||
MszCSRFP.setFromHeaders(result);
|
||||
|
||||
const body = result.body();
|
||||
if(body.error !== undefined)
|
||||
throw body.error;
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const msgsRestore = async msgs => {
|
||||
const result = await $x.post('/messages/restore', { type: 'json' }, {
|
||||
_csrfp: MszCSRFP.getToken(),
|
||||
messages: msgs.map(extractMsgIds).join(','),
|
||||
});
|
||||
|
||||
MszCSRFP.setFromHeaders(result);
|
||||
|
||||
const body = result.body();
|
||||
if(body.error !== undefined)
|
||||
throw body.error;
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const msgsNuke = async msgs => {
|
||||
const result = await $x.post('/messages/nuke', { type: 'json' }, {
|
||||
_csrfp: MszCSRFP.getToken(),
|
||||
messages: msgs.map(extractMsgIds).join(','),
|
||||
});
|
||||
|
||||
MszCSRFP.setFromHeaders(result);
|
||||
|
||||
const body = result.body();
|
||||
if(body.error !== undefined)
|
||||
throw body.error;
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const msgsUserBtns = Array.from($qa('.js-header-pms-button'));
|
||||
if(msgsUserBtns.length > 0)
|
||||
$x.get('/messages/stats', { type: 'json' }).then(result => {
|
||||
const body = result.body();
|
||||
if(typeof body === 'object' && typeof body.unread === 'number')
|
||||
if(body.unread > 0)
|
||||
for(const msgsUserBtn of msgsUserBtns)
|
||||
msgsUserBtn.append($e({ child: body.unread.toLocaleString(), attrs: { className: 'header__desktop__user__button__count' } }));
|
||||
});
|
||||
|
||||
const msgsListElem = $q('.js-messages-list');
|
||||
const msgsList = msgsListElem instanceof Element ? new MsgMessagesList(msgsListElem) : undefined;
|
||||
|
||||
const msgsListEmptyNotice = $q('.js-messages-folder-empty');
|
||||
|
||||
const msgsThreadElem = $q('.js-messages-thread');
|
||||
const msgsThread = msgsThreadElem instanceof Element ? new MszMessagesThread(msgsThreadElem) : undefined;
|
||||
|
||||
const msgsRecipientElem = $q('.js-messages-recipient');
|
||||
const msgsRecipient = msgsRecipientElem instanceof Element ? new MszMessagesRecipient(msgsRecipientElem) : undefined;
|
||||
|
||||
const msgsReplyElem = $q('.js-messages-reply');
|
||||
const msgsReply = msgsReplyElem instanceof Element ? new MszMessagesReply(msgsReplyElem) : undefined;
|
||||
|
||||
if(msgsReply !== undefined) {
|
||||
if(msgsRecipient !== undefined)
|
||||
msgsRecipient.onUpdate(async info => {
|
||||
msgsReply.setRecipient(typeof info.id === 'string' ? info.id : '');
|
||||
msgsReply.setWarning(info.ban ? `${(typeof info.name === 'string' ? info.name : 'This user')} has been banned and will be unable to respond to your messages.` : undefined);
|
||||
});
|
||||
|
||||
msgsReply.onSubmit(async form => {
|
||||
try {
|
||||
let result;
|
||||
if(typeof form.message === 'string') {
|
||||
result = await msgsUpdate(
|
||||
form.message,
|
||||
form.title,
|
||||
form.body,
|
||||
form.parser,
|
||||
form.draft
|
||||
);
|
||||
} else {
|
||||
result = await msgsCreate(
|
||||
form.title,
|
||||
form.body,
|
||||
form.parser,
|
||||
form.draft,
|
||||
form.recipient,
|
||||
form.reply || ''
|
||||
);
|
||||
}
|
||||
|
||||
if(typeof result.url === 'string')
|
||||
location.assign(result.url);
|
||||
} catch(ex) {
|
||||
return await displayErrorMessage(ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let actSelectAll, actMarkRead, actMoveTrash, actNuke;
|
||||
|
||||
const actSelectAllBtn = $q('.js-messages-actions-select-all');
|
||||
if(actSelectAllBtn instanceof Element) {
|
||||
actSelectAll = new MszMessagesActionButton(actSelectAllBtn);
|
||||
|
||||
if(msgsList !== undefined) {
|
||||
actSelectAll.setAction(async state => {
|
||||
msgsList.setAllSelected(!state);
|
||||
return !state;
|
||||
});
|
||||
msgsList.onSelectedChange((selectedNo, itemNo) => {
|
||||
actSelectAll.setState(selectedNo >= itemNo);
|
||||
});
|
||||
actSelectAll.setState(msgsList.getAllSelected());
|
||||
}
|
||||
}
|
||||
|
||||
const actMarkReadBtn = $q('.js-messages-actions-mark-read');
|
||||
if(actMarkReadBtn instanceof Element) {
|
||||
actMarkRead = new MszMessagesActionButton(actMarkReadBtn);
|
||||
|
||||
if(msgsList !== undefined) {
|
||||
msgsList.onSelectedChange(selectedNo => {
|
||||
const enabled = selectedNo > 0;
|
||||
actMarkRead.setEnabled(enabled);
|
||||
|
||||
if(enabled) {
|
||||
const items = msgsList.getSelectedItems();
|
||||
let readNo = 0, unreadNo = 0;
|
||||
|
||||
for(const item of items) {
|
||||
if(item.isRead())
|
||||
++readNo;
|
||||
else
|
||||
++unreadNo;
|
||||
}
|
||||
|
||||
actMarkRead.setState(readNo > unreadNo);
|
||||
}
|
||||
});
|
||||
actMarkRead.setAction(async state => {
|
||||
const items = msgsList.getSelectedItems();
|
||||
|
||||
const result = await actMarkRead.disableWith(async () => {
|
||||
try {
|
||||
return await msgsMark(items, state ? 'unread' : 'read');
|
||||
} catch(ex) {
|
||||
return await displayErrorMessage(ex);
|
||||
}
|
||||
});
|
||||
|
||||
if(result) {
|
||||
state = !state;
|
||||
|
||||
for(const item of items)
|
||||
item.setRead(state);
|
||||
|
||||
return state;
|
||||
}
|
||||
});
|
||||
} else if(msgsThread !== undefined) {
|
||||
actMarkRead.setAction(async state => {
|
||||
const items = [msgsThread.getMessage()];
|
||||
|
||||
const result = await actMarkRead.disableWith(async () => {
|
||||
try {
|
||||
return await msgsMark(items, state ? 'unread' : 'read');
|
||||
} catch(ex) {
|
||||
return await displayErrorMessage(ex);
|
||||
}
|
||||
});
|
||||
|
||||
return result ? !state : state;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const actMoveTrashBtn = $q('.js-messages-actions-move-trash');
|
||||
if(actMoveTrashBtn instanceof Element) {
|
||||
actMoveTrash = new MszMessagesActionButton(actMoveTrashBtn);
|
||||
|
||||
if(msgsList !== undefined) {
|
||||
msgsList.onSelectedChange(selectedNo => actMoveTrash.setEnabled(selectedNo > 0));
|
||||
actMoveTrash.setAction(async state => {
|
||||
const items = msgsList.getSelectedItems();
|
||||
|
||||
if(!state && !await MszShowConfirmBox(`Are you sure you wish to delete ${items.length} item${items.length === 1 ? '' : 's'}?`, 'Confirmation'))
|
||||
return;
|
||||
|
||||
const result = await actMoveTrash.disableWith(async () => {
|
||||
try {
|
||||
if(state)
|
||||
return await msgsRestore(items);
|
||||
return await msgsDelete(items);
|
||||
} catch(ex) {
|
||||
return await displayErrorMessage(ex);
|
||||
}
|
||||
});
|
||||
|
||||
if(result)
|
||||
for(const message of items)
|
||||
msgsList.removeItem(message);
|
||||
|
||||
if(msgsListEmptyNotice instanceof Element)
|
||||
msgsListEmptyNotice.hidden = msgsList.getItemsCount() > 0;
|
||||
});
|
||||
} else if(msgsThread !== undefined) {
|
||||
actMoveTrash.setAction(async state => {
|
||||
if(!state && !await MszShowConfirmBox('Are you sure you wish to delete this message?', 'Confirmation'))
|
||||
return;
|
||||
|
||||
const items = [msgsThread.getMessage()];
|
||||
|
||||
const result = await actMoveTrash.disableWith(async () => {
|
||||
try {
|
||||
if(state)
|
||||
return await msgsRestore(items);
|
||||
return await msgsDelete(items);
|
||||
} catch(ex) {
|
||||
return await displayErrorMessage(ex);
|
||||
}
|
||||
});
|
||||
|
||||
if(result) {
|
||||
state = !state;
|
||||
|
||||
if(msgsReply !== undefined)
|
||||
msgsReply.setHidden(state);
|
||||
|
||||
const msg = msgsThread.getMessage();
|
||||
if(msg !== undefined)
|
||||
msg.setDeleted(state);
|
||||
|
||||
return state;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const actNukeBtn = $q('.js-messages-actions-nuke');
|
||||
if(actNukeBtn instanceof Element) {
|
||||
actNuke = new MszMessagesActionButton(actNukeBtn, true);
|
||||
|
||||
if(msgsList !== undefined) {
|
||||
msgsList.onSelectedChange(selectedNo => actNuke.setEnabled(selectedNo > 0));
|
||||
actNuke.setAction(async () => {
|
||||
const items = msgsList.getSelectedItems();
|
||||
|
||||
if(!await MszShowConfirmBox(`Are you sure you wish to PERMANENTLY delete ${items.length} item${items.length === 1 ? '' : 's'}?`, 'Confirmation'))
|
||||
return;
|
||||
|
||||
const result = await actNuke.disableWith(async () => {
|
||||
try {
|
||||
return await msgsNuke(items);
|
||||
} catch(ex) {
|
||||
return await displayErrorMessage(ex);
|
||||
}
|
||||
});
|
||||
|
||||
if(result)
|
||||
for(const message of items)
|
||||
msgsList.removeItem(message);
|
||||
|
||||
if(msgsListEmptyNotice instanceof Element)
|
||||
msgsListEmptyNotice.hidden = msgsList.getItemsCount() > 0;
|
||||
});
|
||||
} else if(msgsThread !== undefined) {
|
||||
actMoveTrash.watchState(state => {
|
||||
actNuke.setHidden(!state);
|
||||
});
|
||||
actNuke.setAction(async () => {
|
||||
if(!await MszShowConfirmBox('Are you sure you wish to PERMANENTLY delete this message?', 'Confirmation'))
|
||||
return;
|
||||
|
||||
const items = [msgsThread.getMessage()];
|
||||
|
||||
const result = await actNuke.disableWith(async () => {
|
||||
try {
|
||||
return await msgsNuke(items);
|
||||
} catch(ex) {
|
||||
return await displayErrorMessage(ex);
|
||||
}
|
||||
});
|
||||
|
||||
if(result)
|
||||
location.assign('/messages');
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
56
assets/misuzu.js/messages/recipient.js
Normal file
56
assets/misuzu.js/messages/recipient.js
Normal file
|
@ -0,0 +1,56 @@
|
|||
#include csrfp.js
|
||||
#include utility.js
|
||||
|
||||
const MszMessagesRecipient = function(element) {
|
||||
if(!(element instanceof Element))
|
||||
throw 'element must be an instance of Element';
|
||||
|
||||
const avatarElem = element.querySelector('.js-messages-recipient-avatar img');
|
||||
const nameInput = element.querySelector('.js-messages-recipient-name');
|
||||
|
||||
let updateHandler = undefined;
|
||||
const update = async () => {
|
||||
const result = await $x.post(element.dataset.msgLookup, { type: 'json' }, {
|
||||
_csrfp: MszCSRFP.getToken(),
|
||||
name: nameInput.value,
|
||||
});
|
||||
|
||||
MszCSRFP.setFromHeaders(result);
|
||||
|
||||
const body = result.body();
|
||||
|
||||
if(updateHandler !== undefined)
|
||||
await updateHandler(body);
|
||||
|
||||
if(typeof body.avatar === 'string')
|
||||
avatarElem.src = body.avatar;
|
||||
|
||||
if(typeof body.name === 'string')
|
||||
nameInput.value = body.name;
|
||||
};
|
||||
|
||||
let nameTimeout = null;
|
||||
nameInput.addEventListener('input', () => {
|
||||
if(nameTimeout !== undefined)
|
||||
return;
|
||||
|
||||
nameTimeout = setTimeout(() => {
|
||||
update().finally(() => {
|
||||
clearTimeout(nameTimeout);
|
||||
nameTimeout = undefined;
|
||||
});
|
||||
}, 750);
|
||||
});
|
||||
|
||||
update().finally(() => nameTimeout = undefined);
|
||||
|
||||
return {
|
||||
getElement: () => element,
|
||||
onUpdate: handler => {
|
||||
if(typeof handler !== 'function')
|
||||
throw 'handler must be a function';
|
||||
|
||||
updateHandler = handler;
|
||||
},
|
||||
};
|
||||
};
|
171
assets/misuzu.js/messages/reply.jsx
Normal file
171
assets/misuzu.js/messages/reply.jsx
Normal file
|
@ -0,0 +1,171 @@
|
|||
#include parsing.js
|
||||
#include ext/eeprom.js
|
||||
|
||||
const MszMessagesReply = function(element) {
|
||||
if(!(element instanceof Element))
|
||||
throw 'element must be an Element';
|
||||
|
||||
const form = element.querySelector('.js-messages-reply-form');
|
||||
const bodyElem = form.querySelector('.js-messages-reply-body');
|
||||
const actsElem = form.querySelector('.js-messages-reply-actions');
|
||||
const parserSelect = form.querySelector('.js-messages-reply-parser');
|
||||
const saveBtn = form.querySelector('.js-messages-reply-save');
|
||||
const sendBtn = form.querySelector('.js-messages-reply-send');
|
||||
const warnElem = form.querySelector('.js-reply-form-warning');
|
||||
const warnText = warnElem instanceof Element ? warnElem.querySelector('.js-reply-form-warning-text') : undefined;
|
||||
|
||||
let submitHandler;
|
||||
form.addEventListener('submit', ev => {
|
||||
ev.preventDefault();
|
||||
|
||||
if(typeof submitHandler === 'function') {
|
||||
const fields = Array.from(form.elements);
|
||||
const result = {};
|
||||
|
||||
for(const field of fields) {
|
||||
if((field instanceof HTMLButtonElement || (field instanceof HTMLInputElement && field.type === 'submit')) && ev.submitter !== field)
|
||||
continue;
|
||||
|
||||
if(typeof field.name === 'string' && field.name.length > 0)
|
||||
result[field.name] = field.value;
|
||||
}
|
||||
|
||||
submitHandler(result);
|
||||
}
|
||||
});
|
||||
|
||||
bodyElem.addEventListener('keydown', ev => {
|
||||
if((ev.code === 'Enter' || ev.code === 'NumpadEnter') && ev.ctrlKey && !ev.altKey && !ev.metaKey) {
|
||||
ev.preventDefault();
|
||||
|
||||
if(ev.shiftKey)
|
||||
saveBtn.click();
|
||||
else
|
||||
sendBtn.click();
|
||||
}
|
||||
});
|
||||
|
||||
const switchButtons = parser => {
|
||||
$rc(actsElem);
|
||||
|
||||
const tags = MszParsing.getTagsFor(parser);
|
||||
actsElem.hidden = tags.length < 1;
|
||||
for(const tag of tags)
|
||||
actsElem.appendChild(<button class="messages-reply-action" type="button" title={tag.summary} onclick={() => $insertTags(bodyElem, tag.open, tag.close)}>
|
||||
<i class={tag.icon}/>
|
||||
</button>);
|
||||
};
|
||||
|
||||
switchButtons(parserSelect.value);
|
||||
|
||||
parserSelect.addEventListener('change', () => {
|
||||
switchButtons(parserSelect.value);
|
||||
});
|
||||
|
||||
// this implementation is godawful but it'll do for now lol
|
||||
// need to make it easier to share the forum's implementation
|
||||
MszEEPROM.init()
|
||||
.catch(() => console.error('Failed to initialise EEPROM'))
|
||||
.then(() => {
|
||||
const eepromClient = new EEPROM(peepApp, peepPath);
|
||||
const eepromHandleFileUpload = async file => {
|
||||
const uploadTask = eepromClient.create(file);
|
||||
|
||||
try {
|
||||
const fileInfo = await uploadTask.start();
|
||||
const parserMode = parseInt(parserSelect.value);
|
||||
let insertText = location.protocol + fileInfo.url;
|
||||
|
||||
if(parserMode == 1) { // bbcode
|
||||
if(fileInfo.isImage())
|
||||
insertText = `[img]${fileInfo.url}[/img]`;
|
||||
else if(fileInfo.isAudio())
|
||||
insertText = `[audio]${fileInfo.url}[/audio]`;
|
||||
else if(fileInfo.isVideo())
|
||||
insertText = `[video]${fileInfo.url}[/video]`;
|
||||
} else if(parserMode == 2) { // markdown
|
||||
if(fileInfo.isMedia())
|
||||
insertText = `![](${fileInfo.url})`;
|
||||
}
|
||||
|
||||
$insertTags(bodyElem, insertText, '');
|
||||
bodyElem.value = bodyElem.value.trim();
|
||||
} catch(ex) {
|
||||
let errorText = 'Upload aborted.';
|
||||
|
||||
if(!ex.aborted) {
|
||||
console.error(ex);
|
||||
errorText = ex.toString();
|
||||
}
|
||||
|
||||
await MszShowMessageBox(errorText, 'Upload Error');
|
||||
}
|
||||
};
|
||||
|
||||
bodyElem.addEventListener('paste', ev => {
|
||||
if(ev.clipboardData && ev.clipboardData.files.length > 0) {
|
||||
ev.preventDefault();
|
||||
|
||||
const files = ev.clipboardData.files;
|
||||
for(const file of files)
|
||||
eepromHandleFileUpload(file);
|
||||
}
|
||||
});
|
||||
|
||||
document.body.addEventListener('dragenter', ev => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
});
|
||||
document.body.addEventListener('dragover', ev => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
});
|
||||
document.body.addEventListener('dragleave', ev => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
});
|
||||
document.body.addEventListener('drop', ev => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
if(ev.dataTransfer && ev.dataTransfer.files.length > 0) {
|
||||
const files = ev.dataTransfer.files;
|
||||
for(const file of files)
|
||||
eepromHandleFileUpload(file);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
getElement: () => element,
|
||||
setWarning: text => {
|
||||
if(warnElem === undefined || warnText === undefined)
|
||||
return;
|
||||
|
||||
if(text === undefined) {
|
||||
warnElem.hidden = true;
|
||||
warnText.textContent = '';
|
||||
} else {
|
||||
warnElem.hidden = false;
|
||||
warnText.textContent = text;
|
||||
}
|
||||
},
|
||||
setRecipient: userId => {
|
||||
for(const field of form.elements)
|
||||
if(field.name === 'recipient') {
|
||||
field.value = userId;
|
||||
break;
|
||||
}
|
||||
},
|
||||
getHidden: () => element.hidden,
|
||||
setHidden: state => {
|
||||
element.hidden = state;
|
||||
},
|
||||
onSubmit: handler => {
|
||||
if(typeof handler !== 'function')
|
||||
throw 'handler must be a function';
|
||||
|
||||
submitHandler = handler;
|
||||
},
|
||||
};
|
||||
};
|
78
assets/misuzu.js/messages/thread.js
Normal file
78
assets/misuzu.js/messages/thread.js
Normal file
|
@ -0,0 +1,78 @@
|
|||
const MszMessagesThread = function(thread) {
|
||||
if(!(thread instanceof Element))
|
||||
throw 'thread must be an element';
|
||||
|
||||
const messages = Array.from(thread.querySelectorAll('.js-messages-message')).map(elem => new MszMessagesThreadMessage(elem));
|
||||
const message = messages.find(msg => msg.isFull());
|
||||
|
||||
return {
|
||||
getMessage: () => message,
|
||||
getMessages: () => messages,
|
||||
};
|
||||
};
|
||||
|
||||
const MszMessagesThreadMessage = function(message) {
|
||||
if(!(message instanceof Element))
|
||||
throw 'message must be an element';
|
||||
|
||||
const msgId = message.dataset.msgId;
|
||||
const type = message.dataset.msgType;
|
||||
const url = message.dataset.msgUrl;
|
||||
|
||||
if(type === 'snip') {
|
||||
message.addEventListener('click', ev => {
|
||||
if(typeof url !== 'string')
|
||||
return;
|
||||
|
||||
let target = ev.target;
|
||||
while(target !== message) {
|
||||
if(target instanceof HTMLAnchorElement)
|
||||
return;
|
||||
|
||||
target = target.parentNode;
|
||||
}
|
||||
|
||||
ev.preventDefault();
|
||||
location.assign(url);
|
||||
});
|
||||
} else if(type === 'full') {
|
||||
message.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'start',
|
||||
});
|
||||
}
|
||||
|
||||
const isRead = () => message.dataset.msgRead === 'read';
|
||||
const setRead = state => {
|
||||
message.dataset.msgRead = state ? 'read' : 'unread';
|
||||
};
|
||||
|
||||
const isSent = () => message.dataset.msgSent === 'sent';
|
||||
const setSent = state => {
|
||||
message.dataset.msgRead = state ? 'sent' : 'draft';
|
||||
};
|
||||
|
||||
const isDeleted = () => message.dataset.msgDeleted === 'yes';
|
||||
const setDeleted = state => {
|
||||
if(state) {
|
||||
message.dataset.msgDeleted = 'yes';
|
||||
message.classList.add('messages-message-deleted');
|
||||
} else {
|
||||
message.dataset.msgDeleted = 'no';
|
||||
message.classList.remove('messages-message-deleted');
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
getId: () => msgId,
|
||||
getType: () => type,
|
||||
isFull: () => type === 'full',
|
||||
isSnippet: () => type === 'snip',
|
||||
isRead: isRead,
|
||||
setRead: setRead,
|
||||
isSent: isSent,
|
||||
setSent: setSent,
|
||||
isDeleted: isDeleted,
|
||||
setDeleted: setDeleted,
|
||||
};
|
||||
};
|
|
@ -1,49 +1,73 @@
|
|||
#include utility.js
|
||||
|
||||
const MszShowMessageBox = async (text, title, buttons, target) => {
|
||||
if(typeof text !== 'string')
|
||||
throw 'text must be a string';
|
||||
const MszShowConfirmBox = async (text, title, target) => {
|
||||
let result = false;
|
||||
|
||||
await MszShowMessageBox(text, title, [
|
||||
{ text: 'Yes', callback: async () => result = true },
|
||||
{ text: 'No' },
|
||||
], target);
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
const MszShowMessageBox = (text, title, buttons, target) => {
|
||||
if(typeof text !== 'string') {
|
||||
if(text !== undefined && text !== null && typeof text.toString === 'function')
|
||||
text = text.toString();
|
||||
else throw 'text must be a string';
|
||||
}
|
||||
if(!(target instanceof Element))
|
||||
target = document.body;
|
||||
|
||||
if(target.querySelector('.messagebox'))
|
||||
return false;
|
||||
|
||||
if(typeof title !== 'string')
|
||||
title = 'Information';
|
||||
if(!Array.isArray(buttons))
|
||||
buttons = [];
|
||||
|
||||
let buttonsElem;
|
||||
const html = <div class="messagebox">
|
||||
<div class="container messagebox__container">
|
||||
<div class="container__title">
|
||||
<div class="container__title__background"/>
|
||||
<div class="container__title__text">{title}</div>
|
||||
</div>
|
||||
<div class="container__content">{text}</div>
|
||||
{buttonsElem = <div class="messagebox__buttons"/>}
|
||||
</div>
|
||||
</div>;
|
||||
|
||||
let firstButton;
|
||||
if(buttons.length < 1) {
|
||||
firstButton = <button class="input__button" onclick={() => html.remove()}>OK</button>;
|
||||
buttonsElem.appendChild(firstButton);
|
||||
} else {
|
||||
for(const button of buttons) {
|
||||
const buttonElem = <button class="input__button" onclick={() => { html.remove(); if(typeof button === 'function') button.callback(); }}>
|
||||
{button.text}
|
||||
</button>;
|
||||
buttonsElem.appendChild(buttonElem);
|
||||
|
||||
if(firstButton === undefined)
|
||||
firstButton = buttonElem;
|
||||
return new Promise((resolve, reject) => {
|
||||
if(target.querySelector('.messagebox')) {
|
||||
reject();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
target.appendChild(html);
|
||||
firstButton.focus();
|
||||
let buttonsElem;
|
||||
const html = <div class="messagebox">
|
||||
<div class="container messagebox__container">
|
||||
<div class="container__title">
|
||||
<div class="container__title__background"/>
|
||||
<div class="container__title__text">{title}</div>
|
||||
</div>
|
||||
<div class="container__content">{text}</div>
|
||||
{buttonsElem = <div class="messagebox__buttons"/>}
|
||||
</div>
|
||||
</div>;
|
||||
|
||||
return true;
|
||||
let firstButton;
|
||||
if(buttons.length < 1) {
|
||||
firstButton = <button class="input__button" onclick={() => {
|
||||
html.remove();
|
||||
resolve();
|
||||
}}>OK</button>;
|
||||
buttonsElem.appendChild(firstButton);
|
||||
} else {
|
||||
for(const button of buttons) {
|
||||
const buttonElem = <button class="input__button" onclick={() => {
|
||||
html.remove();
|
||||
|
||||
if(typeof button.callback === 'function')
|
||||
button.callback().finally(() => resolve());
|
||||
else
|
||||
resolve();
|
||||
}}>{button.text}</button>;
|
||||
buttonsElem.appendChild(buttonElem);
|
||||
|
||||
if(firstButton === undefined)
|
||||
firstButton = buttonElem;
|
||||
}
|
||||
}
|
||||
|
||||
target.appendChild(html);
|
||||
firstButton.focus();
|
||||
});
|
||||
};
|
||||
|
|
56
assets/misuzu.js/parsing.js
Normal file
56
assets/misuzu.js/parsing.js
Normal file
|
@ -0,0 +1,56 @@
|
|||
// welcome to the shitty temporary file for managing the bbcode/markdown/whatever button
|
||||
const MszParsing = (() => {
|
||||
const defineTag = (name, open, close, summary, icon) => {
|
||||
return {
|
||||
name: name,
|
||||
open: open,
|
||||
close: close,
|
||||
summary: summary,
|
||||
icon: icon,
|
||||
};
|
||||
};
|
||||
|
||||
const bbTags = [
|
||||
defineTag('bb-bold', '[b]', '[/b]', 'Bold [b]<text>[/b]', 'fas fa-bold fa-fw'),
|
||||
defineTag('bb-italic', '[i]', '[/i]', 'Italic [i]<text>[/i]', 'fas fa-italic fa-fw'),
|
||||
defineTag('bb-underline', '[u]', '[/u]', 'Underline [u]<text>[/u]', 'fas fa-underline fa-fw'),
|
||||
defineTag('bb-strike', '[s]', '[/s]', 'Strikethrough [s]<text>[/s]', 'fas fa-strikethrough fa-fw'),
|
||||
defineTag('bb-link', '[url=]', '[/url]', 'Link [url]<url>[/url] or [url=<url>]<text>[/url]', 'fas fa-link fa-fw'),
|
||||
defineTag('bb-image', '[img]', '[/img]', 'Image [img]<url>[/img]', 'fas fa-image fa-fw'),
|
||||
defineTag('bb-audio', '[audio]', '[/audio]', 'Audio [audio]<url>[/audio]', 'fas fa-music fa-fw'),
|
||||
defineTag('bb-video', '[video]', '[/video]', 'Video [video]<url>[/video]', 'fas fa-video fa-fw'),
|
||||
defineTag('bb-code', '[code]', '[/code]', 'Code [code]<code>[/code]', 'fas fa-code fa-fw'),
|
||||
defineTag('bb-zalgo', '[zalgo]', '[/zalgo]', 'Zalgo [zalgo]<text>[/zalgo]', 'fas fa-frog fa-fw'),
|
||||
];
|
||||
|
||||
const mdTags = [
|
||||
defineTag('md-bold', '**', '**', 'Bold **<text>**', 'fas fa-bold fa-fw'),
|
||||
defineTag('md-italic', '*', '*', 'Italic *<text>* or _<text>_', 'fas fa-italic fa-fw'),
|
||||
defineTag('md-underline', '__', '__', 'Underline __<text>__', 'fas fa-underline fa-fw'),
|
||||
defineTag('md-strike', '~~', '~~', 'Strikethrough ~~<text>~~', 'fas fa-strikethrough fa-fw'),
|
||||
defineTag('md-link', '[](', ')', 'Link [<text>](<url>)', 'fas fa-link fa-fw'),
|
||||
defineTag('md-image', '![](', ')', 'Image ![<alt text>](<url>)', 'fas fa-image fa-fw'),
|
||||
defineTag('md-audio', '![](', ')', 'Audio ![<alt text>](<url>)', 'fas fa-music fa-fw'),
|
||||
defineTag('md-video', '![](', ')', 'Video ![<alt text>](<url>)', 'fas fa-video fa-fw'),
|
||||
defineTag('md-code', '```', '```', 'Code `<code>` or ```<code>```', 'fas fa-code fa-fw'),
|
||||
];
|
||||
|
||||
const getTagsFor = parser => {
|
||||
if(typeof parser !== 'number')
|
||||
parser = parseInt(parser);
|
||||
|
||||
if(parser === 1)
|
||||
return bbTags;
|
||||
if(parser === 2)
|
||||
return mdTags;
|
||||
|
||||
return [];
|
||||
};
|
||||
|
||||
return {
|
||||
getTagsFor: getTagsFor,
|
||||
getTagsForPlainText: () => getTagsFor(0),
|
||||
getTagsForBBcode: () => getTagsFor(1),
|
||||
getTagsForMarkdown: () => getTagsFor(2),
|
||||
};
|
||||
})();
|
|
@ -28,7 +28,7 @@ const MszWatcher = function() {
|
|||
};
|
||||
};
|
||||
|
||||
const MszWatcherCollection = function() {
|
||||
const MszWatchers = function() {
|
||||
const watchers = new Map;
|
||||
|
||||
const getWatcher = name => {
|
||||
|
|
266
composer.lock
generated
266
composer.lock
generated
|
@ -8,16 +8,16 @@
|
|||
"packages": [
|
||||
{
|
||||
"name": "chillerlan/php-qrcode",
|
||||
"version": "4.4.0",
|
||||
"version": "4.4.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/chillerlan/php-qrcode.git",
|
||||
"reference": "52889cd7ab1b78e6a345edafe24aa74bc5becc08"
|
||||
"reference": "f5e243f3b61a60934780579430a951460f40888d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/chillerlan/php-qrcode/zipball/52889cd7ab1b78e6a345edafe24aa74bc5becc08",
|
||||
"reference": "52889cd7ab1b78e6a345edafe24aa74bc5becc08",
|
||||
"url": "https://api.github.com/repos/chillerlan/php-qrcode/zipball/f5e243f3b61a60934780579430a951460f40888d",
|
||||
"reference": "f5e243f3b61a60934780579430a951460f40888d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -27,10 +27,10 @@
|
|||
},
|
||||
"require-dev": {
|
||||
"phan/phan": "^5.4",
|
||||
"phpmd/phpmd": "^2.13",
|
||||
"phpmd/phpmd": "^2.15",
|
||||
"phpunit/phpunit": "^9.6",
|
||||
"setasign/fpdf": "^1.8.2",
|
||||
"squizlabs/php_codesniffer": "^3.7"
|
||||
"squizlabs/php_codesniffer": "^3.8"
|
||||
},
|
||||
"suggest": {
|
||||
"chillerlan/php-authenticator": "Yet another Google authenticator! Also creates URIs for mobile apps.",
|
||||
|
@ -73,7 +73,7 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/chillerlan/php-qrcode/issues",
|
||||
"source": "https://github.com/chillerlan/php-qrcode/tree/4.4.0"
|
||||
"source": "https://github.com/chillerlan/php-qrcode/tree/4.4.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
@ -85,20 +85,20 @@
|
|||
"type": "ko_fi"
|
||||
}
|
||||
],
|
||||
"time": "2023-11-23T23:53:20+00:00"
|
||||
"time": "2024-01-06T16:56:58+00:00"
|
||||
},
|
||||
{
|
||||
"name": "chillerlan/php-settings-container",
|
||||
"version": "3.1.0",
|
||||
"version": "3.2.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/chillerlan/php-settings-container.git",
|
||||
"reference": "4d02944424fa1f48abca96353257c93cbac856c1"
|
||||
"reference": "8f93648fac8e6bacac8e00a8d325eba4950295e6"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/chillerlan/php-settings-container/zipball/4d02944424fa1f48abca96353257c93cbac856c1",
|
||||
"reference": "4d02944424fa1f48abca96353257c93cbac856c1",
|
||||
"url": "https://api.github.com/repos/chillerlan/php-settings-container/zipball/8f93648fac8e6bacac8e00a8d325eba4950295e6",
|
||||
"reference": "8f93648fac8e6bacac8e00a8d325eba4950295e6",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -107,9 +107,9 @@
|
|||
},
|
||||
"require-dev": {
|
||||
"phan/phan": "^5.4",
|
||||
"phpmd/phpmd": "^2.13",
|
||||
"phpunit/phpunit": "^10.2",
|
||||
"squizlabs/php_codesniffer": "^3.7"
|
||||
"phpmd/phpmd": "^2.15",
|
||||
"phpunit/phpunit": "^10.5",
|
||||
"squizlabs/php_codesniffer": "^3.9"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
|
@ -150,31 +150,31 @@
|
|||
"type": "ko_fi"
|
||||
}
|
||||
],
|
||||
"time": "2023-07-17T20:46:37+00:00"
|
||||
"time": "2024-03-02T20:07:15+00:00"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/lexer",
|
||||
"version": "3.0.0",
|
||||
"version": "3.0.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/doctrine/lexer.git",
|
||||
"reference": "84a527db05647743d50373e0ec53a152f2cde568"
|
||||
"reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/doctrine/lexer/zipball/84a527db05647743d50373e0ec53a152f2cde568",
|
||||
"reference": "84a527db05647743d50373e0ec53a152f2cde568",
|
||||
"url": "https://api.github.com/repos/doctrine/lexer/zipball/31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd",
|
||||
"reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^8.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"doctrine/coding-standard": "^10",
|
||||
"phpstan/phpstan": "^1.9",
|
||||
"phpunit/phpunit": "^9.5",
|
||||
"doctrine/coding-standard": "^12",
|
||||
"phpstan/phpstan": "^1.10",
|
||||
"phpunit/phpunit": "^10.5",
|
||||
"psalm/plugin-phpunit": "^0.18.3",
|
||||
"vimeo/psalm": "^5.0"
|
||||
"vimeo/psalm": "^5.21"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
|
@ -211,7 +211,7 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/doctrine/lexer/issues",
|
||||
"source": "https://github.com/doctrine/lexer/tree/3.0.0"
|
||||
"source": "https://github.com/doctrine/lexer/tree/3.0.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
@ -227,7 +227,7 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-12-15T16:57:16+00:00"
|
||||
"time": "2024-02-05T11:56:58+00:00"
|
||||
},
|
||||
{
|
||||
"name": "egulias/email-validator",
|
||||
|
@ -352,7 +352,7 @@
|
|||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://patchii.net/flash/index.git",
|
||||
"reference": "e31781c69f0b13fe251771c8e7e529222630a44f"
|
||||
"reference": "9d5b050b8928435416a7efbebe2a19ae8e626224"
|
||||
},
|
||||
"require": {
|
||||
"ext-mbstring": "*",
|
||||
|
@ -390,7 +390,7 @@
|
|||
],
|
||||
"description": "Composer package for the common library for my projects.",
|
||||
"homepage": "https://railgun.sh/index",
|
||||
"time": "2023-11-20T19:01:19+00:00"
|
||||
"time": "2024-03-28T23:27:04+00:00"
|
||||
},
|
||||
{
|
||||
"name": "flashwave/sasae",
|
||||
|
@ -398,7 +398,7 @@
|
|||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://patchii.net/flash/sasae.git",
|
||||
"reference": "b56dd222acb8f138729e6258d4a90bbb8401ff52"
|
||||
"reference": "c8a9f2974e6591215b3f898dd5525de1e8367f66"
|
||||
},
|
||||
"require": {
|
||||
"flashwave/index": "dev-master",
|
||||
|
@ -431,7 +431,7 @@
|
|||
],
|
||||
"description": "A wrapper for Twig with added common functionality.",
|
||||
"homepage": "https://railgun.sh/sasae",
|
||||
"time": "2023-11-20T19:09:35+00:00"
|
||||
"time": "2024-01-04T02:13:42+00:00"
|
||||
},
|
||||
{
|
||||
"name": "flashwave/syokuhou",
|
||||
|
@ -439,7 +439,7 @@
|
|||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://patchii.net/flash/syokuhou.git",
|
||||
"reference": "fdf3c38cc216bf7024af331cbe1758532355c22f"
|
||||
"reference": "c1fe9371ada20fcea51c225cc53b9ceae4642bc4"
|
||||
},
|
||||
"require": {
|
||||
"flashwave/index": "dev-master",
|
||||
|
@ -470,7 +470,7 @@
|
|||
],
|
||||
"description": "Configuration library for PHP.",
|
||||
"homepage": "https://railgun.sh/syokuhou",
|
||||
"time": "2023-11-20T19:10:04+00:00"
|
||||
"time": "2024-01-04T02:12:49+00:00"
|
||||
},
|
||||
{
|
||||
"name": "guzzlehttp/psr7",
|
||||
|
@ -590,16 +590,16 @@
|
|||
},
|
||||
{
|
||||
"name": "jean85/pretty-package-versions",
|
||||
"version": "2.0.5",
|
||||
"version": "2.0.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Jean85/pretty-package-versions.git",
|
||||
"reference": "ae547e455a3d8babd07b96966b17d7fd21d9c6af"
|
||||
"reference": "f9fdd29ad8e6d024f52678b570e5593759b550b4"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/ae547e455a3d8babd07b96966b17d7fd21d9c6af",
|
||||
"reference": "ae547e455a3d8babd07b96966b17d7fd21d9c6af",
|
||||
"url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/f9fdd29ad8e6d024f52678b570e5593759b550b4",
|
||||
"reference": "f9fdd29ad8e6d024f52678b570e5593759b550b4",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -607,9 +607,9 @@
|
|||
"php": "^7.1|^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"friendsofphp/php-cs-fixer": "^2.17",
|
||||
"friendsofphp/php-cs-fixer": "^3.2",
|
||||
"jean85/composer-provided-replaced-stub-package": "^1.0",
|
||||
"phpstan/phpstan": "^0.12.66",
|
||||
"phpstan/phpstan": "^1.4",
|
||||
"phpunit/phpunit": "^7.5|^8.5|^9.4",
|
||||
"vimeo/psalm": "^4.3"
|
||||
},
|
||||
|
@ -643,22 +643,22 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/Jean85/pretty-package-versions/issues",
|
||||
"source": "https://github.com/Jean85/pretty-package-versions/tree/2.0.5"
|
||||
"source": "https://github.com/Jean85/pretty-package-versions/tree/2.0.6"
|
||||
},
|
||||
"time": "2021-10-08T21:21:46+00:00"
|
||||
"time": "2024-03-08T09:58:59+00:00"
|
||||
},
|
||||
{
|
||||
"name": "matomo/device-detector",
|
||||
"version": "6.2.0",
|
||||
"version": "6.3.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/matomo-org/device-detector.git",
|
||||
"reference": "3577abbfea71eaf88d4cd432274428c39601754f"
|
||||
"reference": "35efad75b31f2596701834d19f097497909572a4"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/matomo-org/device-detector/zipball/3577abbfea71eaf88d4cd432274428c39601754f",
|
||||
"reference": "3577abbfea71eaf88d4cd432274428c39601754f",
|
||||
"url": "https://api.github.com/repos/matomo-org/device-detector/zipball/35efad75b31f2596701834d19f097497909572a4",
|
||||
"reference": "35efad75b31f2596701834d19f097497909572a4",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -670,8 +670,8 @@
|
|||
},
|
||||
"require-dev": {
|
||||
"matthiasmullie/scrapbook": "^1.4.7",
|
||||
"mayflower/mo4-coding-standard": "^v8.0.0",
|
||||
"phpstan/phpstan": "^0.12.52",
|
||||
"mayflower/mo4-coding-standard": "^v9.0.0",
|
||||
"phpstan/phpstan": "^1.10.44",
|
||||
"phpunit/phpunit": "^8.5.8",
|
||||
"psr/cache": "^1.0.1",
|
||||
"psr/simple-cache": "^1.0.1",
|
||||
|
@ -714,14 +714,14 @@
|
|||
"source": "https://github.com/matomo-org/matomo",
|
||||
"wiki": "https://dev.matomo.org/"
|
||||
},
|
||||
"time": "2023-11-15T09:44:42+00:00"
|
||||
"time": "2024-02-16T16:26:57+00:00"
|
||||
},
|
||||
{
|
||||
"name": "mustangostang/spyc",
|
||||
"version": "0.6.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:mustangostang/spyc.git",
|
||||
"url": "https://github.com/mustangostang/spyc.git",
|
||||
"reference": "4627c838b16550b666d15aeae1e5289dd5b77da0"
|
||||
},
|
||||
"dist": {
|
||||
|
@ -764,6 +764,10 @@
|
|||
"yaml",
|
||||
"yml"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/mustangostang/spyc/issues",
|
||||
"source": "https://github.com/mustangostang/spyc/tree/0.6.3"
|
||||
},
|
||||
"time": "2019-09-10T13:16:29+00:00"
|
||||
},
|
||||
{
|
||||
|
@ -1128,16 +1132,16 @@
|
|||
},
|
||||
{
|
||||
"name": "sentry/sentry",
|
||||
"version": "4.1.0",
|
||||
"version": "4.6.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/getsentry/sentry-php.git",
|
||||
"reference": "89666f297891ff937fceb2f3d1fb967a6848cf37"
|
||||
"reference": "5a94184175e5830b589bf923da8c9c3af2c0f409"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/getsentry/sentry-php/zipball/89666f297891ff937fceb2f3d1fb967a6848cf37",
|
||||
"reference": "89666f297891ff937fceb2f3d1fb967a6848cf37",
|
||||
"url": "https://api.github.com/repos/getsentry/sentry-php/zipball/5a94184175e5830b589bf923da8c9c3af2c0f409",
|
||||
"reference": "5a94184175e5830b589bf923da8c9c3af2c0f409",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -1161,7 +1165,7 @@
|
|||
"phpbench/phpbench": "^1.0",
|
||||
"phpstan/phpstan": "^1.3",
|
||||
"phpunit/phpunit": "^8.5.14|^9.4",
|
||||
"symfony/phpunit-bridge": "^5.2|^6.0",
|
||||
"symfony/phpunit-bridge": "^5.2|^6.0|^7.0",
|
||||
"vimeo/psalm": "^4.17"
|
||||
},
|
||||
"suggest": {
|
||||
|
@ -1201,7 +1205,7 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/getsentry/sentry-php/issues",
|
||||
"source": "https://github.com/getsentry/sentry-php/tree/4.1.0"
|
||||
"source": "https://github.com/getsentry/sentry-php/tree/4.6.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
@ -1213,7 +1217,7 @@
|
|||
"type": "custom"
|
||||
}
|
||||
],
|
||||
"time": "2023-12-04T12:41:21+00:00"
|
||||
"time": "2024-03-08T08:18:09+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/deprecation-contracts",
|
||||
|
@ -1284,16 +1288,16 @@
|
|||
},
|
||||
{
|
||||
"name": "symfony/event-dispatcher",
|
||||
"version": "v7.0.0",
|
||||
"version": "v7.0.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/event-dispatcher.git",
|
||||
"reference": "c459b40ffe67c49af6fd392aac374c9edf8a027e"
|
||||
"reference": "834c28d533dd0636f910909d01b9ff45cc094b5e"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/c459b40ffe67c49af6fd392aac374c9edf8a027e",
|
||||
"reference": "c459b40ffe67c49af6fd392aac374c9edf8a027e",
|
||||
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/834c28d533dd0636f910909d01b9ff45cc094b5e",
|
||||
"reference": "834c28d533dd0636f910909d01b9ff45cc094b5e",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -1344,7 +1348,7 @@
|
|||
"description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/event-dispatcher/tree/v7.0.0"
|
||||
"source": "https://github.com/symfony/event-dispatcher/tree/v7.0.3"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
@ -1360,7 +1364,7 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-07-27T16:29:09+00:00"
|
||||
"time": "2024-01-23T15:02:46+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/event-dispatcher-contracts",
|
||||
|
@ -1440,16 +1444,16 @@
|
|||
},
|
||||
{
|
||||
"name": "symfony/mailer",
|
||||
"version": "v6.4.0",
|
||||
"version": "v6.4.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/mailer.git",
|
||||
"reference": "ca8dcf8892cdc5b4358ecf2528429bb5e706f7ba"
|
||||
"reference": "791c5d31a8204cf3db0c66faab70282307f4376b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/mailer/zipball/ca8dcf8892cdc5b4358ecf2528429bb5e706f7ba",
|
||||
"reference": "ca8dcf8892cdc5b4358ecf2528429bb5e706f7ba",
|
||||
"url": "https://api.github.com/repos/symfony/mailer/zipball/791c5d31a8204cf3db0c66faab70282307f4376b",
|
||||
"reference": "791c5d31a8204cf3db0c66faab70282307f4376b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -1500,7 +1504,7 @@
|
|||
"description": "Helps sending emails",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/mailer/tree/v6.4.0"
|
||||
"source": "https://github.com/symfony/mailer/tree/v6.4.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
@ -1516,20 +1520,20 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-11-12T18:02:22+00:00"
|
||||
"time": "2024-02-03T21:33:47+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/mime",
|
||||
"version": "v7.0.0",
|
||||
"version": "v7.0.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/mime.git",
|
||||
"reference": "0a2fff95c1a10df97f571d67e76c7ae0f0d4f535"
|
||||
"reference": "c1ffe24ba6fdc3e3f0f3fcb93519103b326a3716"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/mime/zipball/0a2fff95c1a10df97f571d67e76c7ae0f0d4f535",
|
||||
"reference": "0a2fff95c1a10df97f571d67e76c7ae0f0d4f535",
|
||||
"url": "https://api.github.com/repos/symfony/mime/zipball/c1ffe24ba6fdc3e3f0f3fcb93519103b326a3716",
|
||||
"reference": "c1ffe24ba6fdc3e3f0f3fcb93519103b326a3716",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -1583,7 +1587,7 @@
|
|||
"mime-type"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/mime/tree/v7.0.0"
|
||||
"source": "https://github.com/symfony/mime/tree/v7.0.3"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
@ -1599,7 +1603,7 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-10-19T14:20:43+00:00"
|
||||
"time": "2024-01-30T08:34:29+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/options-resolver",
|
||||
|
@ -1670,16 +1674,16 @@
|
|||
},
|
||||
{
|
||||
"name": "symfony/polyfill-ctype",
|
||||
"version": "v1.28.0",
|
||||
"version": "v1.29.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-ctype.git",
|
||||
"reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb"
|
||||
"reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb",
|
||||
"reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ef4d7e442ca910c4764bce785146269b30cb5fc4",
|
||||
"reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -1693,9 +1697,6 @@
|
|||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "1.28-dev"
|
||||
},
|
||||
"thanks": {
|
||||
"name": "symfony/polyfill",
|
||||
"url": "https://github.com/symfony/polyfill"
|
||||
|
@ -1732,7 +1733,7 @@
|
|||
"portable"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.28.0"
|
||||
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.29.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
@ -1748,20 +1749,20 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-01-26T09:26:14+00:00"
|
||||
"time": "2024-01-29T20:11:03+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-intl-idn",
|
||||
"version": "v1.28.0",
|
||||
"version": "v1.29.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-intl-idn.git",
|
||||
"reference": "ecaafce9f77234a6a449d29e49267ba10499116d"
|
||||
"reference": "a287ed7475f85bf6f61890146edbc932c0fff919"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/ecaafce9f77234a6a449d29e49267ba10499116d",
|
||||
"reference": "ecaafce9f77234a6a449d29e49267ba10499116d",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/a287ed7475f85bf6f61890146edbc932c0fff919",
|
||||
"reference": "a287ed7475f85bf6f61890146edbc932c0fff919",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -1774,9 +1775,6 @@
|
|||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "1.28-dev"
|
||||
},
|
||||
"thanks": {
|
||||
"name": "symfony/polyfill",
|
||||
"url": "https://github.com/symfony/polyfill"
|
||||
|
@ -1819,7 +1817,7 @@
|
|||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.28.0"
|
||||
"source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.29.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
@ -1835,20 +1833,20 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-01-26T09:30:37+00:00"
|
||||
"time": "2024-01-29T20:11:03+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-intl-normalizer",
|
||||
"version": "v1.28.0",
|
||||
"version": "v1.29.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-intl-normalizer.git",
|
||||
"reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92"
|
||||
"reference": "bc45c394692b948b4d383a08d7753968bed9a83d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92",
|
||||
"reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/bc45c394692b948b4d383a08d7753968bed9a83d",
|
||||
"reference": "bc45c394692b948b4d383a08d7753968bed9a83d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -1859,9 +1857,6 @@
|
|||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "1.28-dev"
|
||||
},
|
||||
"thanks": {
|
||||
"name": "symfony/polyfill",
|
||||
"url": "https://github.com/symfony/polyfill"
|
||||
|
@ -1903,7 +1898,7 @@
|
|||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.28.0"
|
||||
"source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.29.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
@ -1919,20 +1914,20 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-01-26T09:26:14+00:00"
|
||||
"time": "2024-01-29T20:11:03+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-mbstring",
|
||||
"version": "v1.28.0",
|
||||
"version": "v1.29.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-mbstring.git",
|
||||
"reference": "42292d99c55abe617799667f454222c54c60e229"
|
||||
"reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/42292d99c55abe617799667f454222c54c60e229",
|
||||
"reference": "42292d99c55abe617799667f454222c54c60e229",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9773676c8a1bb1f8d4340a62efe641cf76eda7ec",
|
||||
"reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -1946,9 +1941,6 @@
|
|||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "1.28-dev"
|
||||
},
|
||||
"thanks": {
|
||||
"name": "symfony/polyfill",
|
||||
"url": "https://github.com/symfony/polyfill"
|
||||
|
@ -1986,7 +1978,7 @@
|
|||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.28.0"
|
||||
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.29.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
@ -2002,20 +1994,20 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-07-28T09:04:16+00:00"
|
||||
"time": "2024-01-29T20:11:03+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-php72",
|
||||
"version": "v1.28.0",
|
||||
"version": "v1.29.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-php72.git",
|
||||
"reference": "70f4aebd92afca2f865444d30a4d2151c13c3179"
|
||||
"reference": "861391a8da9a04cbad2d232ddd9e4893220d6e25"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/70f4aebd92afca2f865444d30a4d2151c13c3179",
|
||||
"reference": "70f4aebd92afca2f865444d30a4d2151c13c3179",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/861391a8da9a04cbad2d232ddd9e4893220d6e25",
|
||||
"reference": "861391a8da9a04cbad2d232ddd9e4893220d6e25",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -2023,9 +2015,6 @@
|
|||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "1.28-dev"
|
||||
},
|
||||
"thanks": {
|
||||
"name": "symfony/polyfill",
|
||||
"url": "https://github.com/symfony/polyfill"
|
||||
|
@ -2062,7 +2051,7 @@
|
|||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-php72/tree/v1.28.0"
|
||||
"source": "https://github.com/symfony/polyfill-php72/tree/v1.29.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
@ -2078,20 +2067,20 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-01-26T09:26:14+00:00"
|
||||
"time": "2024-01-29T20:11:03+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-php80",
|
||||
"version": "v1.28.0",
|
||||
"version": "v1.29.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-php80.git",
|
||||
"reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5"
|
||||
"reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/6caa57379c4aec19c0a12a38b59b26487dcfe4b5",
|
||||
"reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/87b68208d5c1188808dd7839ee1e6c8ec3b02f1b",
|
||||
"reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -2099,9 +2088,6 @@
|
|||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "1.28-dev"
|
||||
},
|
||||
"thanks": {
|
||||
"name": "symfony/polyfill",
|
||||
"url": "https://github.com/symfony/polyfill"
|
||||
|
@ -2145,7 +2131,7 @@
|
|||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-php80/tree/v1.28.0"
|
||||
"source": "https://github.com/symfony/polyfill-php80/tree/v1.29.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
@ -2161,25 +2147,25 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-01-26T09:26:14+00:00"
|
||||
"time": "2024-01-29T20:11:03+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/service-contracts",
|
||||
"version": "v3.4.0",
|
||||
"version": "v3.4.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/service-contracts.git",
|
||||
"reference": "b3313c2dbffaf71c8de2934e2ea56ed2291a3838"
|
||||
"reference": "fe07cbc8d837f60caf7018068e350cc5163681a0"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/service-contracts/zipball/b3313c2dbffaf71c8de2934e2ea56ed2291a3838",
|
||||
"reference": "b3313c2dbffaf71c8de2934e2ea56ed2291a3838",
|
||||
"url": "https://api.github.com/repos/symfony/service-contracts/zipball/fe07cbc8d837f60caf7018068e350cc5163681a0",
|
||||
"reference": "fe07cbc8d837f60caf7018068e350cc5163681a0",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.1",
|
||||
"psr/container": "^2.0"
|
||||
"psr/container": "^1.1|^2.0"
|
||||
},
|
||||
"conflict": {
|
||||
"ext-psr": "<1.1|>=2"
|
||||
|
@ -2227,7 +2213,7 @@
|
|||
"standards"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/service-contracts/tree/v3.4.0"
|
||||
"source": "https://github.com/symfony/service-contracts/tree/v3.4.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
@ -2243,7 +2229,7 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-07-30T20:28:31+00:00"
|
||||
"time": "2023-12-26T14:02:43+00:00"
|
||||
},
|
||||
{
|
||||
"name": "twig/html-extra",
|
||||
|
@ -2385,16 +2371,16 @@
|
|||
"packages-dev": [
|
||||
{
|
||||
"name": "phpstan/phpstan",
|
||||
"version": "1.10.50",
|
||||
"version": "1.10.66",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpstan/phpstan.git",
|
||||
"reference": "06a98513ac72c03e8366b5a0cb00750b487032e4"
|
||||
"reference": "94779c987e4ebd620025d9e5fdd23323903950bd"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/06a98513ac72c03e8366b5a0cb00750b487032e4",
|
||||
"reference": "06a98513ac72c03e8366b5a0cb00750b487032e4",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/94779c987e4ebd620025d9e5fdd23323903950bd",
|
||||
"reference": "94779c987e4ebd620025d9e5fdd23323903950bd",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -2443,7 +2429,7 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-12-13T10:59:42+00:00"
|
||||
"time": "2024-03-28T16:17:31+00:00"
|
||||
}
|
||||
],
|
||||
"aliases": [],
|
||||
|
@ -2457,5 +2443,5 @@
|
|||
"prefer-lowest": false,
|
||||
"platform": [],
|
||||
"platform-dev": [],
|
||||
"plugin-api-version": "2.3.0"
|
||||
"plugin-api-version": "2.6.0"
|
||||
}
|
||||
|
|
48
database/2024_01_30_233734_create_messages_table.php
Normal file
48
database/2024_01_30_233734_create_messages_table.php
Normal file
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
use Index\Data\IDbConnection;
|
||||
use Index\Data\Migration\IDbMigration;
|
||||
|
||||
final class CreateMessagesTable_20240130_233734 implements IDbMigration {
|
||||
public function migrate(IDbConnection $conn): void {
|
||||
$conn->execute('
|
||||
CREATE TABLE msz_messages (
|
||||
msg_id BINARY(8) NOT NULL,
|
||||
msg_owner_id INT(10) UNSIGNED NOT NULL,
|
||||
msg_author_id INT(10) UNSIGNED NULL DEFAULT NULL,
|
||||
msg_recipient_id INT(10) UNSIGNED NULL DEFAULT NULL,
|
||||
msg_reply_to BINARY(8) NULL DEFAULT NULL,
|
||||
msg_title TINYTEXT NOT NULL COLLATE "utf8mb4_unicode_520_ci",
|
||||
msg_body TEXT NOT NULL COLLATE "utf8mb4_unicode_520_ci",
|
||||
msg_parser TINYINT(3) UNSIGNED NOT NULL,
|
||||
msg_created TIMESTAMP NOT NULL DEFAULT current_timestamp(),
|
||||
msg_sent TIMESTAMP NULL DEFAULT NULL,
|
||||
msg_read TIMESTAMP NULL DEFAULT NULL,
|
||||
msg_deleted TIMESTAMP NULL DEFAULT NULL,
|
||||
PRIMARY KEY (msg_id, msg_owner_id),
|
||||
KEY messages_owner_foreign (msg_owner_id),
|
||||
KEY messages_author_foreign (msg_author_id),
|
||||
KEY messages_recipient_foreign (msg_recipient_id),
|
||||
KEY messages_reply_to_index (msg_reply_to),
|
||||
KEY messages_created_index (msg_created),
|
||||
KEY messages_sent_index (msg_sent),
|
||||
KEY messages_read_index (msg_read),
|
||||
KEY messages_deleted_index (msg_deleted),
|
||||
CONSTRAINT messages_owner_foreign
|
||||
FOREIGN KEY (msg_owner_id)
|
||||
REFERENCES msz_users (user_id)
|
||||
ON UPDATE CASCADE
|
||||
ON DELETE CASCADE,
|
||||
CONSTRAINT messages_author_foreign
|
||||
FOREIGN KEY (msg_author_id)
|
||||
REFERENCES msz_users (user_id)
|
||||
ON UPDATE CASCADE
|
||||
ON DELETE SET NULL,
|
||||
CONSTRAINT messages_recipient_foreign
|
||||
FOREIGN KEY (msg_recipient_id)
|
||||
REFERENCES msz_users (user_id)
|
||||
ON UPDATE CASCADE
|
||||
ON DELETE SET NULL
|
||||
) ENGINE=InnoDB COLLATE=utf8mb4_bin;
|
||||
');
|
||||
}
|
||||
}
|
461
package-lock.json
generated
461
package-lock.json
generated
|
@ -5,19 +5,19 @@
|
|||
"packages": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"@swc/core": "^1.3.69",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"cssnano": "^6.0.1",
|
||||
"postcss": "^8.4.26"
|
||||
"@swc/core": "^1.4.11",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"cssnano": "^6.1.2",
|
||||
"postcss": "^8.4.38"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/core": {
|
||||
"version": "1.3.105",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core/-/core-1.3.105.tgz",
|
||||
"integrity": "sha512-me2VZyr3OjqRpFrYQJJYy7x/zbFSl9nt+MAGnIcBtjDsN00iTVqEaKxBjPBFQV9BDAgPz2SRWes/DhhVm5SmMw==",
|
||||
"version": "1.4.11",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core/-/core-1.4.11.tgz",
|
||||
"integrity": "sha512-WKEakMZxkVwRdgMN4AMJ9K5nysY8g8npgQPczmjBeNK5In7QEAZAJwnyccrWwJZU0XjVeHn2uj+XbOKdDW17rg==",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@swc/counter": "^0.1.1",
|
||||
"@swc/counter": "^0.1.2",
|
||||
"@swc/types": "^0.1.5"
|
||||
},
|
||||
"engines": {
|
||||
|
@ -28,16 +28,16 @@
|
|||
"url": "https://opencollective.com/swc"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@swc/core-darwin-arm64": "1.3.105",
|
||||
"@swc/core-darwin-x64": "1.3.105",
|
||||
"@swc/core-linux-arm-gnueabihf": "1.3.105",
|
||||
"@swc/core-linux-arm64-gnu": "1.3.105",
|
||||
"@swc/core-linux-arm64-musl": "1.3.105",
|
||||
"@swc/core-linux-x64-gnu": "1.3.105",
|
||||
"@swc/core-linux-x64-musl": "1.3.105",
|
||||
"@swc/core-win32-arm64-msvc": "1.3.105",
|
||||
"@swc/core-win32-ia32-msvc": "1.3.105",
|
||||
"@swc/core-win32-x64-msvc": "1.3.105"
|
||||
"@swc/core-darwin-arm64": "1.4.11",
|
||||
"@swc/core-darwin-x64": "1.4.11",
|
||||
"@swc/core-linux-arm-gnueabihf": "1.4.11",
|
||||
"@swc/core-linux-arm64-gnu": "1.4.11",
|
||||
"@swc/core-linux-arm64-musl": "1.4.11",
|
||||
"@swc/core-linux-x64-gnu": "1.4.11",
|
||||
"@swc/core-linux-x64-musl": "1.4.11",
|
||||
"@swc/core-win32-arm64-msvc": "1.4.11",
|
||||
"@swc/core-win32-ia32-msvc": "1.4.11",
|
||||
"@swc/core-win32-x64-msvc": "1.4.11"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@swc/helpers": "^0.5.0"
|
||||
|
@ -49,9 +49,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@swc/core-darwin-arm64": {
|
||||
"version": "1.3.105",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.105.tgz",
|
||||
"integrity": "sha512-buWeweLVDXXmcnfIemH4PGnpjwsDTUGitnPchdftb0u1FU8zSSP/lw/pUCBDG/XvWAp7c/aFxgN4CyG0j7eayA==",
|
||||
"version": "1.4.11",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.4.11.tgz",
|
||||
"integrity": "sha512-C1j1Qp/IHSelVWdEnT7f0iONWxQz6FAqzjCF2iaL+0vFg4V5f2nlgrueY8vj5pNNzSGhrAlxsMxEIp4dj1MXkg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
@ -64,9 +64,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@swc/core-darwin-x64": {
|
||||
"version": "1.3.105",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.3.105.tgz",
|
||||
"integrity": "sha512-hFmXPApqjA/8sy/9NpljHVaKi1OvL9QkJ2MbbTCCbJERuHMpMUeMBUWipHRfepGHFhU+9B9zkEup/qJaJR4XIg==",
|
||||
"version": "1.4.11",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.4.11.tgz",
|
||||
"integrity": "sha512-0TTy3Ni8ncgaMCchSQ7FK8ZXQLlamy0FXmGWbR58c+pVZWYZltYPTmheJUvVcR0H2+gPAymRKyfC0iLszDALjg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
@ -79,9 +79,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@swc/core-linux-arm-gnueabihf": {
|
||||
"version": "1.3.105",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.3.105.tgz",
|
||||
"integrity": "sha512-mwXyMC41oMKkKrPpL8uJpOxw7fyfQoVtIw3Y5p0Blabk+espNYqix0E8VymHdRKuLmM//z5wVmMsuHdGBHvZeg==",
|
||||
"version": "1.4.11",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.4.11.tgz",
|
||||
"integrity": "sha512-XJLB71uw0rog4DjYAPxFGAuGCBQpgJDlPZZK6MTmZOvI/1t0+DelJ24IjHIxk500YYM26Yv47xPabqFPD7I2zQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
|
@ -94,9 +94,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@swc/core-linux-arm64-gnu": {
|
||||
"version": "1.3.105",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.105.tgz",
|
||||
"integrity": "sha512-H7yEIVydnUtqBSUxwmO6vpIQn7j+Rr0DF6ZOORPyd/SFzQJK9cJRtmJQ3ZMzlJ1Bb+1gr3MvjgLEnmyCYEm2Hg==",
|
||||
"version": "1.4.11",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.4.11.tgz",
|
||||
"integrity": "sha512-vYQwzJvm/iu052d5Iw27UFALIN5xSrGkPZXxLNMHPySVko2QMNNBv35HLatkEQHbQ3X+VKSW9J9SkdtAvAVRAQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
@ -109,9 +109,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@swc/core-linux-arm64-musl": {
|
||||
"version": "1.3.105",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.105.tgz",
|
||||
"integrity": "sha512-Jg7RTFT3pGFdGt5elPV6oDkinRy7q9cXpenjXnJnM2uvx3jOwnsAhexPyCDHom8SHL0j+9kaLLC66T3Gz1E4UA==",
|
||||
"version": "1.4.11",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.4.11.tgz",
|
||||
"integrity": "sha512-eV+KduiRYUFjPsvbZuJ9aknQH9Tj0U2/G9oIZSzLx/18WsYi+upzHbgxmIIHJ2VJgfd7nN40RI/hMtxNsUzR/g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
@ -124,9 +124,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@swc/core-linux-x64-gnu": {
|
||||
"version": "1.3.105",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.105.tgz",
|
||||
"integrity": "sha512-DJghplpyusAmp1X5pW/y93MmS/u83Sx5GrpJxI6KLPa82+NItTgMcl8KBQmW5GYAJpVKZyaIvBanS5TdR8aN2w==",
|
||||
"version": "1.4.11",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.4.11.tgz",
|
||||
"integrity": "sha512-WA1iGXZ2HpqM1OR9VCQZJ8sQ1KP2or9O4bO8vWZo6HZJIeoQSo7aa9waaCLRpkZvkng1ct/TF/l6ymqSNFXIzQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
@ -139,9 +139,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@swc/core-linux-x64-musl": {
|
||||
"version": "1.3.105",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.105.tgz",
|
||||
"integrity": "sha512-wD5jL2dZH/5nPNssBo6jhOvkI0lmWnVR4vnOXWjuXgjq1S0AJpO5jdre/6pYLmf26hft3M42bteDnjR4AAZ38w==",
|
||||
"version": "1.4.11",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.4.11.tgz",
|
||||
"integrity": "sha512-UkVJToKf0owwQYRnGvjHAeYVDfeimCEcx0VQSbJoN7Iy0ckRZi7YPlmWJU31xtKvikE2bQWCOVe0qbSDqqcWXA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
@ -154,9 +154,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@swc/core-win32-arm64-msvc": {
|
||||
"version": "1.3.105",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.105.tgz",
|
||||
"integrity": "sha512-UqJtwILUHRw2+3UTPnRkZrzM/bGdQtbR4UFdp79mZQYfryeOUVNg7aJj/bWUTkKtLiZ3o+FBNrM/x2X1mJX5bA==",
|
||||
"version": "1.4.11",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.4.11.tgz",
|
||||
"integrity": "sha512-35khwkyly7lF5NDSyvIrukBMzxPorgc5iTSDfVO/LvnmN5+fm4lTlrDr4tUfTdOhv3Emy7CsKlsNAeFRJ+Pm+w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
@ -169,9 +169,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@swc/core-win32-ia32-msvc": {
|
||||
"version": "1.3.105",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.105.tgz",
|
||||
"integrity": "sha512-Z95C6vZgBEJ1snidYyjVKnVWiy/ZpPiIFIXGWkDr4ZyBgL3eZX12M6LzZ+NApHKffrbO4enbFyFomueBQgS2oA==",
|
||||
"version": "1.4.11",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.4.11.tgz",
|
||||
"integrity": "sha512-Wx8/6f0ufgQF2pbVPsJ2dAmFLwIOW+xBE5fxnb7VnEbGkTgP1qMDWiiAtD9rtvDSuODG3i1AEmAak/2HAc6i6A==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
|
@ -184,9 +184,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@swc/core-win32-x64-msvc": {
|
||||
"version": "1.3.105",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.105.tgz",
|
||||
"integrity": "sha512-3J8fkyDPFsS3mszuYUY4Wfk7/B2oio9qXUwF3DzOs2MK+XgdyMLIptIxL7gdfitXJBH8k39uVjrIw1JGJDjyFA==",
|
||||
"version": "1.4.11",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.4.11.tgz",
|
||||
"integrity": "sha512-0xRFW6K9UZQH2NVC/0pVB0GJXS45lY24f+6XaPBF1YnMHd8A8GoHl7ugyM5yNUTe2AKhSgk5fJV00EJt/XBtdQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
@ -199,14 +199,17 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@swc/counter": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.2.tgz",
|
||||
"integrity": "sha512-9F4ys4C74eSTEUNndnER3VJ15oru2NumfQxS8geE+f3eB5xvfxpWyqE5XlVnxb/R14uoXi6SLbBwwiDSkv+XEw=="
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
|
||||
"integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ=="
|
||||
},
|
||||
"node_modules/@swc/types": {
|
||||
"version": "0.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.5.tgz",
|
||||
"integrity": "sha512-myfUej5naTBWnqOCc/MdVOLVjXUXtIA+NpDrDBKJtLLg2shUjBu3cZmB/85RyitKc55+lUUyl7oRfLOvkr2hsw=="
|
||||
"version": "0.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.6.tgz",
|
||||
"integrity": "sha512-/JLo/l2JsT/LRd80C3HfbmVpxOAJ11FO2RCEslFrgzLltoP9j8XIbsyDcfCt2WWyX+CM96rBoNM+IToAkFOugg==",
|
||||
"dependencies": {
|
||||
"@swc/counter": "^0.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@trysound/sax": {
|
||||
"version": "0.2.0",
|
||||
|
@ -217,9 +220,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/autoprefixer": {
|
||||
"version": "10.4.17",
|
||||
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.17.tgz",
|
||||
"integrity": "sha512-/cpVNRLSfhOtcGflT13P2794gVSgmPgTR+erw5ifnMLZb0UnSlkK4tquLmkd3BhA+nLo5tX8Cu0upUsGKvKbmg==",
|
||||
"version": "10.4.19",
|
||||
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz",
|
||||
"integrity": "sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
|
@ -235,8 +238,8 @@
|
|||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"browserslist": "^4.22.2",
|
||||
"caniuse-lite": "^1.0.30001578",
|
||||
"browserslist": "^4.23.0",
|
||||
"caniuse-lite": "^1.0.30001599",
|
||||
"fraction.js": "^4.3.7",
|
||||
"normalize-range": "^0.1.2",
|
||||
"picocolors": "^1.0.0",
|
||||
|
@ -258,9 +261,9 @@
|
|||
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="
|
||||
},
|
||||
"node_modules/browserslist": {
|
||||
"version": "4.22.2",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.2.tgz",
|
||||
"integrity": "sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==",
|
||||
"version": "4.23.0",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz",
|
||||
"integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
|
@ -276,8 +279,8 @@
|
|||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"caniuse-lite": "^1.0.30001565",
|
||||
"electron-to-chromium": "^1.4.601",
|
||||
"caniuse-lite": "^1.0.30001587",
|
||||
"electron-to-chromium": "^1.4.668",
|
||||
"node-releases": "^2.0.14",
|
||||
"update-browserslist-db": "^1.0.13"
|
||||
},
|
||||
|
@ -300,9 +303,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001579",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001579.tgz",
|
||||
"integrity": "sha512-u5AUVkixruKHJjw/pj9wISlcMpgFWzSrczLZbrqBSxukQixmg0SJ5sZTpvaFvxU0HoQKd4yoyAogyrAz9pzJnA==",
|
||||
"version": "1.0.30001600",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001600.tgz",
|
||||
"integrity": "sha512-+2S9/2JFhYmYaDpZvo0lKkfvuKIglrx68MwOBqMGHhQsNkLjB5xtc/TGoEPs+MxjSyN/72qer2g97nzR641mOQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
|
@ -332,9 +335,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/css-declaration-sorter": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.1.1.tgz",
|
||||
"integrity": "sha512-dZ3bVTEEc1vxr3Bek9vGwfB5Z6ESPULhcRvO472mfjVnj8jRcTnKO8/JTczlvxM10Myb+wBM++1MtdO76eWcaQ==",
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.2.0.tgz",
|
||||
"integrity": "sha512-h70rUM+3PNFuaBDTLe8wF/cdWu+dOZmb7pJt8Z2sedYbAcQVQV/tEchueg3GWxwqS0cxtbxmaHEdkNACqcvsow==",
|
||||
"engines": {
|
||||
"node": "^14 || ^16 || >=18"
|
||||
},
|
||||
|
@ -392,12 +395,12 @@
|
|||
}
|
||||
},
|
||||
"node_modules/cssnano": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cssnano/-/cssnano-6.0.3.tgz",
|
||||
"integrity": "sha512-MRq4CIj8pnyZpcI2qs6wswoYoDD1t0aL28n+41c1Ukcpm56m1h6mCexIHBGjfZfnTqtGSSCP4/fB1ovxgjBOiw==",
|
||||
"version": "6.1.2",
|
||||
"resolved": "https://registry.npmjs.org/cssnano/-/cssnano-6.1.2.tgz",
|
||||
"integrity": "sha512-rYk5UeX7VAM/u0lNqewCdasdtPK81CgX8wJFLEIXHbV2oldWRgJAsZrdhRXkV1NJzA2g850KiFm9mMU2HxNxMA==",
|
||||
"dependencies": {
|
||||
"cssnano-preset-default": "^6.0.3",
|
||||
"lilconfig": "^3.0.0"
|
||||
"cssnano-preset-default": "^6.1.2",
|
||||
"lilconfig": "^3.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14 || ^16 || >=18.0"
|
||||
|
@ -411,39 +414,40 @@
|
|||
}
|
||||
},
|
||||
"node_modules/cssnano-preset-default": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-6.0.3.tgz",
|
||||
"integrity": "sha512-4y3H370aZCkT9Ev8P4SO4bZbt+AExeKhh8wTbms/X7OLDo5E7AYUUy6YPxa/uF5Grf+AJwNcCnxKhZynJ6luBA==",
|
||||
"version": "6.1.2",
|
||||
"resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-6.1.2.tgz",
|
||||
"integrity": "sha512-1C0C+eNaeN8OcHQa193aRgYexyJtU8XwbdieEjClw+J9d94E41LwT6ivKH0WT+fYwYWB0Zp3I3IZ7tI/BbUbrg==",
|
||||
"dependencies": {
|
||||
"css-declaration-sorter": "^7.1.1",
|
||||
"cssnano-utils": "^4.0.1",
|
||||
"browserslist": "^4.23.0",
|
||||
"css-declaration-sorter": "^7.2.0",
|
||||
"cssnano-utils": "^4.0.2",
|
||||
"postcss-calc": "^9.0.1",
|
||||
"postcss-colormin": "^6.0.2",
|
||||
"postcss-convert-values": "^6.0.2",
|
||||
"postcss-discard-comments": "^6.0.1",
|
||||
"postcss-discard-duplicates": "^6.0.1",
|
||||
"postcss-discard-empty": "^6.0.1",
|
||||
"postcss-discard-overridden": "^6.0.1",
|
||||
"postcss-merge-longhand": "^6.0.2",
|
||||
"postcss-merge-rules": "^6.0.3",
|
||||
"postcss-minify-font-values": "^6.0.1",
|
||||
"postcss-minify-gradients": "^6.0.1",
|
||||
"postcss-minify-params": "^6.0.2",
|
||||
"postcss-minify-selectors": "^6.0.2",
|
||||
"postcss-normalize-charset": "^6.0.1",
|
||||
"postcss-normalize-display-values": "^6.0.1",
|
||||
"postcss-normalize-positions": "^6.0.1",
|
||||
"postcss-normalize-repeat-style": "^6.0.1",
|
||||
"postcss-normalize-string": "^6.0.1",
|
||||
"postcss-normalize-timing-functions": "^6.0.1",
|
||||
"postcss-normalize-unicode": "^6.0.2",
|
||||
"postcss-normalize-url": "^6.0.1",
|
||||
"postcss-normalize-whitespace": "^6.0.1",
|
||||
"postcss-ordered-values": "^6.0.1",
|
||||
"postcss-reduce-initial": "^6.0.2",
|
||||
"postcss-reduce-transforms": "^6.0.1",
|
||||
"postcss-svgo": "^6.0.2",
|
||||
"postcss-unique-selectors": "^6.0.2"
|
||||
"postcss-colormin": "^6.1.0",
|
||||
"postcss-convert-values": "^6.1.0",
|
||||
"postcss-discard-comments": "^6.0.2",
|
||||
"postcss-discard-duplicates": "^6.0.3",
|
||||
"postcss-discard-empty": "^6.0.3",
|
||||
"postcss-discard-overridden": "^6.0.2",
|
||||
"postcss-merge-longhand": "^6.0.5",
|
||||
"postcss-merge-rules": "^6.1.1",
|
||||
"postcss-minify-font-values": "^6.1.0",
|
||||
"postcss-minify-gradients": "^6.0.3",
|
||||
"postcss-minify-params": "^6.1.0",
|
||||
"postcss-minify-selectors": "^6.0.4",
|
||||
"postcss-normalize-charset": "^6.0.2",
|
||||
"postcss-normalize-display-values": "^6.0.2",
|
||||
"postcss-normalize-positions": "^6.0.2",
|
||||
"postcss-normalize-repeat-style": "^6.0.2",
|
||||
"postcss-normalize-string": "^6.0.2",
|
||||
"postcss-normalize-timing-functions": "^6.0.2",
|
||||
"postcss-normalize-unicode": "^6.1.0",
|
||||
"postcss-normalize-url": "^6.0.2",
|
||||
"postcss-normalize-whitespace": "^6.0.2",
|
||||
"postcss-ordered-values": "^6.0.2",
|
||||
"postcss-reduce-initial": "^6.1.0",
|
||||
"postcss-reduce-transforms": "^6.0.2",
|
||||
"postcss-svgo": "^6.0.3",
|
||||
"postcss-unique-selectors": "^6.0.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14 || ^16 || >=18.0"
|
||||
|
@ -453,9 +457,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/cssnano-utils": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-4.0.1.tgz",
|
||||
"integrity": "sha512-6qQuYDqsGoiXssZ3zct6dcMxiqfT6epy7x4R0TQJadd4LWO3sPR6JH6ZByOvVLoZ6EdwPGgd7+DR1EmX3tiXQQ==",
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-4.0.2.tgz",
|
||||
"integrity": "sha512-ZR1jHg+wZ8o4c3zqf1SIUSTIvm/9mU343FMR6Obe/unskbvpGhZOo1J6d/r8D1pzkRQYuwbcH3hToOuoA2G7oQ==",
|
||||
"engines": {
|
||||
"node": "^14 || ^16 || >=18.0"
|
||||
},
|
||||
|
@ -545,9 +549,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.4.643",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.643.tgz",
|
||||
"integrity": "sha512-QHscvvS7gt155PtoRC0dR2ilhL8E9LHhfTQEq1uD5AL0524rBLAwpAREFH06f87/e45B9XkR6Ki5dbhbCsVEIg=="
|
||||
"version": "1.4.722",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.722.tgz",
|
||||
"integrity": "sha512-5nLE0TWFFpZ80Crhtp4pIp8LXCztjYX41yUcV6b+bKR2PqzjskTMOOlBi1VjBHlvHwS+4gar7kNKOrsbsewEZQ=="
|
||||
},
|
||||
"node_modules/entities": {
|
||||
"version": "4.5.0",
|
||||
|
@ -561,9 +565,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/escalade": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
|
||||
"integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz",
|
||||
"integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
|
@ -581,11 +585,14 @@
|
|||
}
|
||||
},
|
||||
"node_modules/lilconfig": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.0.0.tgz",
|
||||
"integrity": "sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g==",
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.1.tgz",
|
||||
"integrity": "sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antonk52"
|
||||
}
|
||||
},
|
||||
"node_modules/lodash.memoize": {
|
||||
|
@ -650,9 +657,9 @@
|
|||
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.4.33",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz",
|
||||
"integrity": "sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==",
|
||||
"version": "8.4.38",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz",
|
||||
"integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
|
@ -670,7 +677,7 @@
|
|||
"dependencies": {
|
||||
"nanoid": "^3.3.7",
|
||||
"picocolors": "^1.0.0",
|
||||
"source-map-js": "^1.0.2"
|
||||
"source-map-js": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || >=14"
|
||||
|
@ -692,13 +699,13 @@
|
|||
}
|
||||
},
|
||||
"node_modules/postcss-colormin": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-6.0.2.tgz",
|
||||
"integrity": "sha512-TXKOxs9LWcdYo5cgmcSHPkyrLAh86hX1ijmyy6J8SbOhyv6ua053M3ZAM/0j44UsnQNIWdl8gb5L7xX2htKeLw==",
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-6.1.0.tgz",
|
||||
"integrity": "sha512-x9yX7DOxeMAR+BgGVnNSAxmAj98NX/YxEMNFP+SDCEeNLb2r3i6Hh1ksMsnW8Ub5SLCpbescQqn9YEbE9554Sw==",
|
||||
"dependencies": {
|
||||
"browserslist": "^4.22.2",
|
||||
"browserslist": "^4.23.0",
|
||||
"caniuse-api": "^3.0.0",
|
||||
"colord": "^2.9.1",
|
||||
"colord": "^2.9.3",
|
||||
"postcss-value-parser": "^4.2.0"
|
||||
},
|
||||
"engines": {
|
||||
|
@ -709,11 +716,11 @@
|
|||
}
|
||||
},
|
||||
"node_modules/postcss-convert-values": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-6.0.2.tgz",
|
||||
"integrity": "sha512-aeBmaTnGQ+NUSVQT8aY0sKyAD/BaLJenEKZ03YK0JnDE1w1Rr8XShoxdal2V2H26xTJKr3v5haByOhJuyT4UYw==",
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-6.1.0.tgz",
|
||||
"integrity": "sha512-zx8IwP/ts9WvUM6NkVSkiU902QZL1bwPhaVaLynPtCsOTqp+ZKbNi+s6XJg3rfqpKGA/oc7Oxk5t8pOQJcwl/w==",
|
||||
"dependencies": {
|
||||
"browserslist": "^4.22.2",
|
||||
"browserslist": "^4.23.0",
|
||||
"postcss-value-parser": "^4.2.0"
|
||||
},
|
||||
"engines": {
|
||||
|
@ -724,9 +731,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/postcss-discard-comments": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-6.0.1.tgz",
|
||||
"integrity": "sha512-f1KYNPtqYLUeZGCHQPKzzFtsHaRuECe6jLakf/RjSRqvF5XHLZnM2+fXLhb8Qh/HBFHs3M4cSLb1k3B899RYIg==",
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-6.0.2.tgz",
|
||||
"integrity": "sha512-65w/uIqhSBBfQmYnG92FO1mWZjJ4GL5b8atm5Yw2UgrwD7HiNiSSNwJor1eCFGzUgYnN/iIknhNRVqjrrpuglw==",
|
||||
"engines": {
|
||||
"node": "^14 || ^16 || >=18.0"
|
||||
},
|
||||
|
@ -735,9 +742,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/postcss-discard-duplicates": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-6.0.1.tgz",
|
||||
"integrity": "sha512-1hvUs76HLYR8zkScbwyJ8oJEugfPV+WchpnA+26fpJ7Smzs51CzGBHC32RS03psuX/2l0l0UKh2StzNxOrKCYg==",
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-6.0.3.tgz",
|
||||
"integrity": "sha512-+JA0DCvc5XvFAxwx6f/e68gQu/7Z9ud584VLmcgto28eB8FqSFZwtrLwB5Kcp70eIoWP/HXqz4wpo8rD8gpsTw==",
|
||||
"engines": {
|
||||
"node": "^14 || ^16 || >=18.0"
|
||||
},
|
||||
|
@ -746,9 +753,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/postcss-discard-empty": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-6.0.1.tgz",
|
||||
"integrity": "sha512-yitcmKwmVWtNsrrRqGJ7/C0YRy53i0mjexBDQ9zYxDwTWVBgbU4+C9jIZLmQlTDT9zhml+u0OMFJh8+31krmOg==",
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-6.0.3.tgz",
|
||||
"integrity": "sha512-znyno9cHKQsK6PtxL5D19Fj9uwSzC2mB74cpT66fhgOadEUPyXFkbgwm5tvc3bt3NAy8ltE5MrghxovZRVnOjQ==",
|
||||
"engines": {
|
||||
"node": "^14 || ^16 || >=18.0"
|
||||
},
|
||||
|
@ -757,9 +764,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/postcss-discard-overridden": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-6.0.1.tgz",
|
||||
"integrity": "sha512-qs0ehZMMZpSESbRkw1+inkf51kak6OOzNRaoLd/U7Fatp0aN2HQ1rxGOrJvYcRAN9VpX8kUF13R2ofn8OlvFVA==",
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-6.0.2.tgz",
|
||||
"integrity": "sha512-j87xzI4LUggC5zND7KdjsI25APtyMuynXZSujByMaav2roV6OZX+8AaCUcZSWqckZpjAjRyFDdpqybgjFO0HJQ==",
|
||||
"engines": {
|
||||
"node": "^14 || ^16 || >=18.0"
|
||||
},
|
||||
|
@ -768,12 +775,12 @@
|
|||
}
|
||||
},
|
||||
"node_modules/postcss-merge-longhand": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-6.0.2.tgz",
|
||||
"integrity": "sha512-+yfVB7gEM8SrCo9w2lCApKIEzrTKl5yS1F4yGhV3kSim6JzbfLGJyhR1B6X+6vOT0U33Mgx7iv4X9MVWuaSAfw==",
|
||||
"version": "6.0.5",
|
||||
"resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-6.0.5.tgz",
|
||||
"integrity": "sha512-5LOiordeTfi64QhICp07nzzuTDjNSO8g5Ksdibt44d+uvIIAE1oZdRn8y/W5ZtYgRH/lnLDlvi9F8btZcVzu3w==",
|
||||
"dependencies": {
|
||||
"postcss-value-parser": "^4.2.0",
|
||||
"stylehacks": "^6.0.2"
|
||||
"stylehacks": "^6.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14 || ^16 || >=18.0"
|
||||
|
@ -783,14 +790,14 @@
|
|||
}
|
||||
},
|
||||
"node_modules/postcss-merge-rules": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-6.0.3.tgz",
|
||||
"integrity": "sha512-yfkDqSHGohy8sGYIJwBmIGDv4K4/WrJPX355XrxQb/CSsT4Kc/RxDi6akqn5s9bap85AWgv21ArcUWwWdGNSHA==",
|
||||
"version": "6.1.1",
|
||||
"resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-6.1.1.tgz",
|
||||
"integrity": "sha512-KOdWF0gju31AQPZiD+2Ar9Qjowz1LTChSjFFbS+e2sFgc4uHOp3ZvVX4sNeTlk0w2O31ecFGgrFzhO0RSWbWwQ==",
|
||||
"dependencies": {
|
||||
"browserslist": "^4.22.2",
|
||||
"browserslist": "^4.23.0",
|
||||
"caniuse-api": "^3.0.0",
|
||||
"cssnano-utils": "^4.0.1",
|
||||
"postcss-selector-parser": "^6.0.15"
|
||||
"cssnano-utils": "^4.0.2",
|
||||
"postcss-selector-parser": "^6.0.16"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14 || ^16 || >=18.0"
|
||||
|
@ -800,9 +807,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/postcss-minify-font-values": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-6.0.1.tgz",
|
||||
"integrity": "sha512-tIwmF1zUPoN6xOtA/2FgVk1ZKrLcCvE0dpZLtzyyte0j9zUeB8RTbCqrHZGjJlxOvNWKMYtunLrrl7HPOiR46w==",
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-6.1.0.tgz",
|
||||
"integrity": "sha512-gklfI/n+9rTh8nYaSJXlCo3nOKqMNkxuGpTn/Qm0gstL3ywTr9/WRKznE+oy6fvfolH6dF+QM4nCo8yPLdvGJg==",
|
||||
"dependencies": {
|
||||
"postcss-value-parser": "^4.2.0"
|
||||
},
|
||||
|
@ -814,12 +821,12 @@
|
|||
}
|
||||
},
|
||||
"node_modules/postcss-minify-gradients": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-6.0.1.tgz",
|
||||
"integrity": "sha512-M1RJWVjd6IOLPl1hYiOd5HQHgpp6cvJVLrieQYS9y07Yo8itAr6jaekzJphaJFR0tcg4kRewCk3kna9uHBxn/w==",
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-6.0.3.tgz",
|
||||
"integrity": "sha512-4KXAHrYlzF0Rr7uc4VrfwDJ2ajrtNEpNEuLxFgwkhFZ56/7gaE4Nr49nLsQDZyUe+ds+kEhf+YAUolJiYXF8+Q==",
|
||||
"dependencies": {
|
||||
"colord": "^2.9.1",
|
||||
"cssnano-utils": "^4.0.1",
|
||||
"colord": "^2.9.3",
|
||||
"cssnano-utils": "^4.0.2",
|
||||
"postcss-value-parser": "^4.2.0"
|
||||
},
|
||||
"engines": {
|
||||
|
@ -830,12 +837,12 @@
|
|||
}
|
||||
},
|
||||
"node_modules/postcss-minify-params": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-6.0.2.tgz",
|
||||
"integrity": "sha512-zwQtbrPEBDj+ApELZ6QylLf2/c5zmASoOuA4DzolyVGdV38iR2I5QRMsZcHkcdkZzxpN8RS4cN7LPskOkTwTZw==",
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-6.1.0.tgz",
|
||||
"integrity": "sha512-bmSKnDtyyE8ujHQK0RQJDIKhQ20Jq1LYiez54WiaOoBtcSuflfK3Nm596LvbtlFcpipMjgClQGyGr7GAs+H1uA==",
|
||||
"dependencies": {
|
||||
"browserslist": "^4.22.2",
|
||||
"cssnano-utils": "^4.0.1",
|
||||
"browserslist": "^4.23.0",
|
||||
"cssnano-utils": "^4.0.2",
|
||||
"postcss-value-parser": "^4.2.0"
|
||||
},
|
||||
"engines": {
|
||||
|
@ -846,11 +853,11 @@
|
|||
}
|
||||
},
|
||||
"node_modules/postcss-minify-selectors": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-6.0.2.tgz",
|
||||
"integrity": "sha512-0b+m+w7OAvZejPQdN2GjsXLv5o0jqYHX3aoV0e7RBKPCsB7TYG5KKWBFhGnB/iP3213Ts8c5H4wLPLMm7z28Sg==",
|
||||
"version": "6.0.4",
|
||||
"resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-6.0.4.tgz",
|
||||
"integrity": "sha512-L8dZSwNLgK7pjTto9PzWRoMbnLq5vsZSTu8+j1P/2GB8qdtGQfn+K1uSvFgYvgh83cbyxT5m43ZZhUMTJDSClQ==",
|
||||
"dependencies": {
|
||||
"postcss-selector-parser": "^6.0.15"
|
||||
"postcss-selector-parser": "^6.0.16"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14 || ^16 || >=18.0"
|
||||
|
@ -860,9 +867,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/postcss-normalize-charset": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-6.0.1.tgz",
|
||||
"integrity": "sha512-aW5LbMNRZ+oDV57PF9K+WI1Z8MPnF+A8qbajg/T8PP126YrGX1f9IQx21GI2OlGz7XFJi/fNi0GTbY948XJtXg==",
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-6.0.2.tgz",
|
||||
"integrity": "sha512-a8N9czmdnrjPHa3DeFlwqst5eaL5W8jYu3EBbTTkI5FHkfMhFZh1EGbku6jhHhIzTA6tquI2P42NtZ59M/H/kQ==",
|
||||
"engines": {
|
||||
"node": "^14 || ^16 || >=18.0"
|
||||
},
|
||||
|
@ -871,9 +878,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/postcss-normalize-display-values": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-6.0.1.tgz",
|
||||
"integrity": "sha512-mc3vxp2bEuCb4LgCcmG1y6lKJu1Co8T+rKHrcbShJwUmKJiEl761qb/QQCfFwlrvSeET3jksolCR/RZuMURudw==",
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-6.0.2.tgz",
|
||||
"integrity": "sha512-8H04Mxsb82ON/aAkPeq8kcBbAtI5Q2a64X/mnRRfPXBq7XeogoQvReqxEfc0B4WPq1KimjezNC8flUtC3Qz6jg==",
|
||||
"dependencies": {
|
||||
"postcss-value-parser": "^4.2.0"
|
||||
},
|
||||
|
@ -885,9 +892,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/postcss-normalize-positions": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-6.0.1.tgz",
|
||||
"integrity": "sha512-HRsq8u/0unKNvm0cvwxcOUEcakFXqZ41fv3FOdPn916XFUrympjr+03oaLkuZENz3HE9RrQE9yU0Xv43ThWjQg==",
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-6.0.2.tgz",
|
||||
"integrity": "sha512-/JFzI441OAB9O7VnLA+RtSNZvQ0NCFZDOtp6QPFo1iIyawyXg0YI3CYM9HBy1WvwCRHnPep/BvI1+dGPKoXx/Q==",
|
||||
"dependencies": {
|
||||
"postcss-value-parser": "^4.2.0"
|
||||
},
|
||||
|
@ -899,9 +906,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/postcss-normalize-repeat-style": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-6.0.1.tgz",
|
||||
"integrity": "sha512-Gbb2nmCy6tTiA7Sh2MBs3fj9W8swonk6lw+dFFeQT68B0Pzwp1kvisJQkdV6rbbMSd9brMlS8I8ts52tAGWmGQ==",
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-6.0.2.tgz",
|
||||
"integrity": "sha512-YdCgsfHkJ2jEXwR4RR3Tm/iOxSfdRt7jplS6XRh9Js9PyCR/aka/FCb6TuHT2U8gQubbm/mPmF6L7FY9d79VwQ==",
|
||||
"dependencies": {
|
||||
"postcss-value-parser": "^4.2.0"
|
||||
},
|
||||
|
@ -913,9 +920,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/postcss-normalize-string": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-6.0.1.tgz",
|
||||
"integrity": "sha512-5Fhx/+xzALJD9EI26Aq23hXwmv97Zfy2VFrt5PLT8lAhnBIZvmaT5pQk+NuJ/GWj/QWaKSKbnoKDGLbV6qnhXg==",
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-6.0.2.tgz",
|
||||
"integrity": "sha512-vQZIivlxlfqqMp4L9PZsFE4YUkWniziKjQWUtsxUiVsSSPelQydwS8Wwcuw0+83ZjPWNTl02oxlIvXsmmG+CiQ==",
|
||||
"dependencies": {
|
||||
"postcss-value-parser": "^4.2.0"
|
||||
},
|
||||
|
@ -927,9 +934,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/postcss-normalize-timing-functions": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-6.0.1.tgz",
|
||||
"integrity": "sha512-4zcczzHqmCU7L5dqTB9rzeqPWRMc0K2HoR+Bfl+FSMbqGBUcP5LRfgcH4BdRtLuzVQK1/FHdFoGT3F7rkEnY+g==",
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-6.0.2.tgz",
|
||||
"integrity": "sha512-a+YrtMox4TBtId/AEwbA03VcJgtyW4dGBizPl7e88cTFULYsprgHWTbfyjSLyHeBcK/Q9JhXkt2ZXiwaVHoMzA==",
|
||||
"dependencies": {
|
||||
"postcss-value-parser": "^4.2.0"
|
||||
},
|
||||
|
@ -941,11 +948,11 @@
|
|||
}
|
||||
},
|
||||
"node_modules/postcss-normalize-unicode": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-6.0.2.tgz",
|
||||
"integrity": "sha512-Ff2VdAYCTGyMUwpevTZPZ4w0+mPjbZzLLyoLh/RMpqUqeQKZ+xMm31hkxBavDcGKcxm6ACzGk0nBfZ8LZkStKA==",
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-6.1.0.tgz",
|
||||
"integrity": "sha512-QVC5TQHsVj33otj8/JD869Ndr5Xcc/+fwRh4HAsFsAeygQQXm+0PySrKbr/8tkDKzW+EVT3QkqZMfFrGiossDg==",
|
||||
"dependencies": {
|
||||
"browserslist": "^4.22.2",
|
||||
"browserslist": "^4.23.0",
|
||||
"postcss-value-parser": "^4.2.0"
|
||||
},
|
||||
"engines": {
|
||||
|
@ -956,9 +963,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/postcss-normalize-url": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-6.0.1.tgz",
|
||||
"integrity": "sha512-jEXL15tXSvbjm0yzUV7FBiEXwhIa9H88JOXDGQzmcWoB4mSjZIsmtto066s2iW9FYuIrIF4k04HA2BKAOpbsaQ==",
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-6.0.2.tgz",
|
||||
"integrity": "sha512-kVNcWhCeKAzZ8B4pv/DnrU1wNh458zBNp8dh4y5hhxih5RZQ12QWMuQrDgPRw3LRl8mN9vOVfHl7uhvHYMoXsQ==",
|
||||
"dependencies": {
|
||||
"postcss-value-parser": "^4.2.0"
|
||||
},
|
||||
|
@ -970,9 +977,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/postcss-normalize-whitespace": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-6.0.1.tgz",
|
||||
"integrity": "sha512-76i3NpWf6bB8UHlVuLRxG4zW2YykF9CTEcq/9LGAiz2qBuX5cBStadkk0jSkg9a9TCIXbMQz7yzrygKoCW9JuA==",
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-6.0.2.tgz",
|
||||
"integrity": "sha512-sXZ2Nj1icbJOKmdjXVT9pnyHQKiSAyuNQHSgRCUgThn2388Y9cGVDR+E9J9iAYbSbLHI+UUwLVl1Wzco/zgv0Q==",
|
||||
"dependencies": {
|
||||
"postcss-value-parser": "^4.2.0"
|
||||
},
|
||||
|
@ -984,11 +991,11 @@
|
|||
}
|
||||
},
|
||||
"node_modules/postcss-ordered-values": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-6.0.1.tgz",
|
||||
"integrity": "sha512-XXbb1O/MW9HdEhnBxitZpPFbIvDgbo9NK4c/5bOfiKpnIGZDoL2xd7/e6jW5DYLsWxBbs+1nZEnVgnjnlFViaA==",
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-6.0.2.tgz",
|
||||
"integrity": "sha512-VRZSOB+JU32RsEAQrO94QPkClGPKJEL/Z9PCBImXMhIeK5KAYo6slP/hBYlLgrCjFxyqvn5VC81tycFEDBLG1Q==",
|
||||
"dependencies": {
|
||||
"cssnano-utils": "^4.0.1",
|
||||
"cssnano-utils": "^4.0.2",
|
||||
"postcss-value-parser": "^4.2.0"
|
||||
},
|
||||
"engines": {
|
||||
|
@ -999,11 +1006,11 @@
|
|||
}
|
||||
},
|
||||
"node_modules/postcss-reduce-initial": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-6.0.2.tgz",
|
||||
"integrity": "sha512-YGKalhNlCLcjcLvjU5nF8FyeCTkCO5UtvJEt0hrPZVCTtRLSOH4z00T1UntQPj4dUmIYZgMj8qK77JbSX95hSw==",
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-6.1.0.tgz",
|
||||
"integrity": "sha512-RarLgBK/CrL1qZags04oKbVbrrVK2wcxhvta3GCxrZO4zveibqbRPmm2VI8sSgCXwoUHEliRSbOfpR0b/VIoiw==",
|
||||
"dependencies": {
|
||||
"browserslist": "^4.22.2",
|
||||
"browserslist": "^4.23.0",
|
||||
"caniuse-api": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
|
@ -1014,9 +1021,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/postcss-reduce-transforms": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-6.0.1.tgz",
|
||||
"integrity": "sha512-fUbV81OkUe75JM+VYO1gr/IoA2b/dRiH6HvMwhrIBSUrxq3jNZQZitSnugcTLDi1KkQh1eR/zi+iyxviUNBkcQ==",
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-6.0.2.tgz",
|
||||
"integrity": "sha512-sB+Ya++3Xj1WaT9+5LOOdirAxP7dJZms3GRcYheSPi1PiTMigsxHAdkrbItHxwYHr4kt1zL7mmcHstgMYT+aiA==",
|
||||
"dependencies": {
|
||||
"postcss-value-parser": "^4.2.0"
|
||||
},
|
||||
|
@ -1028,9 +1035,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/postcss-selector-parser": {
|
||||
"version": "6.0.15",
|
||||
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.15.tgz",
|
||||
"integrity": "sha512-rEYkQOMUCEMhsKbK66tbEU9QVIxbhN18YiniAwA7XQYTVBqrBy+P2p5JcdqsHgKM2zWylp8d7J6eszocfds5Sw==",
|
||||
"version": "6.0.16",
|
||||
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz",
|
||||
"integrity": "sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==",
|
||||
"dependencies": {
|
||||
"cssesc": "^3.0.0",
|
||||
"util-deprecate": "^1.0.2"
|
||||
|
@ -1040,9 +1047,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/postcss-svgo": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-6.0.2.tgz",
|
||||
"integrity": "sha512-IH5R9SjkTkh0kfFOQDImyy1+mTCb+E830+9SV1O+AaDcoHTvfsvt6WwJeo7KwcHbFnevZVCsXhDmjFiGVuwqFQ==",
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-6.0.3.tgz",
|
||||
"integrity": "sha512-dlrahRmxP22bX6iKEjOM+c8/1p+81asjKT+V5lrgOH944ryx/OHpclnIbGsKVd3uWOXFLYJwCVf0eEkJGvO96g==",
|
||||
"dependencies": {
|
||||
"postcss-value-parser": "^4.2.0",
|
||||
"svgo": "^3.2.0"
|
||||
|
@ -1055,11 +1062,11 @@
|
|||
}
|
||||
},
|
||||
"node_modules/postcss-unique-selectors": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-6.0.2.tgz",
|
||||
"integrity": "sha512-8IZGQ94nechdG7Y9Sh9FlIY2b4uS8/k8kdKRX040XHsS3B6d1HrJAkXrBSsSu4SuARruSsUjW3nlSw8BHkaAYQ==",
|
||||
"version": "6.0.4",
|
||||
"resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-6.0.4.tgz",
|
||||
"integrity": "sha512-K38OCaIrO8+PzpArzkLKB42dSARtC2tmG6PvD4b1o1Q2E9Os8jzfWFfSy/rixsHwohtsDdFtAWGjFVFUdwYaMg==",
|
||||
"dependencies": {
|
||||
"postcss-selector-parser": "^6.0.15"
|
||||
"postcss-selector-parser": "^6.0.16"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14 || ^16 || >=18.0"
|
||||
|
@ -1074,20 +1081,20 @@
|
|||
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
|
||||
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
|
||||
"integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/stylehacks": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-6.0.2.tgz",
|
||||
"integrity": "sha512-00zvJGnCu64EpMjX8b5iCZ3us2Ptyw8+toEkb92VdmkEaRaSGBNKAoK6aWZckhXxmQP8zWiTaFaiMGIU8Ve8sg==",
|
||||
"version": "6.1.1",
|
||||
"resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-6.1.1.tgz",
|
||||
"integrity": "sha512-gSTTEQ670cJNoaeIp9KX6lZmm8LJ3jPB5yJmX8Zq/wQxOsAFXV3qjWzHas3YYk1qesuVIyYWWUpZ0vSE/dTSGg==",
|
||||
"dependencies": {
|
||||
"browserslist": "^4.22.2",
|
||||
"postcss-selector-parser": "^6.0.15"
|
||||
"browserslist": "^4.23.0",
|
||||
"postcss-selector-parser": "^6.0.16"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14 || ^16 || >=18.0"
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"@swc/core": "^1.3.69",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"cssnano": "^6.0.1",
|
||||
"postcss": "^8.4.26"
|
||||
"@swc/core": "^1.4.11",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"cssnano": "^6.1.2",
|
||||
"postcss": "^8.4.38"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -168,7 +168,7 @@ $perms = $perms->checkMany([
|
|||
]);
|
||||
|
||||
Template::render('forum.forum', [
|
||||
'forum_breadcrumbs' => $forumCategories->getCategoryAncestry($categoryInfo),
|
||||
'forum_breadcrumbs' => iterator_to_array($forumCategories->getCategoryAncestry($categoryInfo)),
|
||||
'global_accent_colour' => $forumCategories->getCategoryColour($categoryInfo),
|
||||
'forum_info' => $categoryInfo,
|
||||
'forum_children' => $children,
|
||||
|
|
|
@ -296,7 +296,7 @@ try {
|
|||
}
|
||||
|
||||
Template::render('forum.posting', [
|
||||
'posting_breadcrumbs' => $forumCategories->getCategoryAncestry($categoryInfo),
|
||||
'posting_breadcrumbs' => iterator_to_array($forumCategories->getCategoryAncestry($categoryInfo)),
|
||||
'global_accent_colour' => $forumCategories->getCategoryColour($categoryInfo),
|
||||
'posting_user' => $currentUser,
|
||||
'posting_user_colour' => $usersCtx->getUserColour($currentUser),
|
||||
|
|
|
@ -291,7 +291,11 @@ $postInfos = $forumPosts->getPosts(
|
|||
if(empty($postInfos))
|
||||
Template::throwError(404);
|
||||
|
||||
$originalPostInfo = $forumPosts->getPost(topicInfo: $topicInfo);
|
||||
try {
|
||||
$originalPostInfo = $forumPosts->getPost(topicInfo: $topicInfo);
|
||||
} catch(RuntimeException $ex) {
|
||||
Template::throwError(404);
|
||||
}
|
||||
|
||||
$posts = [];
|
||||
|
||||
|
@ -326,7 +330,7 @@ $perms = $perms->checkMany([
|
|||
]);
|
||||
|
||||
Template::render('forum.topic', [
|
||||
'topic_breadcrumbs' => $forumCategories->getCategoryAncestry($topicInfo),
|
||||
'topic_breadcrumbs' => iterator_to_array($forumCategories->getCategoryAncestry($topicInfo)),
|
||||
'global_accent_colour' => $forumCategories->getCategoryColour($topicInfo),
|
||||
'topic_info' => $topicInfo,
|
||||
'category_info' => $categoryInfo,
|
||||
|
|
|
@ -18,7 +18,7 @@ else
|
|||
try {
|
||||
$isNew = false;
|
||||
$emoteInfo = $emotes->getEmote($emoteId);
|
||||
$emoteStrings = $emotes->getEmoteStrings($emoteInfo);
|
||||
$emoteStrings = iterator_to_array($emotes->getEmoteStrings($emoteInfo));
|
||||
} catch(RuntimeException $ex) {
|
||||
Template::throwError(404);
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ $pagination = new Pagination($auditLog->countLogs(), 50);
|
|||
if(!$pagination->hasValidOffset())
|
||||
Template::throwError(404);
|
||||
|
||||
$logs = $auditLog->getLogs(pagination: $pagination);
|
||||
$logs = iterator_to_array($auditLog->getLogs(pagination: $pagination));
|
||||
$userInfos = [];
|
||||
$userColours = [];
|
||||
|
||||
|
|
|
@ -106,7 +106,7 @@ if(CSRF::validateRequest() && $canEdit) {
|
|||
}
|
||||
|
||||
$existingRoles = [];
|
||||
foreach($roles->getRoles(userInfo: $userInfo) as $roleInfo)
|
||||
foreach(iterator_to_array($roles->getRoles(userInfo: $userInfo)) as $roleInfo)
|
||||
$existingRoles[$roleInfo->getId()] = $roleInfo;
|
||||
|
||||
$removeRoles = [];
|
||||
|
@ -226,7 +226,7 @@ if(CSRF::validateRequest() && $canEdit) {
|
|||
return;
|
||||
}
|
||||
|
||||
$rolesAll = $roles->getRoles();
|
||||
$rolesAll = iterator_to_array($roles->getRoles());
|
||||
$userRoleIds = $users->hasRoles($userInfo, $rolesAll);
|
||||
|
||||
Template::render('manage.users.user', [
|
||||
|
|
|
@ -71,15 +71,16 @@ $notices = [];
|
|||
$userRank = $usersCtx->getUserRank($userInfo);
|
||||
$viewerRank = $usersCtx->getUserRank($viewerInfo);
|
||||
|
||||
$viewerPerms = $authInfo->getPerms('user');
|
||||
$viewerPermsGlobal = $authInfo->getPerms('global');
|
||||
$viewerPermsUser = $authInfo->getPerms('user');
|
||||
|
||||
$activeBanInfo = $usersCtx->tryGetActiveBan($userInfo);
|
||||
$isBanned = $activeBanInfo !== null;
|
||||
$profileFields = $msz->getProfileFields();
|
||||
$viewingOwnProfile = (string)$viewerId === $userInfo->getId();
|
||||
$canManageWarnings = $viewerPerms->check(Perm::U_WARNINGS_MANAGE);
|
||||
$canManageWarnings = $viewerPermsUser->check(Perm::U_WARNINGS_MANAGE);
|
||||
$canEdit = !$viewingAsGuest && ((!$isBanned && $viewingOwnProfile) || $viewerInfo->isSuperUser() || (
|
||||
$viewerPerms->check(Perm::U_USERS_MANAGE) && ($viewingOwnProfile || $viewerRank > $userRank)
|
||||
$viewerPermsUser->check(Perm::U_USERS_MANAGE) && ($viewingOwnProfile || $viewerRank > $userRank)
|
||||
));
|
||||
$avatarInfo = new UserAvatarAsset($userInfo);
|
||||
$backgroundInfo = new UserBackgroundAsset($userInfo);
|
||||
|
@ -88,7 +89,7 @@ if($isEditing) {
|
|||
if(!$canEdit)
|
||||
Template::throwError(403);
|
||||
|
||||
$perms = $viewerPerms->checkMany([
|
||||
$perms = $viewerPermsUser->checkMany([
|
||||
'edit_profile' => Perm::U_PROFILE_EDIT,
|
||||
'edit_avatar' => Perm::U_AVATAR_CHANGE,
|
||||
'edit_background' => PERM::U_PROFILE_BACKGROUND_CHANGE,
|
||||
|
@ -112,7 +113,7 @@ if($isEditing) {
|
|||
if(!$perms->edit_profile) {
|
||||
$notices[] = 'You\'re not allowed to edit your profile';
|
||||
} else {
|
||||
$profileFieldInfos = $profileFields->getFields();
|
||||
$profileFieldInfos = iterator_to_array($profileFields->getFields());
|
||||
$profileFieldsSetInfos = [];
|
||||
$profileFieldsSetValues = [];
|
||||
$profileFieldsRemove = [];
|
||||
|
@ -298,7 +299,7 @@ $profileStats->forum_post_count = $forumCtx->countTotalUserPosts($userInfo);
|
|||
$profileStats->comments_count = $msz->getComments()->countPosts(userInfo: $userInfo, deleted: false);
|
||||
|
||||
if(!$viewingAsGuest) {
|
||||
Template::set('profile_warnings', $usersCtx->getWarnings()->getWarningsWithDefaultBacklog($userInfo));
|
||||
Template::set('profile_warnings', iterator_to_array($usersCtx->getWarnings()->getWarningsWithDefaultBacklog($userInfo)));
|
||||
|
||||
if((!$isBanned || $canEdit)) {
|
||||
$unranked = $cfg->getValues([
|
||||
|
@ -322,9 +323,9 @@ if(!$viewingAsGuest) {
|
|||
);
|
||||
$activeTopicInfo = $activeTopicStats->success ? $forumTopics->getTopic(topicId: $activeTopicStats->topicId) : null;
|
||||
|
||||
$profileFieldValues = $profileFields->getFieldValues($userInfo);
|
||||
$profileFieldInfos = $profileFieldInfos ?? $profileFields->getFields(fieldValueInfos: $isEditing ? null : $profileFieldValues);
|
||||
$profileFieldFormats = $profileFields->getFieldFormats(fieldValueInfos: $profileFieldValues);
|
||||
$profileFieldValues = iterator_to_array($profileFields->getFieldValues($userInfo));
|
||||
$profileFieldInfos = $profileFieldInfos ?? iterator_to_array($profileFields->getFields(fieldValueInfos: $isEditing ? null : $profileFieldValues));
|
||||
$profileFieldFormats = iterator_to_array($profileFields->getFieldFormats(fieldValueInfos: $profileFieldValues));
|
||||
|
||||
$profileFieldRawValues = [];
|
||||
$profileFieldLinkValues = [];
|
||||
|
@ -384,4 +385,5 @@ Template::render('profile.index', [
|
|||
'profile_ban_info' => $activeBanInfo,
|
||||
'profile_avatar_info' => $avatarInfo,
|
||||
'profile_background_info' => $backgroundInfo,
|
||||
'profile_can_send_messages' => $viewerPermsGlobal->check(Perm::G_MESSAGES_SEND),
|
||||
]);
|
||||
|
|
|
@ -116,7 +116,7 @@ if($isVerifiedRequest && !empty($_POST['current_password'])) {
|
|||
if($_SERVER['REQUEST_METHOD'] === 'POST' && $isVerifiedRequest)
|
||||
$userInfo = $users->getUser($userInfo->getId(), 'id');
|
||||
|
||||
$userRoles = $roles->getRoles(userInfo: $userInfo);
|
||||
$userRoles = iterator_to_array($roles->getRoles(userInfo: $userInfo));
|
||||
|
||||
Template::render('settings.account', [
|
||||
'errors' => $errors,
|
||||
|
|
|
@ -129,6 +129,7 @@ if(isset($_POST['action']) && is_string($_POST['action'])) {
|
|||
$tmpFiles[] = db_to_zip($archive, $userInfo, 'forum_topics_redirects', ['topic_id:s', 'user_id:s:n', 'topic_redir_url:s', 'topic_redir_created:t']);
|
||||
$tmpFiles[] = db_to_zip($archive, $userInfo, 'forum_topics_track', ['user_id:s', 'topic_id:s', 'forum_id:s', 'track_last_read:t']);
|
||||
$tmpFiles[] = db_to_zip($archive, $userInfo, 'login_attempts', ['user_id:s:n', 'attempt_success:b', 'attempt_ip:a', 'attempt_country:s', 'attempt_created:t', 'attempt_user_agent:s']);
|
||||
$tmpFiles[] = db_to_zip($archive, $userInfo, 'messages', ['msg_id:s', 'msg_owner_id:s', 'msg_author_id:s:n', 'msg_recipient_id:s:n', 'msg_reply_to:s:n', 'msg_title:s', 'msg_body:s', 'msg_parser:i', 'msg_created:t', 'msg_sent:t:n', 'msg_read:t:n', 'msg_deleted:t:n'], 'msg_owner_id');
|
||||
$tmpFiles[] = db_to_zip($archive, $userInfo, 'news_posts', ['post_id:s', 'category_id:s', 'user_id:s:n', 'comment_section_id:s:n', 'post_is_featured:b', 'post_title:s', 'post_text:s', 'post_scheduled:t', 'post_created:t', 'post_updated:t', 'post_deleted:t:n']);
|
||||
$tmpFiles[] = db_to_zip($archive, $userInfo, 'perms', ['user_id:s:n', 'role_id:s:n', 'forum_id:s:n', 'perms_category:s', 'perms_allow:i', 'perms_deny:i']);
|
||||
$tmpFiles[] = db_to_zip($archive, $userInfo, 'perms_calculated', ['user_id:s:n', 'forum_id:s:n', 'perms_category:s', 'perms_calculated:i']);
|
||||
|
|
|
@ -15,8 +15,8 @@ $auditLog = $msz->getAuditLog();
|
|||
$loginHistoryPagination = new Pagination($loginAttempts->countAttempts(userInfo: $currentUser), 5, 'hp');
|
||||
$accountLogPagination = new Pagination($auditLog->countLogs(userInfo: $currentUser), 10, 'ap');
|
||||
|
||||
$loginHistory = $loginAttempts->getAttempts(userInfo: $currentUser, pagination: $loginHistoryPagination);
|
||||
$auditLogs = $auditLog->getLogs(userInfo: $currentUser, pagination: $accountLogPagination);
|
||||
$loginHistory = iterator_to_array($loginAttempts->getAttempts(userInfo: $currentUser, pagination: $loginHistoryPagination));
|
||||
$auditLogs = iterator_to_array($auditLog->getLogs(userInfo: $currentUser, pagination: $accountLogPagination));
|
||||
|
||||
Template::render('settings.logs', [
|
||||
'login_history_list' => $loginHistory,
|
||||
|
|
|
@ -61,7 +61,7 @@ class AuditLog {
|
|||
UserInfo|string|null $userInfo = null,
|
||||
IPAddress|string|null $remoteAddr = null,
|
||||
?Pagination $pagination = null
|
||||
): array {
|
||||
): iterable {
|
||||
if($userInfo instanceof UserInfo)
|
||||
$userInfo = $userInfo->getId();
|
||||
if($remoteAddr instanceof IPAddress)
|
||||
|
@ -98,13 +98,8 @@ class AuditLog {
|
|||
}
|
||||
|
||||
$stmt->execute();
|
||||
$result = $stmt->getResult();
|
||||
$logs = [];
|
||||
|
||||
while($result->next())
|
||||
$logs[] = new AuditLogInfo($result);
|
||||
|
||||
return $logs;
|
||||
return $stmt->getResult()->getIterator(AuditLogInfo::fromResult(...));
|
||||
}
|
||||
|
||||
public function createLog(
|
||||
|
|
|
@ -7,20 +7,24 @@ use Index\Data\IDbResult;
|
|||
use Index\Net\IPAddress;
|
||||
|
||||
class AuditLogInfo {
|
||||
private ?string $userId;
|
||||
private string $action;
|
||||
private array $params;
|
||||
private int $created;
|
||||
private string $address;
|
||||
private string $country;
|
||||
public function __construct(
|
||||
private ?string $userId,
|
||||
private string $action,
|
||||
private array $params,
|
||||
private int $created,
|
||||
private string $address,
|
||||
private string $country,
|
||||
) {}
|
||||
|
||||
public function __construct(IDbResult $result) {
|
||||
$this->userId = $result->isNull(0) ? null : (string)$result->getInteger(0);
|
||||
$this->action = $result->getString(1);
|
||||
$this->params = json_decode($result->getString(2));
|
||||
$this->created = $result->getInteger(3);
|
||||
$this->address = $result->isNull(4) ? '::1' : $result->getString(4); // apparently this being NULL is possible?
|
||||
$this->country = $result->getString(5);
|
||||
public static function fromResult(IDbResult $result): AuditLogInfo {
|
||||
return new AuditLogInfo(
|
||||
userId: $result->getStringOrNull(0),
|
||||
action: $result->getString(1),
|
||||
params: json_decode($result->getString(2)),
|
||||
created: $result->getInteger(3),
|
||||
address: $result->isNull(4) ? '::1' : $result->getString(4), // apparently this being NULL is possible?
|
||||
country: $result->getString(5),
|
||||
);
|
||||
}
|
||||
|
||||
public function hasUserId(): bool {
|
||||
|
|
|
@ -7,22 +7,26 @@ use Index\Net\IPAddress;
|
|||
use Misuzu\ClientInfo;
|
||||
|
||||
class LoginAttemptInfo {
|
||||
private ?string $userId;
|
||||
private bool $success;
|
||||
private string $remoteAddr;
|
||||
private string $countryCode;
|
||||
private int $created;
|
||||
private string $userAgent;
|
||||
private string $clientInfo;
|
||||
public function __construct(
|
||||
private ?string $userId,
|
||||
private bool $success,
|
||||
private string $remoteAddr,
|
||||
private string $countryCode,
|
||||
private int $created,
|
||||
private string $userAgent,
|
||||
private string $clientInfo,
|
||||
) {}
|
||||
|
||||
public function __construct(IDbResult $result) {
|
||||
$this->userId = $result->isNull(0) ? null : (string)$result->getInteger(0);
|
||||
$this->success = $result->getInteger(1) !== 0;
|
||||
$this->remoteAddr = $result->getString(2);
|
||||
$this->countryCode = $result->getString(3);
|
||||
$this->created = $result->getInteger(4);
|
||||
$this->userAgent = $result->getString(5);
|
||||
$this->clientInfo = $result->getString(6);
|
||||
public static function fromResult(IDbResult $result): LoginAttemptInfo {
|
||||
return new LoginAttemptInfo(
|
||||
userId: $result->getStringOrNull(0),
|
||||
success: $result->getBoolean(1),
|
||||
remoteAddr: $result->getString(2),
|
||||
countryCode: $result->getString(3),
|
||||
created: $result->getInteger(4),
|
||||
userAgent: $result->getString(5),
|
||||
clientInfo: $result->getString(6),
|
||||
);
|
||||
}
|
||||
|
||||
public function hasUserId(): bool {
|
||||
|
|
|
@ -83,7 +83,7 @@ class LoginAttempts {
|
|||
IPAddress|string|null $remoteAddr = null,
|
||||
TimeSpan|int|null $timeRange = null,
|
||||
?Pagination $pagination = null
|
||||
): array {
|
||||
): iterable {
|
||||
if($userInfo instanceof UserInfo)
|
||||
$userInfo = $userInfo->getId();
|
||||
if($remoteAddr instanceof IPAddress)
|
||||
|
@ -127,13 +127,7 @@ class LoginAttempts {
|
|||
}
|
||||
$stmt->execute();
|
||||
|
||||
$result = $stmt->getResult();
|
||||
$attempts = [];
|
||||
|
||||
while($result->next())
|
||||
$attempts[] = new LoginAttemptInfo($result);
|
||||
|
||||
return $attempts;
|
||||
return $stmt->getResult()->getIterator(LoginAttemptInfo::fromResult(...));
|
||||
}
|
||||
|
||||
public function recordAttempt(
|
||||
|
|
|
@ -7,32 +7,36 @@ use Index\Net\IPAddress;
|
|||
use Misuzu\ClientInfo;
|
||||
|
||||
class SessionInfo {
|
||||
private string $id;
|
||||
private string $userId;
|
||||
private string $token;
|
||||
private string $firstRemoteAddr;
|
||||
private ?string $lastRemoteAddr;
|
||||
private string $userAgent;
|
||||
private string $clientInfo;
|
||||
private string $countryCode;
|
||||
private int $expires;
|
||||
private bool $bumpExpires;
|
||||
private int $created;
|
||||
private ?int $lastActive;
|
||||
public function __construct(
|
||||
private string $id,
|
||||
private string $userId,
|
||||
private string $token,
|
||||
private string $firstRemoteAddr,
|
||||
private ?string $lastRemoteAddr,
|
||||
private string $userAgent,
|
||||
private string $clientInfo,
|
||||
private string $countryCode,
|
||||
private int $expires,
|
||||
private bool $bumpExpires,
|
||||
private int $created,
|
||||
private ?int $lastActive,
|
||||
) {}
|
||||
|
||||
public function __construct(IDbResult $result) {
|
||||
$this->id = (string)$result->getInteger(0);
|
||||
$this->userId = (string)$result->getInteger(1);
|
||||
$this->token = $result->getString(2);
|
||||
$this->firstRemoteAddr = $result->getString(3);
|
||||
$this->lastRemoteAddr = $result->isNull(4) ? null : $result->getString(4);
|
||||
$this->userAgent = $result->getString(5);
|
||||
$this->clientInfo = $result->getString(6);
|
||||
$this->countryCode = $result->getString(7);
|
||||
$this->expires = $result->getInteger(8);
|
||||
$this->bumpExpires = $result->getInteger(9) !== 0;
|
||||
$this->created = $result->getInteger(10);
|
||||
$this->lastActive = $result->isNull(11) ? null : $result->getInteger(11);
|
||||
public static function fromResult(IDbResult $result): SessionInfo {
|
||||
return new SessionInfo(
|
||||
id: $result->getString(0),
|
||||
userId: $result->getString(1),
|
||||
token: $result->getString(2),
|
||||
firstRemoteAddr: $result->getString(3),
|
||||
lastRemoteAddr: $result->getStringOrNull(4),
|
||||
userAgent: $result->getString(5),
|
||||
clientInfo: $result->getString(6),
|
||||
countryCode: $result->getString(7),
|
||||
expires: $result->getInteger(8),
|
||||
bumpExpires: $result->getBoolean(9),
|
||||
created: $result->getInteger(10),
|
||||
lastActive: $result->getIntegerOrNull(11),
|
||||
);
|
||||
}
|
||||
|
||||
public function getId(): string {
|
||||
|
|
|
@ -58,7 +58,7 @@ class Sessions {
|
|||
public function getSessions(
|
||||
UserInfo|string|null $userInfo = null,
|
||||
?Pagination $pagination = null
|
||||
): array {
|
||||
): iterable {
|
||||
if($userInfo instanceof UserInfo)
|
||||
$userInfo = $userInfo->getId();
|
||||
|
||||
|
@ -85,13 +85,7 @@ class Sessions {
|
|||
}
|
||||
$stmt->execute();
|
||||
|
||||
$result = $stmt->getResult();
|
||||
$sessions = [];
|
||||
|
||||
while($result->next())
|
||||
$sessions[] = new SessionInfo($result);
|
||||
|
||||
return $sessions;
|
||||
return $stmt->getResult()->getIterator(SessionInfo::fromResult(...));
|
||||
}
|
||||
|
||||
public function getSession(
|
||||
|
@ -124,7 +118,7 @@ class Sessions {
|
|||
if(!$result->next())
|
||||
throw new RuntimeException('Session not found.');
|
||||
|
||||
return new SessionInfo($result);
|
||||
return SessionInfo::fromResult($result);
|
||||
}
|
||||
|
||||
public function createSession(
|
||||
|
|
|
@ -5,20 +5,24 @@ use Index\DateTime;
|
|||
use Index\Data\IDbResult;
|
||||
|
||||
class ChangeInfo {
|
||||
private string $id;
|
||||
private ?string $userId;
|
||||
private int $action;
|
||||
private int $created;
|
||||
private string $summary;
|
||||
private string $body;
|
||||
public function __construct(
|
||||
private string $id,
|
||||
private ?string $userId,
|
||||
private int $action,
|
||||
private int $created,
|
||||
private string $summary,
|
||||
private string $body,
|
||||
) {}
|
||||
|
||||
public function __construct(IDbResult $result) {
|
||||
$this->id = (string)$result->getInteger(0);
|
||||
$this->userId = $result->isNull(1) ? null : (string)$result->getInteger(1);
|
||||
$this->action = $result->getInteger(2);
|
||||
$this->created = $result->getInteger(3);
|
||||
$this->summary = $result->getString(4);
|
||||
$this->body = $result->getString(5);
|
||||
public static function fromResult(IDbResult $result): ChangeInfo {
|
||||
return new ChangeInfo(
|
||||
id: $result->getString(0),
|
||||
userId: $result->getStringOrNull(1),
|
||||
action: $result->getInteger(2),
|
||||
created: $result->getInteger(3),
|
||||
summary: $result->getString(4),
|
||||
body: $result->getString(5),
|
||||
);
|
||||
}
|
||||
|
||||
public function getId(): string {
|
||||
|
|
|
@ -121,7 +121,7 @@ class Changelog {
|
|||
DateTime|int|null $dateTime = null,
|
||||
?array $tags = null,
|
||||
?Pagination $pagination = null
|
||||
): array {
|
||||
): iterable {
|
||||
if($userInfo instanceof UserInfo)
|
||||
$userInfo = $userInfo->getId();
|
||||
if($dateTime instanceof DateTime)
|
||||
|
@ -169,13 +169,7 @@ class Changelog {
|
|||
|
||||
$stmt->execute();
|
||||
|
||||
$result = $stmt->getResult();
|
||||
$changes = [];
|
||||
|
||||
while($result->next())
|
||||
$changes[] = new ChangeInfo($result);
|
||||
|
||||
return $changes;
|
||||
return $stmt->getResult()->getIterator(ChangeInfo::fromResult(...));
|
||||
}
|
||||
|
||||
public function getChange(string $changeId): ChangeInfo {
|
||||
|
@ -187,7 +181,7 @@ class Changelog {
|
|||
if(!$result->next())
|
||||
throw new RuntimeException('No tag with that ID exists.');
|
||||
|
||||
return new ChangeInfo($result);
|
||||
return ChangeInfo::fromResult($result);
|
||||
}
|
||||
|
||||
public function createChange(
|
||||
|
|
|
@ -3,20 +3,12 @@ namespace Misuzu\Changelog;
|
|||
|
||||
use ErrorException;
|
||||
use RuntimeException;
|
||||
use Index\Routing\Route;
|
||||
use Index\Routing\RouteHandler;
|
||||
use Misuzu\Pagination;
|
||||
use Misuzu\SiteInfo;
|
||||
use Misuzu\Template;
|
||||
use Index\Http\Routing\{HttpGet,RouteHandler};
|
||||
use Misuzu\{Pagination,SiteInfo,Template};
|
||||
use Misuzu\Auth\AuthInfo;
|
||||
use Misuzu\Comments\Comments;
|
||||
use Misuzu\Comments\CommentsEx;
|
||||
use Misuzu\Feeds\Feed;
|
||||
use Misuzu\Feeds\FeedItem;
|
||||
use Misuzu\Feeds\AtomFeedSerializer;
|
||||
use Misuzu\Feeds\RssFeedSerializer;
|
||||
use Misuzu\URLs\URLInfo;
|
||||
use Misuzu\URLs\URLRegistry;
|
||||
use Misuzu\Comments\{Comments,CommentsEx};
|
||||
use Misuzu\Feeds\{Feed,FeedItem,AtomFeedSerializer,RssFeedSerializer};
|
||||
use Misuzu\URLs\{URLInfo,URLRegistry};
|
||||
use Misuzu\Users\UsersContext;
|
||||
|
||||
final class ChangelogRoutes extends RouteHandler {
|
||||
|
@ -34,7 +26,7 @@ final class ChangelogRoutes extends RouteHandler {
|
|||
return $comments->getCommentsForLayout($categoryName);
|
||||
}
|
||||
|
||||
#[Route('GET', '/changelog')]
|
||||
#[HttpGet('/changelog')]
|
||||
#[URLInfo('changelog-index', '/changelog', ['date' => '<date>', 'user' => '<user>', 'tags' => '<tags>', 'p' => '<page>'])]
|
||||
public function getIndex($response, $request) {
|
||||
$filterDate = (string)$request->getParam('date');
|
||||
|
@ -73,15 +65,16 @@ final class ChangelogRoutes extends RouteHandler {
|
|||
if(!$pagination->hasValidOffset())
|
||||
return 404;
|
||||
|
||||
$changeInfos = $this->changelog->getChanges(userInfo: $filterUser, dateTime: $filterDate, tags: $filterTags, pagination: $pagination);
|
||||
if(empty($changeInfos))
|
||||
return 404;
|
||||
|
||||
$changes = [];
|
||||
$changeInfos = $this->changelog->getChanges(userInfo: $filterUser, dateTime: $filterDate, tags: $filterTags, pagination: $pagination);
|
||||
$commentsCategoryName = null;
|
||||
|
||||
foreach($changeInfos as $changeInfo) {
|
||||
$userInfo = $changeInfo->hasUserId() ? $this->usersCtx->getUserInfo($changeInfo->getUserId()) : null;
|
||||
|
||||
if($commentsCategoryName === null)
|
||||
$commentsCategoryName = $changeInfo->getCommentsCategoryName();
|
||||
|
||||
$changes[] = [
|
||||
'change' => $changeInfo,
|
||||
'user' => $userInfo,
|
||||
|
@ -89,17 +82,20 @@ final class ChangelogRoutes extends RouteHandler {
|
|||
];
|
||||
}
|
||||
|
||||
if(empty($changes))
|
||||
return 404;
|
||||
|
||||
return Template::renderRaw('changelog.index', [
|
||||
'changelog_infos' => $changes,
|
||||
'changelog_date' => $filterDate,
|
||||
'changelog_user' => $filterUser,
|
||||
'changelog_tags' => $filterTags,
|
||||
'changelog_pagination' => $pagination,
|
||||
'comments_info' => empty($filterDate) ? null : $this->getCommentsInfo($changeInfos[0]->getCommentsCategoryName()),
|
||||
'comments_info' => empty($filterDate) && $commentsCategoryName !== null ? null : $this->getCommentsInfo($commentsCategoryName),
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route('GET', '/changelog/change/:id')]
|
||||
#[HttpGet('/changelog/change/([0-9]+)')]
|
||||
#[URLInfo('changelog-change', '/changelog/change/<change>')]
|
||||
#[URLInfo('changelog-change-comments', '/changelog/change/<change>', fragment: 'comments')]
|
||||
public function getChange($response, $request, string $changeId) {
|
||||
|
@ -148,14 +144,14 @@ final class ChangelogRoutes extends RouteHandler {
|
|||
return $feed;
|
||||
}
|
||||
|
||||
#[Route('GET', '/changelog.rss')]
|
||||
#[HttpGet('/changelog.rss')]
|
||||
#[URLInfo('changelog-feed-rss', '/changelog.rss')]
|
||||
public function getFeedRSS($response) {
|
||||
$response->setContentType('application/rss+xml; charset=utf-8');
|
||||
return (new RssFeedSerializer)->serializeFeed($this->createFeed('rss'));
|
||||
}
|
||||
|
||||
#[Route('GET', '/changelog.atom')]
|
||||
#[HttpGet('/changelog.atom')]
|
||||
#[URLInfo('changelog-feed-atom', '/changelog.atom')]
|
||||
public function getFeedAtom($response) {
|
||||
$response->setContentType('application/atom+xml; charset=utf-8');
|
||||
|
|
|
@ -46,7 +46,7 @@ class Comments {
|
|||
public function getCategories(
|
||||
UserInfo|string|null $owner = null,
|
||||
?Pagination $pagination = null
|
||||
): array {
|
||||
): iterable {
|
||||
if($owner instanceof UserInfo)
|
||||
$owner = $owner->getId();
|
||||
|
||||
|
@ -72,13 +72,7 @@ class Comments {
|
|||
|
||||
$stmt->execute();
|
||||
|
||||
$result = $stmt->getResult();
|
||||
$categories = [];
|
||||
|
||||
while($result->next())
|
||||
$categories[] = new CommentsCategoryInfo($result);
|
||||
|
||||
return $categories;
|
||||
return $stmt->getResult()->getIterator(CommentsCategoryInfo::fromResult(...));
|
||||
}
|
||||
|
||||
public function getCategory(
|
||||
|
@ -124,7 +118,7 @@ class Comments {
|
|||
if(!$result->next())
|
||||
throw new RuntimeException('Comments category not found.');
|
||||
|
||||
return new CommentsCategoryInfo($result);
|
||||
return CommentsCategoryInfo::fromResult($result);
|
||||
}
|
||||
|
||||
public function checkCategoryNameExists(string $name): bool {
|
||||
|
@ -276,7 +270,7 @@ class Comments {
|
|||
?bool $deleted = null,
|
||||
bool $includeRepliesCount = false,
|
||||
bool $includeVotesCount = false
|
||||
): array {
|
||||
): iterable {
|
||||
if($categoryInfo instanceof CommentsCategoryInfo)
|
||||
$categoryInfo = $categoryInfo->getId();
|
||||
if($parentInfo instanceof CommentsPostInfo)
|
||||
|
@ -330,13 +324,7 @@ class Comments {
|
|||
$stmt->addParameter(++$args, $userInfo instanceof UserInfo ? $userInfo->getId() : $userInfo);
|
||||
$stmt->execute();
|
||||
|
||||
$posts = [];
|
||||
$result = $stmt->getResult();
|
||||
|
||||
while($result->next())
|
||||
$posts[] = new CommentsPostInfo($result, $includeRepliesCount, $includeVotesCount);
|
||||
|
||||
return $posts;
|
||||
return $stmt->getResult()->getIterator(fn($result) => CommentsPostInfo::fromResult($result, $includeRepliesCount, $includeVotesCount));
|
||||
}
|
||||
|
||||
public function getPost(
|
||||
|
@ -362,7 +350,7 @@ class Comments {
|
|||
if(!$result->next())
|
||||
throw new RuntimeException('No comment with that ID exists.');
|
||||
|
||||
return new CommentsPostInfo($result, $includeRepliesCount, $includeVotesCount);
|
||||
return CommentsPostInfo::fromResult($result, $includeRepliesCount, $includeVotesCount);
|
||||
}
|
||||
|
||||
public function createPost(
|
||||
|
|
|
@ -6,20 +6,24 @@ use Index\Data\IDbResult;
|
|||
use Misuzu\Users\UserInfo;
|
||||
|
||||
class CommentsCategoryInfo {
|
||||
private string $id;
|
||||
private string $name;
|
||||
private ?string $ownerId;
|
||||
private int $created;
|
||||
private ?int $locked;
|
||||
private int $comments;
|
||||
public function __construct(
|
||||
private string $id,
|
||||
private string $name,
|
||||
private ?string $ownerId,
|
||||
private int $created,
|
||||
private ?int $locked,
|
||||
private int $comments,
|
||||
) {}
|
||||
|
||||
public function __construct(IDbResult $result) {
|
||||
$this->id = (string)$result->getInteger(0);
|
||||
$this->name = $result->getString(1);
|
||||
$this->ownerId = $result->isNull(2) ? null : (string)$result->getInteger(2);
|
||||
$this->created = $result->getInteger(3);
|
||||
$this->locked = $result->isNull(4) ? null : $result->getInteger(4);
|
||||
$this->comments = $result->getInteger(5);
|
||||
public static function fromResult(IDbResult $result): CommentsCategoryInfo {
|
||||
return new CommentsCategoryInfo(
|
||||
id: $result->getString(0),
|
||||
name: $result->getString(1),
|
||||
ownerId: $result->getStringOrNull(2),
|
||||
created: $result->getInteger(3),
|
||||
locked: $result->getIntegerOrNull(4),
|
||||
comments: $result->getInteger(5),
|
||||
);
|
||||
}
|
||||
|
||||
public function getId(): string {
|
||||
|
|
|
@ -5,47 +5,53 @@ use Index\DateTime;
|
|||
use Index\Data\IDbResult;
|
||||
|
||||
class CommentsPostInfo {
|
||||
private string $id;
|
||||
private string $categoryId;
|
||||
private ?string $userId;
|
||||
private ?string $replyingTo;
|
||||
private string $body;
|
||||
private int $created;
|
||||
private ?int $pinned;
|
||||
private ?int $updated;
|
||||
private ?int $deleted;
|
||||
private int $replies;
|
||||
private int $votesTotal;
|
||||
private int $votesPositive;
|
||||
private int $votesNegative;
|
||||
|
||||
public function __construct(
|
||||
private string $id,
|
||||
private string $categoryId,
|
||||
private ?string $userId,
|
||||
private ?string $replyingTo,
|
||||
private string $body,
|
||||
private int $created,
|
||||
private ?int $pinned,
|
||||
private ?int $updated,
|
||||
private ?int $deleted,
|
||||
private int $replies,
|
||||
private int $votesTotal,
|
||||
private int $votesPositive,
|
||||
private int $votesNegative,
|
||||
) {}
|
||||
|
||||
public static function fromResult(
|
||||
IDbResult $result,
|
||||
bool $includeRepliesCount = false,
|
||||
bool $includeVotesCount = false
|
||||
) {
|
||||
$args = 0;
|
||||
$this->id = (string)$result->getInteger($args);
|
||||
$this->categoryId = (string)$result->getInteger(++$args);
|
||||
$this->userId = $result->isNull(++$args) ? null : (string)$result->getInteger($args);
|
||||
$this->replyingTo = $result->isNull(++$args) ? null : (string)$result->getInteger($args);
|
||||
$this->body = $result->getString(++$args);
|
||||
$this->created = $result->getInteger(++$args);
|
||||
$this->pinned = $result->isNull(++$args) ? null : $result->getInteger($args);
|
||||
$this->updated = $result->isNull(++$args) ? null : $result->getInteger($args);
|
||||
$this->deleted = $result->isNull(++$args) ? null : $result->getInteger($args);
|
||||
): CommentsPostInfo {
|
||||
$args = [];
|
||||
$count = 0;
|
||||
|
||||
$this->replies = $includeRepliesCount ? $result->getInteger(++$args) : 0;
|
||||
$args[] = $result->getString($count); // id
|
||||
$args[] = $result->getString(++$count); // categoryId
|
||||
$args[] = $result->getStringOrNull(++$count); // userId
|
||||
$args[] = $result->getStringOrNull(++$count); // replyingTo
|
||||
$args[] = $result->getString(++$count); // body
|
||||
$args[] = $result->getInteger(++$count); // created
|
||||
$args[] = $result->getIntegerOrNull(++$count); // pinned
|
||||
$args[] = $result->getIntegerOrNull(++$count); // updated
|
||||
$args[] = $result->getIntegerOrNull(++$count); // deleted
|
||||
|
||||
$args[] = $includeRepliesCount ? $result->getInteger(++$count) : 0;
|
||||
|
||||
if($includeVotesCount) {
|
||||
$this->votesTotal = $result->getInteger(++$args);
|
||||
$this->votesPositive = $result->getInteger(++$args);
|
||||
$this->votesNegative = $result->getInteger(++$args);
|
||||
$args[] = $result->getInteger(++$count); // votesTotal
|
||||
$args[] = $result->getInteger(++$count); // votesPositive
|
||||
$args[] = $result->getInteger(++$count); // votesNegative
|
||||
} else {
|
||||
$this->votesTotal = 0;
|
||||
$this->votesPositive = 0;
|
||||
$this->votesNegative = 0;
|
||||
$args[] = 0;
|
||||
$args[] = 0;
|
||||
$args[] = 0;
|
||||
}
|
||||
|
||||
return new CommentsPostInfo(...$args);
|
||||
}
|
||||
|
||||
public function getId(): string {
|
||||
|
|
|
@ -5,14 +5,18 @@ use Index\DateTime;
|
|||
use Index\Data\IDbResult;
|
||||
|
||||
class CounterInfo {
|
||||
private string $name;
|
||||
private int $value;
|
||||
private int $updated;
|
||||
public function __construct(
|
||||
private string $name,
|
||||
private int $value,
|
||||
private int $updated,
|
||||
) {}
|
||||
|
||||
public function __construct(IDbResult $result) {
|
||||
$this->name = $result->getString(0);
|
||||
$this->value = $result->getInteger(1);
|
||||
$this->updated = $result->getInteger(2);
|
||||
public static function fromResult(IDbResult $result): CounterInfo {
|
||||
return new CounterInfo(
|
||||
name: $result->getString(0),
|
||||
value: $result->getInteger(1),
|
||||
updated: $result->getInteger(2),
|
||||
);
|
||||
}
|
||||
|
||||
public function getName(): string {
|
||||
|
|
|
@ -25,7 +25,7 @@ class Counters {
|
|||
public function getCounters(
|
||||
?string $orderBy = null,
|
||||
?Pagination $pagination = null
|
||||
): array {
|
||||
): iterable {
|
||||
$hasOrderBy = $orderBy !== null;
|
||||
$hasPagination = $pagination !== null;
|
||||
|
||||
|
@ -47,13 +47,7 @@ class Counters {
|
|||
}
|
||||
$stmt->execute();
|
||||
|
||||
$result = $stmt->getResult();
|
||||
$counters = [];
|
||||
|
||||
while($result->next())
|
||||
$counters[] = new CounterInfo($result);
|
||||
|
||||
return $counters;
|
||||
return $stmt->getResult()->getIterator(CounterInfo::fromResult(...));
|
||||
}
|
||||
|
||||
public function get(array|string $names): array|int {
|
||||
|
|
|
@ -5,16 +5,20 @@ use Stringable;
|
|||
use Index\Data\IDbResult;
|
||||
|
||||
class EmoteInfo implements Stringable {
|
||||
private string $id;
|
||||
private int $order;
|
||||
private int $rank;
|
||||
private string $url;
|
||||
public function __construct(
|
||||
private string $id,
|
||||
private int $order,
|
||||
private int $rank,
|
||||
private string $url,
|
||||
) {}
|
||||
|
||||
public function __construct(IDbResult $result) {
|
||||
$this->id = (string)$result->getInteger(0);
|
||||
$this->order = $result->getInteger(1);
|
||||
$this->rank = $result->getInteger(2);
|
||||
$this->url = $result->getString(3);
|
||||
public static function fromResult(IDbResult $result): EmoteInfo {
|
||||
return new EmoteInfo(
|
||||
id: $result->getString(0),
|
||||
order: $result->getInteger(1),
|
||||
rank: $result->getInteger(2),
|
||||
url: $result->getString(3),
|
||||
);
|
||||
}
|
||||
|
||||
public function getId(): string {
|
||||
|
|
|
@ -5,14 +5,18 @@ use Stringable;
|
|||
use Index\Data\IDbResult;
|
||||
|
||||
class EmoteStringInfo implements Stringable {
|
||||
private string $emoteId;
|
||||
private int $order;
|
||||
private string $string;
|
||||
public function __construct(
|
||||
private string $emoteId,
|
||||
private int $order,
|
||||
private string $string,
|
||||
) {}
|
||||
|
||||
public function __construct(IDbResult $result) {
|
||||
$this->emoteId = (string)$result->getInteger(0);
|
||||
$this->order = $result->getInteger(1);
|
||||
$this->string = $result->getString(2);
|
||||
public static function fromResult(IDbResult $result): EmoteStringInfo {
|
||||
return new EmoteStringInfo(
|
||||
emoteId: $result->getString(0),
|
||||
order: $result->getInteger(1),
|
||||
string: $result->getString(2),
|
||||
);
|
||||
}
|
||||
|
||||
public function getEmoteId(): string {
|
||||
|
|
|
@ -31,7 +31,7 @@ class Emotes {
|
|||
if(!$result->next())
|
||||
throw new RuntimeException('No emoticon with that ID exists.');
|
||||
|
||||
return new EmoteInfo($result);
|
||||
return EmoteInfo::fromResult($result);
|
||||
}
|
||||
|
||||
public static function emoteOrderOptions(): array {
|
||||
|
@ -43,7 +43,7 @@ class Emotes {
|
|||
?int $minRank = null,
|
||||
?string $orderBy = null,
|
||||
?bool $reverse = null
|
||||
): array {
|
||||
): iterable {
|
||||
$hasMinRank = $minRank !== null;
|
||||
$hasOrderBy = $orderBy !== null;
|
||||
$hasReverse = $reverse !== null;
|
||||
|
@ -65,13 +65,7 @@ class Emotes {
|
|||
$stmt->addParameter(1, $minRank);
|
||||
$stmt->execute();
|
||||
|
||||
$emotes = [];
|
||||
$result = $stmt->getResult();
|
||||
|
||||
while($result->next())
|
||||
$emotes[] = new EmoteInfo($result);
|
||||
|
||||
return $emotes;
|
||||
return $stmt->getResult()->getIterator(EmoteInfo::fromResult(...));
|
||||
}
|
||||
|
||||
private static function checkEmoteUrlInternal(string $url): string {
|
||||
|
@ -156,7 +150,7 @@ class Emotes {
|
|||
$stmt->execute();
|
||||
}
|
||||
|
||||
public function getEmoteStrings(EmoteInfo|string $infoOrId): array {
|
||||
public function getEmoteStrings(EmoteInfo|string $infoOrId): iterable {
|
||||
if($infoOrId instanceof EmoteInfo)
|
||||
$infoOrId = $infoOrId->getId();
|
||||
|
||||
|
@ -164,13 +158,7 @@ class Emotes {
|
|||
$stmt->addParameter(1, $infoOrId);
|
||||
$stmt->execute();
|
||||
|
||||
$strings = [];
|
||||
$result = $stmt->getResult();
|
||||
|
||||
while($result->next())
|
||||
$strings[] = new EmoteStringInfo($result);
|
||||
|
||||
return $strings;
|
||||
return $stmt->getResult()->getIterator(EmoteStringInfo::fromResult(...));
|
||||
}
|
||||
|
||||
private static function checkEmoteStringInternal(string $string): string {
|
||||
|
|
|
@ -12,19 +12,20 @@ use Misuzu\Pagination;
|
|||
use Misuzu\Users\UserInfo;
|
||||
|
||||
class ForumCategories {
|
||||
private IDbConnection $dbConn;
|
||||
private DbStatementCache $cache;
|
||||
|
||||
public function __construct(IDbConnection $dbConn) {
|
||||
$this->dbConn = $dbConn;
|
||||
$this->cache = new DbStatementCache($dbConn);
|
||||
}
|
||||
|
||||
public static function convertCategoryListToTree(
|
||||
array $catInfos,
|
||||
iterable $catInfos,
|
||||
ForumCategoryInfo|string|null $parentInfo = null,
|
||||
?Colour $colour = null
|
||||
): array {
|
||||
if(!is_array($catInfos))
|
||||
$catInfos = iterator_to_array($catInfos);
|
||||
|
||||
$colour ??= Colour::none();
|
||||
$tree = [];
|
||||
$predicate = $parentInfo
|
||||
|
@ -103,7 +104,7 @@ class ForumCategories {
|
|||
?bool $hidden = null,
|
||||
bool $asTree = false,
|
||||
?Pagination $pagination = null
|
||||
): array {
|
||||
): iterable {
|
||||
$isRootParent = false;
|
||||
$hasParentInfo = $parentInfo !== false;
|
||||
$hasType = $type !== null;
|
||||
|
@ -155,11 +156,7 @@ class ForumCategories {
|
|||
}
|
||||
$stmt->execute();
|
||||
|
||||
$result = $stmt->getResult();
|
||||
$cats = [];
|
||||
|
||||
while($result->next())
|
||||
$cats[] = new ForumCategoryInfo($result);
|
||||
$cats = $stmt->getResult()->getIterator(ForumCategoryInfo::fromResult(...));
|
||||
|
||||
if($asTree)
|
||||
$cats = self::convertCategoryListToTree($cats);
|
||||
|
@ -216,7 +213,7 @@ class ForumCategories {
|
|||
if(!$result->next())
|
||||
throw new RuntimeException('Forum category info not found.');
|
||||
|
||||
return new ForumCategoryInfo($result);
|
||||
return ForumCategoryInfo::fromResult($result);
|
||||
}
|
||||
|
||||
public function updateCategory(
|
||||
|
@ -261,7 +258,7 @@ class ForumCategories {
|
|||
|
||||
public function getCategoryAncestry(
|
||||
ForumCategoryInfo|ForumTopicInfo|ForumPostInfo|string $categoryInfo
|
||||
): array {
|
||||
): iterable {
|
||||
if($categoryInfo instanceof ForumCategoryInfo)
|
||||
$categoryInfo = $categoryInfo->getId();
|
||||
elseif($categoryInfo instanceof ForumTopicInfo || $categoryInfo instanceof ForumPostInfo)
|
||||
|
@ -277,13 +274,7 @@ class ForumCategories {
|
|||
$stmt->addParameter(1, $categoryInfo);
|
||||
$stmt->execute();
|
||||
|
||||
$result = $stmt->getResult();
|
||||
$cats = [];
|
||||
|
||||
while($result->next())
|
||||
$cats[] = new ForumCategoryInfo($result);
|
||||
|
||||
return $cats;
|
||||
return $stmt->getResult()->getIterator(ForumCategoryInfo::fromResult(...));
|
||||
}
|
||||
|
||||
public function getCategoryChildren(
|
||||
|
@ -291,7 +282,7 @@ class ForumCategories {
|
|||
bool $includeSelf = false,
|
||||
?bool $hidden = null,
|
||||
bool $asTree = false
|
||||
): array {
|
||||
): iterable {
|
||||
if($parentInfo instanceof ForumCategoryInfo)
|
||||
$parentInfo = $parentInfo->getId();
|
||||
|
||||
|
@ -319,11 +310,7 @@ class ForumCategories {
|
|||
$stmt->addParameter(++$args, $parentInfo);
|
||||
$stmt->execute();
|
||||
|
||||
$result = $stmt->getResult();
|
||||
$cats = [];
|
||||
|
||||
while($result->next())
|
||||
$cats[] = new ForumCategoryInfo($result);
|
||||
$cats = $stmt->getResult()->getIterator(ForumCategoryInfo::fromResult(...));
|
||||
|
||||
if($asTree)
|
||||
$cats = self::convertCategoryListToTree($cats, $parentInfo);
|
||||
|
|
|
@ -26,38 +26,42 @@ class ForumCategoryInfo {
|
|||
self::TYPE_DISCUSSION,
|
||||
];
|
||||
|
||||
private string $id;
|
||||
private int $order;
|
||||
private ?string $parentId;
|
||||
private string $name;
|
||||
private int $type;
|
||||
private ?string $desc;
|
||||
private ?string $icon;
|
||||
private ?int $colour;
|
||||
private ?string $link;
|
||||
private ?int $clicks;
|
||||
private int $created;
|
||||
private bool $archived;
|
||||
private bool $hidden;
|
||||
private int $topicsCount;
|
||||
private int $postsCount;
|
||||
public function __construct(
|
||||
private string $id,
|
||||
private int $order,
|
||||
private ?string $parentId,
|
||||
private string $name,
|
||||
private int $type,
|
||||
private ?string $desc,
|
||||
private ?string $icon,
|
||||
private ?int $colour,
|
||||
private ?string $link,
|
||||
private ?int $clicks,
|
||||
private int $created,
|
||||
private bool $archived,
|
||||
private bool $hidden,
|
||||
private int $topicsCount,
|
||||
private int $postsCount,
|
||||
) {}
|
||||
|
||||
public function __construct(IDbResult $result) {
|
||||
$this->id = (string)$result->getInteger(0);
|
||||
$this->order = $result->getInteger(1);
|
||||
$this->parentId = $result->isNull(2) ? null : (string)$result->getInteger(2);
|
||||
$this->name = $result->getString(3);
|
||||
$this->type = $result->getInteger(4);
|
||||
$this->desc = $result->isNull(5) ? null : $result->getString(5);
|
||||
$this->icon = $result->isNull(6) ? null : $result->getString(6);
|
||||
$this->colour = $result->isNull(7) ? null : $result->getInteger(7);
|
||||
$this->link = $result->isNull(8) ? null : $result->getString(8);
|
||||
$this->clicks = $result->isNull(9) ? null : $result->getInteger(9);
|
||||
$this->created = $result->getInteger(10);
|
||||
$this->archived = $result->getInteger(11) !== 0;
|
||||
$this->hidden = $result->getInteger(12) !== 0;
|
||||
$this->topicsCount = $result->getInteger(13);
|
||||
$this->postsCount = $result->getInteger(14);
|
||||
public static function fromResult(IDbResult $result): ForumCategoryInfo {
|
||||
return new ForumCategoryInfo(
|
||||
id: $result->getString(0),
|
||||
order: $result->getInteger(1),
|
||||
parentId: $result->getStringOrNull(2),
|
||||
name: $result->getString(3),
|
||||
type: $result->getInteger(4),
|
||||
desc: $result->getStringOrNull(5),
|
||||
icon: $result->getStringOrNull(6),
|
||||
colour: $result->getIntegerOrNull(7),
|
||||
link: $result->getStringOrNull(8),
|
||||
clicks: $result->getIntegerOrNull(9),
|
||||
created: $result->getInteger(10),
|
||||
archived: $result->getBoolean(11),
|
||||
hidden: $result->getBoolean(12),
|
||||
topicsCount: $result->getInteger(13),
|
||||
postsCount: $result->getInteger(14),
|
||||
);
|
||||
}
|
||||
|
||||
public function getId(): string {
|
||||
|
|
|
@ -7,30 +7,34 @@ use Index\Net\IPAddress;
|
|||
use Misuzu\Parsers\Parser;
|
||||
|
||||
class ForumPostInfo {
|
||||
private string $id;
|
||||
private string $topicId;
|
||||
private string $categoryId;
|
||||
private ?string $userId;
|
||||
private string $remoteAddr;
|
||||
private string $body;
|
||||
private int $parser;
|
||||
private bool $displaySignature;
|
||||
private int $created;
|
||||
private ?int $edited;
|
||||
private ?int $deleted;
|
||||
public function __construct(
|
||||
private string $id,
|
||||
private string $topicId,
|
||||
private string $categoryId,
|
||||
private ?string $userId,
|
||||
private string $remoteAddr,
|
||||
private string $body,
|
||||
private int $parser,
|
||||
private bool $displaySignature,
|
||||
private int $created,
|
||||
private ?int $edited,
|
||||
private ?int $deleted,
|
||||
) {}
|
||||
|
||||
public function __construct(IDbResult $result) {
|
||||
$this->id = (string)$result->getInteger(0);
|
||||
$this->topicId = (string)$result->getInteger(1);
|
||||
$this->categoryId = (string)$result->getInteger(2);
|
||||
$this->userId = $result->isNull(3) ? null : (string)$result->getInteger(3);
|
||||
$this->remoteAddr = $result->getString(4);
|
||||
$this->body = $result->getString(5);
|
||||
$this->parser = $result->getInteger(6);
|
||||
$this->displaySignature = $result->getInteger(7) !== 0;
|
||||
$this->created = $result->getInteger(8);
|
||||
$this->edited = $result->isNull(9) ? null : $result->getInteger(9);
|
||||
$this->deleted = $result->isNull(10) ? null : $result->getInteger(10);
|
||||
public static function fromResult(IDbResult $result): ForumPostInfo {
|
||||
return new ForumPostInfo(
|
||||
id: $result->getString(0),
|
||||
topicId: $result->getString(1),
|
||||
categoryId: $result->getString(2),
|
||||
userId: $result->getStringOrNull(3),
|
||||
remoteAddr: $result->getString(4),
|
||||
body: $result->getString(5),
|
||||
parser: $result->getInteger(6),
|
||||
displaySignature: $result->getBoolean(7),
|
||||
created: $result->getInteger(8),
|
||||
edited: $result->getIntegerOrNull(9),
|
||||
deleted: $result->getIntegerOrNull(10),
|
||||
);
|
||||
}
|
||||
|
||||
public function getId(): string {
|
||||
|
|
|
@ -84,7 +84,7 @@ class ForumPosts {
|
|||
?array $searchQuery = null,
|
||||
?bool $deleted = null,
|
||||
?Pagination $pagination = null
|
||||
): array {
|
||||
): iterable {
|
||||
// remove this hack when search server
|
||||
$hasSearchQuery = $searchQuery !== null;
|
||||
$doSearchOrder = false;
|
||||
|
@ -189,13 +189,7 @@ class ForumPosts {
|
|||
}
|
||||
$stmt->execute();
|
||||
|
||||
$result = $stmt->getResult();
|
||||
$posts = [];
|
||||
|
||||
while($result->next())
|
||||
$posts[] = new ForumPostInfo($result);
|
||||
|
||||
return $posts;
|
||||
return $stmt->getResult()->getIterator(ForumPostInfo::fromResult(...));
|
||||
}
|
||||
|
||||
public function getPost(
|
||||
|
@ -267,7 +261,7 @@ class ForumPosts {
|
|||
if(!$result->next())
|
||||
throw new RuntimeException('Forum post not found.');
|
||||
|
||||
return new ForumPostInfo($result);
|
||||
return ForumPostInfo::fromResult($result);
|
||||
}
|
||||
|
||||
public function createPost(
|
||||
|
|
|
@ -17,32 +17,36 @@ class ForumTopicInfo {
|
|||
'global' => self::TYPE_GLOBAL,
|
||||
];
|
||||
|
||||
private string $id;
|
||||
private string $categoryId;
|
||||
private ?string $userId;
|
||||
private int $type;
|
||||
private string $title;
|
||||
private int $postsCount;
|
||||
private int $deletedPostsCount;
|
||||
private int $viewsCount;
|
||||
private int $created;
|
||||
private int $bumped;
|
||||
private ?int $deleted;
|
||||
private ?int $locked;
|
||||
public function __construct(
|
||||
private string $id,
|
||||
private string $categoryId,
|
||||
private ?string $userId,
|
||||
private int $type,
|
||||
private string $title,
|
||||
private int $postsCount,
|
||||
private int $deletedPostsCount,
|
||||
private int $viewsCount,
|
||||
private int $created,
|
||||
private int $bumped,
|
||||
private ?int $deleted,
|
||||
private ?int $locked,
|
||||
) {}
|
||||
|
||||
public function __construct(IDbResult $result) {
|
||||
$this->id = (string)$result->getInteger(0);
|
||||
$this->categoryId = (string)$result->getInteger(1);
|
||||
$this->userId = $result->isNull(2) ? null : (string)$result->getInteger(2);
|
||||
$this->type = $result->getInteger(3);
|
||||
$this->title = $result->getString(4);
|
||||
$this->viewsCount = $result->getInteger(5);
|
||||
$this->created = $result->getInteger(6);
|
||||
$this->bumped = $result->getInteger(7);
|
||||
$this->deleted = $result->isNull(8) ? null : $result->getInteger(8);
|
||||
$this->locked = $result->isNull(9) ? null : $result->getInteger(9);
|
||||
$this->postsCount = $result->getInteger(10);
|
||||
$this->deletedPostsCount = $result->getInteger(11);
|
||||
public static function fromResult(IDbResult $result): ForumTopicInfo {
|
||||
return new ForumTopicInfo(
|
||||
id: $result->getString(0),
|
||||
categoryId: $result->getString(1),
|
||||
userId: $result->getStringOrNull(2),
|
||||
type: $result->getInteger(3),
|
||||
title: $result->getString(4),
|
||||
viewsCount: $result->getInteger(5),
|
||||
created: $result->getInteger(6),
|
||||
bumped: $result->getInteger(7),
|
||||
deleted: $result->getIntegerOrNull(8),
|
||||
locked: $result->getIntegerOrNull(9),
|
||||
postsCount: $result->getInteger(10),
|
||||
deletedPostsCount: $result->getInteger(11),
|
||||
);
|
||||
}
|
||||
|
||||
public function getId(): string {
|
||||
|
|
|
@ -5,16 +5,20 @@ use Index\DateTime;
|
|||
use Index\Data\IDbResult;
|
||||
|
||||
class ForumTopicRedirectInfo {
|
||||
private string $topicId;
|
||||
private ?string $userId;
|
||||
private string $link;
|
||||
private int $created;
|
||||
public function __construct(
|
||||
private string $topicId,
|
||||
private ?string $userId,
|
||||
private string $link,
|
||||
private int $created,
|
||||
) {}
|
||||
|
||||
public function __construct(IDbResult $result) {
|
||||
$this->topicId = (string)$result->getInteger(0);
|
||||
$this->userId = $result->isNull(1) ? null : (string)$result->getInteger(1);
|
||||
$this->link = $result->getString(2);
|
||||
$this->created = $result->getInteger(3);
|
||||
public static function fromResult(IDbResult $result): ForumTopicRedirectInfo {
|
||||
return new ForumTopicRedirectInfo(
|
||||
topicId: $result->getString(0),
|
||||
userId: $result->getStringOrNull(1),
|
||||
link: $result->getString(2),
|
||||
created: $result->getInteger(3),
|
||||
);
|
||||
}
|
||||
|
||||
public function getTopicId(): string {
|
||||
|
|
|
@ -38,7 +38,7 @@ class ForumTopicRedirects {
|
|||
public function getTopicRedirects(
|
||||
UserInfo|string|null $userInfo = null,
|
||||
?Pagination $pagination = null
|
||||
): array {
|
||||
): iterable {
|
||||
if($userInfo instanceof UserInfo)
|
||||
$userInfo = $userInfo->getId();
|
||||
|
||||
|
@ -61,13 +61,7 @@ class ForumTopicRedirects {
|
|||
}
|
||||
$stmt->execute();
|
||||
|
||||
$result = $stmt->getResult();
|
||||
$redirs = [];
|
||||
|
||||
while($result->next())
|
||||
$redirs[] = new ForumTopicRedirectInfo($result);
|
||||
|
||||
return $redirs;
|
||||
return $stmt->getResult()->getIterator(ForumTopicRedirectInfo::fromResult(...));
|
||||
}
|
||||
|
||||
public function hasTopicRedirect(ForumTopicInfo|string $topicInfo): bool {
|
||||
|
@ -97,7 +91,7 @@ class ForumTopicRedirects {
|
|||
if(!$result->next())
|
||||
throw new RuntimeException('Could not find that forum topic redirect.');
|
||||
|
||||
return new ForumTopicRedirectInfo($result);
|
||||
return ForumTopicRedirectInfo::fromResult($result);
|
||||
}
|
||||
|
||||
public function createTopicRedirect(
|
||||
|
|
|
@ -91,7 +91,7 @@ class ForumTopics {
|
|||
?bool $global = null,
|
||||
?bool $deleted = null,
|
||||
?Pagination $pagination = null
|
||||
): array {
|
||||
): iterable {
|
||||
// remove this hack when search server
|
||||
$hasSearchQuery = $searchQuery !== null;
|
||||
$hasAfterTopicId = false;
|
||||
|
@ -194,13 +194,7 @@ class ForumTopics {
|
|||
}
|
||||
$stmt->execute();
|
||||
|
||||
$result = $stmt->getResult();
|
||||
$topics = [];
|
||||
|
||||
while($result->next())
|
||||
$topics[] = new ForumTopicInfo($result);
|
||||
|
||||
return $topics;
|
||||
return $stmt->getResult()->getIterator(ForumTopicInfo::fromResult(...));
|
||||
}
|
||||
|
||||
public function getTopic(
|
||||
|
@ -239,7 +233,7 @@ class ForumTopics {
|
|||
if(!$result->next())
|
||||
throw new RuntimeException('Forum topic not found.');
|
||||
|
||||
return new ForumTopicInfo($result);
|
||||
return ForumTopicInfo::fromResult($result);
|
||||
}
|
||||
|
||||
public function createTopic(
|
||||
|
|
|
@ -3,14 +3,10 @@ namespace Misuzu\Home;
|
|||
|
||||
use RuntimeException;
|
||||
use Index\DateTime;
|
||||
use Index\Data\DbTools;
|
||||
use Index\Data\IDbConnection;
|
||||
use Index\Routing\Route;
|
||||
use Index\Routing\RouteHandler;
|
||||
use Index\Data\{DbTools,IDbConnection};
|
||||
use Index\Http\Routing\{HttpGet,RouteHandler};
|
||||
use Syokuhou\IConfig;
|
||||
use Misuzu\Pagination;
|
||||
use Misuzu\SiteInfo;
|
||||
use Misuzu\Template;
|
||||
use Misuzu\{Pagination,SiteInfo,Template};
|
||||
use Misuzu\Auth\AuthInfo;
|
||||
use Misuzu\Changelog\Changelog;
|
||||
use Misuzu\Comments\Comments;
|
||||
|
@ -43,7 +39,7 @@ class HomeRoutes extends RouteHandler {
|
|||
]);
|
||||
}
|
||||
|
||||
private function getOnlineUsers(): array {
|
||||
private function getOnlineUsers(): iterable {
|
||||
return $this->usersCtx->getUsers()->getUsers(
|
||||
lastActiveInMinutes: 5,
|
||||
deleted: false,
|
||||
|
@ -53,14 +49,14 @@ class HomeRoutes extends RouteHandler {
|
|||
|
||||
private array $newsCategoryInfos = [];
|
||||
|
||||
private function getFeaturedNewsPosts(int $amount, bool $decorate): array {
|
||||
private function getFeaturedNewsPosts(int $amount, bool $decorate): iterable {
|
||||
$postInfos = $this->news->getPosts(
|
||||
onlyFeatured: true,
|
||||
pagination: new Pagination($amount)
|
||||
);
|
||||
|
||||
if(!$decorate)
|
||||
return $postInfos;
|
||||
return iterator_to_array($postInfos);
|
||||
|
||||
$posts = [];
|
||||
|
||||
|
@ -154,7 +150,7 @@ class HomeRoutes extends RouteHandler {
|
|||
return $topics;
|
||||
}
|
||||
|
||||
#[Route('GET', '/')]
|
||||
#[HttpGet('/')]
|
||||
#[URLInfo('index', '/')]
|
||||
public function getIndex(...$args) {
|
||||
return $this->authInfo->isLoggedIn()
|
||||
|
@ -164,9 +160,9 @@ class HomeRoutes extends RouteHandler {
|
|||
|
||||
public function getHome() {
|
||||
$stats = $this->getStats();
|
||||
$onlineUserInfos = $this->getOnlineUsers();
|
||||
$onlineUserInfos = iterator_to_array($this->getOnlineUsers());
|
||||
$featuredNews = $this->getFeaturedNewsPosts(5, true);
|
||||
$changelog = $this->changelog->getChanges(pagination: new Pagination(10));
|
||||
$changelog = iterator_to_array($this->changelog->getChanges(pagination: new Pagination(10)));
|
||||
|
||||
$stats['users:online:recent'] = count($onlineUserInfos);
|
||||
|
||||
|
@ -201,7 +197,7 @@ class HomeRoutes extends RouteHandler {
|
|||
]);
|
||||
}
|
||||
|
||||
#[Route('GET', '/_landing')]
|
||||
#[HttpGet('/_landing')]
|
||||
public function getLanding($response, $request) {
|
||||
$config = $this->config->getValues([
|
||||
['social.embed_linked:b'],
|
||||
|
@ -221,7 +217,7 @@ class HomeRoutes extends RouteHandler {
|
|||
} else $linkedData = null;
|
||||
|
||||
$stats = $this->getStats();
|
||||
$onlineUserInfos = $this->getOnlineUsers();
|
||||
$onlineUserInfos = iterator_to_array($this->getOnlineUsers());
|
||||
$featuredNews = $this->getFeaturedNewsPosts(3, false);
|
||||
$popularTopics = $this->getPopularForumTopics($config['landing.forum_categories']);
|
||||
$activeTopics = $this->getActiveForumTopics($config['landing.forum_categories']);
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
<?php
|
||||
namespace Misuzu\Info;
|
||||
|
||||
use Index\Routing\Route;
|
||||
use Index\Routing\RouteHandler;
|
||||
use Index\Http\Routing\{HttpGet,RouteHandler};
|
||||
use Misuzu\Template;
|
||||
use Misuzu\Parsers\Parser;
|
||||
use Misuzu\URLs\URLInfo;
|
||||
|
@ -18,23 +17,16 @@ class InfoRoutes extends RouteHandler {
|
|||
'index' => 'Index Project » %s',
|
||||
];
|
||||
|
||||
private static function checkName(string $name): bool {
|
||||
return preg_match('#^([A-Za-z0-9_]+)$#', $name) === 1;
|
||||
}
|
||||
|
||||
#[Route('GET', '/info')]
|
||||
#[HttpGet('/info')]
|
||||
#[URLInfo('info-index', '/info')]
|
||||
public function getIndex() {
|
||||
return Template::renderRaw('info.index');
|
||||
}
|
||||
|
||||
#[Route('GET', '/info/:name')]
|
||||
#[HttpGet('/info/([A-Za-z0-9_]+)')]
|
||||
#[URLInfo('info', '/info/<title>')]
|
||||
#[URLInfo('info-doc', '/info/<title>')]
|
||||
public function getDocsPage($response, $request, string $name) {
|
||||
if(!self::checkName($name))
|
||||
return 404;
|
||||
|
||||
return $this->serveMarkdownDocument(
|
||||
sprintf('%s/%s.md', self::DOCS_PATH, $name)
|
||||
);
|
||||
|
@ -78,13 +70,11 @@ class InfoRoutes extends RouteHandler {
|
|||
return '';
|
||||
}
|
||||
|
||||
#[Route('GET', '/info/:project/:name')]
|
||||
#[HttpGet('/info/([A-Za-z0-9_]+)/([A-Za-z0-9_]+)')]
|
||||
#[URLInfo('info-project-doc', '/info/<project>/<title>')]
|
||||
public function getProjectPage($response, $request, string $project, string $name) {
|
||||
if(!array_key_exists($project, self::PROJECT_PATHS))
|
||||
return 404;
|
||||
if(!self::checkName($name))
|
||||
return 404;
|
||||
|
||||
$projectPath = self::PROJECT_PATHS[$project];
|
||||
$titleSuffix = array_key_exists($project, self::PROJECT_SUFFIXES) ? self::PROJECT_SUFFIXES[$project] : '';
|
||||
|
|
|
@ -1,11 +1,8 @@
|
|||
<?php
|
||||
namespace Misuzu;
|
||||
|
||||
use Index\Routing\Route;
|
||||
use Index\Routing\RouteHandler;
|
||||
use Misuzu\URLs\IURLSource;
|
||||
use Misuzu\URLs\URLInfo;
|
||||
use Misuzu\URLs\URLRegistry;
|
||||
use Index\Http\Routing\{HttpGet,RouteHandler};
|
||||
use Misuzu\URLs\{IURLSource,URLInfo,URLRegistry};
|
||||
|
||||
class LegacyRoutes extends RouteHandler implements IURLSource {
|
||||
public function __construct(
|
||||
|
@ -124,27 +121,27 @@ class LegacyRoutes extends RouteHandler implements IURLSource {
|
|||
$urls->register('manage-role', '/manage/users/role.php', ['r' => '<role>']);
|
||||
}
|
||||
|
||||
#[Route('GET', '/index.php')]
|
||||
#[HttpGet('/index.php')]
|
||||
public function getIndexPHP($response): void {
|
||||
$response->redirect($this->urls->format('index'), true);
|
||||
}
|
||||
|
||||
#[Route('GET', '/info.php')]
|
||||
#[HttpGet('/info.php')]
|
||||
public function getInfoPHP($response): void {
|
||||
$response->redirect($this->urls->format('info'), true);
|
||||
}
|
||||
|
||||
#[Route('GET', '/info.php/:name')]
|
||||
#[HttpGet('/info.php/([A-Za-z0-9_]+)')]
|
||||
public function getInfoDocsPHP($response, $request, string $name): void {
|
||||
$response->redirect($this->urls->format('info', ['title' => $name]), true);
|
||||
}
|
||||
|
||||
#[Route('GET', '/info.php/:project/:name')]
|
||||
#[HttpGet('/info.php/([A-Za-z0-9_]+)/([A-Za-z0-9_]+)')]
|
||||
public function getInfoProjectPHP($response, $request, string $project, string $name): void {
|
||||
$response->redirect($this->urls->format('info', ['title' => $project . '/' . $name]), true);
|
||||
}
|
||||
|
||||
#[Route('GET', '/news.php')]
|
||||
#[HttpGet('/news.php')]
|
||||
public function getNewsPHP($response, $request): void {
|
||||
$postId = (int)($request->getParam('n', FILTER_SANITIZE_NUMBER_INT) ?? $request->getParam('p', FILTER_SANITIZE_NUMBER_INT));
|
||||
|
||||
|
@ -159,28 +156,28 @@ class LegacyRoutes extends RouteHandler implements IURLSource {
|
|||
$response->redirect($location, true);
|
||||
}
|
||||
|
||||
#[Route('GET', '/news.php/rss')]
|
||||
#[HttpGet('/news.php/rss')]
|
||||
public function getNewsRssPHP($response, $request): void {
|
||||
$catId = (int)$request->getParam('c', FILTER_SANITIZE_NUMBER_INT);
|
||||
$location = $this->urls->format($catId > 0 ? 'news-category-feed-rss' : 'news-feed-rss', ['category' => $catId]);
|
||||
$response->redirect($location, true);
|
||||
}
|
||||
|
||||
#[Route('GET', '/news.php/atom')]
|
||||
#[HttpGet('/news.php/atom')]
|
||||
public function getNewsAtomPHP($response, $request): void {
|
||||
$catId = (int)$request->getParam('c', FILTER_SANITIZE_NUMBER_INT);
|
||||
$location = $this->urls->format($catId > 0 ? 'news-category-feed-atom' : 'news-feed-atom', ['category' => $catId]);
|
||||
$response->redirect($location, true);
|
||||
}
|
||||
|
||||
#[Route('GET', '/news/index.php')]
|
||||
#[HttpGet('/news/index.php')]
|
||||
public function getNewsIndexPHP($response, $request): void {
|
||||
$response->redirect($this->urls->format('news-index', [
|
||||
'page' => $request->getParam('page', FILTER_SANITIZE_NUMBER_INT),
|
||||
]), true);
|
||||
}
|
||||
|
||||
#[Route('GET', '/news/category.php')]
|
||||
#[HttpGet('/news/category.php')]
|
||||
public function getNewsCategoryPHP($response, $request): void {
|
||||
$response->redirect($this->urls->format('news-category', [
|
||||
'category' => $request->getParam('c', FILTER_SANITIZE_NUMBER_INT),
|
||||
|
@ -188,19 +185,19 @@ class LegacyRoutes extends RouteHandler implements IURLSource {
|
|||
]), true);
|
||||
}
|
||||
|
||||
#[Route('GET', '/news/post.php')]
|
||||
#[HttpGet('/news/post.php')]
|
||||
public function getNewsPostPHP($response, $request): void {
|
||||
$response->redirect($this->urls->format('news-post', [
|
||||
'post' => $request->getParam('p', FILTER_SANITIZE_NUMBER_INT),
|
||||
]), true);
|
||||
}
|
||||
|
||||
#[Route('GET', '/news/feed.php')]
|
||||
#[HttpGet('/news/feed.php')]
|
||||
public function getNewsFeedPHP($response, $request): int {
|
||||
return 400;
|
||||
}
|
||||
|
||||
#[Route('GET', '/news/feed.php/rss')]
|
||||
#[HttpGet('/news/feed.php/rss')]
|
||||
public function getNewsFeedRssPHP($response, $request): void {
|
||||
$catId = (int)$request->getParam('c', FILTER_SANITIZE_NUMBER_INT);
|
||||
$response->redirect($this->urls->format(
|
||||
|
@ -209,7 +206,7 @@ class LegacyRoutes extends RouteHandler implements IURLSource {
|
|||
), true);
|
||||
}
|
||||
|
||||
#[Route('GET', '/news/feed.php/atom')]
|
||||
#[HttpGet('/news/feed.php/atom')]
|
||||
public function getNewsFeedAtomPHP($response, $request): void {
|
||||
$catId = (int)$request->getParam('c', FILTER_SANITIZE_NUMBER_INT);
|
||||
$response->redirect($this->urls->format(
|
||||
|
@ -218,7 +215,7 @@ class LegacyRoutes extends RouteHandler implements IURLSource {
|
|||
), true);
|
||||
}
|
||||
|
||||
#[Route('GET', '/changelog.php')]
|
||||
#[HttpGet('/changelog.php')]
|
||||
public function getChangelogPHP($response, $request): void {
|
||||
$changeId = $request->getParam('c', FILTER_SANITIZE_NUMBER_INT);
|
||||
if($changeId) {
|
||||
|
@ -232,7 +229,7 @@ class LegacyRoutes extends RouteHandler implements IURLSource {
|
|||
]), true);
|
||||
}
|
||||
|
||||
#[Route('GET', '/auth.php')]
|
||||
#[HttpGet('/auth.php')]
|
||||
public function getAuthPHP($response, $request): void {
|
||||
$response->redirect($this->urls->format(match($request->getParam('m')) {
|
||||
'logout' => 'auth-logout',
|
||||
|
@ -243,44 +240,44 @@ class LegacyRoutes extends RouteHandler implements IURLSource {
|
|||
}), true);
|
||||
}
|
||||
|
||||
#[Route('GET', '/auth')]
|
||||
#[Route('GET', '/auth/index.php')]
|
||||
#[HttpGet('/auth')]
|
||||
#[HttpGet('/auth/index.php')]
|
||||
public function getAuthIndexPHP($response, $request): void {
|
||||
$response->redirect($this->urls->format('auth-login'), true);
|
||||
}
|
||||
|
||||
#[Route('GET', '/settings.php')]
|
||||
#[HttpGet('/settings.php')]
|
||||
public function getSettingsPHP($response): void {
|
||||
$response->redirect($this->urls->format('settings-index'), true);
|
||||
}
|
||||
|
||||
#[Route('GET', '/settings')]
|
||||
#[HttpGet('/settings')]
|
||||
public function getSettingsIndex($response): void {
|
||||
$response->redirect($this->urls->format('settings-account'));
|
||||
}
|
||||
|
||||
#[Route('GET', '/settings/index.php')]
|
||||
#[HttpGet('/settings/index.php')]
|
||||
public function getSettingsIndexPHP($response): void {
|
||||
$response->redirect($this->urls->format('settings-account'), true);
|
||||
}
|
||||
|
||||
#[Route('GET', '/manage')]
|
||||
#[HttpGet('/manage')]
|
||||
#[URLInfo('manage-index', '/manage')]
|
||||
public function getManageIndex($response): void {
|
||||
$response->redirect($this->urls->format('manage-general-overview'));
|
||||
}
|
||||
|
||||
#[Route('GET', '/manage/index.php')]
|
||||
#[HttpGet('/manage/index.php')]
|
||||
public function getManageIndexPHP($response): void {
|
||||
$response->redirect($this->urls->format('manage-general-overview'), true);
|
||||
}
|
||||
|
||||
#[Route('GET', '/manage/news')]
|
||||
#[HttpGet('/manage/news')]
|
||||
public function getManageNewsIndex($response): void {
|
||||
$response->redirect($this->urls->format('manage-news-categories'));
|
||||
}
|
||||
|
||||
#[Route('GET', '/manage/news/index.php')]
|
||||
#[HttpGet('/manage/news/index.php')]
|
||||
public function getManageNewsIndexPHP($response): void {
|
||||
$response->redirect($this->urls->format('manage-news-categories'), true);
|
||||
}
|
||||
|
|
152
src/Messages/MessageInfo.php
Normal file
152
src/Messages/MessageInfo.php
Normal file
|
@ -0,0 +1,152 @@
|
|||
<?php
|
||||
namespace Misuzu\Messages;
|
||||
|
||||
use Index\DateTime;
|
||||
use Index\Data\IDbResult;
|
||||
use Misuzu\Parsers\Parser;
|
||||
|
||||
class MessageInfo {
|
||||
public function __construct(
|
||||
private string $messageId,
|
||||
private string $ownerId,
|
||||
private ?string $authorId,
|
||||
private ?string $recipientId,
|
||||
private ?string $replyTo,
|
||||
private string $title,
|
||||
private string $body,
|
||||
private int $parser,
|
||||
private int $created,
|
||||
private ?int $sent,
|
||||
private ?int $read,
|
||||
private ?int $deleted,
|
||||
) {}
|
||||
|
||||
public static function fromResult(IDbResult $result): MessageInfo {
|
||||
return new MessageInfo(
|
||||
messageId: $result->getString(0),
|
||||
ownerId: $result->getString(1),
|
||||
authorId: $result->getStringOrNull(2),
|
||||
recipientId: $result->getStringOrNull(3),
|
||||
replyTo: $result->getStringOrNull(4),
|
||||
title: $result->getString(5),
|
||||
body: $result->getString(6),
|
||||
parser: $result->getInteger(7),
|
||||
created: $result->getInteger(8),
|
||||
sent: $result->getIntegerOrNull(9),
|
||||
read: $result->getIntegerOrNull(10),
|
||||
deleted: $result->getIntegerOrNull(11),
|
||||
);
|
||||
}
|
||||
|
||||
public function getId(): string {
|
||||
return $this->messageId;
|
||||
}
|
||||
|
||||
public function getOwnerId(): string {
|
||||
return $this->ownerId;
|
||||
}
|
||||
|
||||
public function hasAuthorId(): bool {
|
||||
return $this->authorId !== null;
|
||||
}
|
||||
|
||||
public function getAuthorId(): ?string {
|
||||
return $this->authorId;
|
||||
}
|
||||
|
||||
public function hasRecipientId(): bool {
|
||||
return $this->recipientId !== null;
|
||||
}
|
||||
|
||||
public function getRecipientId(): ?string {
|
||||
return $this->recipientId;
|
||||
}
|
||||
|
||||
public function hasReplyToId(): bool {
|
||||
return $this->replyTo !== null;
|
||||
}
|
||||
|
||||
public function getReplyToId(): ?string {
|
||||
return $this->replyTo;
|
||||
}
|
||||
|
||||
public function getTitle(): string {
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
public function getBody(): string {
|
||||
return $this->body;
|
||||
}
|
||||
|
||||
public function getParser(): int {
|
||||
return $this->parser;
|
||||
}
|
||||
|
||||
public function isBodyPlain(): bool {
|
||||
return $this->parser === Parser::PLAIN;
|
||||
}
|
||||
|
||||
public function isBodyBBCode(): bool {
|
||||
return $this->parser === Parser::BBCODE;
|
||||
}
|
||||
|
||||
public function isBodyMarkdown(): bool {
|
||||
return $this->parser === Parser::MARKDOWN;
|
||||
}
|
||||
|
||||
public function getCreatedTime(): int {
|
||||
return $this->created;
|
||||
}
|
||||
|
||||
public function getCreatedAt(): DateTime {
|
||||
return DateTime::fromUnixTimeSeconds($this->created);
|
||||
}
|
||||
|
||||
public function isSent(): bool {
|
||||
return $this->sent !== null;
|
||||
}
|
||||
|
||||
public function getSentTime(): ?int {
|
||||
return $this->sent;
|
||||
}
|
||||
|
||||
public function getSentAt(): ?DateTime {
|
||||
return $this->sent === null ? null : DateTime::fromUnixTimeSeconds($this->sent);
|
||||
}
|
||||
|
||||
public function isRead(): bool {
|
||||
return $this->read !== null;
|
||||
}
|
||||
|
||||
public function getReadTime(): ?int {
|
||||
return $this->read;
|
||||
}
|
||||
|
||||
public function getReadAt(): ?DateTime {
|
||||
return $this->read === null ? null : DateTime::fromUnixTimeSeconds($this->read);
|
||||
}
|
||||
|
||||
public function isDeleted(): bool {
|
||||
return $this->deleted !== null;
|
||||
}
|
||||
|
||||
public function getDeletedTime(): ?int {
|
||||
return $this->deleted;
|
||||
}
|
||||
|
||||
public function getDeletedAt(): ?DateTime {
|
||||
return $this->deleted === null ? null : DateTime::fromUnixTimeSeconds($this->deleted);
|
||||
}
|
||||
|
||||
public function getDisplayTime(): int {
|
||||
if($this->isSent())
|
||||
return $this->getSentTime();
|
||||
return $this->getCreatedTime();
|
||||
}
|
||||
|
||||
public function getDisplayAt(): DateTime {
|
||||
if($this->isSent())
|
||||
return $this->getSentAt();
|
||||
return $this->getCreatedAt();
|
||||
}
|
||||
}
|
15
src/Messages/MessagesContext.php
Normal file
15
src/Messages/MessagesContext.php
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
namespace Misuzu\Messages;
|
||||
|
||||
use Index\Data\IDbConnection;
|
||||
class MessagesContext {
|
||||
private MessagesDatabase $database;
|
||||
|
||||
public function __construct(IDbConnection $dbConn) {
|
||||
$this->database = new MessagesDatabase($dbConn);
|
||||
}
|
||||
|
||||
public function getDatabase(): MessagesDatabase {
|
||||
return $this->database;
|
||||
}
|
||||
}
|
393
src/Messages/MessagesDatabase.php
Normal file
393
src/Messages/MessagesDatabase.php
Normal file
|
@ -0,0 +1,393 @@
|
|||
<?php
|
||||
namespace Misuzu\Messages;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
use Index\DateTime;
|
||||
use Index\Data\{DbStatementCache,DbTools,IDbConnection};
|
||||
use Misuzu\Pagination;
|
||||
use Misuzu\Users\UserInfo;
|
||||
|
||||
class MessagesDatabase {
|
||||
private DbStatementCache $cache;
|
||||
|
||||
public function __construct(IDbConnection $dbConn) {
|
||||
$this->cache = new DbStatementCache($dbConn);
|
||||
}
|
||||
|
||||
public function countMessages(
|
||||
UserInfo|string|null $ownerInfo = null,
|
||||
UserInfo|string|null $authorInfo = null,
|
||||
UserInfo|string|null $recipientInfo = null,
|
||||
MessageInfo|string|null $repliesFor = null,
|
||||
MessageInfo|string|null $replyTo = null,
|
||||
?bool $sent = null,
|
||||
?bool $read = null,
|
||||
?bool $deleted = null
|
||||
): int {
|
||||
$hasOwnerInfo = $ownerInfo !== null;
|
||||
$hasAuthorInfo = $authorInfo !== null;
|
||||
$hasRecipientInfo = $recipientInfo !== null;
|
||||
$hasRepliesFor = $repliesFor !== null;
|
||||
$hasReplyTo = $replyTo !== null;
|
||||
$hasSent = $sent !== null;
|
||||
$hasRead = $read !== null;
|
||||
$hasDeleted = $deleted !== null;
|
||||
|
||||
$args = 0;
|
||||
$query = 'SELECT COUNT(*) FROM msz_messages';
|
||||
if($hasOwnerInfo) {
|
||||
++$args;
|
||||
$query .= ' WHERE msg_owner_id = ?';
|
||||
}
|
||||
if($hasAuthorInfo)
|
||||
$query .= sprintf(' %s msg_author_id = ?', ++$args > 1 ? 'AND' : 'WHERE');
|
||||
if($hasRecipientInfo)
|
||||
$query .= sprintf(' %s msg_recipient_id = ?', ++$args > 1 ? 'AND' : 'WHERE');
|
||||
if($hasRepliesFor)
|
||||
$query .= sprintf(' %s msg_reply_to = ?', ++$args > 1 ? 'AND' : 'WHERE');
|
||||
if($hasReplyTo) {
|
||||
$query .= sprintf(' %s msg_id = ', ++$args > 1 ? 'AND' : 'WHERE');
|
||||
|
||||
if($replyTo instanceof MessageInfo)
|
||||
$query .= '?';
|
||||
else
|
||||
$query .= '(SELECT reply_to FROM msz_messages WHERE msg_id = ?)';
|
||||
}
|
||||
if($hasSent)
|
||||
$query .= sprintf(' %s msg_sent %s NULL', ++$args > 1 ? 'AND' : 'WHERE', $sent ? 'IS NOT' : 'IS');
|
||||
if($hasRead)
|
||||
$query .= sprintf(' %s msg_read %s NULL', ++$args > 1 ? 'AND' : 'WHERE', $read ? 'IS NOT' : 'IS');
|
||||
if($hasDeleted)
|
||||
$query .= sprintf(' %s msg_deleted %s NULL', ++$args > 1 ? 'AND' : 'WHERE', $deleted ? 'IS NOT' : 'IS');
|
||||
$query .= ' ORDER BY msg_created DESC';
|
||||
|
||||
$args = 0;
|
||||
$stmt = $this->cache->get($query);
|
||||
|
||||
if($hasOwnerInfo)
|
||||
$stmt->addParameter(++$args, $ownerInfo instanceof UserInfo ? $ownerInfo->getId() : $ownerInfo);
|
||||
if($hasAuthorInfo)
|
||||
$stmt->addParameter(++$args, $authorInfo instanceof UserInfo ? $authorInfo->getId() : $authorInfo);
|
||||
if($hasRecipientInfo)
|
||||
$stmt->addParameter(++$args, $recipientInfo instanceof UserInfo ? $recipientInfo->getId() : $recipientInfo);
|
||||
if($hasRepliesFor)
|
||||
$stmt->addParameter(++$args, $repliesFor instanceof MessageInfo ? $repliesFor->getId() : $repliesFor);
|
||||
if($hasReplyTo)
|
||||
$stmt->addParameter(++$args, $replyTo instanceof MessageInfo ? $replyTo->getReplyToId() : $replyTo);
|
||||
|
||||
$stmt->execute();
|
||||
$result = $stmt->getResult();
|
||||
|
||||
return $result->next() ? $result->getInteger(0) : 0;
|
||||
}
|
||||
|
||||
public function getMessages(
|
||||
UserInfo|string|null $ownerInfo = null,
|
||||
UserInfo|string|null $authorInfo = null,
|
||||
UserInfo|string|null $recipientInfo = null,
|
||||
MessageInfo|string|null $repliesFor = null,
|
||||
MessageInfo|string|null $replyTo = null,
|
||||
?bool $sent = null,
|
||||
?bool $read = null,
|
||||
?bool $deleted = null,
|
||||
?Pagination $pagination = null
|
||||
): iterable {
|
||||
$hasOwnerInfo = $ownerInfo !== null;
|
||||
$hasAuthorInfo = $authorInfo !== null;
|
||||
$hasRecipientInfo = $recipientInfo !== null;
|
||||
$hasRepliesFor = $repliesFor !== null;
|
||||
$hasReplyTo = $replyTo !== null;
|
||||
$hasSent = $sent !== null;
|
||||
$hasRead = $read !== null;
|
||||
$hasDeleted = $deleted !== null;
|
||||
$hasPagination = $pagination !== null;
|
||||
|
||||
$args = 0;
|
||||
$query = 'SELECT msg_id, msg_owner_id, msg_author_id, msg_recipient_id, msg_reply_to, msg_title, msg_body, msg_parser, UNIX_TIMESTAMP(msg_created), UNIX_TIMESTAMP(msg_sent), UNIX_TIMESTAMP(msg_read), UNIX_TIMESTAMP(msg_deleted) FROM msz_messages';
|
||||
if($hasOwnerInfo) {
|
||||
++$args;
|
||||
$query .= ' WHERE msg_owner_id = ?';
|
||||
}
|
||||
if($hasAuthorInfo)
|
||||
$query .= sprintf(' %s msg_author_id = ?', ++$args > 1 ? 'AND' : 'WHERE');
|
||||
if($hasRecipientInfo)
|
||||
$query .= sprintf(' %s msg_recipient_id = ?', ++$args > 1 ? 'AND' : 'WHERE');
|
||||
if($hasRepliesFor)
|
||||
$query .= sprintf(' %s msg_reply_to = ?', ++$args > 1 ? 'AND' : 'WHERE');
|
||||
if($hasReplyTo) {
|
||||
$query .= sprintf(' %s msg_id = ', ++$args > 1 ? 'AND' : 'WHERE');
|
||||
|
||||
if($replyTo instanceof MessageInfo)
|
||||
$query .= '?';
|
||||
else
|
||||
$query .= '(SELECT reply_to FROM msz_messages WHERE msg_id = ?)';
|
||||
}
|
||||
if($hasSent)
|
||||
$query .= sprintf(' %s msg_sent %s NULL', ++$args > 1 ? 'AND' : 'WHERE', $sent ? 'IS NOT' : 'IS');
|
||||
if($hasRead)
|
||||
$query .= sprintf(' %s msg_read %s NULL', ++$args > 1 ? 'AND' : 'WHERE', $read ? 'IS NOT' : 'IS');
|
||||
if($hasDeleted)
|
||||
$query .= sprintf(' %s msg_deleted %s NULL', ++$args > 1 ? 'AND' : 'WHERE', $deleted ? 'IS NOT' : 'IS');
|
||||
$query .= ' ORDER BY msg_created DESC';
|
||||
if($hasPagination)
|
||||
$query .= ' LIMIT ? OFFSET ?';
|
||||
|
||||
$args = 0;
|
||||
$stmt = $this->cache->get($query);
|
||||
|
||||
if($hasOwnerInfo)
|
||||
$stmt->addParameter(++$args, $ownerInfo instanceof UserInfo ? $ownerInfo->getId() : $ownerInfo);
|
||||
if($hasAuthorInfo)
|
||||
$stmt->addParameter(++$args, $authorInfo instanceof UserInfo ? $authorInfo->getId() : $authorInfo);
|
||||
if($hasRecipientInfo)
|
||||
$stmt->addParameter(++$args, $recipientInfo instanceof UserInfo ? $recipientInfo->getId() : $recipientInfo);
|
||||
if($hasRepliesFor)
|
||||
$stmt->addParameter(++$args, $repliesFor instanceof MessageInfo ? $repliesFor->getId() : $repliesFor);
|
||||
if($hasReplyTo)
|
||||
$stmt->addParameter(++$args, $replyTo instanceof MessageInfo ? $replyTo->getReplyToId() : $replyTo);
|
||||
if($hasPagination) {
|
||||
$stmt->addParameter(++$args, $pagination->getRange());
|
||||
$stmt->addParameter(++$args, $pagination->getOffset());
|
||||
}
|
||||
|
||||
$stmt->execute();
|
||||
|
||||
return $stmt->getResult()->getIterator(MessageInfo::fromResult(...));
|
||||
}
|
||||
|
||||
public function getMessageInfo(
|
||||
UserInfo|string $ownerInfo,
|
||||
MessageInfo|string $messageInfoOrId,
|
||||
bool $useReplyTo = false
|
||||
): MessageInfo {
|
||||
$stmt = $this->cache->get(sprintf(
|
||||
'SELECT msg_id, msg_owner_id, msg_author_id, msg_recipient_id, msg_reply_to, msg_title, msg_body, msg_parser, UNIX_TIMESTAMP(msg_created), UNIX_TIMESTAMP(msg_sent), UNIX_TIMESTAMP(msg_read), UNIX_TIMESTAMP(msg_deleted) FROM msz_messages WHERE msg_id = %s AND msg_owner_id = ?',
|
||||
!$useReplyTo || $messageInfoOrId instanceof MessageInfo ? '?' : '(SELECT msg_reply_to FROM msz_messages WHERE msg_id = ?)'
|
||||
));
|
||||
|
||||
if($messageInfoOrId instanceof MessageInfo)
|
||||
$stmt->addParameter(1, $useReplyTo ? $messageInfoOrId->getReplyToId() : $messageInfoOrId->getId());
|
||||
else
|
||||
$stmt->addParameter(1, $messageInfoOrId);
|
||||
$stmt->addParameter(2, $ownerInfo instanceof UserInfo ? $ownerInfo->getId() : $ownerInfo);
|
||||
$stmt->execute();
|
||||
|
||||
$result = $stmt->getResult();
|
||||
if(!$result->next())
|
||||
throw new RuntimeException('Message not found.');
|
||||
|
||||
return MessageInfo::fromResult($result);
|
||||
}
|
||||
|
||||
public function createMessage(
|
||||
string $messageId,
|
||||
UserInfo|string $ownerInfo,
|
||||
UserInfo|string|null $authorInfo,
|
||||
UserInfo|string|null $recipientInfo,
|
||||
string $title,
|
||||
string $body,
|
||||
int $parser,
|
||||
MessageInfo|string|null $replyTo = null,
|
||||
DateTime|int|null $sentAt = null,
|
||||
DateTime|int|null $readAt = null
|
||||
): MessageInfo {
|
||||
$stmt = $this->cache->get('INSERT INTO msz_messages (msg_id, msg_owner_id, msg_author_id, msg_recipient_id, msg_reply_to, msg_title, msg_body, msg_parser, msg_sent, msg_read) VALUES (?, ?, ?, ?, ?, ?, ?, ?, FROM_UNIXTIME(?), FROM_UNIXTIME(?))');
|
||||
$stmt->addParameter(1, $messageId);
|
||||
$stmt->addParameter(2, $ownerInfo instanceof UserInfo ? $ownerInfo->getId() : $ownerInfo);
|
||||
$stmt->addParameter(3, $authorInfo instanceof UserInfo ? $authorInfo->getId() : $authorInfo);
|
||||
$stmt->addParameter(4, $recipientInfo instanceof UserInfo ? $recipientInfo->getId() : $recipientInfo);
|
||||
$stmt->addParameter(5, $replyTo instanceof MessageInfo ? $replyTo->getId() : $replyTo);
|
||||
$stmt->addParameter(6, $title);
|
||||
$stmt->addParameter(7, $body);
|
||||
$stmt->addParameter(8, $parser);
|
||||
$stmt->addParameter(9, $sentAt instanceof DateTime ? $sentAt->getUnixTimeSeconds() : $sentAt);
|
||||
$stmt->addParameter(10, $readAt instanceof DateTime ? $readAt->getUnixTimeSeconds() : $readAt);
|
||||
$stmt->execute();
|
||||
|
||||
return $this->getMessageInfo($ownerInfo, $messageId);
|
||||
}
|
||||
|
||||
public function updateMessage(
|
||||
UserInfo|string|null $ownerInfo = null,
|
||||
MessageInfo|string|null $messageInfo = null,
|
||||
?string $title = null,
|
||||
?string $body = null,
|
||||
?int $parser = null,
|
||||
DateTime|int|null|false $sentAt = false,
|
||||
DateTime|int|null|false $readAt = false
|
||||
): void {
|
||||
$setQuery = [];
|
||||
$setValues = [];
|
||||
$whereQuery = [];
|
||||
$whereValues = [];
|
||||
|
||||
if($ownerInfo !== null) {
|
||||
$whereQuery[] = 'msg_owner_id = ?';
|
||||
$whereValues[] = $ownerInfo instanceof UserInfo ? $ownerInfo->getId() : $ownerInfo;
|
||||
}
|
||||
|
||||
if($messageInfo !== null) {
|
||||
$whereQuery[] = 'msg_id = ?';
|
||||
$whereValues[] = $messageInfo instanceof MessageInfo ? $messageInfo->getId() : $messageInfo;
|
||||
}
|
||||
|
||||
if($title !== null) {
|
||||
$setQuery[] = 'msg_title = ?';
|
||||
$setValues[] = $title;
|
||||
}
|
||||
|
||||
if($body !== null) {
|
||||
$setQuery[] = 'msg_body = ?';
|
||||
$setValues[] = $body;
|
||||
}
|
||||
|
||||
if($parser !== null) {
|
||||
$setQuery[] = 'msg_parser = ?';
|
||||
$setValues[] = $parser;
|
||||
}
|
||||
|
||||
if($sentAt !== false) {
|
||||
$setQuery[] = 'msg_sent = FROM_UNIXTIME(?)';
|
||||
$setValues[] = $sentAt instanceof DateTime ? $sentAt->getUnixTimeSeconds() : $sentAt;
|
||||
}
|
||||
|
||||
if($readAt !== false) {
|
||||
$setQuery[] = 'msg_read = FROM_UNIXTIME(?)';
|
||||
$setValues[] = $readAt instanceof DateTime ? $readAt->getUnixTimeSeconds() : $readAt;
|
||||
}
|
||||
|
||||
if(empty($whereQuery))
|
||||
throw new InvalidArgumentException('$ownerInfo or $messageInfo must be specified.');
|
||||
if(empty($setQuery))
|
||||
return;
|
||||
|
||||
$args = 0;
|
||||
$stmt = $this->cache->get(sprintf(
|
||||
'UPDATE msz_messages SET %s WHERE %s',
|
||||
implode(', ', $setQuery),
|
||||
implode(' AND ', $whereQuery)
|
||||
));
|
||||
|
||||
foreach($setValues as $value)
|
||||
$stmt->addParameter(++$args, $value);
|
||||
foreach($whereValues as $value)
|
||||
$stmt->addParameter(++$args, $value);
|
||||
|
||||
$stmt->execute();
|
||||
}
|
||||
|
||||
public function deleteMessages(
|
||||
UserInfo|string|null $ownerInfo,
|
||||
MessageInfo|array|string|null $messageInfos
|
||||
): void {
|
||||
$hasOwnerInfo = $ownerInfo !== null;
|
||||
$hasMessageInfos = $messageInfos !== null;
|
||||
|
||||
$query = 'UPDATE msz_messages SET msg_deleted = NOW() WHERE msg_deleted IS NULL';
|
||||
if($hasOwnerInfo)
|
||||
$query .= ' AND msg_owner_id = ?';
|
||||
if($hasMessageInfos) {
|
||||
if(!is_array($messageInfos))
|
||||
$messageInfos = [$messageInfos];
|
||||
|
||||
$query .= sprintf(
|
||||
' AND msg_id IN (%s)',
|
||||
DbTools::prepareListString($messageInfos)
|
||||
);
|
||||
}
|
||||
|
||||
$args = 0;
|
||||
$stmt = $this->cache->get($query);
|
||||
|
||||
if($hasOwnerInfo)
|
||||
$stmt->addParameter(++$args, $ownerInfo instanceof UserInfo ? $ownerInfo->getId() : $ownerInfo);
|
||||
if($hasMessageInfos)
|
||||
foreach($messageInfos as $messageInfo) {
|
||||
if(is_string($messageInfo))
|
||||
$stmt->addParameter(++$args, $messageInfo);
|
||||
elseif($messageInfo instanceof MessageInfo)
|
||||
$stmt->addParameter(++$args, $messageInfo->getId());
|
||||
else
|
||||
throw new InvalidArgumentException('$messageInfos must be an array of strings or MessageInfo instances.');
|
||||
}
|
||||
|
||||
$stmt->execute();
|
||||
}
|
||||
|
||||
public function restoreMessages(
|
||||
UserInfo|string|null $ownerInfo,
|
||||
MessageInfo|array|string|null $messageInfos
|
||||
): void {
|
||||
$hasOwnerInfo = $ownerInfo !== null;
|
||||
$hasMessageInfos = $messageInfos !== null;
|
||||
|
||||
$query = 'UPDATE msz_messages SET msg_deleted = NULL WHERE msg_deleted IS NOT NULL';
|
||||
if($hasOwnerInfo)
|
||||
$query .= ' AND msg_owner_id = ?';
|
||||
if($hasMessageInfos) {
|
||||
if(!is_array($messageInfos))
|
||||
$messageInfos = [$messageInfos];
|
||||
|
||||
$query .= sprintf(
|
||||
' AND msg_id IN (%s)',
|
||||
DbTools::prepareListString($messageInfos)
|
||||
);
|
||||
}
|
||||
|
||||
$args = 0;
|
||||
$stmt = $this->cache->get($query);
|
||||
|
||||
if($hasOwnerInfo)
|
||||
$stmt->addParameter(++$args, $ownerInfo instanceof UserInfo ? $ownerInfo->getId() : $ownerInfo);
|
||||
if($hasMessageInfos)
|
||||
foreach($messageInfos as $messageInfo) {
|
||||
if(is_string($messageInfo))
|
||||
$stmt->addParameter(++$args, $messageInfo);
|
||||
elseif($messageInfo instanceof MessageInfo)
|
||||
$stmt->addParameter(++$args, $messageInfo->getId());
|
||||
else
|
||||
throw new InvalidArgumentException('$messageInfos must be an array of strings or MessageInfo instances.');
|
||||
}
|
||||
|
||||
$stmt->execute();
|
||||
}
|
||||
|
||||
public function nukeMessages(
|
||||
UserInfo|string|null $ownerInfo,
|
||||
MessageInfo|array|string|null $messageInfos
|
||||
): void {
|
||||
$hasOwnerInfo = $ownerInfo !== null;
|
||||
$hasMessageInfos = $messageInfos !== null;
|
||||
|
||||
$query = 'DELETE FROM msz_messages WHERE msg_deleted IS NOT NULL';
|
||||
if($hasOwnerInfo)
|
||||
$query .= ' AND msg_owner_id = ?';
|
||||
if($hasMessageInfos) {
|
||||
if(!is_array($messageInfos))
|
||||
$messageInfos = [$messageInfos];
|
||||
|
||||
$query .= sprintf(
|
||||
' AND msg_id IN (%s)',
|
||||
DbTools::prepareListString($messageInfos)
|
||||
);
|
||||
}
|
||||
|
||||
$args = 0;
|
||||
$stmt = $this->cache->get($query);
|
||||
|
||||
if($hasOwnerInfo)
|
||||
$stmt->addParameter(++$args, $ownerInfo instanceof UserInfo ? $ownerInfo->getId() : $ownerInfo);
|
||||
if($hasMessageInfos)
|
||||
foreach($messageInfos as $messageInfo) {
|
||||
if(is_string($messageInfo))
|
||||
$stmt->addParameter(++$args, $messageInfo);
|
||||
elseif($messageInfo instanceof MessageInfo)
|
||||
$stmt->addParameter(++$args, $messageInfo->getId());
|
||||
else
|
||||
throw new InvalidArgumentException('$messageInfos must be an array of strings or MessageInfo instances.');
|
||||
}
|
||||
|
||||
$stmt->execute();
|
||||
}
|
||||
}
|
632
src/Messages/MessagesRoutes.php
Normal file
632
src/Messages/MessagesRoutes.php
Normal file
|
@ -0,0 +1,632 @@
|
|||
<?php
|
||||
namespace Misuzu\Messages;
|
||||
|
||||
use stdClass;
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
use Index\XString;
|
||||
use Index\Http\Routing\{HttpGet,HttpMiddleware,HttpPost,RouteHandler};
|
||||
use Syokuhou\IConfig;
|
||||
use Misuzu\{CSRF,Pagination,Perm,Template};
|
||||
use Misuzu\Auth\AuthInfo;
|
||||
use Misuzu\Parsers\Parser;
|
||||
use Misuzu\Perms\Permissions;
|
||||
use Misuzu\URLs\{URLInfo,URLRegistry};
|
||||
use Misuzu\Users\{UsersContext,UserInfo};
|
||||
|
||||
class MessagesRoutes extends RouteHandler {
|
||||
public const FOLDER_META = [
|
||||
'inbox' => [ 'title' => 'Inbox', 'icon' => 'fas fa-inbox fa-fw' ],
|
||||
'drafts' => [ 'title' => 'Drafts', 'icon' => 'fas fa-pencil-alt fa-fw' ],
|
||||
'sent' => [ 'title' => 'Sent', 'icon' => 'fas fa-paper-plane fa-fw' ],
|
||||
'trash' => [ 'title' => 'Trash', 'icon' => 'fas fa-trash-alt fa-fw' ],
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
private IConfig $config,
|
||||
private URLRegistry $urls,
|
||||
private AuthInfo $authInfo,
|
||||
private MessagesContext $msgsCtx,
|
||||
private UsersContext $usersCtx,
|
||||
private Permissions $perms
|
||||
) {}
|
||||
|
||||
private bool $canSendMessages;
|
||||
|
||||
#[HttpMiddleware('/messages')]
|
||||
public function checkAccess($response, $request) {
|
||||
// should probably be a permission or something too
|
||||
if(!$this->authInfo->isLoggedIn())
|
||||
return 401;
|
||||
|
||||
$globalPerms = $this->authInfo->getPerms('global');
|
||||
if(!$globalPerms->check(Perm::G_MESSAGES_VIEW))
|
||||
return 403;
|
||||
|
||||
$this->canSendMessages = $globalPerms->check(Perm::G_MESSAGES_SEND)
|
||||
&& !$this->usersCtx->hasActiveBan($this->authInfo->getUserInfo());
|
||||
|
||||
if($request->getMethod() === 'POST' && $request->isFormContent()) {
|
||||
$content = $request->getContent();
|
||||
if(!$content->hasParam('_csrfp') || !CSRF::validate((string)$content->getParam('_csrfp')))
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'msgs:verify',
|
||||
'text' => 'Request verification failed! Refresh the page and try again.',
|
||||
],
|
||||
];
|
||||
|
||||
$response->setHeader('X-CSRFP-Token', CSRF::token());
|
||||
}
|
||||
}
|
||||
|
||||
private function populateMessage(MessageInfo $messageInfo): object {
|
||||
$message = new stdClass;
|
||||
|
||||
$message->info = $messageInfo;
|
||||
$message->author_info = $messageInfo->hasAuthorId() ? $this->usersCtx->getUserInfo($messageInfo->getAuthorId(), 'id') : null;
|
||||
$message->author_colour = $this->usersCtx->getUserColour($message->author_info);
|
||||
$message->recipient_info = $messageInfo->hasRecipientId() ? $this->usersCtx->getUserInfo($messageInfo->getRecipientId(), 'id') : null;
|
||||
$message->recipient_colour = $this->usersCtx->getUserColour($message->recipient_info);
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
#[HttpGet('/messages')]
|
||||
#[URLInfo('messages-index', '/messages', ['folder' => '<folder>', 'page' => '<page>'])]
|
||||
public function getIndex($response, $request, string $folderName = '') {
|
||||
$folderName = (string)$request->getParam('folder');
|
||||
if($folderName === '')
|
||||
$folderName = 'inbox';
|
||||
|
||||
if(!array_key_exists($folderName, self::FOLDER_META))
|
||||
return 404;
|
||||
|
||||
$folderInbox = $folderName === 'inbox';
|
||||
$folderDrafts = $folderName === 'drafts';
|
||||
$folderSent = $folderName === 'sent';
|
||||
$folderTrash = $folderName === 'trash';
|
||||
|
||||
$selfInfo = $this->authInfo->getUserInfo();
|
||||
$msgsDb = $this->msgsCtx->getDatabase();
|
||||
|
||||
$authorInfo = !$folderTrash && $folderSent ? $selfInfo : null;
|
||||
$recipientInfo = !$folderTrash && $folderInbox ? $selfInfo : null;
|
||||
$sent = $folderTrash ? null : !$folderDrafts;
|
||||
$deleted = $folderTrash;
|
||||
|
||||
$pagination = new Pagination($msgsDb->countMessages(
|
||||
ownerInfo: $selfInfo,
|
||||
authorInfo: $authorInfo,
|
||||
recipientInfo: $recipientInfo,
|
||||
sent: $sent,
|
||||
deleted: $deleted,
|
||||
), 50, 'page');
|
||||
|
||||
$messageInfos = $msgsDb->getMessages(
|
||||
ownerInfo: $selfInfo,
|
||||
authorInfo: $authorInfo,
|
||||
recipientInfo: $recipientInfo,
|
||||
sent: $sent,
|
||||
deleted: $deleted,
|
||||
pagination: $pagination,
|
||||
);
|
||||
|
||||
$messages = [];
|
||||
foreach($messageInfos as $messageInfo)
|
||||
$messages[] = $this->populateMessage($messageInfo);
|
||||
|
||||
return Template::renderRaw('messages.index', [
|
||||
'can_send_messages' => $this->canSendMessages,
|
||||
'folder_name' => $folderName,
|
||||
'folder_meta' => self::FOLDER_META,
|
||||
'folder_messages' => $messages,
|
||||
'folder_pagination' => $pagination,
|
||||
]);
|
||||
}
|
||||
|
||||
#[HttpGet('/messages/stats')]
|
||||
#[URLInfo('messages-stats', '/messages/stats')]
|
||||
public function getStats() {
|
||||
$selfInfo = $this->authInfo->getUserInfo();
|
||||
$msgsDb = $this->msgsCtx->getDatabase();
|
||||
|
||||
return [
|
||||
'unread' => $msgsDb->countMessages(
|
||||
ownerInfo: $selfInfo,
|
||||
recipientInfo: $selfInfo,
|
||||
sent: true,
|
||||
deleted: false,
|
||||
read: false,
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
#[HttpPost('/messages/recipient')]
|
||||
#[URLInfo('messages-recipient', '/messages/recipient')]
|
||||
public function postRecipient($response, $request) {
|
||||
if(!$request->isFormContent())
|
||||
return 400;
|
||||
if(!$this->canSendMessages)
|
||||
return 403;
|
||||
|
||||
$content = $request->getContent();
|
||||
$name = trim((string)$content->getParam('name'));
|
||||
|
||||
// flappy hacks
|
||||
if(str_starts_with(mb_strtolower($name), 'flappyzor'))
|
||||
$name = 14;
|
||||
|
||||
$userInfo = null;
|
||||
if(!empty($name))
|
||||
try {
|
||||
$userInfo = $this->usersCtx->getUserInfo($name, 'messaging');
|
||||
} catch(InvalidArgumentException $ex) {
|
||||
} catch(RuntimeException $ex) {}
|
||||
|
||||
if($userInfo === null)
|
||||
return [
|
||||
'avatar' => $this->urls->format('user-avatar', [
|
||||
'res' => 200,
|
||||
]),
|
||||
];
|
||||
|
||||
return [
|
||||
'id' => $userInfo->getId(),
|
||||
'name' => $userInfo->getName(),
|
||||
'ban' => $this->usersCtx->hasActiveBan($userInfo),
|
||||
'avatar' => $this->urls->format('user-avatar', [
|
||||
'user' => $userInfo->getId(),
|
||||
'res' => 200,
|
||||
]),
|
||||
];
|
||||
}
|
||||
|
||||
#[HttpGet('/messages/compose')]
|
||||
#[URLInfo('messages-compose', '/messages/compose', ['recipient' => '<recipient>'])]
|
||||
public function getEditor($response, $request) {
|
||||
if(!$this->canSendMessages)
|
||||
return 403;
|
||||
|
||||
return Template::renderRaw('messages.compose', [
|
||||
'recipient' => (string)$request->getParam('recipient'),
|
||||
]);
|
||||
}
|
||||
|
||||
#[HttpGet('/messages/([A-Za-z0-9]+)')]
|
||||
#[URLInfo('messages-view', '/messages/<message>')]
|
||||
public function getView($response, $request, string $messageId) {
|
||||
if(strlen($messageId) !== 8)
|
||||
return 404;
|
||||
|
||||
$selfInfo = $this->authInfo->getUserInfo();
|
||||
$msgsDb = $this->msgsCtx->getDatabase();
|
||||
|
||||
try {
|
||||
$messageInfo = $msgsDb->getMessageInfo($selfInfo, $messageId);
|
||||
} catch(RuntimeException $ex) {
|
||||
return 404;
|
||||
}
|
||||
|
||||
if(!$messageInfo->isRead())
|
||||
$msgsDb->updateMessage(
|
||||
ownerInfo: $selfInfo,
|
||||
messageInfo: $messageInfo,
|
||||
readAt: time(),
|
||||
);
|
||||
|
||||
$message = $this->populateMessage($messageInfo);
|
||||
|
||||
$replyTo = null;
|
||||
if($messageInfo->hasReplyToId()) {
|
||||
try {
|
||||
$replyTo = $this->populateMessage(
|
||||
$msgsDb->getMessageInfo($selfInfo, $messageInfo, true)
|
||||
);
|
||||
} catch(RuntimeException $ex) {}
|
||||
}
|
||||
|
||||
$repliesForInfos = $msgsDb->getMessages(
|
||||
ownerInfo: $selfInfo,
|
||||
repliesFor: $messageInfo,
|
||||
deleted: false,
|
||||
);
|
||||
|
||||
$draftInfo = null;
|
||||
$repliesFor = [];
|
||||
foreach($repliesForInfos as $repliesForInfo) {
|
||||
$repliesFor[] = $this->populateMessage($repliesForInfo);
|
||||
|
||||
if(!$repliesForInfo->isSent() && $draftInfo === null)
|
||||
$draftInfo = $repliesForInfo;
|
||||
}
|
||||
|
||||
return Template::renderRaw('messages.thread', [
|
||||
'can_send_messages' => $this->canSendMessages,
|
||||
'self_info' => $selfInfo,
|
||||
'reply_to' => $replyTo,
|
||||
'message' => $message,
|
||||
'draft_info' => $draftInfo,
|
||||
'replies_for' => $repliesFor,
|
||||
]);
|
||||
}
|
||||
|
||||
private function checkCanReceiveMessages(UserInfo|string $userInfo): ?array {
|
||||
$globalPerms = $this->perms->getPermissions('global', $userInfo);
|
||||
if(!$globalPerms->check(Perm::G_MESSAGES_VIEW))
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'msgs:recipient_cannot_recv',
|
||||
'text' => 'This person is not allowed to receive messages.',
|
||||
],
|
||||
];
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function checkMessageFields(string $title, string $body, int $parser): ?array {
|
||||
if(!Parser::isValid($parser))
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'msgs:invalid_parser',
|
||||
'text' => 'Invalid parser selected.',
|
||||
],
|
||||
];
|
||||
|
||||
$lengths = $this->config->getValues([
|
||||
['title.minLength:i', 1],
|
||||
['title.maxLength:i', 200],
|
||||
['body.minLength:i', 1],
|
||||
['body.maxLength:i', 60000],
|
||||
]);
|
||||
|
||||
$titleLength = mb_strlen(trim($title));
|
||||
|
||||
if($titleLength < $lengths['title.minLength'])
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'msgs:title_too_short',
|
||||
'args' => [$lengths['title.minLength'], $titleLength],
|
||||
'text' => sprintf('Title may not be shorter than %d characters. You entered %d characters.', $lengths['title.minLength'], $titleLength),
|
||||
],
|
||||
];
|
||||
|
||||
if($titleLength > $lengths['title.maxLength'])
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'msgs:title_too_long',
|
||||
'args' => [$lengths['title.maxLength'], $titleLength],
|
||||
'text' => sprintf('Title may not be longer than %d characters. You entered %d characters.', $lengths['title.maxLength'], $titleLength),
|
||||
],
|
||||
];
|
||||
|
||||
$bodyLength = mb_strlen(trim($body));
|
||||
|
||||
if($bodyLength < $lengths['body.minLength'])
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'msgs:body_too_short',
|
||||
'args' => [$lengths['body.minLength'], $bodyLength],
|
||||
'text' => sprintf('Message may not be shorter than %d characters. You entered %d characters.', $lengths['body.minLength'], $bodyLength),
|
||||
],
|
||||
];
|
||||
|
||||
if($bodyLength > $lengths['body.maxLength'])
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'msgs:body_too_long',
|
||||
'args' => [$lengths['body.maxLength'], $bodyLength],
|
||||
'text' => sprintf('Message may not be longer than %d characters. You entered %d characters.', $lengths['body.maxLength'], $bodyLength),
|
||||
],
|
||||
];
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
#[HttpPost('/messages/create')]
|
||||
#[URLInfo('messages-create', '/messages/create')]
|
||||
public function postCreate($response, $request) {
|
||||
if(!$request->isFormContent())
|
||||
return 400;
|
||||
if(!$this->canSendMessages)
|
||||
return 403;
|
||||
|
||||
$content = $request->getContent();
|
||||
$recipient = (string)$content->getParam('recipient');
|
||||
$replyTo = (string)$content->getParam('reply');
|
||||
$title = (string)$content->getParam('title');
|
||||
$body = (string)$content->getParam('body');
|
||||
$parser = (int)$content->getParam('parser', FILTER_SANITIZE_NUMBER_INT);
|
||||
$draft = !empty($content->getParam('draft'));
|
||||
|
||||
$error = $this->checkMessageFields($title, $body, $parser);
|
||||
if($error !== null)
|
||||
return $error;
|
||||
|
||||
$selfInfo = $this->authInfo->getUserInfo();
|
||||
$msgsDb = $this->msgsCtx->getDatabase();
|
||||
|
||||
try {
|
||||
$recipientInfo = $this->usersCtx->getUserInfo($recipient, 'messaging');
|
||||
} catch(InvalidArgumentException $ex) {
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'msgs:recipient_invalid',
|
||||
'text' => 'Name of the recipient was incorrectly formatted.',
|
||||
'jeff' => $recipient,
|
||||
],
|
||||
];
|
||||
} catch(RuntimeException $ex) {
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'msgs:recipient_not_found',
|
||||
'text' => 'Recipient does not exist.',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
$error = $this->checkCanReceiveMessages($recipientInfo);
|
||||
if($error !== null)
|
||||
return $error;
|
||||
|
||||
$replyToInfo = null;
|
||||
if(!empty($replyTo)) {
|
||||
try {
|
||||
$replyToInfo = $msgsDb->getMessageInfo($selfInfo, $replyTo);
|
||||
} catch(RuntimeException $ex) {
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'msgs:reply_not_found',
|
||||
'text' => 'The message you are trying to reply to does not exist.',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
if(!$replyToInfo->isSent())
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'msgs:draft_reply',
|
||||
'text' => 'You cannot reply to a draft.',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
$msgId = XString::random(8);
|
||||
$sentAt = $draft ? null : time();
|
||||
|
||||
// own copy
|
||||
$msgsDb->createMessage(
|
||||
messageId: $msgId,
|
||||
ownerInfo: $selfInfo,
|
||||
authorInfo: $selfInfo,
|
||||
recipientInfo: $recipientInfo,
|
||||
title: $title,
|
||||
body: $body,
|
||||
parser: $parser,
|
||||
replyTo: $replyToInfo,
|
||||
sentAt: $sentAt
|
||||
);
|
||||
|
||||
// recipient copy
|
||||
if($sentAt !== null && $recipientInfo->getId() !== $selfInfo->getId())
|
||||
$msgsDb->createMessage(
|
||||
messageId: $msgId,
|
||||
ownerInfo: $recipientInfo,
|
||||
authorInfo: $selfInfo,
|
||||
recipientInfo: $recipientInfo,
|
||||
title: $title,
|
||||
body: $body,
|
||||
parser: $parser,
|
||||
replyTo: $replyToInfo,
|
||||
sentAt: $sentAt
|
||||
);
|
||||
|
||||
return [
|
||||
'id' => $msgId,
|
||||
'url' => $this->urls->format('messages-view', ['message' => $msgId]),
|
||||
];
|
||||
}
|
||||
|
||||
#[HttpPost('/messages/([A-Za-z0-9]+)')]
|
||||
#[URLInfo('messages-update', '/messages/<message>')]
|
||||
public function postUpdate($response, $request, string $messageId) {
|
||||
if(!$request->isFormContent())
|
||||
return 400;
|
||||
if(!$this->canSendMessages)
|
||||
return 403;
|
||||
|
||||
$content = $request->getContent();
|
||||
$title = (string)$content->getParam('title');
|
||||
$body = (string)$content->getParam('body');
|
||||
$parser = (int)$content->getParam('parser', FILTER_SANITIZE_NUMBER_INT);
|
||||
$draft = !empty($content->getParam('draft'));
|
||||
|
||||
$error = $this->checkMessageFields($title, $body, $parser);
|
||||
if($error !== null)
|
||||
return $error;
|
||||
|
||||
$selfInfo = $this->authInfo->getUserInfo();
|
||||
$msgsDb = $this->msgsCtx->getDatabase();
|
||||
|
||||
try {
|
||||
$messageInfo = $msgsDb->getMessageInfo($selfInfo, $messageId);
|
||||
} catch(RuntimeException $ex) {
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'msgs:edit_not_found',
|
||||
'text' => 'The message you are trying to edit does not exist.',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
if(!$messageInfo->hasAuthorId() || $messageInfo->getAuthorId() !== $selfInfo->getId())
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'msgs:not_author',
|
||||
'text' => 'You are not the author of this message.',
|
||||
],
|
||||
];
|
||||
|
||||
if(!$messageInfo->hasRecipientId())
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'msgs:recipient_gone',
|
||||
'text' => 'The recipient of this message no longer exists, it cannot be sent or edited.',
|
||||
],
|
||||
];
|
||||
|
||||
if($messageInfo->isSent())
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'msgs:not_draft',
|
||||
'text' => 'You cannot edit a message that has already been sent.',
|
||||
],
|
||||
];
|
||||
|
||||
$error = $this->checkCanReceiveMessages($messageInfo->getRecipientId());
|
||||
if($error !== null)
|
||||
return $error;
|
||||
|
||||
$sentAt = $draft ? null : time();
|
||||
|
||||
$msgsDb->updateMessage(
|
||||
ownerInfo: $selfInfo,
|
||||
messageInfo: $messageInfo,
|
||||
title: $title,
|
||||
body: $body,
|
||||
parser: $parser,
|
||||
sentAt: $sentAt,
|
||||
);
|
||||
|
||||
// recipient copy
|
||||
if($sentAt !== null && $messageInfo->getRecipientId() !== $selfInfo->getId())
|
||||
$msgsDb->createMessage(
|
||||
messageId: $messageId,
|
||||
ownerInfo: $messageInfo->getRecipientId(),
|
||||
authorInfo: $selfInfo,
|
||||
recipientInfo: $messageInfo->getRecipientId(),
|
||||
title: $title,
|
||||
body: $body,
|
||||
parser: $parser,
|
||||
replyTo: $messageInfo->getReplyToId(),
|
||||
sentAt: $sentAt
|
||||
);
|
||||
|
||||
return [
|
||||
'id' => $messageId,
|
||||
'url' => $this->urls->format('messages-view', ['message' => $messageId]),
|
||||
];
|
||||
}
|
||||
|
||||
#[HttpPost('/messages/mark')]
|
||||
#[URLInfo('messages-mark', '/messages/mark')]
|
||||
public function postMark($response, $request) {
|
||||
if(!$request->isFormContent())
|
||||
return 400;
|
||||
|
||||
$content = $request->getContent();
|
||||
$type = (string)$content->getParam('type');
|
||||
$messages = explode(',', (string)$content->getParam('messages'));
|
||||
|
||||
if($type !== 'read' && $type !== 'unread')
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'msgs:unsupported_mark',
|
||||
'text' => 'Attempting to mark message with an unsupported state.',
|
||||
],
|
||||
];
|
||||
|
||||
$selfInfo = $this->authInfo->getUserInfo();
|
||||
$msgsDb = $this->msgsCtx->getDatabase();
|
||||
|
||||
foreach($messages as $messageId)
|
||||
$msgsDb->updateMessage(
|
||||
ownerInfo: $selfInfo,
|
||||
messageInfo: $messageId,
|
||||
readAt: $type === 'read' ? time() : null,
|
||||
);
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
#[HttpPost('/messages/delete')]
|
||||
#[URLInfo('messages-delete', '/messages/delete')]
|
||||
public function postDelete($response, $request) {
|
||||
if(!$request->isFormContent())
|
||||
return 400;
|
||||
|
||||
$content = $request->getContent();
|
||||
|
||||
$messages = (string)$content->getParam('messages');
|
||||
if($messages === '')
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'msgs:empty',
|
||||
'text' => 'No messages were supplied.',
|
||||
],
|
||||
];
|
||||
|
||||
$messages = explode(',', $messages);
|
||||
|
||||
$this->msgsCtx->getDatabase()->deleteMessages(
|
||||
$this->authInfo->getUserInfo(),
|
||||
$messages
|
||||
);
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
#[HttpPost('/messages/restore')]
|
||||
#[URLInfo('messages-restore', '/messages/restore')]
|
||||
public function postRestore($response, $request) {
|
||||
if(!$request->isFormContent())
|
||||
return 400;
|
||||
|
||||
$content = $request->getContent();
|
||||
|
||||
$messages = (string)$content->getParam('messages');
|
||||
if($messages === '')
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'msgs:empty',
|
||||
'text' => 'No messages were supplied.',
|
||||
],
|
||||
];
|
||||
|
||||
$messages = explode(',', $messages);
|
||||
|
||||
$this->msgsCtx->getDatabase()->restoreMessages(
|
||||
$this->authInfo->getUserInfo(),
|
||||
$messages
|
||||
);
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
#[HttpPost('/messages/nuke')]
|
||||
#[URLInfo('messages-nuke', '/messages/nuke')]
|
||||
public function postNuke($response, $request) {
|
||||
if(!$request->isFormContent())
|
||||
return 400;
|
||||
|
||||
$content = $request->getContent();
|
||||
|
||||
$messages = (string)$content->getParam('messages');
|
||||
if($messages === '')
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'msgs:empty',
|
||||
'text' => 'No messages were supplied.',
|
||||
],
|
||||
];
|
||||
|
||||
$messages = explode(',', $messages);
|
||||
|
||||
$this->msgsCtx->getDatabase()->nukeMessages(
|
||||
$this->authInfo->getUserInfo(),
|
||||
$messages
|
||||
);
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
|
@ -3,33 +3,23 @@ namespace Misuzu;
|
|||
|
||||
use Index\Environment;
|
||||
use Index\Data\IDbConnection;
|
||||
use Index\Data\Migration\IDbMigrationRepo;
|
||||
use Index\Data\Migration\DbMigrationManager;
|
||||
use Index\Data\Migration\FsDbMigrationRepo;
|
||||
use Index\Data\Migration\{IDbMigrationRepo,DbMigrationManager,FsDbMigrationRepo};
|
||||
use Sasae\SasaeEnvironment;
|
||||
use Syokuhou\IConfig;
|
||||
use Misuzu\Template;
|
||||
use Misuzu\Auth\AuthContext;
|
||||
use Misuzu\Auth\AuthInfo;
|
||||
use Misuzu\Auth\{AuthContext,AuthInfo};
|
||||
use Misuzu\AuditLog\AuditLog;
|
||||
use Misuzu\Changelog\Changelog;
|
||||
use Misuzu\Changelog\ChangelogRoutes;
|
||||
use Misuzu\Comments\Comments;
|
||||
use Misuzu\Counters\Counters;
|
||||
use Misuzu\Emoticons\Emotes;
|
||||
use Misuzu\Forum\ForumContext;
|
||||
use Misuzu\Home\HomeRoutes;
|
||||
use Misuzu\Info\InfoRoutes;
|
||||
use Misuzu\Messages\MessagesContext;
|
||||
use Misuzu\News\News;
|
||||
use Misuzu\News\NewsRoutes;
|
||||
use Misuzu\Perms\Permissions;
|
||||
use Misuzu\Profile\ProfileFields;
|
||||
use Misuzu\Satori\SatoriRoutes;
|
||||
use Misuzu\SharpChat\SharpChatRoutes;
|
||||
use Misuzu\URLs\URLRegistry;
|
||||
use Misuzu\Users\UsersContext;
|
||||
use Misuzu\Users\UserInfo;
|
||||
use Misuzu\Users\Assets\AssetsRoutes;
|
||||
use Misuzu\Users\{UsersContext,UserInfo};
|
||||
|
||||
// this class should function as the root for everything going forward
|
||||
// no more magical static classes that are just kind of assumed to exist
|
||||
|
@ -51,8 +41,9 @@ class MisuzuContext {
|
|||
private Comments $comments;
|
||||
|
||||
private AuthContext $authCtx;
|
||||
private UsersContext $usersCtx;
|
||||
private ForumContext $forumCtx;
|
||||
private MessagesContext $messagesCtx;
|
||||
private UsersContext $usersCtx;
|
||||
|
||||
private ProfileFields $profileFields;
|
||||
|
||||
|
@ -72,8 +63,9 @@ class MisuzuContext {
|
|||
$this->siteInfo = new SiteInfo($config->scopeTo('site'));
|
||||
|
||||
$this->authCtx = new AuthContext($dbConn, $config->scopeTo('auth'));
|
||||
$this->usersCtx = new UsersContext($dbConn);
|
||||
$this->forumCtx = new ForumContext($dbConn);
|
||||
$this->messagesCtx = new MessagesContext($dbConn);
|
||||
$this->usersCtx = new UsersContext($dbConn);
|
||||
|
||||
$this->auditLog = new AuditLog($dbConn);
|
||||
$this->changelog = new Changelog($dbConn);
|
||||
|
@ -196,6 +188,7 @@ class MisuzuContext {
|
|||
$globals = $this->config->getValues([
|
||||
['eeprom.path:s', '', 'eeprom_path'],
|
||||
['eeprom.app:s', '', 'eeprom_app'],
|
||||
['eeprom.appmsgs:s', '', 'eeprom_app_messages'],
|
||||
]);
|
||||
|
||||
$isDebug = Environment::isDebug();
|
||||
|
@ -219,9 +212,8 @@ class MisuzuContext {
|
|||
$routingCtx = new RoutingContext();
|
||||
|
||||
$this->urls = $routingCtx->getURLs();
|
||||
$routingCtx->registerDefaultErrorPages();
|
||||
|
||||
$routingCtx->register(new HomeRoutes(
|
||||
$routingCtx->register(new \Misuzu\Home\HomeRoutes(
|
||||
$this->config,
|
||||
$this->dbConn,
|
||||
$this->siteInfo,
|
||||
|
@ -233,15 +225,15 @@ class MisuzuContext {
|
|||
$this->usersCtx
|
||||
));
|
||||
|
||||
$routingCtx->register(new AssetsRoutes(
|
||||
$routingCtx->register(new \Misuzu\Users\Assets\AssetsRoutes(
|
||||
$this->authInfo,
|
||||
$this->urls,
|
||||
$this->usersCtx
|
||||
));
|
||||
|
||||
$routingCtx->register(new InfoRoutes);
|
||||
$routingCtx->register(new \Misuzu\Info\InfoRoutes);
|
||||
|
||||
$routingCtx->register(new NewsRoutes(
|
||||
$routingCtx->register(new \Misuzu\News\NewsRoutes(
|
||||
$this->siteInfo,
|
||||
$this->authInfo,
|
||||
$this->urls,
|
||||
|
@ -250,7 +242,16 @@ class MisuzuContext {
|
|||
$this->comments
|
||||
));
|
||||
|
||||
$routingCtx->register(new ChangelogRoutes(
|
||||
$routingCtx->register(new \Misuzu\Messages\MessagesRoutes(
|
||||
$this->config->scopeTo('messages'),
|
||||
$this->urls,
|
||||
$this->authInfo,
|
||||
$this->messagesCtx,
|
||||
$this->usersCtx,
|
||||
$this->perms
|
||||
));
|
||||
|
||||
$routingCtx->register(new \Misuzu\Changelog\ChangelogRoutes(
|
||||
$this->siteInfo,
|
||||
$this->urls,
|
||||
$this->changelog,
|
||||
|
@ -259,8 +260,9 @@ class MisuzuContext {
|
|||
$this->comments
|
||||
));
|
||||
|
||||
$routingCtx->register(new SharpChatRoutes(
|
||||
$routingCtx->register(new \Misuzu\SharpChat\SharpChatRoutes(
|
||||
$this->config->scopeTo('sockChat'),
|
||||
$this->config->scopeTo('impersonate'),
|
||||
$this->urls,
|
||||
$this->usersCtx,
|
||||
$this->authCtx,
|
||||
|
@ -269,7 +271,7 @@ class MisuzuContext {
|
|||
$this->authInfo
|
||||
));
|
||||
|
||||
$routingCtx->register(new SatoriRoutes(
|
||||
$routingCtx->register(new \Misuzu\Satori\SatoriRoutes(
|
||||
$this->config->scopeTo('satori'),
|
||||
$this->usersCtx,
|
||||
$this->forumCtx,
|
||||
|
|
|
@ -141,12 +141,20 @@ final class MisuzuSasaeExtension extends AbstractExtension {
|
|||
|
||||
if($authInfo->isLoggedIn()) {
|
||||
$userInfo = $authInfo->getUserInfo();
|
||||
$globalPerms = $authInfo->getPerms('global');
|
||||
|
||||
$menu[] = [
|
||||
'title' => 'Profile',
|
||||
'url' => $urls->format('user-profile', ['user' => $userInfo->getId()]),
|
||||
'icon' => 'fas fa-user fa-fw',
|
||||
];
|
||||
if($globalPerms->check(Perm::G_MESSAGES_VIEW))
|
||||
$menu[] = [
|
||||
'title' => 'Messages',
|
||||
'url' => $urls->format('messages-index'),
|
||||
'icon' => 'fas fa-envelope fa-fw',
|
||||
'class' => 'js-header-pms-button',
|
||||
];
|
||||
$menu[] = [
|
||||
'title' => 'Settings',
|
||||
'url' => $urls->format('settings-index'),
|
||||
|
@ -158,7 +166,7 @@ final class MisuzuSasaeExtension extends AbstractExtension {
|
|||
'icon' => 'fas fa-search fa-fw',
|
||||
];
|
||||
|
||||
if(!$usersCtx->hasActiveBan($userInfo) && $authInfo->getPerms('global')->check(Perm::G_IS_JANITOR)) {
|
||||
if(!$usersCtx->hasActiveBan($userInfo) && $globalPerms->check(Perm::G_IS_JANITOR)) {
|
||||
// restore behaviour where clicking this button switches between
|
||||
// site version and broom version
|
||||
if($inBroomCloset)
|
||||
|
|
|
@ -41,7 +41,7 @@ class News {
|
|||
public function getCategories(
|
||||
?bool $hidden = null,
|
||||
?Pagination $pagination = null
|
||||
): array {
|
||||
): iterable {
|
||||
$hasHidden = $hidden !== null;
|
||||
$hasPagination = $pagination !== null;
|
||||
|
||||
|
@ -61,13 +61,7 @@ class News {
|
|||
|
||||
$stmt->execute();
|
||||
|
||||
$result = $stmt->getResult();
|
||||
$categories = [];
|
||||
|
||||
while($result->next())
|
||||
$categories[] = new NewsCategoryInfo($result);
|
||||
|
||||
return $categories;
|
||||
return $stmt->getResult()->getIterator(NewsCategoryInfo::fromResult(...));
|
||||
}
|
||||
|
||||
public function getCategory(
|
||||
|
@ -107,7 +101,7 @@ class News {
|
|||
if(!$result->next())
|
||||
throw new RuntimeException('News category not found.');
|
||||
|
||||
return new NewsCategoryInfo($result);
|
||||
return NewsCategoryInfo::fromResult($result);
|
||||
}
|
||||
|
||||
public function createCategory(
|
||||
|
@ -224,7 +218,7 @@ class News {
|
|||
bool $includeScheduled = false,
|
||||
bool $includeDeleted = false,
|
||||
?Pagination $pagination = null
|
||||
): array {
|
||||
): iterable {
|
||||
if($categoryInfo instanceof NewsCategoryInfo)
|
||||
$categoryInfo = $categoryInfo->getId();
|
||||
|
||||
|
@ -271,13 +265,7 @@ class News {
|
|||
|
||||
$stmt->execute();
|
||||
|
||||
$result = $stmt->getResult();
|
||||
$posts = [];
|
||||
|
||||
while($result->next())
|
||||
$posts[] = new NewsPostInfo($result);
|
||||
|
||||
return $posts;
|
||||
return $stmt->getResult()->getIterator(NewsPostInfo::fromResult(...));
|
||||
}
|
||||
|
||||
public function getPost(string $postId): NewsPostInfo {
|
||||
|
@ -289,7 +277,7 @@ class News {
|
|||
if(!$result->next())
|
||||
throw new RuntimeException('No news post with that ID exists.');
|
||||
|
||||
return new NewsPostInfo($result);
|
||||
return NewsPostInfo::fromResult($result);
|
||||
}
|
||||
|
||||
public function createPost(
|
||||
|
|
|
@ -5,20 +5,24 @@ use Index\DateTime;
|
|||
use Index\Data\IDbResult;
|
||||
|
||||
class NewsCategoryInfo {
|
||||
private string $id;
|
||||
private string $name;
|
||||
private string $description;
|
||||
private bool $hidden;
|
||||
private int $created;
|
||||
private int $posts;
|
||||
public function __construct(
|
||||
private string $id,
|
||||
private string $name,
|
||||
private string $description,
|
||||
private bool $hidden,
|
||||
private int $created,
|
||||
private int $posts,
|
||||
) {}
|
||||
|
||||
public function __construct(IDbResult $result) {
|
||||
$this->id = (string)$result->getInteger(0);
|
||||
$this->name = $result->getString(1);
|
||||
$this->description = $result->getString(2);
|
||||
$this->hidden = $result->getInteger(3) !== 0;
|
||||
$this->created = $result->getInteger(4);
|
||||
$this->posts = $result->getInteger(5);
|
||||
public static function fromResult(IDbResult $result): NewsCategoryInfo {
|
||||
return new NewsCategoryInfo(
|
||||
id: $result->getString(0),
|
||||
name: $result->getString(1),
|
||||
description: $result->getString(2),
|
||||
hidden: $result->getBoolean(3),
|
||||
created: $result->getInteger(4),
|
||||
posts: $result->getInteger(5),
|
||||
);
|
||||
}
|
||||
|
||||
public function getId(): string {
|
||||
|
|
|
@ -5,30 +5,34 @@ use Index\DateTime;
|
|||
use Index\Data\IDbResult;
|
||||
|
||||
class NewsPostInfo {
|
||||
private string $id;
|
||||
private string $categoryId;
|
||||
private ?string $userId;
|
||||
private ?string $commentsSectionId;
|
||||
private bool $featured;
|
||||
private string $title;
|
||||
private string $body;
|
||||
private int $scheduled;
|
||||
private int $created;
|
||||
private int $updated;
|
||||
private ?int $deleted;
|
||||
public function __construct(
|
||||
private string $id,
|
||||
private string $categoryId,
|
||||
private ?string $userId,
|
||||
private ?string $commentsSectionId,
|
||||
private bool $featured,
|
||||
private string $title,
|
||||
private string $body,
|
||||
private int $scheduled,
|
||||
private int $created,
|
||||
private int $updated,
|
||||
private ?int $deleted,
|
||||
) {}
|
||||
|
||||
public function __construct(IDbResult $result) {
|
||||
$this->id = (string)$result->getInteger(0);
|
||||
$this->categoryId = (string)$result->getInteger(1);
|
||||
$this->userId = $result->isNull(2) ? null : (string)$result->getInteger(2);
|
||||
$this->commentsSectionId = $result->isNull(3) ? null : (string)$result->getInteger(3);
|
||||
$this->featured = $result->getInteger(4) !== 0;
|
||||
$this->title = $result->getString(5);
|
||||
$this->body = $result->getString(6);
|
||||
$this->scheduled = $result->getInteger(7);
|
||||
$this->created = $result->getInteger(8);
|
||||
$this->updated = $result->getInteger(9);
|
||||
$this->deleted = $result->isNull(10) ? null : $result->getInteger(10);
|
||||
public static function fromResult(IDbResult $result): NewsPostInfo {
|
||||
return new NewsPostInfo(
|
||||
id: $result->getString(0),
|
||||
categoryId: $result->getString(1),
|
||||
userId: $result->getStringOrNull(2),
|
||||
commentsSectionId: $result->getStringOrNull(3),
|
||||
featured: $result->getBoolean(4),
|
||||
title: $result->getString(5),
|
||||
body: $result->getString(6),
|
||||
scheduled: $result->getInteger(7),
|
||||
created: $result->getInteger(8),
|
||||
updated: $result->getInteger(9),
|
||||
deleted: $result->getIntegerOrNull(10),
|
||||
);
|
||||
}
|
||||
|
||||
public function getId(): string {
|
||||
|
|
|
@ -3,24 +3,14 @@ namespace Misuzu\News;
|
|||
|
||||
use RuntimeException;
|
||||
use Index\DateTime;
|
||||
use Index\Data\DbTools;
|
||||
use Index\Data\IDbConnection;
|
||||
use Index\Routing\Route;
|
||||
use Index\Routing\RouteHandler;
|
||||
use Misuzu\Pagination;
|
||||
use Misuzu\SiteInfo;
|
||||
use Misuzu\Template;
|
||||
use Index\Data\{DbTools,IDbConnection};
|
||||
use Index\Http\Routing\{HttpGet,RouteHandler};
|
||||
use Misuzu\{Pagination,SiteInfo,Template};
|
||||
use Misuzu\Auth\AuthInfo;
|
||||
use Misuzu\Comments\Comments;
|
||||
use Misuzu\Comments\CommentsCategory;
|
||||
use Misuzu\Comments\CommentsEx;
|
||||
use Misuzu\Feeds\Feed;
|
||||
use Misuzu\Feeds\FeedItem;
|
||||
use Misuzu\Feeds\AtomFeedSerializer;
|
||||
use Misuzu\Feeds\RssFeedSerializer;
|
||||
use Misuzu\Comments\{Comments,CommentsCategory,CommentsEx};
|
||||
use Misuzu\Feeds\{Feed,FeedItem,AtomFeedSerializer,RssFeedSerializer};
|
||||
use Misuzu\Parsers\Parser;
|
||||
use Misuzu\URLs\URLInfo;
|
||||
use Misuzu\URLs\URLRegistry;
|
||||
use Misuzu\URLs\{URLInfo,URLRegistry};
|
||||
use Misuzu\Users\UsersContext;
|
||||
|
||||
class NewsRoutes extends RouteHandler {
|
||||
|
@ -91,7 +81,7 @@ class NewsRoutes extends RouteHandler {
|
|||
return $posts;
|
||||
}
|
||||
|
||||
#[Route('GET', '/news')]
|
||||
#[HttpGet('/news')]
|
||||
#[URLInfo('news-index', '/news', ['p' => '<page>'])]
|
||||
public function getIndex() {
|
||||
$categories = $this->news->getCategories(hidden: false);
|
||||
|
@ -109,24 +99,21 @@ class NewsRoutes extends RouteHandler {
|
|||
]);
|
||||
}
|
||||
|
||||
#[Route('GET', '/news.rss')]
|
||||
#[HttpGet('/news.rss')]
|
||||
#[URLInfo('news-feed-rss', '/news.rss')]
|
||||
public function getFeedRss($response) {
|
||||
return $this->getFeed($response, 'rss');
|
||||
}
|
||||
|
||||
#[Route('GET', '/news.atom')]
|
||||
#[HttpGet('/news.atom')]
|
||||
#[URLInfo('news-feed-atom', '/news.atom')]
|
||||
public function getFeedAtom($response) {
|
||||
return $this->getFeed($response, 'atom');
|
||||
}
|
||||
|
||||
#[Route('GET', '/news/:category')]
|
||||
#[HttpGet('/news/([0-9]+)(?:\.(rss|atom))?')]
|
||||
#[URLInfo('news-category', '/news/<category>', ['p' => '<page>'])]
|
||||
public function getCategory($response, $request, string $fileName) {
|
||||
$categoryId = pathinfo($fileName, PATHINFO_FILENAME);
|
||||
$type = pathinfo($fileName, PATHINFO_EXTENSION);
|
||||
|
||||
public function getCategory($response, $request, string $categoryId, string $type = '') {
|
||||
try {
|
||||
$categoryInfo = $this->news->getCategory(categoryId: $categoryId);
|
||||
} catch(RuntimeException $ex) {
|
||||
|
@ -163,7 +150,7 @@ class NewsRoutes extends RouteHandler {
|
|||
return $this->getFeed($response, 'atom', $categoryInfo);
|
||||
}
|
||||
|
||||
#[Route('GET', '/news/post/:id')]
|
||||
#[HttpGet('/news/post/([0-9]+)')]
|
||||
#[URLInfo('news-post', '/news/post/<post>')]
|
||||
#[URLInfo('news-post-comments', '/news/post/<post>', fragment: 'comments')]
|
||||
public function getPost($response, $request, string $postId) {
|
||||
|
|
20
src/Perm.php
20
src/Perm.php
|
@ -7,7 +7,8 @@ use stdClass;
|
|||
// To avoid future conflicts, unused/deprecated permissions should remain defined for any given category
|
||||
final class Perm {
|
||||
// GLOBAL ALLOCATION:
|
||||
// 0bXXXXX_XXXXXXXX_CCCCCCCC_FFFFFFFF_NNNNNNNN_LLLLLLLL_GGGGGGGG
|
||||
// 0bXXXXX_XXXXXXXX_CCCCCCCC_MMMMFFFF_NNNNNNNN_LLLLLLLL_GGGGGGGG
|
||||
// M -> Messages permissions
|
||||
// G -> General global permissions
|
||||
// L -> Changelog permissions
|
||||
// N -> News permissions
|
||||
|
@ -34,6 +35,9 @@ final class Perm {
|
|||
public const G_FORUM_LEADERBOARD_VIEW = 0b00000_00000000_00000000_00000010_00000000_00000000_00000000;
|
||||
public const G_FORUM_TOPIC_REDIRS_MANAGE = 0b00000_00000000_00000000_00000100_00000000_00000000_00000000;
|
||||
|
||||
public const G_MESSAGES_VIEW = 0b00000_00000000_00000000_00010000_00000000_00000000_00000000;
|
||||
public const G_MESSAGES_SEND = 0b00000_00000000_00000000_00100000_00000000_00000000_00000000;
|
||||
|
||||
public const G_COMMENTS_CREATE = 0b00000_00000000_00000001_00000000_00000000_00000000_00000000;
|
||||
public const G_COMMENTS_EDIT_OWN = 0b00000_00000000_00000010_00000000_00000000_00000000_00000000; // unused: editing not implemented
|
||||
public const G_COMMENTS_EDIT_ANY = 0b00000_00000000_00000100_00000000_00000000_00000000_00000000; // unused: editing not implemented
|
||||
|
@ -115,7 +119,7 @@ final class Perm {
|
|||
public const INFO_FOR_ROLE = self::INFO_FOR_USER; // just alias for now, no clue if this will ever desync
|
||||
public const INFO_FOR_FORUM_CATEGORY = ['forum'];
|
||||
|
||||
public const LISTS_FOR_USER = ['global:general', 'global:changelog', 'global:news', 'global:forum', 'global:comments', 'user:personal', 'user:manage', 'chat:general'];
|
||||
public const LISTS_FOR_USER = ['global:general', 'global:changelog', 'global:news', 'global:forum', 'global:messages', 'global:comments', 'user:personal', 'user:manage', 'chat:general'];
|
||||
public const LISTS_FOR_ROLE = self::LISTS_FOR_USER; // idem
|
||||
public const LISTS_FOR_FORUM_CATEGORY = ['forum:category', 'forum:topic', 'forum:post'];
|
||||
|
||||
|
@ -161,6 +165,15 @@ final class Perm {
|
|||
],
|
||||
],
|
||||
|
||||
'global:messages' => [
|
||||
'title' => 'Private Messages Permissions',
|
||||
'perms' => [
|
||||
'global',
|
||||
self::G_MESSAGES_VIEW,
|
||||
self::G_MESSAGES_SEND,
|
||||
],
|
||||
],
|
||||
|
||||
'global:comments' => [
|
||||
'title' => 'Comments Permissions',
|
||||
'perms' => [
|
||||
|
@ -290,6 +303,9 @@ final class Perm {
|
|||
self::G_FORUM_LEADERBOARD_VIEW => 'Can view forum leaderboard.',
|
||||
self::G_FORUM_TOPIC_REDIRS_MANAGE => 'Can create redirects for deleted forum topics.',
|
||||
|
||||
self::G_MESSAGES_VIEW => 'Can view private messages.',
|
||||
self::G_MESSAGES_SEND => 'Can send private messages.',
|
||||
|
||||
self::G_COMMENTS_CREATE => 'Can post comments.',
|
||||
self::G_COMMENTS_EDIT_OWN => 'Can edit own comments.',
|
||||
self::G_COMMENTS_EDIT_ANY => 'Can edit ANY comment.',
|
||||
|
|
|
@ -6,24 +6,30 @@ use Index\Data\IDbResult;
|
|||
class PermissionInfo implements IPermissionResult {
|
||||
use PermissionResultShared;
|
||||
|
||||
private ?string $userId;
|
||||
private ?string $roleId;
|
||||
private ?string $forumCategoryId;
|
||||
private string $category;
|
||||
private int $allow;
|
||||
private int $deny;
|
||||
private int $calculated;
|
||||
|
||||
public function __construct(IDbResult $result) {
|
||||
$this->userId = $result->isNull(0) ? null : $result->getString(0);
|
||||
$this->roleId = $result->isNull(1) ? null : $result->getString(1);
|
||||
$this->forumCategoryId = $result->isNull(2) ? null : $result->getString(2);
|
||||
$this->category = $result->getString(3);
|
||||
$this->allow = $result->getInteger(4);
|
||||
$this->deny = $result->getInteger(5);
|
||||
public function __construct(
|
||||
private ?string $userId,
|
||||
private ?string $roleId,
|
||||
private ?string $forumCategoryId,
|
||||
private string $category,
|
||||
private int $allow,
|
||||
private int $deny,
|
||||
) {
|
||||
$this->calculated = $this->allow & ~$this->deny;
|
||||
}
|
||||
|
||||
public static function fromResult(IDbResult $result): PermissionInfo {
|
||||
return new PermissionInfo(
|
||||
userId: $result->getStringOrNull(0),
|
||||
roleId: $result->getStringOrNull(1),
|
||||
forumCategoryId: $result->getStringOrNull(2),
|
||||
category: $result->getString(3),
|
||||
allow: $result->getInteger(4),
|
||||
deny: $result->getInteger(5),
|
||||
);
|
||||
}
|
||||
|
||||
public function hasUserId(): bool {
|
||||
return $this->userId !== null;
|
||||
}
|
||||
|
|
|
@ -76,11 +76,11 @@ class Permissions {
|
|||
$result = $stmt->getResult();
|
||||
|
||||
if(is_string($categoryNames))
|
||||
return $result->next() ? new PermissionInfo($result) : null;
|
||||
return $result->next() ? PermissionInfo::fromResult($result) : null;
|
||||
|
||||
$perms = [];
|
||||
while($result->next())
|
||||
$perms[$result->getString(3)] = new PermissionInfo($result);
|
||||
$perms[$result->getString(3)] = PermissionInfo::fromResult($result);
|
||||
|
||||
return $perms;
|
||||
}
|
||||
|
|
|
@ -4,18 +4,22 @@ namespace Misuzu\Profile;
|
|||
use Index\Data\IDbResult;
|
||||
|
||||
class ProfileFieldFormatInfo {
|
||||
private string $id;
|
||||
private string $fieldId;
|
||||
private ?string $regex;
|
||||
private ?string $linkFormat;
|
||||
private string $displayFormat;
|
||||
public function __construct(
|
||||
private string $id,
|
||||
private string $fieldId,
|
||||
private ?string $regex,
|
||||
private ?string $linkFormat,
|
||||
private string $displayFormat,
|
||||
) {}
|
||||
|
||||
public function __construct(IDbResult $result) {
|
||||
$this->id = (string)$result->getInteger(0);
|
||||
$this->fieldId = (string)$result->getInteger(1);
|
||||
$this->regex = $result->isNull(2) ? null : $result->getString(2);
|
||||
$this->linkFormat = $result->isNull(3) ? null : $result->getString(3);
|
||||
$this->displayFormat = $result->getString(4);
|
||||
public static function fromResult(IDbResult $result): ProfileFieldFormatInfo {
|
||||
return new ProfileFieldFormatInfo(
|
||||
id: $result->getString(0),
|
||||
fieldId: $result->getString(1),
|
||||
regex: $result->getStringOrNull(2),
|
||||
linkFormat: $result->getStringOrNull(3),
|
||||
displayFormat: $result->getString(4),
|
||||
);
|
||||
}
|
||||
|
||||
public function getId(): string {
|
||||
|
|
|
@ -4,18 +4,22 @@ namespace Misuzu\Profile;
|
|||
use Index\Data\IDbResult;
|
||||
|
||||
class ProfileFieldInfo {
|
||||
private string $id;
|
||||
private int $order;
|
||||
private string $name;
|
||||
private string $title;
|
||||
private string $regex;
|
||||
public function __construct(
|
||||
private string $id,
|
||||
private int $order,
|
||||
private string $name,
|
||||
private string $title,
|
||||
private string $regex,
|
||||
) {}
|
||||
|
||||
public function __construct(IDbResult $result) {
|
||||
$this->id = (string)$result->getInteger(0);
|
||||
$this->order = $result->getInteger(1);
|
||||
$this->name = $result->getString(2);
|
||||
$this->title = $result->getString(3);
|
||||
$this->regex = $result->getString(4);
|
||||
public static function fromResult(IDbResult $result): ProfileFieldInfo {
|
||||
return new ProfileFieldInfo(
|
||||
id: $result->getString(0),
|
||||
order: $result->getInteger(1),
|
||||
name: $result->getString(2),
|
||||
title: $result->getString(3),
|
||||
regex: $result->getString(4),
|
||||
);
|
||||
}
|
||||
|
||||
public function getId(): string {
|
||||
|
|
|
@ -4,16 +4,20 @@ namespace Misuzu\Profile;
|
|||
use Index\Data\IDbResult;
|
||||
|
||||
class ProfileFieldValueInfo {
|
||||
private string $fieldId;
|
||||
private string $userId;
|
||||
private string $formatId;
|
||||
private string $value;
|
||||
public function __construct(
|
||||
private string $fieldId,
|
||||
private string $userId,
|
||||
private string $formatId,
|
||||
private string $value,
|
||||
) {}
|
||||
|
||||
public function __construct(IDbResult $result) {
|
||||
$this->fieldId = (string)$result->getInteger(0);
|
||||
$this->userId = (string)$result->getInteger(1);
|
||||
$this->formatId = (string)$result->getInteger(2);
|
||||
$this->value = $result->getString(3);
|
||||
public static function fromResult(IDbResult $result): ProfileFieldValueInfo {
|
||||
return new ProfileFieldValueInfo(
|
||||
fieldId: $result->getString(0),
|
||||
userId: $result->getString(1),
|
||||
formatId: $result->getString(2),
|
||||
value: $result->getString(3)
|
||||
);
|
||||
}
|
||||
|
||||
public function getFieldId(): string {
|
||||
|
|
|
@ -16,10 +16,15 @@ class ProfileFields {
|
|||
$this->cache = new DbStatementCache($dbConn);
|
||||
}
|
||||
|
||||
public function getFields(?array $fieldValueInfos = null): array {
|
||||
public function getFields(?iterable $fieldValueInfos = null): iterable {
|
||||
$hasFieldValueInfos = $fieldValueInfos !== null;
|
||||
if($hasFieldValueInfos && empty($fieldValueInfos))
|
||||
return [];
|
||||
if($hasFieldValueInfos) {
|
||||
if(!is_array($fieldValueInfos))
|
||||
$fieldValueInfos = iterator_to_array($fieldValueInfos);
|
||||
|
||||
if(empty($fieldValueInfos))
|
||||
return [];
|
||||
}
|
||||
|
||||
$query = 'SELECT field_id, field_order, field_key, field_title, field_regex FROM msz_profile_fields';
|
||||
if($hasFieldValueInfos)
|
||||
|
@ -37,13 +42,8 @@ class ProfileFields {
|
|||
}
|
||||
|
||||
$stmt->execute();
|
||||
$result = $stmt->getResult();
|
||||
$fields = [];
|
||||
|
||||
while($result->next())
|
||||
$fields[] = new ProfileFieldInfo($result);
|
||||
|
||||
return $fields;
|
||||
return $stmt->getResult()->getIterator(ProfileFieldInfo::fromResult(...));
|
||||
}
|
||||
|
||||
public function getField(string $fieldId): ProfileFieldInfo {
|
||||
|
@ -55,20 +55,30 @@ class ProfileFields {
|
|||
if(!$result->next())
|
||||
throw new RuntimeException('No field found with the provided field id.');
|
||||
|
||||
return new ProfileFieldInfo($result);
|
||||
return ProfileFieldInfo::fromResult($result);
|
||||
}
|
||||
|
||||
public function getFieldFormats(
|
||||
?array $fieldInfos = null,
|
||||
?array $fieldValueInfos = null
|
||||
): array {
|
||||
?iterable $fieldInfos = null,
|
||||
?iterable $fieldValueInfos = null
|
||||
): iterable {
|
||||
$hasFieldInfos = $fieldInfos !== null;
|
||||
$hasFieldValueInfos = $fieldValueInfos !== null;
|
||||
|
||||
if($hasFieldInfos && empty($fieldInfos))
|
||||
return [];
|
||||
if($hasFieldValueInfos && empty($fieldValueInfos))
|
||||
return [];
|
||||
if($hasFieldInfos) {
|
||||
if(!is_array($fieldInfos))
|
||||
$fieldInfos = iterator_to_array($fieldInfos);
|
||||
|
||||
if(empty($fieldInfos))
|
||||
return [];
|
||||
}
|
||||
if($hasFieldValueInfos) {
|
||||
if(!is_array($fieldValueInfos))
|
||||
$fieldValueInfos = iterator_to_array($fieldValueInfos);
|
||||
|
||||
if(empty($fieldValueInfos))
|
||||
return [];
|
||||
}
|
||||
|
||||
$args = 0;
|
||||
$query = 'SELECT format_id, field_id, format_regex, format_link, format_display FROM msz_profile_fields_formats';
|
||||
|
@ -101,13 +111,8 @@ class ProfileFields {
|
|||
}
|
||||
|
||||
$stmt->execute();
|
||||
$result = $stmt->getResult();
|
||||
$formats = [];
|
||||
|
||||
while($result->next())
|
||||
$formats[] = new ProfileFieldFormatInfo($result);
|
||||
|
||||
return $formats;
|
||||
return $stmt->getResult()->getIterator(ProfileFieldFormatInfo::fromResult(...));
|
||||
}
|
||||
|
||||
public function getFieldFormat(string $formatId): ProfileFieldFormatInfo {
|
||||
|
@ -119,7 +124,7 @@ class ProfileFields {
|
|||
if(!$result->next())
|
||||
throw new RuntimeException('No format found with the provided format id.');
|
||||
|
||||
return new ProfileFieldFormatInfo($result);
|
||||
return ProfileFieldFormatInfo::fromResult($result);
|
||||
}
|
||||
|
||||
public function selectFieldFormat(
|
||||
|
@ -138,10 +143,10 @@ class ProfileFields {
|
|||
if(!$result->next())
|
||||
throw new RuntimeException('Could not determine an appropriate format for this field (missing default formatting)');
|
||||
|
||||
return new ProfileFieldFormatInfo($result);
|
||||
return ProfileFieldFormatInfo::fromResult($result);
|
||||
}
|
||||
|
||||
public function getFieldValues(UserInfo|string $userInfo): array {
|
||||
public function getFieldValues(UserInfo|string $userInfo): iterable {
|
||||
if($userInfo instanceof UserInfo)
|
||||
$userInfo = $userInfo->getId();
|
||||
|
||||
|
@ -151,13 +156,7 @@ class ProfileFields {
|
|||
$stmt->addParameter(1, $userInfo);
|
||||
$stmt->execute();
|
||||
|
||||
$result = $stmt->getResult();
|
||||
$values = [];
|
||||
|
||||
while($result->next())
|
||||
$values[] = new ProfileFieldValueInfo($result);
|
||||
|
||||
return $values;
|
||||
return $stmt->getResult()->getIterator(ProfileFieldValueInfo::fromResult(...));
|
||||
}
|
||||
|
||||
public function getFieldValue(
|
||||
|
@ -179,7 +178,7 @@ class ProfileFields {
|
|||
if(!$result->next())
|
||||
throw new RuntimeException('No value for this field and user combination found.');
|
||||
|
||||
return new ProfileFieldValueInfo($result);
|
||||
return ProfileFieldValueInfo::fromResult($result);
|
||||
}
|
||||
|
||||
public function setFieldValues(
|
||||
|
|
|
@ -1,21 +1,16 @@
|
|||
<?php
|
||||
namespace Misuzu;
|
||||
|
||||
use Index\Http\HttpFx;
|
||||
use Index\Http\HttpRequest;
|
||||
use Index\Routing\IRouter;
|
||||
use Index\Routing\IRouteHandler;
|
||||
use Misuzu\URLs\IURLSource;
|
||||
use Misuzu\URLs\URLInfo;
|
||||
use Misuzu\URLs\URLRegistry;
|
||||
use Index\Http\Routing\{HttpRouter,IRouter,IRouteHandler};
|
||||
use Misuzu\URLs\{IURLSource,URLInfo,URLRegistry};
|
||||
|
||||
class RoutingContext {
|
||||
private URLRegistry $urls;
|
||||
private HttpFx $router;
|
||||
private HttpRouter $router;
|
||||
|
||||
public function __construct() {
|
||||
$this->urls = new URLRegistry;
|
||||
$this->router = new HttpFx;
|
||||
$this->router = new HttpRouter(errorHandler: new RoutingErrorHandler);
|
||||
$this->router->use('/', fn($resp) => $resp->setPoweredBy('Misuzu'));
|
||||
}
|
||||
|
||||
|
@ -27,25 +22,6 @@ class RoutingContext {
|
|||
return $this->router;
|
||||
}
|
||||
|
||||
public function registerDefaultErrorPages(): void {
|
||||
$this->router->addErrorHandler(400, fn($resp) => $resp->setContent(Template::renderRaw('errors.400')));
|
||||
$this->router->addErrorHandler(403, fn($resp) => $resp->setContent(Template::renderRaw('errors.403')));
|
||||
$this->router->addErrorHandler(404, fn($resp) => $resp->setContent(Template::renderRaw('errors.404')));
|
||||
$this->router->addErrorHandler(500, fn($resp) => $resp->setContent(file_get_contents(MSZ_TEMPLATES . '/500.html')));
|
||||
$this->router->addErrorHandler(503, fn($resp) => $resp->setContent(file_get_contents(MSZ_TEMPLATES . '/503.html')));
|
||||
}
|
||||
|
||||
public static function registerSimpleErrorPages(IRouter $router, string $path): void {
|
||||
if($router instanceof HttpFx)
|
||||
$router->use($path, function() use($router) {
|
||||
$router->addErrorHandler(400, fn($resp) => $resp->setContent('HTTP 400'));
|
||||
$router->addErrorHandler(403, fn($resp) => $resp->setContent('HTTP 403'));
|
||||
$router->addErrorHandler(404, fn($resp) => $resp->setContent('HTTP 404'));
|
||||
$router->addErrorHandler(500, fn($resp) => $resp->setContent('HTTP 500'));
|
||||
$router->addErrorHandler(503, fn($resp) => $resp->setContent('HTTP 503'));
|
||||
});
|
||||
}
|
||||
|
||||
public function register(IRouteHandler|IURLSource $handler): void {
|
||||
if($handler instanceof IRouteHandler)
|
||||
$this->router->register($handler);
|
||||
|
@ -54,7 +30,7 @@ class RoutingContext {
|
|||
URLInfo::handleAttributes($this->urls, $handler);
|
||||
}
|
||||
|
||||
public function dispatch(?HttpRequest $request = null): void {
|
||||
$this->router->dispatch($request);
|
||||
public function dispatch(...$args): void {
|
||||
$this->router->dispatch(...$args);
|
||||
}
|
||||
}
|
||||
|
|
29
src/RoutingErrorHandler.php
Normal file
29
src/RoutingErrorHandler.php
Normal file
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
namespace Misuzu;
|
||||
|
||||
use Index\Http\{HttpResponseBuilder,HttpRequest};
|
||||
use Index\Http\ErrorHandling\HtmlErrorHandler;
|
||||
|
||||
class RoutingErrorHandler extends HtmlErrorHandler {
|
||||
public function handle(HttpResponseBuilder $response, HttpRequest $request, int $code, string $message): void {
|
||||
if(str_starts_with($request->getPath(), '/_')) {
|
||||
$response->setTypePlain();
|
||||
$response->setContent(sprintf('HTTP %03d', $code));
|
||||
return;
|
||||
}
|
||||
|
||||
if($code === 500 || $code === 503) {
|
||||
$response->setTypeHTML();
|
||||
$response->setContent(file_get_contents(sprintf('%s/%03d.html', MSZ_TEMPLATES, $code)));
|
||||
return;
|
||||
}
|
||||
|
||||
if($code === 401 || $code === 403 || $code === 404) {
|
||||
$response->setTypeHTML();
|
||||
$response->setContent(Template::renderRaw(sprintf('errors.%03d', $code)));
|
||||
return;
|
||||
}
|
||||
|
||||
parent::handle($response, $request, $code, $message);
|
||||
}
|
||||
}
|
|
@ -3,9 +3,7 @@ namespace Misuzu\Satori;
|
|||
|
||||
use RuntimeException;
|
||||
use Index\Colour\Colour;
|
||||
use Index\Routing\IRouter;
|
||||
use Index\Routing\IRouteHandler;
|
||||
use Index\Routing\Route;
|
||||
use Index\Http\Routing\{HttpGet,HttpMiddleware,RouteHandler};
|
||||
use Syokuhou\IConfig;
|
||||
use Misuzu\Pagination;
|
||||
use Misuzu\RoutingContext;
|
||||
|
@ -13,7 +11,7 @@ use Misuzu\Forum\ForumContext;
|
|||
use Misuzu\Profile\ProfileFields;
|
||||
use Misuzu\Users\UsersContext;
|
||||
|
||||
final class SatoriRoutes implements IRouteHandler {
|
||||
final class SatoriRoutes extends RouteHandler {
|
||||
public function __construct(
|
||||
private IConfig $config,
|
||||
private UsersContext $usersCtx,
|
||||
|
@ -21,12 +19,7 @@ final class SatoriRoutes implements IRouteHandler {
|
|||
private ProfileFields $profileFields
|
||||
) {}
|
||||
|
||||
public function registerRoutes(IRouter $router): void {
|
||||
RoutingContext::registerSimpleErrorPages($router, '/_satori');
|
||||
Route::handleAttributes($router, $this);
|
||||
}
|
||||
|
||||
#[Route('/_satori')]
|
||||
#[HttpMiddleware('/_satori')]
|
||||
public function verifyRequest($response, $request) {
|
||||
$secretKey = $this->config->getString('secret');
|
||||
|
||||
|
@ -46,7 +39,7 @@ final class SatoriRoutes implements IRouteHandler {
|
|||
}
|
||||
}
|
||||
|
||||
#[Route('GET', '/_satori/get-profile-field')]
|
||||
#[HttpGet('/_satori/get-profile-field')]
|
||||
public function getProfileField($response, $request): array {
|
||||
$userId = (string)$request->getParam('user', FILTER_SANITIZE_NUMBER_INT);
|
||||
$fieldId = (string)$request->getParam('field', FILTER_SANITIZE_NUMBER_INT);
|
||||
|
@ -62,7 +55,7 @@ final class SatoriRoutes implements IRouteHandler {
|
|||
];
|
||||
}
|
||||
|
||||
#[Route('GET', '/_satori/get-recent-forum-posts')]
|
||||
#[HttpGet('/_satori/get-recent-forum-posts')]
|
||||
public function getRecentForumPosts($response, $request): array {
|
||||
$categoryIds = $this->config->getArray('forum.categories');
|
||||
if(empty($categoryIds))
|
||||
|
@ -104,7 +97,7 @@ final class SatoriRoutes implements IRouteHandler {
|
|||
return $posts;
|
||||
}
|
||||
|
||||
#[Route('GET', '/_satori/get-recent-registrations')]
|
||||
#[HttpGet('/_satori/get-recent-registrations')]
|
||||
public function getRecentRegistrations($response, $request) {
|
||||
$batchSize = $this->config->getInteger('users.batch', 10);
|
||||
$backlogDays = $this->config->getInteger('users.backlog', 7);
|
||||
|
|
|
@ -3,25 +3,21 @@ namespace Misuzu\SharpChat;
|
|||
|
||||
use RuntimeException;
|
||||
use Index\Colour\Colour;
|
||||
use Index\Routing\IRouter;
|
||||
use Index\Routing\IRouteHandler;
|
||||
use Index\Routing\Route;
|
||||
use Index\Http\Routing\{HandlerAttribute,HttpDelete,HttpGet,HttpOptions,HttpPost,RouteHandler};
|
||||
use Syokuhou\IConfig;
|
||||
use Misuzu\RoutingContext;
|
||||
use Misuzu\Auth\AuthContext;
|
||||
use Misuzu\Auth\AuthInfo;
|
||||
use Misuzu\Auth\Sessions;
|
||||
use Misuzu\Auth\{AuthContext,AuthInfo,Sessions};
|
||||
use Misuzu\Emoticons\Emotes;
|
||||
use Misuzu\Perms\Permissions;
|
||||
use Misuzu\URLs\URLRegistry;
|
||||
use Misuzu\Users\Bans;
|
||||
use Misuzu\Users\UsersContext;
|
||||
use Misuzu\Users\{Bans,UsersContext,UserInfo};
|
||||
|
||||
final class SharpChatRoutes implements IRouteHandler {
|
||||
final class SharpChatRoutes extends RouteHandler {
|
||||
private string $hashKey;
|
||||
|
||||
public function __construct(
|
||||
private IConfig $config,
|
||||
private IConfig $impersonateConfig, // this sucks lol
|
||||
private URLRegistry $urls,
|
||||
private UsersContext $usersCtx,
|
||||
private AuthContext $authCtx,
|
||||
|
@ -32,13 +28,8 @@ final class SharpChatRoutes implements IRouteHandler {
|
|||
$this->hashKey = $this->config->getString('hashKey', 'woomy');
|
||||
}
|
||||
|
||||
public function registerRoutes(IRouter $router): void {
|
||||
RoutingContext::registerSimpleErrorPages($router, '/_sockchat');
|
||||
Route::handleAttributes($router, $this);
|
||||
}
|
||||
|
||||
#[Route('OPTIONS', '/_sockchat/emotes')]
|
||||
#[Route('GET', '/_sockchat/emotes')]
|
||||
#[HttpOptions('/_sockchat/emotes')]
|
||||
#[HttpGet('/_sockchat/emotes')]
|
||||
public function getEmotes($response, $request): array|int {
|
||||
$response->setHeader('Access-Control-Allow-Origin', '*');
|
||||
$response->setHeader('Access-Control-Allow-Methods', 'GET');
|
||||
|
@ -66,7 +57,7 @@ final class SharpChatRoutes implements IRouteHandler {
|
|||
return $out;
|
||||
}
|
||||
|
||||
#[Route('GET', '/_sockchat/login')]
|
||||
#[HttpGet('/_sockchat/login')]
|
||||
public function getLogin($response, $request) {
|
||||
if(!$this->authInfo->isLoggedIn()) {
|
||||
$response->redirect($this->urls->format('auth-login'));
|
||||
|
@ -79,8 +70,16 @@ final class SharpChatRoutes implements IRouteHandler {
|
|||
));
|
||||
}
|
||||
|
||||
#[Route('OPTIONS', '/_sockchat/token')]
|
||||
#[Route('GET', '/_sockchat/token')]
|
||||
private function canImpersonateUserId(UserInfo $impersonator, string $targetId): bool {
|
||||
if($impersonator->isSuperUser())
|
||||
return true;
|
||||
|
||||
$whitelist = $this->impersonateConfig->getArray(sprintf('allow.u%s', $impersonator->getId()));
|
||||
return in_array($targetId, $whitelist, true);
|
||||
}
|
||||
|
||||
#[HttpOptions('/_sockchat/token')]
|
||||
#[HttpGet('/_sockchat/token')]
|
||||
public function getToken($response, $request) {
|
||||
$host = $request->hasHeader('Host') ? $request->getHeaderFirstLine('Host') : '';
|
||||
$origin = $request->hasHeader('Origin') ? $request->getHeaderFirstLine('Origin') : '';
|
||||
|
@ -121,7 +120,7 @@ final class SharpChatRoutes implements IRouteHandler {
|
|||
return ['ok' => false, 'err' => 'user'];
|
||||
|
||||
$userInfo = $this->usersCtx->getUsers()->getUser($sessionInfo->getUserId(), 'id');
|
||||
$userId = $tokenInfo->hasImpersonatedUserId() && $userInfo->isSuperUser()
|
||||
$userId = $tokenInfo->hasImpersonatedUserId() && $this->canImpersonateUserId($userInfo, $tokenInfo->getImpersonatedUserId())
|
||||
? $tokenInfo->getImpersonatedUserId()
|
||||
: $userInfo->getId();
|
||||
|
||||
|
@ -134,7 +133,7 @@ final class SharpChatRoutes implements IRouteHandler {
|
|||
];
|
||||
}
|
||||
|
||||
#[Route('POST', '/_sockchat/bump')]
|
||||
#[HttpPost('/_sockchat/bump')]
|
||||
public function postBump($response, $request) {
|
||||
if(!$request->hasHeader('X-SharpChat-Signature'))
|
||||
return 400;
|
||||
|
@ -164,7 +163,7 @@ final class SharpChatRoutes implements IRouteHandler {
|
|||
$this->usersCtx->getUsers()->recordUserActivity($userId, remoteAddr: $ipAddr);
|
||||
}
|
||||
|
||||
#[Route('POST', '/_sockchat/verify')]
|
||||
#[HttpPost('/_sockchat/verify')]
|
||||
public function postVerify($response, $request) {
|
||||
if(!$request->hasHeader('X-SharpChat-Signature'))
|
||||
return 400;
|
||||
|
@ -215,7 +214,7 @@ final class SharpChatRoutes implements IRouteHandler {
|
|||
$this->authCtx->getSessions()->recordSessionActivity(sessionInfo: $sessionInfo, remoteAddr: $ipAddress);
|
||||
|
||||
$userInfo = $this->usersCtx->getUsers()->getUser($sessionInfo->getUserId(), 'id');
|
||||
if($tokenInfo->hasImpersonatedUserId() && $userInfo->isSuperUser()) {
|
||||
if($tokenInfo->hasImpersonatedUserId() && $this->canImpersonateUserId($userInfo, $tokenInfo->getImpersonatedUserId())) {
|
||||
$userInfoReal = $userInfo;
|
||||
|
||||
try {
|
||||
|
@ -245,7 +244,7 @@ final class SharpChatRoutes implements IRouteHandler {
|
|||
];
|
||||
}
|
||||
|
||||
#[Route('GET', '/_sockchat/bans/list')]
|
||||
#[HttpGet('/_sockchat/bans/list')]
|
||||
public function getBanList($response, $request) {
|
||||
if(!$request->hasHeader('X-SharpChat-Signature'))
|
||||
return 400;
|
||||
|
@ -280,7 +279,7 @@ final class SharpChatRoutes implements IRouteHandler {
|
|||
return $list;
|
||||
}
|
||||
|
||||
#[Route('GET', '/_sockchat/bans/check')]
|
||||
#[HttpGet('/_sockchat/bans/check')]
|
||||
public function getBanCheck($response, $request) {
|
||||
if(!$request->hasHeader('X-SharpChat-Signature'))
|
||||
return 400;
|
||||
|
@ -318,7 +317,7 @@ final class SharpChatRoutes implements IRouteHandler {
|
|||
];
|
||||
}
|
||||
|
||||
#[Route('POST', '/_sockchat/bans/create')]
|
||||
#[HttpPost('/_sockchat/bans/create')]
|
||||
public function postBanCreate($response, $request): int {
|
||||
if(!$request->hasHeader('X-SharpChat-Signature') || !$request->isFormContent())
|
||||
return 400;
|
||||
|
@ -392,7 +391,7 @@ final class SharpChatRoutes implements IRouteHandler {
|
|||
return 201;
|
||||
}
|
||||
|
||||
#[Route('DELETE', '/_sockchat/bans/revoke')]
|
||||
#[HttpDelete('/_sockchat/bans/revoke')]
|
||||
public function deleteBanRevoke($response, $request): int {
|
||||
if(!$request->hasHeader('X-SharpChat-Signature'))
|
||||
return 400;
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
<?php
|
||||
namespace Misuzu;
|
||||
|
||||
use Index\Routing\IRouter;
|
||||
use Index\Http\HttpFx;
|
||||
|
||||
final class Tools {
|
||||
public static function isLocalURL(
|
||||
string $url,
|
||||
|
|
|
@ -3,14 +3,11 @@ namespace Misuzu\Users\Assets;
|
|||
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
use Index\Routing\Route;
|
||||
use Index\Routing\RouteHandler;
|
||||
use Index\Http\Routing\{HttpGet,RouteHandler};
|
||||
use Misuzu\Perm;
|
||||
use Misuzu\Auth\AuthInfo;
|
||||
use Misuzu\URLs\URLInfo;
|
||||
use Misuzu\URLs\URLRegistry;
|
||||
use Misuzu\Users\UsersContext;
|
||||
use Misuzu\Users\UserInfo;
|
||||
use Misuzu\URLs\{URLInfo,URLRegistry};
|
||||
use Misuzu\Users\{UsersContext,UserInfo};
|
||||
|
||||
class AssetsRoutes extends RouteHandler {
|
||||
public function __construct(
|
||||
|
@ -29,11 +26,10 @@ class AssetsRoutes extends RouteHandler {
|
|||
return true;
|
||||
}
|
||||
|
||||
#[Route('GET', '/assets/avatar')]
|
||||
#[Route('GET', '/assets/avatar/:filename')]
|
||||
#[HttpGet('/assets/avatar')]
|
||||
#[HttpGet('/assets/avatar/([0-9]+)(?:\.[a-z]+)?')]
|
||||
#[URLInfo('user-avatar', '/assets/avatar/<user>', ['res' => '<res>'])]
|
||||
public function getAvatar($response, $request, string $fileName = '') {
|
||||
$userId = pathinfo($fileName, PATHINFO_FILENAME);
|
||||
public function getAvatar($response, $request, string $userId = '') {
|
||||
$assetInfo = new StaticUserImageAsset(MSZ_PUBLIC . '/images/no-avatar.png', MSZ_PUBLIC);
|
||||
|
||||
try {
|
||||
|
@ -52,12 +48,10 @@ class AssetsRoutes extends RouteHandler {
|
|||
$this->serveAsset($response, $request, $assetInfo);
|
||||
}
|
||||
|
||||
#[Route('GET', '/assets/profile-background')]
|
||||
#[Route('GET', '/assets/profile-background/:filename')]
|
||||
#[HttpGet('/assets/profile-background')]
|
||||
#[HttpGet('/assets/profile-background/([0-9]+)(?:\.[a-z]+)?')]
|
||||
#[URLInfo('user-background', '/assets/profile-background/<user>')]
|
||||
public function getProfileBackground($response, $request, string $fileName = '') {
|
||||
$userId = pathinfo($fileName, PATHINFO_FILENAME);
|
||||
|
||||
public function getProfileBackground($response, $request, string $userId = '') {
|
||||
try {
|
||||
$userInfo = $this->usersCtx->getUserInfo($userId);
|
||||
} catch(RuntimeException $ex) {
|
||||
|
@ -78,7 +72,7 @@ class AssetsRoutes extends RouteHandler {
|
|||
$this->serveAsset($response, $request, $assetInfo);
|
||||
}
|
||||
|
||||
#[Route('GET', '/user-assets.php')]
|
||||
#[HttpGet('/user-assets.php')]
|
||||
public function getUserAssets($response, $request) {
|
||||
$userId = (string)$request->getParam('u', FILTER_SANITIZE_NUMBER_INT);
|
||||
$mode = (string)$request->getParam('m');
|
||||
|
|
|
@ -5,24 +5,28 @@ use Index\DateTime;
|
|||
use Index\Data\IDbResult;
|
||||
|
||||
class BanInfo {
|
||||
private string $id;
|
||||
private string $userId;
|
||||
private ?string $modId;
|
||||
private int $severity;
|
||||
private string $publicReason;
|
||||
private string $privateReason;
|
||||
private int $created;
|
||||
private ?int $expires;
|
||||
public function __construct(
|
||||
private string $id,
|
||||
private string $userId,
|
||||
private ?string $modId,
|
||||
private int $severity,
|
||||
private string $publicReason,
|
||||
private string $privateReason,
|
||||
private int $created,
|
||||
private ?int $expires,
|
||||
) {}
|
||||
|
||||
public function __construct(IDbResult $result) {
|
||||
$this->id = (string)$result->getInteger(0);
|
||||
$this->userId = (string)$result->getInteger(1);
|
||||
$this->modId = $result->isNull(2) ? null : (string)$result->getInteger(2);
|
||||
$this->severity = $result->getInteger(3);
|
||||
$this->publicReason = $result->getString(4);
|
||||
$this->privateReason = $result->getString(5);
|
||||
$this->created = $result->getInteger(6);
|
||||
$this->expires = $result->isNull(7) ? null : $result->getInteger(7);
|
||||
public static function fromResult(IDbResult $result): BanInfo {
|
||||
return new BanInfo(
|
||||
id: $result->getString(0),
|
||||
userId: $result->getString(1),
|
||||
modId: $result->getStringOrNull(2),
|
||||
severity: $result->getInteger(3),
|
||||
publicReason: $result->getString(4),
|
||||
privateReason: $result->getString(5),
|
||||
created: $result->getInteger(6),
|
||||
expires: $result->getIntegerOrNull(7),
|
||||
);
|
||||
}
|
||||
|
||||
public function getId(): string {
|
||||
|
|
|
@ -64,7 +64,7 @@ class Bans {
|
|||
?bool $activeOnly = null,
|
||||
?bool $activeFirst = null,
|
||||
?Pagination $pagination = null
|
||||
): array {
|
||||
): iterable {
|
||||
if($userInfo instanceof UserInfo)
|
||||
$userInfo = $userInfo->getId();
|
||||
|
||||
|
@ -101,13 +101,7 @@ class Bans {
|
|||
}
|
||||
$stmt->execute();
|
||||
|
||||
$result = $stmt->getResult();
|
||||
$bans = [];
|
||||
|
||||
while($result->next())
|
||||
$bans[] = new BanInfo($result);
|
||||
|
||||
return $bans;
|
||||
return $stmt->getResult()->getIterator(BanInfo::fromResult(...));
|
||||
}
|
||||
|
||||
public function getBan(string $banId): BanInfo {
|
||||
|
@ -119,7 +113,7 @@ class Bans {
|
|||
if(!$result->next())
|
||||
throw new RuntimeException('No ban with ID $banId found.');
|
||||
|
||||
return new BanInfo($result);
|
||||
return BanInfo::fromResult($result);
|
||||
}
|
||||
|
||||
public function countActiveBans(
|
||||
|
@ -153,7 +147,7 @@ class Bans {
|
|||
$stmt->execute();
|
||||
|
||||
$result = $stmt->getResult();
|
||||
return $result->next() ? new BanInfo($result) : null;
|
||||
return $result->next() ? BanInfo::fromResult($result) : null;
|
||||
}
|
||||
|
||||
public function createBan(
|
||||
|
|
|
@ -5,20 +5,24 @@ use Index\DateTime;
|
|||
use Index\Data\IDbResult;
|
||||
|
||||
class ModNoteInfo {
|
||||
private string $noteId;
|
||||
private string $userId;
|
||||
private ?string $authorId;
|
||||
private int $created;
|
||||
private string $title;
|
||||
private string $body;
|
||||
public function __construct(
|
||||
private string $noteId,
|
||||
private string $userId,
|
||||
private ?string $authorId,
|
||||
private int $created,
|
||||
private string $title,
|
||||
private string $body,
|
||||
) {}
|
||||
|
||||
public function __construct(IDbResult $result) {
|
||||
$this->noteId = (string)$result->getInteger(0);
|
||||
$this->userId = (string)$result->getInteger(1);
|
||||
$this->authorId = $result->isNull(2) ? null : (string)$result->getInteger(2);
|
||||
$this->created = $result->getInteger(3);
|
||||
$this->title = $result->getString(4);
|
||||
$this->body = $result->getString(5);
|
||||
public static function fromResult(IDbResult $result): ModNoteInfo {
|
||||
return new ModNoteInfo(
|
||||
noteId: $result->getString(0),
|
||||
userId: $result->getString(1),
|
||||
authorId: $result->getStringOrNull(2),
|
||||
created: $result->getInteger(3),
|
||||
title: $result->getString(4),
|
||||
body: $result->getString(5)
|
||||
);
|
||||
}
|
||||
|
||||
public function getId(): string {
|
||||
|
|
|
@ -59,7 +59,7 @@ class ModNotes {
|
|||
UserInfo|string|null $userInfo = null,
|
||||
UserInfo|string|null $authorInfo = null,
|
||||
?Pagination $pagination = null
|
||||
): array {
|
||||
): iterable {
|
||||
if($userInfo instanceof UserInfo)
|
||||
$userInfo = $userInfo->getId();
|
||||
if($authorInfo instanceof UserInfo)
|
||||
|
@ -93,13 +93,7 @@ class ModNotes {
|
|||
}
|
||||
$stmt->execute();
|
||||
|
||||
$result = $stmt->getResult();
|
||||
$notes = [];
|
||||
|
||||
while($result->next())
|
||||
$notes[] = new ModNoteInfo($result);
|
||||
|
||||
return $notes;
|
||||
return $stmt->getResult()->getIterator(ModNoteInfo::fromResult(...));
|
||||
}
|
||||
|
||||
public function getNote(string $noteId): ModNoteInfo {
|
||||
|
@ -111,7 +105,7 @@ class ModNotes {
|
|||
if(!$result->next())
|
||||
throw new RuntimeException('No note with ID $noteId found.');
|
||||
|
||||
return new ModNoteInfo($result);
|
||||
return ModNoteInfo::fromResult($result);
|
||||
}
|
||||
|
||||
public function createNote(
|
||||
|
|
|
@ -7,26 +7,30 @@ use Index\Colour\Colour;
|
|||
use Index\Data\IDbResult;
|
||||
|
||||
class RoleInfo implements Stringable {
|
||||
private string $id;
|
||||
private int $rank;
|
||||
private string $name;
|
||||
private ?string $title;
|
||||
private ?string $description;
|
||||
private bool $hidden;
|
||||
private bool $leavable;
|
||||
private ?int $colour;
|
||||
private int $created;
|
||||
public function __construct(
|
||||
private string $id,
|
||||
private int $rank,
|
||||
private string $name,
|
||||
private ?string $title,
|
||||
private ?string $description,
|
||||
private bool $hidden,
|
||||
private bool $leavable,
|
||||
private ?int $colour,
|
||||
private int $created,
|
||||
) {}
|
||||
|
||||
public function __construct(IDbResult $result) {
|
||||
$this->id = (string)$result->getInteger(0);
|
||||
$this->rank = $result->getInteger(1);
|
||||
$this->name = $result->getString(2);
|
||||
$this->title = $result->isNull(3) ? null : $result->getString(3);
|
||||
$this->description = $result->isNull(4) ? null : $result->getString(4);
|
||||
$this->hidden = $result->getInteger(5) !== 0;
|
||||
$this->leavable = $result->getInteger(6) !== 0;
|
||||
$this->colour = $result->isNull(7) ? null : $result->getInteger(7);
|
||||
$this->created = $result->getInteger(8);
|
||||
public static function fromResult(IDbResult $result): RoleInfo {
|
||||
return new RoleInfo(
|
||||
id: $result->getString(0),
|
||||
rank: $result->getInteger(1),
|
||||
name: $result->getString(2),
|
||||
title: $result->getStringOrNull(3),
|
||||
description: $result->getStringOrNull(4),
|
||||
hidden: $result->getBoolean(5),
|
||||
leavable: $result->getBoolean(6),
|
||||
colour: $result->getIntegerOrNull(7),
|
||||
created: $result->getInteger(8),
|
||||
);
|
||||
}
|
||||
|
||||
public function getId(): string {
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue