diff --git a/com.woltlab.wcf/objectTypeDefinition.xml b/com.woltlab.wcf/objectTypeDefinition.xml
index 38524d0327e..8ea654c528d 100644
--- a/com.woltlab.wcf/objectTypeDefinition.xml
+++ b/com.woltlab.wcf/objectTypeDefinition.xml
@@ -27,6 +27,7 @@
com.woltlab.wcf.message.quote
+ wcf\system\message\quote\IMessageQuoteHandler
com.woltlab.wcf.user.recentActivityEvent
diff --git a/ts/WoltLabSuite/Core/Api/Messages/Author.ts b/ts/WoltLabSuite/Core/Api/Messages/Author.ts
deleted file mode 100644
index a2a08210dcf..00000000000
--- a/ts/WoltLabSuite/Core/Api/Messages/Author.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-/**
- * Requests render a full quote of a message.
- *
- * @author Olaf Braun
- * @copyright 2001-2024 WoltLab GmbH
- * @license GNU Lesser General Public License
- * @since 6.2
- * @woltlabExcludeBundle tiny
- */
-
-import { prepareRequest } from "WoltLabSuite/Core/Ajax/Backend";
-import { ApiResult, apiResultFromError, apiResultFromValue } from "../Result";
-
-type Response = {
- objectID: number;
- authorID: number;
- author: string;
- time: string;
- title: string;
- link: string;
- avatar: string;
-};
-
-export async function getMessageAuthor(className: string, objectID: number): Promise> {
- const url = new URL(window.WSC_RPC_API_URL + "core/messages/message-author");
- url.searchParams.set("className", className);
- url.searchParams.set("objectID", objectID.toString());
-
- let response: Response;
- try {
- response = (await prepareRequest(url).get().allowCaching().fetchAsJson()) as Response;
- } catch (e) {
- return apiResultFromError(e);
- }
-
- return apiResultFromValue(response);
-}
diff --git a/ts/WoltLabSuite/Core/Api/Messages/RenderQuote.ts b/ts/WoltLabSuite/Core/Api/Messages/RenderQuote.ts
index f426872ea73..449b0b67fab 100644
--- a/ts/WoltLabSuite/Core/Api/Messages/RenderQuote.ts
+++ b/ts/WoltLabSuite/Core/Api/Messages/RenderQuote.ts
@@ -13,11 +13,8 @@ import { ApiResult, apiResultFromError, apiResultFromValue } from "../Result";
type Response = {
objectID: number;
- authorID: number | null;
author: string;
- time: string;
link: string;
- title: string;
avatar: string;
message: string | null;
rawMessage: string | null;
@@ -25,13 +22,12 @@ type Response = {
export async function renderQuote(
objectType: string,
- className: string,
objectID: number,
+ isFullQuote: boolean,
): Promise> {
const url = new URL(window.WSC_RPC_API_URL + "core/messages/render-quote");
url.searchParams.set("objectType", objectType);
- url.searchParams.set("className", className);
- url.searchParams.set("fullQuote", "true");
+ url.searchParams.set("isFullQuote", String(isFullQuote));
url.searchParams.set("objectID", objectID.toString());
let response: Response;
diff --git a/ts/WoltLabSuite/Core/Component/Quote/List.ts b/ts/WoltLabSuite/Core/Component/Quote/List.ts
index 1b51acec89b..0c951898b33 100644
--- a/ts/WoltLabSuite/Core/Component/Quote/List.ts
+++ b/ts/WoltLabSuite/Core/Component/Quote/List.ts
@@ -78,10 +78,15 @@ class QuoteList {
fragment.querySelector('button[data-action="insert"]')!.addEventListener("click", () => {
markQuoteAsUsed(this.#editorId, uuid);
+ const content = quote.rawMessage || quote.message;
+ if (content === null) {
+ throw new Error("Expected either the `rawMessage` or `message` to be a string.");
+ }
+
dispatchToCkeditor(this.#editor).insertQuote({
author: message.author,
- content: quote.rawMessage === undefined ? quote.message : quote.rawMessage,
- isText: quote.rawMessage === undefined,
+ content,
+ isText: !quote.rawMessage,
link: message.link,
});
});
@@ -142,21 +147,23 @@ export function setup(editorId: string, containerId?: string): void {
throw new Error(`The editor '${editorId}' does not exist.`);
}
- listenToCkeditor(editor).ready(({ ckeditor }) => {
- if (ckeditor.features.quoteBlock) {
- quoteLists.set(editorId, new QuoteList(editorId, editor, containerId));
- }
-
- if (ckeditor.isVisible()) {
- setActiveEditor(ckeditor, ckeditor.features.quoteBlock);
- }
+ listenToCkeditor(editor)
+ .ready(({ ckeditor }) => {
+ if (ckeditor.features.quoteBlock) {
+ quoteLists.set(editorId, new QuoteList(editorId, editor, containerId));
+ }
- ckeditor.focusTracker.on("change:isFocused", (_evt: unknown, _name: unknown, isFocused: boolean) => {
- if (isFocused) {
+ if (ckeditor.isVisible()) {
setActiveEditor(ckeditor, ckeditor.features.quoteBlock);
}
+
+ ckeditor.focusTracker.on("change:isFocused", (_evt: unknown, _name: unknown, isFocused: boolean) => {
+ if (isFocused) {
+ setActiveEditor(ckeditor, ckeditor.features.quoteBlock);
+ }
+ });
+ })
+ .destroy(() => {
+ removeActiveEditor(editor);
});
- }).destroy(() => {
- removeActiveEditor(editor);
- });
}
diff --git a/ts/WoltLabSuite/Core/Component/Quote/Message.ts b/ts/WoltLabSuite/Core/Component/Quote/Message.ts
index 9f6ce5283f7..30606ee7dd1 100644
--- a/ts/WoltLabSuite/Core/Component/Quote/Message.ts
+++ b/ts/WoltLabSuite/Core/Component/Quote/Message.ts
@@ -17,20 +17,20 @@ import {
getFullQuoteUuid,
saveFullQuote,
markQuoteAsUsed,
- isFullQuoted,
getKey,
removeQuotes,
} from "WoltLabSuite/Core/Component/Quote/Storage";
import { promiseMutex } from "WoltLabSuite/Core/Helper/PromiseMutex";
import { dispatchToCkeditor } from "WoltLabSuite/Core/Component/Ckeditor/Event";
-interface Container {
+type Container = {
element: HTMLElement;
messageBodySelector: string;
objectType: string;
- className: string;
objectId: number;
-}
+ /** @deprecated 6.2 Used for legacy implementations only. */
+ className: string | undefined;
+};
let selectedMessage:
| undefined
@@ -39,12 +39,12 @@ let selectedMessage:
container: Container;
};
-interface ElementBoundaries {
+type ElementBoundaries = {
bottom: number;
left: number;
right: number;
top: number;
-}
+};
const containers = new Map();
const quoteMessageButtons = new Map();
@@ -57,19 +57,19 @@ const copyQuote = document.createElement("div");
export function registerContainer(
containerSelector: string,
messageBodySelector: string,
- className: string,
objectType: string,
+ className?: string,
): void {
wheneverFirstSeen(containerSelector, (container: HTMLElement) => {
const id = DomUtil.identify(container);
- const objectId = ~~container.dataset.objectId!;
+ const objectId = parseInt(container.dataset.objectId || "0");
containers.set(id, {
element: container,
- messageBodySelector: messageBodySelector,
- objectType: objectType,
- className: className,
- objectId: objectId,
+ messageBodySelector,
+ objectType,
+ objectId,
+ className,
});
if (container.classList.contains("jsInvalidQuoteTarget")) {
@@ -80,42 +80,53 @@ export function registerContainer(
container.classList.add("jsQuoteMessageContainer");
const quoteMessage = container.querySelector(".jsQuoteMessage");
- let quoteMessageButton = quoteMessage?.querySelector(".button");
- if (!quoteMessageButton && quoteMessage?.classList.contains("button")) {
+ if (quoteMessage === null) {
+ return;
+ }
+
+ let quoteMessageButton = quoteMessage.querySelector(".button");
+ if (!quoteMessageButton && quoteMessage.classList.contains("button")) {
quoteMessageButton = quoteMessage;
}
- if (quoteMessageButton) {
+ if (quoteMessageButton !== null) {
quoteMessageButtons.set(getKey(objectType, objectId), quoteMessageButton);
- if (isFullQuoted(objectType, objectId)) {
+ if (getFullQuoteUuid(objectType, objectId) !== undefined) {
quoteMessageButton.classList.add("active");
}
}
- quoteMessage?.addEventListener(
+ quoteMessage.addEventListener(
"click",
promiseMutex(async (event: MouseEvent) => {
event.preventDefault();
- if (isFullQuoted(objectType, objectId)) {
- removeQuotes([getFullQuoteUuid(objectType, objectId)!]);
- quoteMessageButton!.classList.remove("active");
+ const uuid = getFullQuoteUuid(objectType, objectId);
+ if (uuid !== undefined) {
+ removeQuotes([uuid]);
+ quoteMessageButton?.classList.remove("active");
+
return;
}
- const quoteMessage = await saveFullQuote(objectType, className, ~~container.dataset.objectId!);
- quoteMessageButton!.classList.add("active");
+ const quote = await saveFullQuote(objectType, objectId, className);
+ quoteMessageButton?.classList.add("active");
if (activeEditor !== undefined) {
+ const content = quote.rawMessage || quote.message;
+ if (content === null) {
+ throw new Error("Expected either the `rawMessage` or `message` to be a string.");
+ }
+
dispatchToCkeditor(activeEditor.sourceElement).insertQuote({
- author: quoteMessage.author,
- content: quoteMessage.rawMessage === undefined ? quoteMessage.message : quoteMessage.rawMessage,
- isText: quoteMessage.rawMessage === undefined,
- link: quoteMessage.link,
+ author: quote.author,
+ content,
+ isText: quote.rawMessage === null,
+ link: quote.link,
});
- markQuoteAsUsed(activeEditor.sourceElement.id, quoteMessage.uuid);
+ markQuoteAsUsed(activeEditor.sourceElement.id, quote.uuid);
}
}),
);
@@ -152,17 +163,22 @@ function setup() {
buttonSaveQuote.addEventListener(
"click",
promiseMutex(async () => {
+ if (selectedMessage === undefined) {
+ return;
+ }
+
await saveQuote(
- selectedMessage!.container.objectType,
- selectedMessage!.container.objectId,
- selectedMessage!.container.className,
- selectedMessage!.message,
+ selectedMessage.container.objectType,
+ selectedMessage.container.objectId,
+ selectedMessage.message,
+ selectedMessage.container.className,
);
removeSelection();
}),
);
copyQuote.appendChild(buttonSaveQuote);
+
const buttonSaveAndInsertQuote = document.createElement("button");
buttonSaveAndInsertQuote.type = "button";
buttonSaveAndInsertQuote.hidden = true;
@@ -171,22 +187,31 @@ function setup() {
buttonSaveAndInsertQuote.addEventListener(
"click",
promiseMutex(async () => {
- const quoteMessage = await saveQuote(
- selectedMessage!.container.objectType,
- selectedMessage!.container.objectId,
- selectedMessage!.container.className,
- selectedMessage!.message,
+ if (selectedMessage === undefined) {
+ return;
+ }
+
+ const quote = await saveQuote(
+ selectedMessage.container.objectType,
+ selectedMessage.container.objectId,
+ selectedMessage.message,
+ selectedMessage.container.className,
);
if (activeEditor !== undefined) {
+ const content = quote.rawMessage || quote.message;
+ if (content === null) {
+ throw new Error("Expected either the `rawMessage` or `message` to be a string.");
+ }
+
dispatchToCkeditor(activeEditor.sourceElement).insertQuote({
- author: quoteMessage.author,
- content: quoteMessage.rawMessage === undefined ? quoteMessage.message : quoteMessage.rawMessage,
- isText: quoteMessage.rawMessage === undefined,
- link: quoteMessage.link,
+ author: quote.author,
+ content,
+ isText: quote.rawMessage === null,
+ link: quote.link,
});
- markQuoteAsUsed(activeEditor.sourceElement.id, quoteMessage.uuid);
+ markQuoteAsUsed(activeEditor.sourceElement.id, quote.uuid);
}
removeSelection();
diff --git a/ts/WoltLabSuite/Core/Component/Quote/Storage.ts b/ts/WoltLabSuite/Core/Component/Quote/Storage.ts
index ba1b9f0d681..38bbb9f18b9 100644
--- a/ts/WoltLabSuite/Core/Component/Quote/Storage.ts
+++ b/ts/WoltLabSuite/Core/Component/Quote/Storage.ts
@@ -10,24 +10,21 @@
import * as Core from "WoltLabSuite/Core/Core";
import { renderQuote } from "WoltLabSuite/Core/Api/Messages/RenderQuote";
-import { getMessageAuthor } from "WoltLabSuite/Core/Api/Messages/Author";
import { refreshQuoteLists } from "WoltLabSuite/Core/Component/Quote/List";
import { resetRemovalQuotes } from "WoltLabSuite/Core/Api/Messages/ResetRemovalQuotes";
import { removeQuoteStatus } from "WoltLabSuite/Core/Component/Quote/Message";
+import { dboAction } from "WoltLabSuite/Core/Ajax";
interface Message {
objectID: number;
- time: string;
- title: string;
link: string;
- authorID: number | null;
author: string;
avatar: string;
}
interface Quote {
- message: string;
- rawMessage?: string;
+ message: string | null;
+ rawMessage: string | null;
}
interface StorageData {
@@ -35,28 +32,51 @@ interface StorageData {
messages: Map;
}
+type LegacyQuoteData = {
+ count: number;
+ fullQuoteMessageIDs: number[];
+ renderedQuote: Message & Quote;
+};
+
const STORAGE_KEY = Core.getStoragePrefix() + "quotes";
const usedQuotes = new Map>();
export async function saveQuote(
objectType: string,
objectId: number,
- objectClassName: string,
message: string,
+ /** @deprecated 6.2 Used for legacy implementations only. */
+ className?: string,
): Promise {
- const result = await getMessageAuthor(objectClassName, objectId);
- if (!result.ok) {
- throw new Error("Error fetching author data");
+ let quote: Message & Quote;
+
+ if (className !== undefined) {
+ const result = (await dboAction("saveQuote", className)
+ .objectIds([objectId])
+ .payload({
+ message,
+ renderQuote: true,
+ })
+ .dispatch()) as LegacyQuoteData;
+ quote = result.renderedQuote;
+ } else {
+ const result = await renderQuote(objectType, objectId, false);
+ if (!result.ok) {
+ throw new Error("Error fetching quote data");
+ }
+
+ quote = result.value;
}
- const uuid = storeQuote(objectType, result.value, {
+ const uuid = storeQuote(objectType, quote, {
message,
+ rawMessage: null,
});
refreshQuoteLists();
return {
- ...result.value,
+ ...quote,
message,
uuid,
};
@@ -64,36 +84,30 @@ export async function saveQuote(
export async function saveFullQuote(
objectType: string,
- objectClassName: string,
objectId: number,
+ /** @deprecated 6.2 Used for legacy implementations only. */
+ className?: string,
): Promise {
- const result = await renderQuote(objectType, objectClassName, objectId);
- if (!result.ok) {
- throw new Error("Error fetching quote data");
- }
-
- const message = {
- objectID: result.value.objectID,
- time: result.value.time,
- title: result.value.title,
- link: result.value.link,
- authorID: result.value.authorID,
- author: result.value.author,
- avatar: result.value.avatar,
- };
+ let message: Message & Quote;
+
+ if (className !== undefined) {
+ const result = (await dboAction("saveFullQuote", className).objectIds([objectId]).dispatch()) as LegacyQuoteData;
+ message = result.renderedQuote;
+ } else {
+ const result = await renderQuote(objectType, objectId, true);
+ if (!result.ok) {
+ throw new Error("Error fetching quote data");
+ }
- const quote = {
- message: result.value.message!,
- rawMessage: result.value.rawMessage!,
- };
+ message = result.value;
+ }
- const uuid = storeQuote(objectType, message, quote);
+ const uuid = storeQuote(objectType, message, message);
refreshQuoteLists();
return {
...message,
- ...quote,
uuid,
};
}
@@ -167,7 +181,7 @@ export function clearQuotesForEditor(editorId: string): void {
usedQuotes.get(editorId)?.forEach((uuid) => {
for (const [key, quotes] of storage.quotes) {
const quote = quotes.get(uuid);
- if (quote?.rawMessage !== undefined) {
+ if (quote?.rawMessage !== null) {
fullQuotes.push(key);
}
@@ -192,24 +206,6 @@ export function clearQuotesForEditor(editorId: string): void {
});
}
-export function isFullQuoted(objectType: string, objectId: number): boolean {
- const key = getKey(objectType, objectId);
- const storage = getStorage();
- const quotes = storage.quotes.get(key);
-
- if (quotes === undefined) {
- return false;
- }
-
- return (
- Array.from(quotes).filter(([, quote]) => {
- if (quote.rawMessage !== undefined) {
- return true;
- }
- }).length > 0
- );
-}
-
function storeQuote(objectType: string, message: Message, quote: Quote): string {
const storage = getStorage();
@@ -221,7 +217,7 @@ function storeQuote(objectType: string, message: Message, quote: Quote): string
storage.messages.set(key, message);
for (const [uuid, q] of storage.quotes.get(key)!) {
- if ((q.rawMessage !== undefined && q.rawMessage === quote.rawMessage) || q.message === quote.message) {
+ if ((q.rawMessage !== null && q.rawMessage === null) || q.message === quote.message) {
return uuid;
}
}
@@ -235,11 +231,14 @@ function storeQuote(objectType: string, message: Message, quote: Quote): string
}
export function getFullQuoteUuid(objectType: string, objectId: number): string | undefined {
- const storage = getStorage();
const key = getKey(objectType, objectId);
+ const quotes = getStorage().quotes.get(key);
+ if (quotes === undefined) {
+ return undefined;
+ }
- for (const [uuid, q] of storage.quotes.get(key)!) {
- if (q.rawMessage !== undefined && q.message !== undefined) {
+ for (const [uuid, q] of quotes) {
+ if (q.rawMessage !== null && q.message !== null) {
return uuid;
}
}
@@ -303,7 +302,7 @@ window.addEventListener("storage", (event) => {
// Update the quote status if the quote was removed in another tab
for (const [key, quotes] of oldValue.quotes) {
for (const [, quote] of quotes) {
- if (quote.rawMessage !== undefined && !newValue.quotes.has(key)) {
+ if (quote.rawMessage !== null && !newValue.quotes.has(key)) {
removeQuoteStatus(key);
}
}
diff --git a/ts/WoltLabSuite/Core/Ui/Message/Quote.ts b/ts/WoltLabSuite/Core/Ui/Message/Quote.ts
index a3dad998ce7..c039368b0d1 100644
--- a/ts/WoltLabSuite/Core/Ui/Message/Quote.ts
+++ b/ts/WoltLabSuite/Core/Ui/Message/Quote.ts
@@ -6,18 +6,15 @@
import { registerContainer } from "WoltLabSuite/Core/Component/Quote/Message";
-// see WCF.Message.Quote.Manager
-export interface WCFMessageQuoteManager {
- supportPaste: () => boolean;
- updateCount: (number, object) => void;
-}
-
+/**
+ * @deprecated 6.2 Use `registerContainer()` without the className parameter.
+ */
export class UiMessageQuote {
/**
* Initializes the quote handler for given object type.
*/
constructor(
- _quoteManager: WCFMessageQuoteManager,
+ _quoteManager: typeof window.WCF.Message.Quote.Manager,
className: string,
objectType: string,
containerSelector: string,
@@ -25,12 +22,7 @@ export class UiMessageQuote {
_messageContentSelector: string,
_supportDirectInsert: boolean,
) {
- // remove "Action" from className
- if (className.endsWith("Action")) {
- className = className.substring(0, className.length - 6);
- }
-
- registerContainer(containerSelector, messageBodySelector, className, objectType);
+ registerContainer(containerSelector, messageBodySelector, objectType, className);
}
}
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Api/Messages/Author.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Api/Messages/Author.js
deleted file mode 100644
index be9deb91cb5..00000000000
--- a/wcfsetup/install/files/js/WoltLabSuite/Core/Api/Messages/Author.js
+++ /dev/null
@@ -1,27 +0,0 @@
-/**
- * Requests render a full quote of a message.
- *
- * @author Olaf Braun
- * @copyright 2001-2024 WoltLab GmbH
- * @license GNU Lesser General Public License
- * @since 6.2
- * @woltlabExcludeBundle tiny
- */
-define(["require", "exports", "WoltLabSuite/Core/Ajax/Backend", "../Result"], function (require, exports, Backend_1, Result_1) {
- "use strict";
- Object.defineProperty(exports, "__esModule", { value: true });
- exports.getMessageAuthor = getMessageAuthor;
- async function getMessageAuthor(className, objectID) {
- const url = new URL(window.WSC_RPC_API_URL + "core/messages/message-author");
- url.searchParams.set("className", className);
- url.searchParams.set("objectID", objectID.toString());
- let response;
- try {
- response = (await (0, Backend_1.prepareRequest)(url).get().allowCaching().fetchAsJson());
- }
- catch (e) {
- return (0, Result_1.apiResultFromError)(e);
- }
- return (0, Result_1.apiResultFromValue)(response);
- }
-});
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Api/Messages/RenderQuote.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Api/Messages/RenderQuote.js
index 5d1c7fd4849..08a5570ee62 100644
--- a/wcfsetup/install/files/js/WoltLabSuite/Core/Api/Messages/RenderQuote.js
+++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Api/Messages/RenderQuote.js
@@ -11,11 +11,10 @@ define(["require", "exports", "WoltLabSuite/Core/Ajax/Backend", "../Result"], fu
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.renderQuote = renderQuote;
- async function renderQuote(objectType, className, objectID) {
+ async function renderQuote(objectType, objectID, isFullQuote) {
const url = new URL(window.WSC_RPC_API_URL + "core/messages/render-quote");
url.searchParams.set("objectType", objectType);
- url.searchParams.set("className", className);
- url.searchParams.set("fullQuote", "true");
+ url.searchParams.set("isFullQuote", String(isFullQuote));
url.searchParams.set("objectID", objectID.toString());
let response;
try {
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Component/Quote/List.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Component/Quote/List.js
index cd8e56d4ef3..1be071b13bb 100644
--- a/wcfsetup/install/files/js/WoltLabSuite/Core/Component/Quote/List.js
+++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Component/Quote/List.js
@@ -61,10 +61,14 @@ define(["require", "exports", "tslib", "WoltLabSuite/Core/Component/Ckeditor/Eve
`);
fragment.querySelector('button[data-action="insert"]').addEventListener("click", () => {
(0, Storage_1.markQuoteAsUsed)(this.#editorId, uuid);
+ const content = quote.rawMessage || quote.message;
+ if (content === null) {
+ throw new Error("Expected either the `rawMessage` or `message` to be a string.");
+ }
(0, Event_1.dispatchToCkeditor)(this.#editor).insertQuote({
author: message.author,
- content: quote.rawMessage === undefined ? quote.message : quote.rawMessage,
- isText: quote.rawMessage === undefined,
+ content,
+ isText: !quote.rawMessage,
link: message.link,
});
});
@@ -110,7 +114,8 @@ define(["require", "exports", "tslib", "WoltLabSuite/Core/Component/Ckeditor/Eve
if (editor === null) {
throw new Error(`The editor '${editorId}' does not exist.`);
}
- (0, Event_1.listenToCkeditor)(editor).ready(({ ckeditor }) => {
+ (0, Event_1.listenToCkeditor)(editor)
+ .ready(({ ckeditor }) => {
if (ckeditor.features.quoteBlock) {
quoteLists.set(editorId, new QuoteList(editorId, editor, containerId));
}
@@ -122,7 +127,8 @@ define(["require", "exports", "tslib", "WoltLabSuite/Core/Component/Ckeditor/Eve
(0, Message_1.setActiveEditor)(ckeditor, ckeditor.features.quoteBlock);
}
});
- }).destroy(() => {
+ })
+ .destroy(() => {
(0, Message_1.removeActiveEditor)(editor);
});
}
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Component/Quote/Message.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Component/Quote/Message.js
index 84db64b7535..044b833170a 100644
--- a/wcfsetup/install/files/js/WoltLabSuite/Core/Component/Quote/Message.js
+++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Component/Quote/Message.js
@@ -23,16 +23,16 @@ define(["require", "exports", "tslib", "WoltLabSuite/Core/Dom/Util", "WoltLabSui
let timerSelectionChange = undefined;
let isMouseDown = false;
const copyQuote = document.createElement("div");
- function registerContainer(containerSelector, messageBodySelector, className, objectType) {
+ function registerContainer(containerSelector, messageBodySelector, objectType, className) {
(0, Selector_1.wheneverFirstSeen)(containerSelector, (container) => {
const id = Util_1.default.identify(container);
- const objectId = ~~container.dataset.objectId;
+ const objectId = parseInt(container.dataset.objectId || "0");
containers.set(id, {
element: container,
- messageBodySelector: messageBodySelector,
- objectType: objectType,
- className: className,
- objectId: objectId,
+ messageBodySelector,
+ objectType,
+ objectId,
+ className,
});
if (container.classList.contains("jsInvalidQuoteTarget")) {
return;
@@ -40,33 +40,41 @@ define(["require", "exports", "tslib", "WoltLabSuite/Core/Dom/Util", "WoltLabSui
container.addEventListener("mousedown", (event) => onMouseDown(event));
container.classList.add("jsQuoteMessageContainer");
const quoteMessage = container.querySelector(".jsQuoteMessage");
- let quoteMessageButton = quoteMessage?.querySelector(".button");
- if (!quoteMessageButton && quoteMessage?.classList.contains("button")) {
+ if (quoteMessage === null) {
+ return;
+ }
+ let quoteMessageButton = quoteMessage.querySelector(".button");
+ if (!quoteMessageButton && quoteMessage.classList.contains("button")) {
quoteMessageButton = quoteMessage;
}
- if (quoteMessageButton) {
+ if (quoteMessageButton !== null) {
quoteMessageButtons.set((0, Storage_1.getKey)(objectType, objectId), quoteMessageButton);
- if ((0, Storage_1.isFullQuoted)(objectType, objectId)) {
+ if ((0, Storage_1.getFullQuoteUuid)(objectType, objectId) !== undefined) {
quoteMessageButton.classList.add("active");
}
}
- quoteMessage?.addEventListener("click", (0, PromiseMutex_1.promiseMutex)(async (event) => {
+ quoteMessage.addEventListener("click", (0, PromiseMutex_1.promiseMutex)(async (event) => {
event.preventDefault();
- if ((0, Storage_1.isFullQuoted)(objectType, objectId)) {
- (0, Storage_1.removeQuotes)([(0, Storage_1.getFullQuoteUuid)(objectType, objectId)]);
- quoteMessageButton.classList.remove("active");
+ const uuid = (0, Storage_1.getFullQuoteUuid)(objectType, objectId);
+ if (uuid !== undefined) {
+ (0, Storage_1.removeQuotes)([uuid]);
+ quoteMessageButton?.classList.remove("active");
return;
}
- const quoteMessage = await (0, Storage_1.saveFullQuote)(objectType, className, ~~container.dataset.objectId);
- quoteMessageButton.classList.add("active");
+ const quote = await (0, Storage_1.saveFullQuote)(objectType, objectId, className);
+ quoteMessageButton?.classList.add("active");
if (activeEditor !== undefined) {
+ const content = quote.rawMessage || quote.message;
+ if (content === null) {
+ throw new Error("Expected either the `rawMessage` or `message` to be a string.");
+ }
(0, Event_1.dispatchToCkeditor)(activeEditor.sourceElement).insertQuote({
- author: quoteMessage.author,
- content: quoteMessage.rawMessage === undefined ? quoteMessage.message : quoteMessage.rawMessage,
- isText: quoteMessage.rawMessage === undefined,
- link: quoteMessage.link,
+ author: quote.author,
+ content,
+ isText: quote.rawMessage === null,
+ link: quote.link,
});
- (0, Storage_1.markQuoteAsUsed)(activeEditor.sourceElement.id, quoteMessage.uuid);
+ (0, Storage_1.markQuoteAsUsed)(activeEditor.sourceElement.id, quote.uuid);
}
}));
});
@@ -93,7 +101,10 @@ define(["require", "exports", "tslib", "WoltLabSuite/Core/Dom/Util", "WoltLabSui
buttonSaveQuote.classList.add("jsQuoteManagerStore");
buttonSaveQuote.textContent = (0, Language_1.getPhrase)("wcf.message.quote.quoteSelected");
buttonSaveQuote.addEventListener("click", (0, PromiseMutex_1.promiseMutex)(async () => {
- await (0, Storage_1.saveQuote)(selectedMessage.container.objectType, selectedMessage.container.objectId, selectedMessage.container.className, selectedMessage.message);
+ if (selectedMessage === undefined) {
+ return;
+ }
+ await (0, Storage_1.saveQuote)(selectedMessage.container.objectType, selectedMessage.container.objectId, selectedMessage.message, selectedMessage.container.className);
removeSelection();
}));
copyQuote.appendChild(buttonSaveQuote);
@@ -103,15 +114,22 @@ define(["require", "exports", "tslib", "WoltLabSuite/Core/Dom/Util", "WoltLabSui
buttonSaveAndInsertQuote.classList.add("jsQuoteManagerQuoteAndInsert");
buttonSaveAndInsertQuote.textContent = (0, Language_1.getPhrase)("wcf.message.quote.quoteAndReply");
buttonSaveAndInsertQuote.addEventListener("click", (0, PromiseMutex_1.promiseMutex)(async () => {
- const quoteMessage = await (0, Storage_1.saveQuote)(selectedMessage.container.objectType, selectedMessage.container.objectId, selectedMessage.container.className, selectedMessage.message);
+ if (selectedMessage === undefined) {
+ return;
+ }
+ const quote = await (0, Storage_1.saveQuote)(selectedMessage.container.objectType, selectedMessage.container.objectId, selectedMessage.message, selectedMessage.container.className);
if (activeEditor !== undefined) {
+ const content = quote.rawMessage || quote.message;
+ if (content === null) {
+ throw new Error("Expected either the `rawMessage` or `message` to be a string.");
+ }
(0, Event_1.dispatchToCkeditor)(activeEditor.sourceElement).insertQuote({
- author: quoteMessage.author,
- content: quoteMessage.rawMessage === undefined ? quoteMessage.message : quoteMessage.rawMessage,
- isText: quoteMessage.rawMessage === undefined,
- link: quoteMessage.link,
+ author: quote.author,
+ content,
+ isText: quote.rawMessage === null,
+ link: quote.link,
});
- (0, Storage_1.markQuoteAsUsed)(activeEditor.sourceElement.id, quoteMessage.uuid);
+ (0, Storage_1.markQuoteAsUsed)(activeEditor.sourceElement.id, quote.uuid);
}
removeSelection();
}));
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Component/Quote/Storage.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Component/Quote/Storage.js
index aa394d70483..a40a03e253a 100644
--- a/wcfsetup/install/files/js/WoltLabSuite/Core/Component/Quote/Storage.js
+++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Component/Quote/Storage.js
@@ -7,7 +7,7 @@
* @since 6.2
* @woltlabExcludeBundle tiny
*/
-define(["require", "exports", "tslib", "WoltLabSuite/Core/Core", "WoltLabSuite/Core/Api/Messages/RenderQuote", "WoltLabSuite/Core/Api/Messages/Author", "WoltLabSuite/Core/Component/Quote/List", "WoltLabSuite/Core/Api/Messages/ResetRemovalQuotes", "WoltLabSuite/Core/Component/Quote/Message"], function (require, exports, tslib_1, Core, RenderQuote_1, Author_1, List_1, ResetRemovalQuotes_1, Message_1) {
+define(["require", "exports", "tslib", "WoltLabSuite/Core/Core", "WoltLabSuite/Core/Api/Messages/RenderQuote", "WoltLabSuite/Core/Component/Quote/List", "WoltLabSuite/Core/Api/Messages/ResetRemovalQuotes", "WoltLabSuite/Core/Component/Quote/Message", "WoltLabSuite/Core/Ajax"], function (require, exports, tslib_1, Core, RenderQuote_1, List_1, ResetRemovalQuotes_1, Message_1, Ajax_1) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.saveQuote = saveQuote;
@@ -19,50 +19,62 @@ define(["require", "exports", "tslib", "WoltLabSuite/Core/Core", "WoltLabSuite/C
exports.markQuoteAsUsed = markQuoteAsUsed;
exports.getUsedQuotes = getUsedQuotes;
exports.clearQuotesForEditor = clearQuotesForEditor;
- exports.isFullQuoted = isFullQuoted;
exports.getFullQuoteUuid = getFullQuoteUuid;
exports.getKey = getKey;
Core = tslib_1.__importStar(Core);
const STORAGE_KEY = Core.getStoragePrefix() + "quotes";
const usedQuotes = new Map();
- async function saveQuote(objectType, objectId, objectClassName, message) {
- const result = await (0, Author_1.getMessageAuthor)(objectClassName, objectId);
- if (!result.ok) {
- throw new Error("Error fetching author data");
+ async function saveQuote(objectType, objectId, message,
+ /** @deprecated 6.2 Used for legacy implementations only. */
+ className) {
+ let quote;
+ if (className !== undefined) {
+ const result = (await (0, Ajax_1.dboAction)("saveQuote", className)
+ .objectIds([objectId])
+ .payload({
+ message,
+ renderQuote: true,
+ })
+ .dispatch());
+ quote = result.renderedQuote;
}
- const uuid = storeQuote(objectType, result.value, {
+ else {
+ const result = await (0, RenderQuote_1.renderQuote)(objectType, objectId, false);
+ if (!result.ok) {
+ throw new Error("Error fetching quote data");
+ }
+ quote = result.value;
+ }
+ const uuid = storeQuote(objectType, quote, {
message,
+ rawMessage: null,
});
(0, List_1.refreshQuoteLists)();
return {
- ...result.value,
+ ...quote,
message,
uuid,
};
}
- async function saveFullQuote(objectType, objectClassName, objectId) {
- const result = await (0, RenderQuote_1.renderQuote)(objectType, objectClassName, objectId);
- if (!result.ok) {
- throw new Error("Error fetching quote data");
- }
- const message = {
- objectID: result.value.objectID,
- time: result.value.time,
- title: result.value.title,
- link: result.value.link,
- authorID: result.value.authorID,
- author: result.value.author,
- avatar: result.value.avatar,
- };
- const quote = {
- message: result.value.message,
- rawMessage: result.value.rawMessage,
- };
- const uuid = storeQuote(objectType, message, quote);
+ async function saveFullQuote(objectType, objectId,
+ /** @deprecated 6.2 Used for legacy implementations only. */
+ className) {
+ let message;
+ if (className !== undefined) {
+ const result = (await (0, Ajax_1.dboAction)("saveFullQuote", className).objectIds([objectId]).dispatch());
+ message = result.renderedQuote;
+ }
+ else {
+ const result = await (0, RenderQuote_1.renderQuote)(objectType, objectId, true);
+ if (!result.ok) {
+ throw new Error("Error fetching quote data");
+ }
+ message = result.value;
+ }
+ const uuid = storeQuote(objectType, message, message);
(0, List_1.refreshQuoteLists)();
return {
...message,
- ...quote,
uuid,
};
}
@@ -118,7 +130,7 @@ define(["require", "exports", "tslib", "WoltLabSuite/Core/Core", "WoltLabSuite/C
usedQuotes.get(editorId)?.forEach((uuid) => {
for (const [key, quotes] of storage.quotes) {
const quote = quotes.get(uuid);
- if (quote?.rawMessage !== undefined) {
+ if (quote?.rawMessage !== null) {
fullQuotes.push(key);
}
quotes.delete(uuid);
@@ -137,19 +149,6 @@ define(["require", "exports", "tslib", "WoltLabSuite/Core/Core", "WoltLabSuite/C
(0, Message_1.removeQuoteStatus)(key);
});
}
- function isFullQuoted(objectType, objectId) {
- const key = getKey(objectType, objectId);
- const storage = getStorage();
- const quotes = storage.quotes.get(key);
- if (quotes === undefined) {
- return false;
- }
- return (Array.from(quotes).filter(([, quote]) => {
- if (quote.rawMessage !== undefined) {
- return true;
- }
- }).length > 0);
- }
function storeQuote(objectType, message, quote) {
const storage = getStorage();
const key = getKey(objectType, message.objectID);
@@ -158,7 +157,7 @@ define(["require", "exports", "tslib", "WoltLabSuite/Core/Core", "WoltLabSuite/C
}
storage.messages.set(key, message);
for (const [uuid, q] of storage.quotes.get(key)) {
- if ((q.rawMessage !== undefined && q.rawMessage === quote.rawMessage) || q.message === quote.message) {
+ if ((q.rawMessage !== null && q.rawMessage === null) || q.message === quote.message) {
return uuid;
}
}
@@ -168,10 +167,13 @@ define(["require", "exports", "tslib", "WoltLabSuite/Core/Core", "WoltLabSuite/C
return uuid;
}
function getFullQuoteUuid(objectType, objectId) {
- const storage = getStorage();
const key = getKey(objectType, objectId);
- for (const [uuid, q] of storage.quotes.get(key)) {
- if (q.rawMessage !== undefined && q.message !== undefined) {
+ const quotes = getStorage().quotes.get(key);
+ if (quotes === undefined) {
+ return undefined;
+ }
+ for (const [uuid, q] of quotes) {
+ if (q.rawMessage !== null && q.message !== null) {
return uuid;
}
}
@@ -220,7 +222,7 @@ define(["require", "exports", "tslib", "WoltLabSuite/Core/Core", "WoltLabSuite/C
// Update the quote status if the quote was removed in another tab
for (const [key, quotes] of oldValue.quotes) {
for (const [, quote] of quotes) {
- if (quote.rawMessage !== undefined && !newValue.quotes.has(key)) {
+ if (quote.rawMessage !== null && !newValue.quotes.has(key)) {
(0, Message_1.removeQuoteStatus)(key);
}
}
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Message/Quote.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Message/Quote.js
index 0d65a51114d..6e25bf60374 100644
--- a/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Message/Quote.js
+++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Message/Quote.js
@@ -7,16 +7,15 @@ define(["require", "exports", "WoltLabSuite/Core/Component/Quote/Message"], func
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.UiMessageQuote = void 0;
+ /**
+ * @deprecated 6.2 Use `registerContainer()` without the className parameter.
+ */
class UiMessageQuote {
/**
* Initializes the quote handler for given object type.
*/
constructor(_quoteManager, className, objectType, containerSelector, messageBodySelector, _messageContentSelector, _supportDirectInsert) {
- // remove "Action" from className
- if (className.endsWith("Action")) {
- className = className.substring(0, className.length - 6);
- }
- (0, Message_1.registerContainer)(containerSelector, messageBodySelector, className, objectType);
+ (0, Message_1.registerContainer)(containerSelector, messageBodySelector, objectType, className);
}
}
exports.UiMessageQuote = UiMessageQuote;
diff --git a/wcfsetup/install/files/lib/bootstrap/com.woltlab.wcf.php b/wcfsetup/install/files/lib/bootstrap/com.woltlab.wcf.php
index e3205fd1895..5d5865c6ca8 100644
--- a/wcfsetup/install/files/lib/bootstrap/com.woltlab.wcf.php
+++ b/wcfsetup/install/files/lib/bootstrap/com.woltlab.wcf.php
@@ -152,7 +152,6 @@ static function (\wcf\event\endpoint\ControllerCollecting $event) {
$event->register(new \wcf\system\endpoint\controller\core\listViews\GetItem());
$event->register(new \wcf\system\endpoint\controller\core\messages\GetMentionSuggestions());
$event->register(new \wcf\system\endpoint\controller\core\messages\RenderQuote());
- $event->register(new \wcf\system\endpoint\controller\core\messages\GetMessageAuthor());
$event->register(new \wcf\system\endpoint\controller\core\messages\ResetRemovalQuotes());
$event->register(new \wcf\system\endpoint\controller\core\sessions\DeleteSession());
$event->register(new \wcf\system\endpoint\controller\core\versionTrackers\RevertVersion());
diff --git a/wcfsetup/install/files/lib/data/IEmbeddedMessageObject.class.php b/wcfsetup/install/files/lib/data/IEmbeddedMessageObject.class.php
deleted file mode 100644
index da483e1eb68..00000000000
--- a/wcfsetup/install/files/lib/data/IEmbeddedMessageObject.class.php
+++ /dev/null
@@ -1,19 +0,0 @@
-
- * @since 6.2
- */
-interface IEmbeddedMessageObject
-{
- /**
- * Loads embedded objects for the given object type and object IDs.
- */
- public function loadEmbeddedObjects(): void;
-}
diff --git a/wcfsetup/install/files/lib/system/endpoint/controller/core/messages/GetMessageAuthor.class.php b/wcfsetup/install/files/lib/system/endpoint/controller/core/messages/GetMessageAuthor.class.php
deleted file mode 100644
index ba531a7e9bd..00000000000
--- a/wcfsetup/install/files/lib/system/endpoint/controller/core/messages/GetMessageAuthor.class.php
+++ /dev/null
@@ -1,66 +0,0 @@
-
- * @since 6.2
- */
-#[GetRequest('/core/messages/message-author')]
-final class GetMessageAuthor implements IController
-{
- #[\Override]
- public function __invoke(ServerRequestInterface $request, array $variables): ResponseInterface
- {
- $parameters = Helper::mapApiParameters($request, GetMessageAuthorParameters::class);
-
- // @phpstan-ignore argument.templateType
- $object = Helper::fetchObjectFromRequestParameter($parameters->objectID, $parameters->className);
- \assert($object instanceof IMessage && $object instanceof DatabaseObject);
-
- $userProfile = UserProfileRuntimeCache::getInstance()->getObject($object->getUserID());
-
- return new JsonResponse(
- [
- "objectID" => $object->getObjectID(),
- "authorID" => $userProfile->getUserID(),
- "author" => $userProfile->getUsername(),
- "title" => $object->getTitle(),
- "avatar" => $userProfile->getAvatar()->getURL(),
- "time" => (new \DateTime('@' . $object->getTime()))->format("c"),
- "link" => $object->getLink(),
- ],
- 200,
- [
- 'cache-control' => [
- 'max-age=300',
- ],
- ]
- );
- }
-}
-
-/** @internal */
-final class GetMessageAuthorParameters
-{
- public function __construct(
- /** @var non-empty-string */
- public readonly string $className,
- /** @var positive-int */
- public readonly int $objectID,
- ) {}
-}
diff --git a/wcfsetup/install/files/lib/system/endpoint/controller/core/messages/RenderQuote.class.php b/wcfsetup/install/files/lib/system/endpoint/controller/core/messages/RenderQuote.class.php
index 979e32d759e..5991b154ac8 100644
--- a/wcfsetup/install/files/lib/system/endpoint/controller/core/messages/RenderQuote.class.php
+++ b/wcfsetup/install/files/lib/system/endpoint/controller/core/messages/RenderQuote.class.php
@@ -5,21 +5,23 @@
use Laminas\Diactoros\Response\JsonResponse;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
-use wcf\data\DatabaseObject;
-use wcf\data\IEmbeddedMessageObject;
use wcf\data\IMessage;
+use wcf\data\object\type\ObjectTypeCache;
use wcf\data\user\UserProfile;
use wcf\http\Helper;
use wcf\system\cache\runtime\UserProfileRuntimeCache;
use wcf\system\endpoint\GetRequest;
use wcf\system\endpoint\IController;
+use wcf\system\exception\NotImplementedException;
+use wcf\system\exception\PermissionDeniedException;
use wcf\system\html\input\HtmlInputProcessor;
+use wcf\system\message\quote\IMessageQuoteHandler;
/**
* Retrieves data for the rendering of a quote.
*
* @author Olaf Braun
- * @copyright 2001-2024 WoltLab GmbH
+ * @copyright 2001-2025 WoltLab GmbH
* @license GNU Lesser General Public License
* @since 6.2
*/
@@ -31,33 +33,38 @@ public function __invoke(ServerRequestInterface $request, array $variables): Res
{
$parameters = Helper::mapApiParameters($request, GetRenderQuoteParameters::class);
- // @phpstan-ignore argument.templateType
- $object = Helper::fetchObjectFromRequestParameter($parameters->objectID, $parameters->className);
- \assert($object instanceof IMessage && $object instanceof DatabaseObject);
+ $objectType = ObjectTypeCache::getInstance()->getObjectTypeByName(
+ 'com.woltlab.wcf.message.quote',
+ $parameters->objectType,
+ );
+ $processor = $objectType->getProcessor();
+ \assert($processor instanceof IMessageQuoteHandler);
- $userProfile = UserProfileRuntimeCache::getInstance()->getObject($object->getUserID());
- if ($userProfile === null) {
- $userProfile = UserProfile::getGuestUserProfile($object->getUsername());
+ $message = null;
+ try {
+ $message = $processor->getMessage($parameters->objectID);
+ } catch (NotImplementedException) {
+ // This can happen for legacy implementations that do not yet
+ // implement the new `getMessage()` method.
}
- if ($object instanceof IEmbeddedMessageObject) {
- $object->loadEmbeddedObjects();
+ if ($message === null) {
+ throw new PermissionDeniedException();
}
- return new JsonResponse(
- [
- "objectID" => $object->getObjectID(),
- "authorID" => $userProfile->getUserID(),
- "author" => $userProfile->getUsername(),
- "avatar" => $userProfile->getAvatar()->getURL(),
- "time" => (new \DateTime('@' . $object->getTime()))->format("c"),
- "title" => $object->getTitle(),
- "link" => $object->getLink(),
- "rawMessage" => $parameters->fullQuote ? $this->renderFullQuote($object) : null,
- "message" => $parameters->fullQuote ? $object->getFormattedMessage() : null
- ],
- 200,
- );
+ $userProfile = UserProfileRuntimeCache::getInstance()->getObject($message->getUserID());
+ if ($userProfile === null) {
+ $userProfile = UserProfile::getGuestUserProfile($message->getUsername());
+ }
+
+ return new JsonResponse([
+ 'objectID' => $parameters->objectID,
+ 'author' => $userProfile->getUsername(),
+ 'avatar' => $userProfile->getAvatar()->getURL(),
+ 'link' => $message->getLink(),
+ 'rawMessage' => $parameters->isFullQuote ? $this->renderFullQuote($message) : null,
+ 'message' => $parameters->isFullQuote ? $message->getFormattedMessage() : null
+ ]);
}
private function renderFullQuote(IMessage $object): string
@@ -78,9 +85,10 @@ final class GetRenderQuoteParameters
{
public function __construct(
/** @var non-empty-string */
- public readonly string $className,
+ public readonly string $objectType,
/** @var positive-int */
public readonly int $objectID,
- public readonly bool $fullQuote = false,
+
+ public readonly bool $isFullQuote,
) {}
}
diff --git a/wcfsetup/install/files/lib/system/message/quote/AbstractMessageQuoteHandler.class.php b/wcfsetup/install/files/lib/system/message/quote/AbstractMessageQuoteHandler.class.php
index 05b5cc78299..d7a1548db27 100644
--- a/wcfsetup/install/files/lib/system/message/quote/AbstractMessageQuoteHandler.class.php
+++ b/wcfsetup/install/files/lib/system/message/quote/AbstractMessageQuoteHandler.class.php
@@ -2,35 +2,42 @@
namespace wcf\system\message\quote;
+use wcf\data\IMessage;
use wcf\system\cache\runtime\UserProfileRuntimeCache;
+use wcf\system\exception\NotImplementedException;
use wcf\system\SingletonFactory;
use wcf\system\WCF;
/**
* Default implementation for quote handlers.
*
- * @author Alexander Ebert
- * @copyright 2001-2019 WoltLab GmbH
- * @license GNU Lesser General Public License
- *
- * @deprecated 6.2
+ * @author Alexander Ebert
+ * @copyright 2001-2025 WoltLab GmbH
+ * @license GNU Lesser General Public License
*/
abstract class AbstractMessageQuoteHandler extends SingletonFactory implements IMessageQuoteHandler
{
/**
* template name
* @var string
+ * @deprecated 6.2
*/
public $templateName = 'messageQuoteList';
/**
* list of quoted message
* @var QuotedMessage[]
+ * @deprecated 6.2
*/
public $quotedMessages = [];
/**
- * @inheritDoc
+ * Renders a template for given quotes.
+ *
+ * @param mixed[][] $data
+ * @param bool $supportPaste
+ * @return string
+ * @deprecated 6.2 Implement `getMessage()` instead.
*/
public function render(array $data, $supportPaste = false)
{
@@ -58,8 +65,12 @@ public function render(array $data, $supportPaste = false)
}
/**
- * @inheritDoc
- * @param bool $renderAsString
+ * Renders a list of quotes for insertation.
+ *
+ * @param mixed[][] $data
+ * @param bool $render
+ * @return string[]
+ * @deprecated 6.2 Implement `getMessage()` instead.
*/
public function renderQuotes(array $data, $render = true, $renderAsString = true)
{
@@ -94,6 +105,7 @@ public function renderQuotes(array $data, $render = true, $renderAsString = true
*
* @param QuotedMessage[] $messages
* @return void
+ * @deprecated 6.2
*/
protected function overrideIsFullQuote(array $messages)
{
@@ -110,6 +122,27 @@ protected function overrideIsFullQuote(array $messages)
*
* @param mixed[][] $data
* @return QuotedMessage[]
+ * @deprecated 6.2 Implement `getMessage()` instead.
+ */
+ protected function getMessages(array $data)
+ {
+ throw new NotImplementedException();
+ }
+
+ /**
+ * @return list
+ * @deprecated 6.2
*/
- abstract protected function getMessages(array $data);
+ public function legacyGetMessages(int $objectID, string $marker): array
+ {
+ return $this->getMessages([
+ $objectID => [$marker],
+ ]);
+ }
+
+ #[\Override]
+ public function getMessage(int $objectID): ?IMessage
+ {
+ throw new NotImplementedException();
+ }
}
diff --git a/wcfsetup/install/files/lib/system/message/quote/IMessageQuoteHandler.class.php b/wcfsetup/install/files/lib/system/message/quote/IMessageQuoteHandler.class.php
index 8b6543815f7..8daee52f60d 100644
--- a/wcfsetup/install/files/lib/system/message/quote/IMessageQuoteHandler.class.php
+++ b/wcfsetup/install/files/lib/system/message/quote/IMessageQuoteHandler.class.php
@@ -2,32 +2,24 @@
namespace wcf\system\message\quote;
+use wcf\data\IMessage;
+
/**
* Default interface for quote handlers.
*
- * @author Alexander Ebert
- * @copyright 2001-2019 WoltLab GmbH
- * @license GNU Lesser General Public License
- *
- * @deprecated 6.2
+ * @author Alexander Ebert
+ * @copyright 2001-2025 WoltLab GmbH
+ * @license GNU Lesser General Public License
*/
interface IMessageQuoteHandler
{
/**
- * Renders a template for given quotes.
+ * Returns the message identified by the provided object id.
*
- * @param mixed[][] $data
- * @param bool $supportPaste
- * @return string
- */
- public function render(array $data, $supportPaste = false);
-
- /**
- * Renders a list of quotes for insertation.
+ * If the object does not exist or is inaccessible by the current user,
+ * `null` must be returned instead.
*
- * @param mixed[][] $data
- * @param bool $render
- * @return string[]
+ * @since 6.2
*/
- public function renderQuotes(array $data, $render = true);
+ public function getMessage(int $objectID): ?IMessage;
}
diff --git a/wcfsetup/install/files/lib/system/message/quote/MessageQuoteManager.class.php b/wcfsetup/install/files/lib/system/message/quote/MessageQuoteManager.class.php
index 0e9d21a9aca..b0030e31f4b 100644
--- a/wcfsetup/install/files/lib/system/message/quote/MessageQuoteManager.class.php
+++ b/wcfsetup/install/files/lib/system/message/quote/MessageQuoteManager.class.php
@@ -3,7 +3,11 @@
namespace wcf\system\message\quote;
use wcf\data\IMessage;
+use wcf\data\object\type\ObjectTypeCache;
+use wcf\data\user\avatar\DefaultAvatar;
+use wcf\system\cache\runtime\UserProfileRuntimeCache;
use wcf\system\event\EventHandler;
+use wcf\system\html\input\HtmlInputProcessor;
use wcf\system\SingletonFactory;
use wcf\system\WCF;
use wcf\util\ArrayUtil;
@@ -15,7 +19,7 @@
* @copyright 2001-2025 WoltLab GmbH
* @license GNU Lesser General Public License
*/
-class MessageQuoteManager extends SingletonFactory
+final class MessageQuoteManager extends SingletonFactory
{
/**
* list of quote ids to be removed
@@ -30,6 +34,19 @@ class MessageQuoteManager extends SingletonFactory
*/
protected array $usedQuotes = [];
+ /**
+ * @var array{
+ * objectType: string,
+ * parentObjectID: int,
+ * objectID: int,
+ * message: string,
+ * fullQuote: string,
+ * }
+ */
+ private array $legacyQuoteData;
+
+ private const LEGACY_QUOTE_MARKER = '@@@legacy_quote@@@';
+
/**
* @inheritDoc
*/
@@ -65,7 +82,33 @@ public function addQuote(
$fullQuote = '',
$returnFalseIfExists = true
) {
- return false;
+ if (isset($this->legacyQuoteData)) {
+ throw new \RuntimeException("Cannot store another quote, there is already one legacy quote present.");
+ }
+
+ if ($fullQuote !== '') {
+ $htmlInputProcessor = new HtmlInputProcessor();
+ $htmlInputProcessor->processIntermediate($fullQuote);
+
+ if (MESSAGE_MAX_QUOTE_DEPTH) {
+ $htmlInputProcessor->enforceQuoteDepth(MESSAGE_MAX_QUOTE_DEPTH - 1, true);
+ }
+
+ $parameters = ['htmlInputProcessor' => $htmlInputProcessor];
+ EventHandler::getInstance()->fireAction($this, 'addFullQuote', $parameters);
+
+ $fullQuote = $htmlInputProcessor->getHtml();
+ }
+
+ $this->legacyQuoteData = [
+ 'objectType' => $objectType,
+ 'parentObjectID' => $parentObjectID,
+ 'objectID' => $objectID,
+ 'message' => $message,
+ 'fullQuote' => $fullQuote,
+ ];
+
+ return self::LEGACY_QUOTE_MARKER;
}
/**
@@ -107,7 +150,48 @@ public function removeQuote($quoteID)
*/
public function getQuoteComponents($quoteID)
{
- return false;
+ if ($quoteID !== self::LEGACY_QUOTE_MARKER) {
+ throw new \RuntimeException("Encountered an unexpected quote id, found '{$quoteID}'.");
+ }
+
+ \assert(isset($this->legacyQuoteData));
+
+ $objectType = ObjectTypeCache::getInstance()->getObjectTypeByName('com.woltlab.wcf.message.quote', $this->legacyQuoteData['objectType']);
+ if ($objectType === null) {
+ throw new \RuntimeException("Cannot find the object type '{$this->legacyQuoteData['objectType']}' for quotes.");
+ }
+
+ $quoteHandler = \call_user_func([$objectType->className, 'getInstance']);
+ \assert($quoteHandler instanceof AbstractMessageQuoteHandler);
+
+ $messages = $quoteHandler->legacyGetMessages($this->legacyQuoteData['objectID'], self::LEGACY_QUOTE_MARKER);
+ $message = \current($messages);
+ \assert($message !== false);
+
+ $avatar = '';
+ if ($message->getUserID()) {
+ $userProfile = UserProfileRuntimeCache::getInstance()->getObject($message->getUserID());
+ if ($userProfile !== null) {
+ $avatar = $userProfile->getAvatar()->getURL();
+ }
+ }
+
+ if ($avatar === '') {
+ $avatar = (new DefaultAvatar($message->getUsername()))->getURL();
+ }
+
+ $messageData = $quoteHandler->renderQuotes([
+ $this->legacyQuoteData['objectID'] => [self::LEGACY_QUOTE_MARKER],
+ ]);
+
+ return [
+ 'objectID' => $this->legacyQuoteData['objectID'],
+ 'author' => $message->getUsername(),
+ 'avatar' => $avatar,
+ 'link' => $message->getLink(),
+ 'message' => $messageData[0],
+ 'rawMessage' => $this->isFullQuote(self::LEGACY_QUOTE_MARKER) ? $message->getMessage() : $this->legacyQuoteData['message'],
+ ];
}
/**
@@ -168,6 +252,13 @@ public function getQuotesByParentObjectID($objectType, $parentObjectID, $markFor
*/
public function getQuote($quoteID, $useFullQuote = true)
{
+ \assert(isset($this->legacyQuoteData));
+ if ($useFullQuote && $this->legacyQuoteData['fullQuote'] !== '') {
+ return $this->legacyQuoteData['fullQuote'];
+ } else {
+ return $this->legacyQuoteData['message'];
+ }
+
return null;
}
@@ -356,7 +447,9 @@ public function removeOrphanedQuotes(array $quoteIDs) {}
*/
public function isFullQuote($quoteID)
{
- return false;
+ \assert(isset($this->legacyQuoteData));
+
+ return $this->legacyQuoteData['fullQuote'] !== '';
}
/**