--- /dev/null
- _editorSuccess: (media: Media, oldCategoryId: number) => {
- if (media.categoryID != oldCategoryId) {
+/**
+ * Initializes modules required for media list view.
+ *
+ * @author Matthias Schmidt
+ * @copyright 2001-2021 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module WoltLabSuite/Core/Controller/Media/List
+ */
+
+import MediaListUpload from "../../Media/List/Upload";
+import * as MediaClipboard from "../../Media/Clipboard";
+import * as EventHandler from "../../Event/Handler";
+import MediaEditor from "../../Media/Editor";
+import * as DomChangeListener from "../../Dom/Change/Listener";
+import { Media, MediaUploadSuccessEventData } from "../../Media/Data";
+import MediaManager from "../../Media/Manager/Base";
+
+const _mediaEditor = new MediaEditor({
++ _editorSuccess: (media: Media, oldCategoryId: number, closedEditorDialog = true) => {
++ if (media.categoryID != oldCategoryId || closedEditorDialog) {
+ window.setTimeout(() => {
+ window.location.reload();
+ }, 500);
+ }
+ },
+});
+const _tableBody = document.getElementById("mediaListTableBody")!;
+let _upload: MediaListUpload;
+
+interface MediaListOptions {
+ categoryId?: number;
+ hasMarkedItems?: boolean;
+}
+
+export function init(options: MediaListOptions): void {
+ options = options || {};
+ _upload = new MediaListUpload("uploadButton", "mediaListTableBody", {
+ categoryId: options.categoryId,
+ multiple: true,
+ elementTagSize: 48,
+ });
+
+ MediaClipboard.init("wcf\\acp\\page\\MediaListPage", options.hasMarkedItems || false, {
+ clipboardDeleteMedia: (mediaIds: number[]) => clipboardDeleteMedia(mediaIds),
+ } as MediaManager);
+
+ addButtonEventListeners();
+
+ DomChangeListener.add("WoltLabSuite/Core/Controller/Media/List", () => addButtonEventListeners());
+
+ EventHandler.add("com.woltlab.wcf.media.upload", "success", (data: MediaUploadSuccessEventData) =>
+ openEditorAfterUpload(data),
+ );
+}
+
+/**
+ * Adds the `click` event listeners to the media edit icons in new media table rows.
+ */
+function addButtonEventListeners(): void {
+ Array.from(_tableBody.getElementsByClassName("jsMediaEditButton")).forEach((button) => {
+ button.classList.remove("jsMediaEditButton");
+ button.addEventListener("click", (ev) => edit(ev));
+ });
+}
+
+/**
+ * Is called when a media edit icon is clicked.
+ */
+function edit(event: Event): void {
+ _mediaEditor.edit(~~(event.currentTarget as HTMLElement).dataset.objectId!);
+}
+
+/**
+ * Opens the media editor after uploading a single file.
+ */
+function openEditorAfterUpload(data: MediaUploadSuccessEventData) {
+ if (data.upload === _upload && !data.isMultiFileUpload && !_upload.hasPendingUploads()) {
+ const keys = Object.keys(data.media);
+
+ if (keys.length) {
+ _mediaEditor.edit(data.media[keys[0]]);
+ }
+ }
+}
+
+/**
+ * Is called after the media files with the given ids have been deleted via clipboard.
+ */
+function clipboardDeleteMedia(mediaIds: number[]) {
+ Array.from(document.getElementsByClassName("jsMediaRow")).forEach((media) => {
+ const mediaID = ~~(media.querySelector(".jsClipboardItem") as HTMLElement).dataset.objectId!;
+
+ if (mediaIds.indexOf(mediaID) !== -1) {
+ media.remove();
+ }
+ });
+
+ if (!document.getElementsByClassName("jsMediaRow").length) {
+ window.location.reload();
+ }
+}
--- /dev/null
- _editorSuccess?: (Media, number?) => void;
+/**
+ * @author Matthias Schmidt
+ * @copyright 2001-2021 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module WoltLabSuite/Core/Media/Data
+ */
+
+import MediaUpload from "./Upload";
+import { FileElements, UploadOptions } from "../Upload/Data";
+import MediaEditor from "./Editor";
+import MediaManager from "./Manager/Base";
+import { RedactorEditor } from "../Ui/Redactor/Editor";
+import { I18nValues } from "../Language/Input";
+
+export interface Media {
+ altText: I18nValues | string;
+ caption: I18nValues | string;
+ categoryID: number;
+ elementTag: string;
+ captionEnableHtml: number;
+ filename: string;
+ formattedFilesize: string;
+ languageID: number | null;
+ isImage: number;
+ isMultilingual: number;
+ link: string;
+ mediaID: number;
+ smallThumbnailLink: string;
+ smallThumbnailType: string;
+ tinyThumbnailLink: string;
+ tinyThumbnailType: string;
+ title: I18nValues | string;
+}
+
+export interface MediaManagerOptions {
+ dialogTitle: string;
+ imagesOnly: boolean;
+ minSearchLength: number;
+}
+
+export const enum MediaInsertType {
+ Separate = "separate",
+}
+
+export interface MediaManagerEditorOptions extends MediaManagerOptions {
+ buttonClass?: string;
+ callbackInsert: (media: Map<number, Media>, insertType: MediaInsertType, thumbnailSize?: string) => void;
+ editor?: RedactorEditor;
+}
+
+export interface MediaManagerSelectOptions extends MediaManagerOptions {
+ buttonClass?: string;
+}
+
+export interface MediaEditorCallbackObject {
+ _editorClose?: () => void;
++ _editorSuccess?: (Media, number?, boolean?) => void;
+}
+
+export interface MediaUploadSuccessEventData {
+ files: FileElements;
+ isMultiFileUpload: boolean;
+ media: Media[];
+ upload: MediaUpload;
+ uploadId: number;
+}
+
+export interface MediaUploadOptions extends UploadOptions {
+ elementTagSize: number;
+ mediaEditor?: MediaEditor;
+ mediaManager?: MediaManager;
+}
+
+export interface MediaListUploadOptions extends MediaUploadOptions {
+ categoryId?: number;
+}
+
+export interface MediaUploadAjaxResponseData {
+ returnValues: {
+ errors: MediaUploadError[];
+ media: Media[];
+ };
+}
+
+export interface MediaUploadError {
+ errorType: string;
+ filename: string;
+}
--- /dev/null
- this._callbackObject._editorSuccess(media);
+/**
+ * Handles editing media files via dialog.
+ *
+ * @author Matthias Schmidt
+ * @copyright 2001-2021 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module WoltLabSuite/Core/Media/Editor
+ */
+
+import * as Core from "../Core";
+import { Media, MediaEditorCallbackObject } from "./Data";
+import { AjaxCallbackObject, AjaxCallbackSetup } from "../Ajax/Data";
+import * as UiNotification from "../Ui/Notification";
+import * as UiDialog from "../Ui/Dialog";
+import { DialogCallbackObject } from "../Ui/Dialog/Data";
+import * as LanguageChooser from "../Language/Chooser";
+import * as LanguageInput from "../Language/Input";
+import * as DomUtil from "../Dom/Util";
+import * as DomTraverse from "../Dom/Traverse";
+import DomChangeListener from "../Dom/Change/Listener";
+import * as Language from "../Language";
+import * as Ajax from "../Ajax";
+import MediaReplace from "./Replace";
+import { I18nValues } from "../Language/Input";
+
+interface InitEditorData {
+ returnValues: {
+ availableLanguageCount: number;
+ categoryIDs: number[];
+ mediaData?: Media;
+ };
+}
+
+class MediaEditor implements AjaxCallbackObject {
+ protected _availableLanguageCount = 1;
+ protected _categoryIds: number[] = [];
+ protected _dialogs = new Map<string, DialogCallbackObject>();
+ protected readonly _callbackObject: MediaEditorCallbackObject;
+ protected _media: Media | null = null;
+ protected _oldCategoryId = 0;
+
+ constructor(callbackObject: MediaEditorCallbackObject) {
+ this._callbackObject = callbackObject || {};
+
+ if (this._callbackObject._editorClose && typeof this._callbackObject._editorClose !== "function") {
+ throw new TypeError("Callback object has no function '_editorClose'.");
+ }
+ if (this._callbackObject._editorSuccess && typeof this._callbackObject._editorSuccess !== "function") {
+ throw new TypeError("Callback object has no function '_editorSuccess'.");
+ }
+ }
+
+ public _ajaxSetup(): ReturnType<AjaxCallbackSetup> {
+ return {
+ data: {
+ actionName: "update",
+ className: "wcf\\data\\media\\MediaAction",
+ },
+ };
+ }
+
+ public _ajaxSuccess(): void {
+ UiNotification.show();
+
+ if (this._callbackObject._editorSuccess) {
+ this._callbackObject._editorSuccess(this._media, this._oldCategoryId);
+ this._oldCategoryId = 0;
+ }
+
+ UiDialog.close(`mediaEditor_${this._media!.mediaID}`);
+
+ this._media = null;
+ }
+
+ /**
+ * Is called if an editor is manually closed by the user.
+ */
+ protected _close(): void {
+ this._media = null;
+
+ if (this._callbackObject._editorClose) {
+ this._callbackObject._editorClose();
+ }
+ }
+
+ /**
+ * Initializes the editor dialog.
+ *
+ * @since 5.3
+ */
+ protected _initEditor(content: HTMLElement, data: InitEditorData): void {
+ this._availableLanguageCount = ~~data.returnValues.availableLanguageCount;
+ this._categoryIds = data.returnValues.categoryIDs.map((number) => ~~number);
+
+ if (data.returnValues.mediaData) {
+ this._media = data.returnValues.mediaData;
+ }
+ const media = this._media!;
+ const mediaId = media.mediaID;
+
+ // make sure that the language chooser is initialized first
+ setTimeout(() => {
+ if (this._availableLanguageCount > 1) {
+ LanguageChooser.setLanguageId(`mediaEditor_${mediaId}_languageID`, media.languageID || window.LANGUAGE_ID);
+ }
+
+ if (this._categoryIds.length) {
+ const categoryID = content.querySelector("select[name=categoryID]") as HTMLSelectElement;
+ if (media.categoryID) {
+ categoryID.value = media.categoryID.toString();
+ } else {
+ categoryID.value = "0";
+ }
+ }
+
+ const title = content.querySelector("input[name=title]") as HTMLInputElement;
+ const altText = content.querySelector("input[name=altText]") as HTMLInputElement;
+ const caption = content.querySelector("textarea[name=caption]") as HTMLInputElement;
+
+ if (this._availableLanguageCount > 1 && media.isMultilingual) {
+ if (document.getElementById(`altText_${mediaId}`)) {
+ LanguageInput.setValues(`altText_${mediaId}`, (media.altText || {}) as I18nValues);
+ }
+
+ if (document.getElementById(`caption_${mediaId}`)) {
+ LanguageInput.setValues(`caption_${mediaId}`, (media.caption || {}) as I18nValues);
+ }
+
+ LanguageInput.setValues(`title_${mediaId}`, (media.title || {}) as I18nValues);
+ } else {
+ title.value = media.title ? media.title[media.languageID || window.LANGUAGE_ID] : "";
+ if (altText) {
+ altText.value = media.altText ? media.altText[media.languageID || window.LANGUAGE_ID] : "";
+ }
+ if (caption) {
+ caption.value = media.caption ? media.caption[media.languageID || window.LANGUAGE_ID] : "";
+ }
+ }
+
+ if (this._availableLanguageCount > 1) {
+ const isMultilingual = content.querySelector("input[name=isMultilingual]") as HTMLInputElement;
+ isMultilingual.addEventListener("change", (ev) => this._updateLanguageFields(ev));
+
+ this._updateLanguageFields(null, isMultilingual);
+ }
+
+ if (altText) {
+ altText.addEventListener("keypress", (ev) => this._keyPress(ev));
+ }
+ title.addEventListener("keypress", (ev) => this._keyPress(ev));
+
+ content.querySelector("button[data-type=submit]")!.addEventListener("click", () => this._saveData());
+
+ // remove focus from input elements and scroll dialog to top
+ (document.activeElement! as HTMLElement).blur();
+ (document.getElementById(`mediaEditor_${mediaId}`)!.parentNode as HTMLElement).scrollTop = 0;
+
+ // Initialize button to replace media file.
+ const uploadButton = content.querySelector(".mediaManagerMediaReplaceButton")!;
+ let target = content.querySelector(".mediaThumbnail");
+ if (!target) {
+ target = document.createElement("div");
+ content.appendChild(target);
+ }
+ new MediaReplace(
+ mediaId,
+ DomUtil.identify(uploadButton),
+ // Pass an anonymous element for non-images which is required internally
+ // but not needed in this case.
+ DomUtil.identify(target),
+ {
+ mediaEditor: this,
+ },
+ );
+
+ DomChangeListener.trigger();
+ }, 200);
+ }
+
+ /**
+ * Handles the `[ENTER]` key to submit the form.
+ */
+ protected _keyPress(event: KeyboardEvent): void {
+ if (event.key === "Enter") {
+ event.preventDefault();
+
+ this._saveData();
+ }
+ }
+
+ /**
+ * Saves the data of the currently edited media.
+ */
+ protected _saveData(): void {
+ const content = UiDialog.getDialog(`mediaEditor_${this._media!.mediaID}`)!.content;
+
+ const categoryId = content.querySelector("select[name=categoryID]") as HTMLSelectElement;
+ const altText = content.querySelector("input[name=altText]") as HTMLInputElement;
+ const caption = content.querySelector("textarea[name=caption]") as HTMLTextAreaElement;
+ const captionEnableHtml = content.querySelector("input[name=captionEnableHtml]") as HTMLInputElement;
+ const title = content.querySelector("input[name=title]") as HTMLInputElement;
+
+ let hasError = false;
+ const altTextError = altText ? DomTraverse.childByClass(altText.parentNode! as HTMLElement, "innerError") : false;
+ const captionError = caption ? DomTraverse.childByClass(caption.parentNode! as HTMLElement, "innerError") : false;
+ const titleError = DomTraverse.childByClass(title.parentNode! as HTMLElement, "innerError");
+
+ // category
+ this._oldCategoryId = this._media!.categoryID;
+ if (this._categoryIds.length) {
+ this._media!.categoryID = ~~categoryId.value;
+
+ // if the selected category id not valid (manipulated DOM), ignore
+ if (this._categoryIds.indexOf(this._media!.categoryID) === -1) {
+ this._media!.categoryID = 0;
+ }
+ }
+
+ // language and multilingualism
+ if (this._availableLanguageCount > 1) {
+ const isMultilingual = content.querySelector("input[name=isMultilingual]") as HTMLInputElement;
+ this._media!.isMultilingual = ~~isMultilingual.checked;
+ this._media!.languageID = this._media!.isMultilingual
+ ? null
+ : LanguageChooser.getLanguageId(`mediaEditor_${this._media!.mediaID}_languageID`);
+ } else {
+ this._media!.languageID = window.LANGUAGE_ID;
+ }
+
+ // altText, caption and title
+ this._media!.altText = {};
+ this._media!.caption = {};
+ this._media!.title = {};
+ if (this._availableLanguageCount > 1 && this._media!.isMultilingual) {
+ if (altText && !LanguageInput.validate(altText.id, true)) {
+ hasError = true;
+ if (!altTextError) {
+ DomUtil.innerError(altText, Language.get("wcf.global.form.error.multilingual"));
+ }
+ }
+ if (caption && !LanguageInput.validate(caption.id, true)) {
+ hasError = true;
+ if (!captionError) {
+ DomUtil.innerError(caption, Language.get("wcf.global.form.error.multilingual"));
+ }
+ }
+ if (!LanguageInput.validate(title.id, true)) {
+ hasError = true;
+ if (!titleError) {
+ DomUtil.innerError(title, Language.get("wcf.global.form.error.multilingual"));
+ }
+ }
+
+ this._media!.altText = altText ? this.mapToI18nValues(LanguageInput.getValues(altText.id)) : "";
+ this._media!.caption = caption ? this.mapToI18nValues(LanguageInput.getValues(caption.id)) : "";
+ this._media!.title = this.mapToI18nValues(LanguageInput.getValues(title.id));
+ } else {
+ this._media!.altText[this._media!.languageID!] = altText ? altText.value : "";
+ this._media!.caption[this._media!.languageID!] = caption ? caption.value : "";
+ this._media!.title[this._media!.languageID!] = title.value;
+ }
+
+ // captionEnableHtml
+ if (captionEnableHtml) {
+ this._media!.captionEnableHtml = ~~captionEnableHtml.checked;
+ } else {
+ this._media!.captionEnableHtml = 0;
+ }
+
+ const aclValues = {
+ allowAll: ~~(document.getElementById(`mediaEditor_${this._media!.mediaID}_aclAllowAll`)! as HTMLInputElement)
+ .checked,
+ group: Array.from(
+ content.querySelectorAll(`input[name="mediaEditor_${this._media!.mediaID}_aclValues[group][]"]`),
+ ).map((aclGroup: HTMLInputElement) => ~~aclGroup.value),
+ user: Array.from(
+ content.querySelectorAll(`input[name="mediaEditor_${this._media!.mediaID}_aclValues[user][]"]`),
+ ).map((aclUser: HTMLInputElement) => ~~aclUser.value),
+ };
+
+ if (!hasError) {
+ if (altTextError) {
+ altTextError.remove();
+ }
+ if (captionError) {
+ captionError.remove();
+ }
+ if (titleError) {
+ titleError.remove();
+ }
+
+ Ajax.api(this, {
+ actionName: "update",
+ objectIDs: [this._media!.mediaID],
+ parameters: {
+ aclValues: aclValues,
+ altText: this._media!.altText,
+ caption: this._media!.caption,
+ data: {
+ captionEnableHtml: this._media!.captionEnableHtml,
+ categoryID: this._media!.categoryID,
+ isMultilingual: this._media!.isMultilingual,
+ languageID: this._media!.languageID,
+ },
+ title: this._media!.title,
+ },
+ });
+ }
+ }
+
+ private mapToI18nValues(values: Map<number, string>): I18nValues {
+ const obj = {};
+ values.forEach((value, key) => (obj[key] = value));
+
+ return obj;
+ }
+
+ /**
+ * Updates language-related input fields depending on whether multilingualis is enabled.
+ */
+ protected _updateLanguageFields(event: Event | null, element?: HTMLInputElement): void {
+ if (event) {
+ element = event.currentTarget as HTMLInputElement;
+ }
+
+ const mediaId = this._media!.mediaID;
+ const languageChooserContainer = document.getElementById(`mediaEditor_${mediaId}_languageIDContainer`)!
+ .parentNode! as HTMLElement;
+
+ if (element!.checked) {
+ LanguageInput.enable(`title_${mediaId}`);
+ if (document.getElementById(`caption_${mediaId}`)) {
+ LanguageInput.enable(`caption_${mediaId}`);
+ }
+ if (document.getElementById(`altText_${mediaId}`)) {
+ LanguageInput.enable(`altText_${mediaId}`);
+ }
+
+ DomUtil.hide(languageChooserContainer);
+ } else {
+ LanguageInput.disable(`title_${mediaId}`);
+ if (document.getElementById(`caption_${mediaId}`)) {
+ LanguageInput.disable(`caption_${mediaId}`);
+ }
+ if (document.getElementById(`altText_${mediaId}`)) {
+ LanguageInput.disable(`altText_${mediaId}`);
+ }
+
+ DomUtil.show(languageChooserContainer);
+ }
+ }
+
+ /**
+ * Edits the media with the given data or id.
+ */
+ public edit(editedMedia: Media | number): void {
+ let media: Media;
+ let mediaId = 0;
+ if (typeof editedMedia === "object") {
+ media = editedMedia;
+ mediaId = media.mediaID;
+ } else {
+ media = {
+ mediaID: editedMedia,
+ } as Media;
+ mediaId = editedMedia;
+ }
+
+ if (this._media !== null) {
+ throw new Error(`Cannot edit media with id ${mediaId} while editing media with id '${this._media.mediaID}'.`);
+ }
+
+ this._media = media;
+
+ if (!this._dialogs.has(`mediaEditor_${mediaId}`)) {
+ this._dialogs.set(`mediaEditor_${mediaId}`, {
+ _dialogSetup: () => {
+ return {
+ id: `mediaEditor_${mediaId}`,
+ options: {
+ backdropCloseOnClick: false,
+ onClose: () => this._close(),
+ title: Language.get("wcf.media.edit"),
+ },
+ source: {
+ after: (content: HTMLElement, responseData: InitEditorData) => this._initEditor(content, responseData),
+ data: {
+ actionName: "getEditorDialog",
+ className: "wcf\\data\\media\\MediaAction",
+ objectIDs: [mediaId],
+ },
+ },
+ };
+ },
+ });
+ }
+
+ UiDialog.open(this._dialogs.get(`mediaEditor_${mediaId}`)!);
+ }
+
+ /**
+ * Updates the data of the currently edited media file.
+ */
+ public updateData(media: Media): void {
+ if (this._callbackObject._editorSuccess) {
++ this._callbackObject._editorSuccess(media, undefined, false);
+ }
+ }
+}
+
+Core.enableLegacyInheritance(MediaEditor);
+
+export = MediaEditor;
--- /dev/null
- _editorSuccess(media: Media, oldCategoryId?: number): void {
+/**
+ * Provides the media manager dialog.
+ *
+ * @author Matthias Schmidt
+ * @copyright 2001-2020 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module WoltLabSuite/Core/Media/Manager/Base
+ */
+
+import * as Core from "../../Core";
+import { Media, MediaManagerOptions, MediaEditorCallbackObject, MediaUploadSuccessEventData } from "../Data";
+import * as Language from "../../Language";
+import * as Permission from "../../Permission";
+import * as DomChangeListener from "../../Dom/Change/Listener";
+import * as EventHandler from "../../Event/Handler";
+import * as DomTraverse from "../../Dom/Traverse";
+import * as DomUtil from "../../Dom/Util";
+import * as UiDialog from "../../Ui/Dialog";
+import { DialogCallbackSetup, DialogCallbackObject } from "../../Ui/Dialog/Data";
+import * as Clipboard from "../../Controller/Clipboard";
+import UiPagination from "../../Ui/Pagination";
+import * as UiNotification from "../../Ui/Notification";
+import * as StringUtil from "../../StringUtil";
+import MediaManagerSearch from "./Search";
+import MediaUpload from "../Upload";
+import MediaEditor from "../Editor";
+import * as MediaClipboard from "../Clipboard";
+import { ObjectActionData } from "../../Ui/Object/Data";
+
+let mediaManagerCounter = 0;
+
+interface DialogInitAjaxResponseData {
+ returnValues: {
+ hasMarkedItems: number;
+ media: object;
+ pageCount: number;
+ };
+}
+
+interface SetMediaAdditionalData {
+ pageCount: number;
+ pageNo: number;
+}
+
+abstract class MediaManager<TOptions extends MediaManagerOptions = MediaManagerOptions>
+ implements DialogCallbackObject, MediaEditorCallbackObject {
+ protected _forceClipboard = false;
+ protected _hadInitiallyMarkedItems = false;
+ protected readonly _id;
+ protected readonly _listItems = new Map<number, HTMLLIElement>();
+ protected _media = new Map<number, Media>();
+ protected _mediaCategorySelect: HTMLSelectElement | null;
+ protected readonly _mediaEditor: MediaEditor | null = null;
+ protected _mediaManagerMediaList: HTMLElement | null = null;
+ protected _pagination: UiPagination | null = null;
+ protected _search: MediaManagerSearch | null = null;
+ protected _upload: any = null;
+ protected readonly _options: TOptions;
+
+ constructor(options: Partial<TOptions>) {
+ this._options = Core.extend(
+ {
+ dialogTitle: Language.get("wcf.media.manager"),
+ imagesOnly: false,
+ minSearchLength: 3,
+ },
+ options,
+ ) as TOptions;
+
+ this._id = `mediaManager${mediaManagerCounter++}`;
+
+ if (Permission.get("admin.content.cms.canManageMedia")) {
+ this._mediaEditor = new MediaEditor(this);
+ }
+
+ DomChangeListener.add("WoltLabSuite/Core/Media/Manager", () => this._addButtonEventListeners());
+
+ EventHandler.add("com.woltlab.wcf.media.upload", "success", (data: MediaUploadSuccessEventData) =>
+ this._openEditorAfterUpload(data),
+ );
+ }
+
+ /**
+ * Adds click event listeners to media buttons.
+ */
+ protected _addButtonEventListeners(): void {
+ if (!this._mediaManagerMediaList || !Permission.get("admin.content.cms.canManageMedia")) return;
+
+ DomTraverse.childrenByTag(this._mediaManagerMediaList, "LI").forEach((listItem) => {
+ const editIcon = listItem.querySelector(".jsMediaEditButton");
+ if (editIcon) {
+ editIcon.classList.remove("jsMediaEditButton");
+ editIcon.addEventListener("click", (ev) => this._editMedia(ev));
+ }
+ });
+ }
+
+ /**
+ * Is called when a new category is selected.
+ */
+ protected _categoryChange(): void {
+ this._search!.search();
+ }
+
+ /**
+ * Handles clicks on the media manager button.
+ */
+ protected _click(event: Event): void {
+ event.preventDefault();
+
+ UiDialog.open(this);
+ }
+
+ /**
+ * Is called if the media manager dialog is closed.
+ */
+ protected _dialogClose(): void {
+ // only show media clipboard if editor is open
+ if (Permission.get("admin.content.cms.canManageMedia") || this._forceClipboard) {
+ Clipboard.hideEditor("com.woltlab.wcf.media");
+ }
+ }
+
+ /**
+ * Initializes the dialog when first loaded.
+ */
+ protected _dialogInit(content: HTMLElement, data: DialogInitAjaxResponseData): void {
+ // store media data locally
+ Object.entries(data.returnValues.media || {}).forEach(([mediaId, media]) => {
+ this._media.set(~~mediaId, media);
+ });
+
+ this._initPagination(~~data.returnValues.pageCount);
+
+ this._hadInitiallyMarkedItems = data.returnValues.hasMarkedItems > 0;
+ }
+
+ /**
+ * Returns all data to setup the media manager dialog.
+ */
+ public _dialogSetup(): ReturnType<DialogCallbackSetup> {
+ return {
+ id: this._id,
+ options: {
+ onClose: () => this._dialogClose(),
+ onShow: () => this._dialogShow(),
+ title: this._options.dialogTitle,
+ },
+ source: {
+ after: (content: HTMLElement, data: DialogInitAjaxResponseData) => this._dialogInit(content, data),
+ data: {
+ actionName: "getManagementDialog",
+ className: "wcf\\data\\media\\MediaAction",
+ parameters: {
+ mode: this.getMode(),
+ imagesOnly: this._options.imagesOnly,
+ },
+ },
+ },
+ };
+ }
+
+ /**
+ * Is called if the media manager dialog is shown.
+ */
+ protected _dialogShow(): void {
+ if (!this._mediaManagerMediaList) {
+ const dialog = this.getDialog();
+
+ this._mediaManagerMediaList = dialog.querySelector(".mediaManagerMediaList");
+
+ this._mediaCategorySelect = dialog.querySelector(".mediaManagerCategoryList > select");
+ if (this._mediaCategorySelect) {
+ this._mediaCategorySelect.addEventListener("change", () => this._categoryChange());
+ }
+
+ // store list items locally
+ const listItems = DomTraverse.childrenByTag(this._mediaManagerMediaList!, "LI");
+ listItems.forEach((listItem: HTMLLIElement) => {
+ this._listItems.set(~~listItem.dataset.objectId!, listItem);
+ });
+
+ if (Permission.get("admin.content.cms.canManageMedia")) {
+ const uploadButton = UiDialog.getDialog(this)!.dialog.querySelector(".mediaManagerMediaUploadButton")!;
+ this._upload = new MediaUpload(DomUtil.identify(uploadButton), DomUtil.identify(this._mediaManagerMediaList!), {
+ mediaManager: this,
+ });
+
+ EventHandler.add("WoltLabSuite/Core/Ui/Object/Action", "delete", (data: ObjectActionData) =>
+ this.removeMedia(~~data.objectElement.dataset.objectId!),
+ );
+ }
+
+ if (Permission.get("admin.content.cms.canManageMedia") || this._forceClipboard) {
+ MediaClipboard.init("menuManagerDialog-" + this.getMode(), this._hadInitiallyMarkedItems ? true : false, this);
+ } else {
+ this._removeClipboardCheckboxes();
+ }
+
+ this._search = new MediaManagerSearch(this);
+
+ if (!listItems.length) {
+ this._search.hideSearch();
+ }
+ }
+
+ // only show media clipboard if editor is open
+ if (Permission.get("admin.content.cms.canManageMedia") || this._forceClipboard) {
+ Clipboard.showEditor();
+ }
+ }
+
+ /**
+ * Opens the media editor for a media file.
+ */
+ protected _editMedia(event: Event): void {
+ if (!Permission.get("admin.content.cms.canManageMedia")) {
+ throw new Error("You are not allowed to edit media files.");
+ }
+
+ UiDialog.close(this);
+
+ const target = event.currentTarget as HTMLElement;
+
+ this._mediaEditor!.edit(this._media.get(~~target.dataset.objectId!)!);
+ }
+
+ /**
+ * Re-opens the manager dialog after closing the editor dialog.
+ */
+ _editorClose(): void {
+ UiDialog.open(this);
+ }
+
+ /**
+ * Re-opens the manager dialog and updates the media data after successfully editing a media file.
+ */
- UiDialog.open(this);
++ _editorSuccess(media: Media, oldCategoryId?: number, closedEditorDialog = true): void {
+ // if the category changed of media changed and category
+ // is selected, check if media list needs to be refreshed
+ if (this._mediaCategorySelect) {
+ const selectedCategoryId = ~~this._mediaCategorySelect.value;
+
+ if (selectedCategoryId) {
+ const newCategoryId = ~~media.categoryID;
+
+ if (
+ oldCategoryId != newCategoryId &&
+ (oldCategoryId == selectedCategoryId || newCategoryId == selectedCategoryId)
+ ) {
+ this._search!.search();
+ }
+ }
+ }
+
++ if (closedEditorDialog) {
++ UiDialog.open(this);
++ }
+
+ this._media.set(~~media.mediaID, media);
+
+ const listItem = this._listItems.get(~~media.mediaID)!;
+ const p = listItem.querySelector(".mediaTitle")!;
+ if (media.isMultilingual) {
+ if (media.title && media.title[window.LANGUAGE_ID]) {
+ p.textContent = media.title[window.LANGUAGE_ID];
+ } else {
+ p.textContent = media.filename;
+ }
+ } else {
+ if (media.title && media.title[media.languageID!]) {
+ p.textContent = media.title[media.languageID!];
+ } else {
+ p.textContent = media.filename;
+ }
+ }
+
+ const thumbnail = listItem.querySelector(".mediaThumbnail")!;
+ thumbnail.innerHTML = media.elementTag;
+ // Bust browser cache by adding additional parameter.
+ const img = thumbnail.querySelector("img");
+ if (img) {
+ img.src += `&refresh=${Date.now()}`;
+ }
+ }
+
+ /**
+ * Initializes the dialog pagination.
+ */
+ protected _initPagination(pageCount: number, pageNo?: number): void {
+ if (pageNo === undefined) pageNo = 1;
+
+ if (pageCount > 1) {
+ const newPagination = document.createElement("div");
+ newPagination.className = "paginationBottom jsPagination";
+ DomUtil.replaceElement(
+ UiDialog.getDialog(this)!.content.querySelector(".jsPagination") as HTMLElement,
+ newPagination,
+ );
+
+ this._pagination = new UiPagination(newPagination, {
+ activePage: pageNo,
+ callbackSwitch: (pageNo: number) => this._search!.search(pageNo),
+ maxPage: pageCount,
+ });
+ } else if (this._pagination) {
+ DomUtil.hide(this._pagination.getElement());
+ }
+ }
+
+ /**
+ * Removes all media clipboard checkboxes.
+ */
+ _removeClipboardCheckboxes(): void {
+ this._mediaManagerMediaList!.querySelectorAll(".mediaCheckbox").forEach((el) => el.remove());
+ }
+
+ /**
+ * Opens the media editor after uploading a single file.
+ *
+ * @since 5.2
+ */
+ _openEditorAfterUpload(data: MediaUploadSuccessEventData): void {
+ if (data.upload === this._upload && !data.isMultiFileUpload && !this._upload.hasPendingUploads()) {
+ const keys = Object.keys(data.media);
+
+ if (keys.length) {
+ UiDialog.close(this);
+
+ this._mediaEditor!.edit(this._media.get(~~data.media[keys[0]].mediaID)!);
+ }
+ }
+ }
+
+ /**
+ * Sets the displayed media (after a search).
+ */
+ _setMedia(media: object): void {
+ this._media = new Map<number, Media>(Object.entries(media).map(([mediaId, media]) => [~~mediaId, media]));
+
+ let info = DomTraverse.nextByClass(this._mediaManagerMediaList!, "info") as HTMLElement;
+
+ if (this._media.size) {
+ if (info) {
+ DomUtil.hide(info);
+ }
+ } else {
+ if (info === null) {
+ info = document.createElement("p");
+ info.className = "info";
+ info.textContent = Language.get("wcf.media.search.noResults");
+ }
+
+ DomUtil.show(info);
+ DomUtil.insertAfter(info, this._mediaManagerMediaList!);
+ }
+
+ DomTraverse.childrenByTag(this._mediaManagerMediaList!, "LI").forEach((listItem) => {
+ if (!this._media.has(~~listItem.dataset.objectId!)) {
+ DomUtil.hide(listItem);
+ } else {
+ DomUtil.show(listItem);
+ }
+ });
+
+ DomChangeListener.trigger();
+
+ if (Permission.get("admin.content.cms.canManageMedia") || this._forceClipboard) {
+ Clipboard.reload();
+ } else {
+ this._removeClipboardCheckboxes();
+ }
+ }
+
+ /**
+ * Adds a media file to the manager.
+ */
+ public addMedia(media: Media, listItem: HTMLLIElement): void {
+ if (!media.languageID) media.isMultilingual = 1;
+
+ this._media.set(~~media.mediaID, media);
+ this._listItems.set(~~media.mediaID, listItem);
+
+ if (this._listItems.size === 1) {
+ this._search!.showSearch();
+ }
+ }
+
+ /**
+ * Is called after the media files with the given ids have been deleted via clipboard.
+ */
+ public clipboardDeleteMedia(mediaIds: number[]): void {
+ mediaIds.forEach((mediaId) => {
+ this.removeMedia(~~mediaId);
+ });
+
+ UiNotification.show();
+ }
+
+ /**
+ * Returns the id of the currently selected category or `0` if no category is selected.
+ */
+ public getCategoryId(): number {
+ if (this._mediaCategorySelect) {
+ return ~~this._mediaCategorySelect.value;
+ }
+
+ return 0;
+ }
+
+ /**
+ * Returns the media manager dialog element.
+ */
+ getDialog(): HTMLElement {
+ return UiDialog.getDialog(this)!.dialog;
+ }
+
+ /**
+ * Returns the mode of the media manager.
+ */
+ public getMode(): string {
+ return "";
+ }
+
+ /**
+ * Returns the media manager option with the given name.
+ */
+ public getOption(name: string): any {
+ if (this._options[name]) {
+ return this._options[name];
+ }
+
+ return null;
+ }
+
+ /**
+ * Removes a media file.
+ */
+ public removeMedia(mediaId: number): void {
+ if (this._listItems.has(mediaId)) {
+ // remove list item
+ try {
+ this._listItems.get(mediaId)!.remove();
+ } catch (e) {
+ // ignore errors if item has already been removed by other code
+ }
+
+ this._listItems.delete(mediaId);
+ this._media.delete(mediaId);
+ }
+ }
+
+ /**
+ * Changes the displayed media to the previously displayed media.
+ */
+ public resetMedia(): void {
+ // calling WoltLabSuite/Core/Media/Manager/Search.search() reloads the first page of the dialog
+ this._search!.search();
+ }
+
+ /**
+ * Sets the media files currently displayed.
+ */
+ setMedia(media: object, template: string, additionalData: SetMediaAdditionalData): void {
+ const hasMedia = Object.entries(media).length > 0;
+
+ if (hasMedia) {
+ const ul = document.createElement("ul");
+ ul.innerHTML = template;
+
+ DomTraverse.childrenByTag(ul, "LI").forEach((listItem) => {
+ if (!this._listItems.has(~~listItem.dataset.objectId!)) {
+ this._listItems.set(~~listItem.dataset.objectId!, listItem);
+
+ this._mediaManagerMediaList!.appendChild(listItem);
+ }
+ });
+ }
+
+ this._initPagination(additionalData.pageCount, additionalData.pageNo);
+
+ this._setMedia(media);
+ }
+
+ /**
+ * Sets up a new media element.
+ */
+ public setupMediaElement(media: Media, mediaElement: HTMLElement): void {
+ const mediaInformation = DomTraverse.childByClass(mediaElement, "mediaInformation")!;
+
+ const buttonGroupNavigation = document.createElement("nav");
+ buttonGroupNavigation.className = "jsMobileNavigation buttonGroupNavigation";
+ mediaInformation.parentNode!.appendChild(buttonGroupNavigation);
+
+ const buttons = document.createElement("ul");
+ buttons.className = "buttonList iconList";
+ buttonGroupNavigation.appendChild(buttons);
+
+ const listItem = document.createElement("li");
+ listItem.className = "mediaCheckbox";
+ buttons.appendChild(listItem);
+
+ const a = document.createElement("a");
+ listItem.appendChild(a);
+
+ const label = document.createElement("label");
+ a.appendChild(label);
+
+ const checkbox = document.createElement("input");
+ checkbox.className = "jsClipboardItem";
+ checkbox.type = "checkbox";
+ checkbox.dataset.objectId = media.mediaID.toString();
+ label.appendChild(checkbox);
+
+ if (Permission.get("admin.content.cms.canManageMedia")) {
+ const editButton = document.createElement("li");
+ editButton.className = "jsMediaEditButton";
+ editButton.dataset.objectId = media.mediaID.toString();
+ buttons.appendChild(editButton);
+
+ editButton.innerHTML = `
+ <a>
+ <span class="icon icon16 fa-pencil jsTooltip" title="${Language.get("wcf.global.button.edit")}"></span>
+ <span class="invisible">${Language.get("wcf.global.button.edit")}</span>
+ </a>`;
+
+ const deleteButton = document.createElement("li");
+ deleteButton.classList.add("jsObjectAction");
+ deleteButton.dataset.objectAction = "delete";
+
+ // use temporary title to not unescape html in filename
+ const uuid = Core.getUuid();
+ deleteButton.dataset.confirmMessage = StringUtil.unescapeHTML(
+ Language.get("wcf.media.delete.confirmMessage", {
+ title: uuid,
+ }),
+ ).replace(uuid, StringUtil.escapeHTML(media.filename));
+ buttons.appendChild(deleteButton);
+
+ deleteButton.innerHTML = `
+ <a>
+ <span class="icon icon16 fa-times jsTooltip" title="${Language.get("wcf.global.button.delete")}"></span>
+ <span class="invisible">${Language.get("wcf.global.button.delete")}</span>
+ </a>`;
+ }
+ }
+}
+
+Core.enableLegacyInheritance(MediaManager);
+
+export = MediaManager;
/**
* Initializes modules required for media list view.
*
- * @author Matthias Schmidt
- * @copyright 2001-2019 WoltLab GmbH
- * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module WoltLabSuite/Core/Controller/Media/List
+ * @author Matthias Schmidt
+ * @copyright 2001-2021 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module WoltLabSuite/Core/Controller/Media/List
*/
-define([
- 'Dom/ChangeListener',
- 'EventHandler',
- 'WoltLabSuite/Core/Controller/Clipboard',
- 'WoltLabSuite/Core/Media/Clipboard',
- 'WoltLabSuite/Core/Media/Editor',
- 'WoltLabSuite/Core/Media/List/Upload'
- ],
- function(
- DomChangeListener,
- EventHandler,
- Clipboard,
- MediaClipboard,
- MediaEditor,
- MediaListUpload
- ) {
- "use strict";
-
- if (!COMPILER_TARGET_DEFAULT) {
- var Fake = function() {};
- Fake.prototype = {
- init: function() {},
- _addButtonEventListeners: function() {},
- _deleteCallback: function() {},
- _deleteMedia: function(mediaIds) {},
- _edit: function() {}
- };
- return Fake;
- }
-
- var _mediaEditor;
- var _tableBody = elById('mediaListTableBody');
- var _clipboardObjectIds = [];
- var _upload;
-
- /**
- * @exports WoltLabSuite/Core/Controller/Media/List
- */
- return {
- init: function(options) {
- options = options || {};
- _upload = new MediaListUpload('uploadButton', 'mediaListTableBody', {
- categoryId: options.categoryId,
- multiple: true,
- elementTagSize: 48
- });
-
- MediaClipboard.init(
- 'wcf\\acp\\page\\MediaListPage',
- options.hasMarkedItems || false,
- this
- );
-
- EventHandler.add('com.woltlab.wcf.media.upload', 'removedErroneousUploadRow', this._deleteCallback.bind(this));
-
- var deleteAction = new WCF.Action.Delete('wcf\\data\\media\\MediaAction', '.jsMediaRow');
- deleteAction.setCallback(this._deleteCallback);
-
- _mediaEditor = new MediaEditor({
- _editorSuccess: function(media, oldCategoryId, closedEditorDialog = true) {
- if (media.categoryID != oldCategoryId || closedEditorDialog) {
- window.setTimeout(function() {
- window.location.reload();
- }, 500);
- }
- }
- });
-
- this._addButtonEventListeners();
-
- DomChangeListener.add('WoltLabSuite/Core/Controller/Media/List', this._addButtonEventListeners.bind(this));
-
- EventHandler.add('com.woltlab.wcf.media.upload', 'success', this._openEditorAfterUpload.bind(this));
- },
-
- /**
- * Adds the `click` event listeners to the media edit icons in
- * new media table rows.
- */
- _addButtonEventListeners: function() {
- var buttons = elByClass('jsMediaEditButton', _tableBody), button;
- while (buttons.length) {
- button = buttons[0];
- button.classList.remove('jsMediaEditButton');
- button.addEventListener(WCF_CLICK_EVENT, this._edit.bind(this));
- }
- },
-
- /**
- * Is triggered after media files have been deleted using the delete icon.
- *
- * @param {int[]?} objectIds
- */
- _deleteCallback: function(objectIds) {
- var tableRowCount = elByTag('tr', _tableBody).length;
- if (objectIds.length === undefined) {
- if (!tableRowCount) {
- window.location.reload();
- }
- }
- else if (objectIds.length === tableRowCount) {
- // table is empty, reload page
- window.location.reload();
- }
- else {
- Clipboard.reload.bind(Clipboard)
- }
- },
-
- /**
- * Is called when a media edit icon is clicked.
- *
- * @param {Event} event
- */
- _edit: function(event) {
- _mediaEditor.edit(elData(event.currentTarget, 'object-id'));
- },
-
- /**
- * Opens the media editor after uploading a single file.
- *
- * @param {object} data upload event data
- * @since 5.2
- */
- _openEditorAfterUpload: function(data) {
- if (data.upload === _upload && !data.isMultiFileUpload && !_upload.hasPendingUploads()) {
- var keys = Object.keys(data.media);
-
- if (keys.length) {
- _mediaEditor.edit(data.media[keys[0]]);
- }
- }
- },
-
- /**
- * Is called after the media files with the given ids have been deleted via clipboard.
- *
- * @param {int[]} mediaIds ids of deleted media files
- */
- clipboardDeleteMedia: function(mediaIds) {
- var mediaRows = elByClass('jsMediaRow');
- for (var i = 0; i < mediaRows.length; i++) {
- var media = mediaRows[i];
- var mediaID = ~~elData(elByClass('jsClipboardItem', media)[0], 'object-id');
-
- if (mediaIds.indexOf(mediaID) !== -1) {
- elRemove(media);
- i--;
- }
- }
-
- if (!mediaRows.length) {
- window.location.reload();
- }
- }
- }
-});
+define(["require", "exports", "tslib", "../../Media/List/Upload", "../../Media/Clipboard", "../../Event/Handler", "../../Media/Editor", "../../Dom/Change/Listener"], function (require, exports, tslib_1, Upload_1, MediaClipboard, EventHandler, Editor_1, DomChangeListener) {
+ "use strict";
+ Object.defineProperty(exports, "__esModule", { value: true });
+ exports.init = void 0;
+ Upload_1 = tslib_1.__importDefault(Upload_1);
+ MediaClipboard = tslib_1.__importStar(MediaClipboard);
+ EventHandler = tslib_1.__importStar(EventHandler);
+ Editor_1 = tslib_1.__importDefault(Editor_1);
+ DomChangeListener = tslib_1.__importStar(DomChangeListener);
+ const _mediaEditor = new Editor_1.default({
- _editorSuccess: (media, oldCategoryId) => {
- if (media.categoryID != oldCategoryId) {
++ _editorSuccess: (media, oldCategoryId, closedEditorDialog = true) => {
++ if (media.categoryID != oldCategoryId || closedEditorDialog) {
+ window.setTimeout(() => {
+ window.location.reload();
+ }, 500);
+ }
+ },
+ });
+ const _tableBody = document.getElementById("mediaListTableBody");
+ let _upload;
+ function init(options) {
+ options = options || {};
+ _upload = new Upload_1.default("uploadButton", "mediaListTableBody", {
+ categoryId: options.categoryId,
+ multiple: true,
+ elementTagSize: 48,
+ });
+ MediaClipboard.init("wcf\\acp\\page\\MediaListPage", options.hasMarkedItems || false, {
+ clipboardDeleteMedia: (mediaIds) => clipboardDeleteMedia(mediaIds),
+ });
+ addButtonEventListeners();
+ DomChangeListener.add("WoltLabSuite/Core/Controller/Media/List", () => addButtonEventListeners());
+ EventHandler.add("com.woltlab.wcf.media.upload", "success", (data) => openEditorAfterUpload(data));
+ }
+ exports.init = init;
+ /**
+ * Adds the `click` event listeners to the media edit icons in new media table rows.
+ */
+ function addButtonEventListeners() {
+ Array.from(_tableBody.getElementsByClassName("jsMediaEditButton")).forEach((button) => {
+ button.classList.remove("jsMediaEditButton");
+ button.addEventListener("click", (ev) => edit(ev));
+ });
+ }
+ /**
+ * Is called when a media edit icon is clicked.
+ */
+ function edit(event) {
+ _mediaEditor.edit(~~event.currentTarget.dataset.objectId);
+ }
+ /**
+ * Opens the media editor after uploading a single file.
+ */
+ function openEditorAfterUpload(data) {
+ if (data.upload === _upload && !data.isMultiFileUpload && !_upload.hasPendingUploads()) {
+ const keys = Object.keys(data.media);
+ if (keys.length) {
+ _mediaEditor.edit(data.media[keys[0]]);
+ }
+ }
+ }
+ /**
+ * Is called after the media files with the given ids have been deleted via clipboard.
+ */
+ function clipboardDeleteMedia(mediaIds) {
+ Array.from(document.getElementsByClassName("jsMediaRow")).forEach((media) => {
+ const mediaID = ~~media.querySelector(".jsClipboardItem").dataset.objectId;
+ if (mediaIds.indexOf(mediaID) !== -1) {
+ media.remove();
+ }
+ });
+ if (!document.getElementsByClassName("jsMediaRow").length) {
+ window.location.reload();
+ }
+ }
+});
/**
* Handles editing media files via dialog.
- *
- * @author Matthias Schmidt
- * @copyright 2001-2019 WoltLab GmbH
- * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module WoltLabSuite/Core/Media/Editor
+ *
+ * @author Matthias Schmidt
+ * @copyright 2001-2021 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module WoltLabSuite/Core/Media/Editor
*/
-define(
- [
- 'Ajax',
- 'Core',
- 'Dictionary',
- 'Dom/ChangeListener',
- 'Dom/Traverse',
- 'Dom/Util',
- 'Language',
- 'Ui/Dialog',
- 'Ui/Notification',
- 'WoltLabSuite/Core/Language/Chooser',
- 'WoltLabSuite/Core/Language/Input',
- 'EventKey',
- 'WoltLabSuite/Core/Media/Replace'
- ],
- function(
- Ajax,
- Core,
- Dictionary,
- DomChangeListener,
- DomTraverse,
- DomUtil,
- Language,
- UiDialog,
- UiNotification,
- LanguageChooser,
- LanguageInput,
- EventKey,
- MediaReplace
- )
-{
- "use strict";
-
- if (!COMPILER_TARGET_DEFAULT) {
- var Fake = function() {};
- Fake.prototype = {
- _ajaxSetup: function() {},
- _ajaxSuccess: function() {},
- _close: function() {},
- _keyPress: function() {},
- _saveData: function() {},
- _updateLanguageFields: function() {},
- edit: function() {}
- };
- return Fake;
- }
-
- /**
- * @constructor
- */
- function MediaEditor(callbackObject) {
- this._callbackObject = callbackObject || {};
-
- if (this._callbackObject._editorClose && typeof this._callbackObject._editorClose !== 'function') {
- throw new TypeError("Callback object has no function '_editorClose'.");
- }
- if (this._callbackObject._editorSuccess && typeof this._callbackObject._editorSuccess !== 'function') {
- throw new TypeError("Callback object has no function '_editorSuccess'.");
- }
-
- this._media = null;
- this._availableLanguageCount = 1;
- this._categoryIds = [];
- this._oldCategoryId = 0;
-
- this._dialogs = new Dictionary();
- }
- MediaEditor.prototype = {
- /**
- * Returns the data for Ajax to setup the Ajax/Request object.
- *
- * @return {object} setup data for Ajax/Request object
- */
- _ajaxSetup: function() {
- return {
- data: {
- actionName: 'update',
- className: 'wcf\\data\\media\\MediaAction'
- }
- };
- },
-
- /**
- * Handles successful AJAX requests.
- *
- * @param {object} data response data
- */
- _ajaxSuccess: function(data) {
- UiNotification.show();
-
- if (this._callbackObject._editorSuccess) {
- this._callbackObject._editorSuccess(this._media, this._oldCategoryId);
- this._oldCategoryId = 0;
- }
-
- UiDialog.close('mediaEditor_' + this._media.mediaID);
-
- this._media = null;
- },
-
- /**
- * Is called if an editor is manually closed by the user.
- */
- _close: function() {
- this._media = null;
-
- if (this._callbackObject._editorClose) {
- this._callbackObject._editorClose();
- }
- },
-
- /**
- * Initializes the editor dialog.
- *
- * @param {HTMLElement} content
- * @param {object} data
- * @since 5.3
- */
- _initEditor: function(content, data) {
- this._availableLanguageCount = ~~data.returnValues.availableLanguageCount;
- this._categoryIds = data.returnValues.categoryIDs.map(function(number) {
- return ~~number;
- });
-
- var didLoadMediaData = false;
- if (data.returnValues.mediaData) {
- this._media = data.returnValues.mediaData;
-
- didLoadMediaData = true;
- }
-
- // make sure that the language chooser is initialized first
- setTimeout(function() {
- if (this._availableLanguageCount > 1) {
- LanguageChooser.setLanguageId('mediaEditor_' + this._media.mediaID + '_languageID', this._media.languageID || LANGUAGE_ID);
- }
-
- if (this._categoryIds.length) {
- elBySel('select[name=categoryID]', content).value = ~~this._media.categoryID;
- }
-
- var title = elBySel('input[name=title]', content);
- var altText = elBySel('input[name=altText]', content);
- var caption = elBySel('textarea[name=caption]', content);
-
- if (this._availableLanguageCount > 1 && this._media.isMultilingual) {
- if (elById('altText_' + this._media.mediaID)) LanguageInput.setValues('altText_' + this._media.mediaID, Dictionary.fromObject(this._media.altText || { }));
- if (elById('caption_' + this._media.mediaID)) LanguageInput.setValues('caption_' + this._media.mediaID, Dictionary.fromObject(this._media.caption || { }));
- LanguageInput.setValues('title_' + this._media.mediaID, Dictionary.fromObject(this._media.title || { }));
- }
- else {
- title.value = this._media.title ? this._media.title[this._media.languageID || LANGUAGE_ID] : '';
- if (altText) altText.value = this._media.altText ? this._media.altText[this._media.languageID || LANGUAGE_ID] : '';
- if (caption) caption.value = this._media.caption ? this._media.caption[this._media.languageID || LANGUAGE_ID] : '';
- }
-
- if (this._availableLanguageCount > 1) {
- var isMultilingual = elBySel('input[name=isMultilingual]', content);
- isMultilingual.addEventListener('change', this._updateLanguageFields.bind(this));
-
- this._updateLanguageFields(null, isMultilingual);
- }
-
- var keyPress = this._keyPress.bind(this);
- if (altText) altText.addEventListener('keypress', keyPress);
- title.addEventListener('keypress', keyPress);
-
- elBySel('button[data-type=submit]', content).addEventListener(WCF_CLICK_EVENT, this._saveData.bind(this));
-
- // remove focus from input elements and scroll dialog to top
- document.activeElement.blur();
- elById('mediaEditor_' + this._media.mediaID).parentNode.scrollTop = 0;
-
- // Initialize button to replace media file.
- var uploadButton = elByClass('mediaManagerMediaReplaceButton', content)[0];
- var target = elByClass('mediaThumbnail', content)[0];
- if (!target) {
- target = elCreate('div');
- content.appendChild(target);
- }
- new MediaReplace(
- this._media.mediaID,
- DomUtil.identify(uploadButton),
- // Pass an anonymous element for non-images which is required internally
- // but not needed in this case.
- DomUtil.identify(target),
- {
- mediaEditor: this
- }
- );
-
- DomChangeListener.trigger();
- }.bind(this), 200);
- },
-
- /**
- * Handles the `[ENTER]` key to submit the form.
- *
- * @param {object} event event object
- */
- _keyPress: function(event) {
- if (EventKey.Enter(event)) {
- event.preventDefault();
-
- this._saveData();
- }
- },
-
- /**
- * Saves the data of the currently edited media.
- */
- _saveData: function() {
- var content = UiDialog.getDialog('mediaEditor_' + this._media.mediaID).content;
-
- var categoryId = elBySel('select[name=categoryID]', content);
- var altText = elBySel('input[name=altText]', content);
- var caption = elBySel('textarea[name=caption]', content);
- var captionEnableHtml = elBySel('input[name=captionEnableHtml]', content);
- var title = elBySel('input[name=title]', content);
-
- var hasError = false;
- var altTextError = (altText ? DomTraverse.childByClass(altText.parentNode.parentNode, 'innerError') : false);
- var captionError = (caption ? DomTraverse.childByClass(caption.parentNode.parentNode, 'innerError') : false);
- var titleError = DomTraverse.childByClass(title.parentNode.parentNode, 'innerError');
-
- // category
- this._oldCategoryId = this._media.categoryID;
- if (this._categoryIds.length) {
- this._media.categoryID = ~~categoryId.value;
-
- // if the selected category id not valid (manipulated DOM), ignore
- if (this._categoryIds.indexOf(this._media.categoryID) === -1) {
- this._media.categoryID = 0;
- }
- }
-
- // language and multilingualism
- if (this._availableLanguageCount > 1) {
- this._media.isMultilingual = ~~elBySel('input[name=isMultilingual]', content).checked;
- this._media.languageID = this._media.isMultilingual ? null : LanguageChooser.getLanguageId('mediaEditor_' + this._media.mediaID + '_languageID');
- }
- else {
- this._media.languageID = LANGUAGE_ID;
- }
-
- // altText, caption and title
- this._media.altText = {};
- this._media.caption = {};
- this._media.title = {};
- if (this._availableLanguageCount > 1 && this._media.isMultilingual) {
- if (elById('altText_' + this._media.mediaID) && !LanguageInput.validate('altText_' + this._media.mediaID, true)) {
- hasError = true;
- if (!altTextError) {
- var error = elCreate('small');
- error.className = 'innerError';
- error.textContent = Language.get('wcf.global.form.error.multilingual');
- altText.parentNode.parentNode.appendChild(error);
- }
- }
- if (elById('caption_' + this._media.mediaID) && !LanguageInput.validate('caption_' + this._media.mediaID, true)) {
- hasError = true;
- if (!captionError) {
- var error = elCreate('small');
- error.className = 'innerError';
- error.textContent = Language.get('wcf.global.form.error.multilingual');
- caption.parentNode.parentNode.appendChild(error);
- }
- }
- if (!LanguageInput.validate('title_' + this._media.mediaID, true)) {
- hasError = true;
- if (!titleError) {
- var error = elCreate('small');
- error.className = 'innerError';
- error.textContent = Language.get('wcf.global.form.error.multilingual');
- title.parentNode.parentNode.appendChild(error);
- }
- }
-
- this._media.altText = (elById('altText_' + this._media.mediaID) ? LanguageInput.getValues('altText_' + this._media.mediaID).toObject() : '');
- this._media.caption = (elById('caption_' + this._media.mediaID) ? LanguageInput.getValues('caption_' + this._media.mediaID).toObject() : '');
- this._media.title = LanguageInput.getValues('title_' + this._media.mediaID).toObject();
- }
- else {
- this._media.altText[this._media.languageID] = (altText ? altText.value : '');
- this._media.caption[this._media.languageID] = (caption ? caption.value : '');
- this._media.title[this._media.languageID] = title.value;
- }
-
- // captionEnableHtml
- if (captionEnableHtml) this._media.captionEnableHtml = ~~captionEnableHtml.checked;
- else this._media.captionEnableHtml = 0;
-
- var aclValues = {
- allowAll: ~~elById('mediaEditor_' + this._media.mediaID + '_aclAllowAll').checked,
- group: [],
- user: []
- };
-
- var aclGroups = elBySelAll('input[name="mediaEditor_' + this._media.mediaID + '_aclValues[group][]"]', content);
- for (var i = 0, length = aclGroups.length; i < length; i++) {
- aclValues.group.push(~~aclGroups[i].value);
- }
-
- var aclUsers = elBySelAll('input[name="mediaEditor_' + this._media.mediaID + '_aclValues[user][]"]', content);
- for (var i = 0, length = aclUsers.length; i < length; i++) {
- aclValues.user.push(~~aclUsers[i].value);
- }
-
- if (!hasError) {
- if (altTextError) elRemove(altTextError);
- if (captionError) elRemove(captionError);
- if (titleError) elRemove(titleError);
-
- Ajax.api(this, {
- actionName: 'update',
- objectIDs: [ this._media.mediaID ],
- parameters: {
- aclValues: aclValues,
- altText: this._media.altText,
- caption: this._media.caption,
- data: {
- captionEnableHtml: this._media.captionEnableHtml,
- categoryID: this._media.categoryID,
- isMultilingual: this._media.isMultilingual,
- languageID: this._media.languageID
- },
- title: this._media.title
- }
- });
- }
- },
-
- /**
- * Updates language-related input fields depending on whether multilingualism
- * is enabled.
- */
- _updateLanguageFields: function(event, element) {
- if (event) element = event.currentTarget;
-
- var languageChooserContainer = elById('mediaEditor_' + this._media.mediaID + '_languageIDContainer').parentNode;
-
- if (element.checked) {
- LanguageInput.enable('title_' + this._media.mediaID);
- if (elById('caption_' + this._media.mediaID)) LanguageInput.enable('caption_' + this._media.mediaID);
- if (elById('altText_' + this._media.mediaID)) LanguageInput.enable('altText_' + this._media.mediaID);
-
- elHide(languageChooserContainer);
- }
- else {
- LanguageInput.disable('title_' + this._media.mediaID);
- if (elById('caption_' + this._media.mediaID)) LanguageInput.disable('caption_' + this._media.mediaID);
- if (elById('altText_' + this._media.mediaID)) LanguageInput.disable('altText_' + this._media.mediaID);
-
- elShow(languageChooserContainer);
- }
- },
-
- /**
- * Edits the media with the given data.
- *
- * @param {object|integer} media data of the edited media or media id for which the data will be loaded
- */
- edit: function(media) {
- if (typeof media !== 'object') {
- media = {
- mediaID: ~~media
- };
- }
-
- if (this._media !== null) {
- throw new Error("Cannot edit media with id '" + media.mediaID + "' while editing media with id '" + this._media.mediaID + "'");
- }
-
- this._media = media;
-
- if (!this._dialogs.has('mediaEditor_' + media.mediaID)) {
- this._dialogs.set('mediaEditor_' + media.mediaID, {
- _dialogSetup: function() {
- return {
- id: 'mediaEditor_' + media.mediaID,
- options: {
- backdropCloseOnClick: false,
- onClose: this._close.bind(this),
- title: Language.get('wcf.media.edit')
- },
- source: {
- after: this._initEditor.bind(this),
- data: {
- actionName: 'getEditorDialog',
- className: 'wcf\\data\\media\\MediaAction',
- objectIDs: [media.mediaID]
- }
- }
- };
- }.bind(this)
- });
- }
-
- UiDialog.open(this._dialogs.get('mediaEditor_' + media.mediaID));
- },
-
- /**
- * Updates the data of the currently edited media file.
- *
- * @param {object} data
- * @since 5.3
- */
- updateData: function(data) {
- if (this._callbackObject._editorSuccess) {
- this._callbackObject._editorSuccess(data, undefined, false);
- }
- }
- };
-
- return MediaEditor;
+define(["require", "exports", "tslib", "../Core", "../Ui/Notification", "../Ui/Dialog", "../Language/Chooser", "../Language/Input", "../Dom/Util", "../Dom/Traverse", "../Dom/Change/Listener", "../Language", "../Ajax", "./Replace"], function (require, exports, tslib_1, Core, UiNotification, UiDialog, LanguageChooser, LanguageInput, DomUtil, DomTraverse, Listener_1, Language, Ajax, Replace_1) {
+ "use strict";
+ Core = tslib_1.__importStar(Core);
+ UiNotification = tslib_1.__importStar(UiNotification);
+ UiDialog = tslib_1.__importStar(UiDialog);
+ LanguageChooser = tslib_1.__importStar(LanguageChooser);
+ LanguageInput = tslib_1.__importStar(LanguageInput);
+ DomUtil = tslib_1.__importStar(DomUtil);
+ DomTraverse = tslib_1.__importStar(DomTraverse);
+ Listener_1 = tslib_1.__importDefault(Listener_1);
+ Language = tslib_1.__importStar(Language);
+ Ajax = tslib_1.__importStar(Ajax);
+ Replace_1 = tslib_1.__importDefault(Replace_1);
+ class MediaEditor {
+ constructor(callbackObject) {
+ this._availableLanguageCount = 1;
+ this._categoryIds = [];
+ this._dialogs = new Map();
+ this._media = null;
+ this._oldCategoryId = 0;
+ this._callbackObject = callbackObject || {};
+ if (this._callbackObject._editorClose && typeof this._callbackObject._editorClose !== "function") {
+ throw new TypeError("Callback object has no function '_editorClose'.");
+ }
+ if (this._callbackObject._editorSuccess && typeof this._callbackObject._editorSuccess !== "function") {
+ throw new TypeError("Callback object has no function '_editorSuccess'.");
+ }
+ }
+ _ajaxSetup() {
+ return {
+ data: {
+ actionName: "update",
+ className: "wcf\\data\\media\\MediaAction",
+ },
+ };
+ }
+ _ajaxSuccess() {
+ UiNotification.show();
+ if (this._callbackObject._editorSuccess) {
+ this._callbackObject._editorSuccess(this._media, this._oldCategoryId);
+ this._oldCategoryId = 0;
+ }
+ UiDialog.close(`mediaEditor_${this._media.mediaID}`);
+ this._media = null;
+ }
+ /**
+ * Is called if an editor is manually closed by the user.
+ */
+ _close() {
+ this._media = null;
+ if (this._callbackObject._editorClose) {
+ this._callbackObject._editorClose();
+ }
+ }
+ /**
+ * Initializes the editor dialog.
+ *
+ * @since 5.3
+ */
+ _initEditor(content, data) {
+ this._availableLanguageCount = ~~data.returnValues.availableLanguageCount;
+ this._categoryIds = data.returnValues.categoryIDs.map((number) => ~~number);
+ if (data.returnValues.mediaData) {
+ this._media = data.returnValues.mediaData;
+ }
+ const media = this._media;
+ const mediaId = media.mediaID;
+ // make sure that the language chooser is initialized first
+ setTimeout(() => {
+ if (this._availableLanguageCount > 1) {
+ LanguageChooser.setLanguageId(`mediaEditor_${mediaId}_languageID`, media.languageID || window.LANGUAGE_ID);
+ }
+ if (this._categoryIds.length) {
+ const categoryID = content.querySelector("select[name=categoryID]");
+ if (media.categoryID) {
+ categoryID.value = media.categoryID.toString();
+ }
+ else {
+ categoryID.value = "0";
+ }
+ }
+ const title = content.querySelector("input[name=title]");
+ const altText = content.querySelector("input[name=altText]");
+ const caption = content.querySelector("textarea[name=caption]");
+ if (this._availableLanguageCount > 1 && media.isMultilingual) {
+ if (document.getElementById(`altText_${mediaId}`)) {
+ LanguageInput.setValues(`altText_${mediaId}`, (media.altText || {}));
+ }
+ if (document.getElementById(`caption_${mediaId}`)) {
+ LanguageInput.setValues(`caption_${mediaId}`, (media.caption || {}));
+ }
+ LanguageInput.setValues(`title_${mediaId}`, (media.title || {}));
+ }
+ else {
+ title.value = media.title ? media.title[media.languageID || window.LANGUAGE_ID] : "";
+ if (altText) {
+ altText.value = media.altText ? media.altText[media.languageID || window.LANGUAGE_ID] : "";
+ }
+ if (caption) {
+ caption.value = media.caption ? media.caption[media.languageID || window.LANGUAGE_ID] : "";
+ }
+ }
+ if (this._availableLanguageCount > 1) {
+ const isMultilingual = content.querySelector("input[name=isMultilingual]");
+ isMultilingual.addEventListener("change", (ev) => this._updateLanguageFields(ev));
+ this._updateLanguageFields(null, isMultilingual);
+ }
+ if (altText) {
+ altText.addEventListener("keypress", (ev) => this._keyPress(ev));
+ }
+ title.addEventListener("keypress", (ev) => this._keyPress(ev));
+ content.querySelector("button[data-type=submit]").addEventListener("click", () => this._saveData());
+ // remove focus from input elements and scroll dialog to top
+ document.activeElement.blur();
+ document.getElementById(`mediaEditor_${mediaId}`).parentNode.scrollTop = 0;
+ // Initialize button to replace media file.
+ const uploadButton = content.querySelector(".mediaManagerMediaReplaceButton");
+ let target = content.querySelector(".mediaThumbnail");
+ if (!target) {
+ target = document.createElement("div");
+ content.appendChild(target);
+ }
+ new Replace_1.default(mediaId, DomUtil.identify(uploadButton),
+ // Pass an anonymous element for non-images which is required internally
+ // but not needed in this case.
+ DomUtil.identify(target), {
+ mediaEditor: this,
+ });
+ Listener_1.default.trigger();
+ }, 200);
+ }
+ /**
+ * Handles the `[ENTER]` key to submit the form.
+ */
+ _keyPress(event) {
+ if (event.key === "Enter") {
+ event.preventDefault();
+ this._saveData();
+ }
+ }
+ /**
+ * Saves the data of the currently edited media.
+ */
+ _saveData() {
+ const content = UiDialog.getDialog(`mediaEditor_${this._media.mediaID}`).content;
+ const categoryId = content.querySelector("select[name=categoryID]");
+ const altText = content.querySelector("input[name=altText]");
+ const caption = content.querySelector("textarea[name=caption]");
+ const captionEnableHtml = content.querySelector("input[name=captionEnableHtml]");
+ const title = content.querySelector("input[name=title]");
+ let hasError = false;
+ const altTextError = altText ? DomTraverse.childByClass(altText.parentNode, "innerError") : false;
+ const captionError = caption ? DomTraverse.childByClass(caption.parentNode, "innerError") : false;
+ const titleError = DomTraverse.childByClass(title.parentNode, "innerError");
+ // category
+ this._oldCategoryId = this._media.categoryID;
+ if (this._categoryIds.length) {
+ this._media.categoryID = ~~categoryId.value;
+ // if the selected category id not valid (manipulated DOM), ignore
+ if (this._categoryIds.indexOf(this._media.categoryID) === -1) {
+ this._media.categoryID = 0;
+ }
+ }
+ // language and multilingualism
+ if (this._availableLanguageCount > 1) {
+ const isMultilingual = content.querySelector("input[name=isMultilingual]");
+ this._media.isMultilingual = ~~isMultilingual.checked;
+ this._media.languageID = this._media.isMultilingual
+ ? null
+ : LanguageChooser.getLanguageId(`mediaEditor_${this._media.mediaID}_languageID`);
+ }
+ else {
+ this._media.languageID = window.LANGUAGE_ID;
+ }
+ // altText, caption and title
+ this._media.altText = {};
+ this._media.caption = {};
+ this._media.title = {};
+ if (this._availableLanguageCount > 1 && this._media.isMultilingual) {
+ if (altText && !LanguageInput.validate(altText.id, true)) {
+ hasError = true;
+ if (!altTextError) {
+ DomUtil.innerError(altText, Language.get("wcf.global.form.error.multilingual"));
+ }
+ }
+ if (caption && !LanguageInput.validate(caption.id, true)) {
+ hasError = true;
+ if (!captionError) {
+ DomUtil.innerError(caption, Language.get("wcf.global.form.error.multilingual"));
+ }
+ }
+ if (!LanguageInput.validate(title.id, true)) {
+ hasError = true;
+ if (!titleError) {
+ DomUtil.innerError(title, Language.get("wcf.global.form.error.multilingual"));
+ }
+ }
+ this._media.altText = altText ? this.mapToI18nValues(LanguageInput.getValues(altText.id)) : "";
+ this._media.caption = caption ? this.mapToI18nValues(LanguageInput.getValues(caption.id)) : "";
+ this._media.title = this.mapToI18nValues(LanguageInput.getValues(title.id));
+ }
+ else {
+ this._media.altText[this._media.languageID] = altText ? altText.value : "";
+ this._media.caption[this._media.languageID] = caption ? caption.value : "";
+ this._media.title[this._media.languageID] = title.value;
+ }
+ // captionEnableHtml
+ if (captionEnableHtml) {
+ this._media.captionEnableHtml = ~~captionEnableHtml.checked;
+ }
+ else {
+ this._media.captionEnableHtml = 0;
+ }
+ const aclValues = {
+ allowAll: ~~document.getElementById(`mediaEditor_${this._media.mediaID}_aclAllowAll`)
+ .checked,
+ group: Array.from(content.querySelectorAll(`input[name="mediaEditor_${this._media.mediaID}_aclValues[group][]"]`)).map((aclGroup) => ~~aclGroup.value),
+ user: Array.from(content.querySelectorAll(`input[name="mediaEditor_${this._media.mediaID}_aclValues[user][]"]`)).map((aclUser) => ~~aclUser.value),
+ };
+ if (!hasError) {
+ if (altTextError) {
+ altTextError.remove();
+ }
+ if (captionError) {
+ captionError.remove();
+ }
+ if (titleError) {
+ titleError.remove();
+ }
+ Ajax.api(this, {
+ actionName: "update",
+ objectIDs: [this._media.mediaID],
+ parameters: {
+ aclValues: aclValues,
+ altText: this._media.altText,
+ caption: this._media.caption,
+ data: {
+ captionEnableHtml: this._media.captionEnableHtml,
+ categoryID: this._media.categoryID,
+ isMultilingual: this._media.isMultilingual,
+ languageID: this._media.languageID,
+ },
+ title: this._media.title,
+ },
+ });
+ }
+ }
+ mapToI18nValues(values) {
+ const obj = {};
+ values.forEach((value, key) => (obj[key] = value));
+ return obj;
+ }
+ /**
+ * Updates language-related input fields depending on whether multilingualis is enabled.
+ */
+ _updateLanguageFields(event, element) {
+ if (event) {
+ element = event.currentTarget;
+ }
+ const mediaId = this._media.mediaID;
+ const languageChooserContainer = document.getElementById(`mediaEditor_${mediaId}_languageIDContainer`)
+ .parentNode;
+ if (element.checked) {
+ LanguageInput.enable(`title_${mediaId}`);
+ if (document.getElementById(`caption_${mediaId}`)) {
+ LanguageInput.enable(`caption_${mediaId}`);
+ }
+ if (document.getElementById(`altText_${mediaId}`)) {
+ LanguageInput.enable(`altText_${mediaId}`);
+ }
+ DomUtil.hide(languageChooserContainer);
+ }
+ else {
+ LanguageInput.disable(`title_${mediaId}`);
+ if (document.getElementById(`caption_${mediaId}`)) {
+ LanguageInput.disable(`caption_${mediaId}`);
+ }
+ if (document.getElementById(`altText_${mediaId}`)) {
+ LanguageInput.disable(`altText_${mediaId}`);
+ }
+ DomUtil.show(languageChooserContainer);
+ }
+ }
+ /**
+ * Edits the media with the given data or id.
+ */
+ edit(editedMedia) {
+ let media;
+ let mediaId = 0;
+ if (typeof editedMedia === "object") {
+ media = editedMedia;
+ mediaId = media.mediaID;
+ }
+ else {
+ media = {
+ mediaID: editedMedia,
+ };
+ mediaId = editedMedia;
+ }
+ if (this._media !== null) {
+ throw new Error(`Cannot edit media with id ${mediaId} while editing media with id '${this._media.mediaID}'.`);
+ }
+ this._media = media;
+ if (!this._dialogs.has(`mediaEditor_${mediaId}`)) {
+ this._dialogs.set(`mediaEditor_${mediaId}`, {
+ _dialogSetup: () => {
+ return {
+ id: `mediaEditor_${mediaId}`,
+ options: {
+ backdropCloseOnClick: false,
+ onClose: () => this._close(),
+ title: Language.get("wcf.media.edit"),
+ },
+ source: {
+ after: (content, responseData) => this._initEditor(content, responseData),
+ data: {
+ actionName: "getEditorDialog",
+ className: "wcf\\data\\media\\MediaAction",
+ objectIDs: [mediaId],
+ },
+ },
+ };
+ },
+ });
+ }
+ UiDialog.open(this._dialogs.get(`mediaEditor_${mediaId}`));
+ }
+ /**
+ * Updates the data of the currently edited media file.
+ */
+ updateData(media) {
+ if (this._callbackObject._editorSuccess) {
- this._callbackObject._editorSuccess(media);
++ this._callbackObject._editorSuccess(media, undefined, false);
+ }
+ }
+ }
+ Core.enableLegacyInheritance(MediaEditor);
+ return MediaEditor;
});
/**
* Provides the media manager dialog.
- *
- * @author Matthias Schmidt
- * @copyright 2001-2019 WoltLab GmbH
- * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module WoltLabSuite/Core/Media/Manager/Base
+ *
+ * @author Matthias Schmidt
+ * @copyright 2001-2020 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module WoltLabSuite/Core/Media/Manager/Base
*/
-define(
- [
- 'Core', 'Dictionary', 'Dom/ChangeListener', 'Dom/Traverse',
- 'Dom/Util', 'EventHandler', 'Language', 'List',
- 'Permission', 'Ui/Dialog', 'Ui/Notification', 'WoltLabSuite/Core/Controller/Clipboard',
- 'WoltLabSuite/Core/Media/Editor', 'WoltLabSuite/Core/Media/Upload', 'WoltLabSuite/Core/Media/Manager/Search', 'StringUtil',
- 'WoltLabSuite/Core/Ui/Pagination',
- 'WoltLabSuite/Core/Media/Clipboard'
- ],
- function(
- Core, Dictionary, DomChangeListener, DomTraverse,
- DomUtil, EventHandler, Language, List,
- Permission, UiDialog, UiNotification, Clipboard,
- MediaEditor, MediaUpload, MediaManagerSearch, StringUtil,
- UiPagination,
- MediaClipboard
- )
-{
- "use strict";
-
- if (!COMPILER_TARGET_DEFAULT) {
- var Fake = function() {};
- Fake.prototype = {
- _addButtonEventListeners: function() {},
- _click: function() {},
- _dialogClose: function() {},
- _dialogInit: function() {},
- _dialogSetup: function() {},
- _dialogShow: function() {},
- _editMedia: function() {},
- _editorClose: function() {},
- _editorSuccess: function() {},
- _removeClipboardCheckboxes: function() {},
- _setMedia: function() {},
- addMedia: function() {},
- clipboardDeleteMedia: function() {},
- getDialog: function() {},
- getMode: function() {},
- getOption: function() {},
- removeMedia: function() {},
- resetMedia: function() {},
- setMedia: function() {},
- setupMediaElement: function() {}
- };
- return Fake;
- }
-
- var _mediaManagerCounter = 0;
-
- /**
- * @constructor
- */
- function MediaManagerBase(options) {
- this._options = Core.extend({
- dialogTitle: Language.get('wcf.media.manager'),
- imagesOnly: false,
- minSearchLength: 3
- }, options);
-
- this._id = 'mediaManager' + _mediaManagerCounter++;
- this._listItems = new Dictionary();
- this._media = new Dictionary();
- this._mediaManagerMediaList = null;
- this._search = null;
- this._upload = null;
- this._forceClipboard = false;
- this._hadInitiallyMarkedItems = false;
- this._pagination = null;
-
- if (Permission.get('admin.content.cms.canManageMedia')) {
- this._mediaEditor = new MediaEditor(this);
- }
-
- DomChangeListener.add('WoltLabSuite/Core/Media/Manager', this._addButtonEventListeners.bind(this));
-
- EventHandler.add('com.woltlab.wcf.media.upload', 'success', this._openEditorAfterUpload.bind(this));
- }
- MediaManagerBase.prototype = {
- /**
- * Adds click event listeners to media buttons.
- */
- _addButtonEventListeners: function() {
- if (!this._mediaManagerMediaList) return;
-
- var listItems = DomTraverse.childrenByTag(this._mediaManagerMediaList, 'LI');
- for (var i = 0, length = listItems.length; i < length; i++) {
- var listItem = listItems[i];
-
- if (Permission.get('admin.content.cms.canManageMedia')) {
- var editIcon = elByClass('jsMediaEditButton', listItem)[0];
- if (editIcon) {
- editIcon.classList.remove('jsMediaEditButton');
- editIcon.addEventListener(WCF_CLICK_EVENT, this._editMedia.bind(this));
- }
- }
- }
- },
-
- /**
- * Is called when a new category is selected.
- */
- _categoryChange: function() {
- this._search.search();
- },
-
- /**
- * Handles clicks on the media manager button.
- *
- * @param {object} event event object
- */
- _click: function(event) {
- event.preventDefault();
-
- UiDialog.open(this);
- },
-
- /**
- * Is called if the media manager dialog is closed.
- */
- _dialogClose: function() {
- // only show media clipboard if editor is open
- if (Permission.get('admin.content.cms.canManageMedia') || this._forceClipboard) {
- Clipboard.hideEditor('com.woltlab.wcf.media');
- }
- },
-
- /**
- * Initializes the dialog when first loaded.
- *
- * @param {string} content dialog content
- * @param {object} data AJAX request's response data
- */
- _dialogInit: function(content, data) {
- // store media data locally
- var media = data.returnValues.media || { };
- for (var mediaId in media) {
- if (objOwns(media, mediaId)) {
- this._media.set(~~mediaId, media[mediaId]);
- }
- }
-
- this._initPagination(~~data.returnValues.pageCount);
-
- this._hadInitiallyMarkedItems = data.returnValues.hasMarkedItems;
- },
-
- /**
- * Returns all data to setup the media manager dialog.
- *
- * @return {object} dialog setup data
- */
- _dialogSetup: function() {
- return {
- id: this._id,
- options: {
- onClose: this._dialogClose.bind(this),
- onShow: this._dialogShow.bind(this),
- title: this._options.dialogTitle
- },
- source: {
- after: this._dialogInit.bind(this),
- data: {
- actionName: 'getManagementDialog',
- className: 'wcf\\data\\media\\MediaAction',
- parameters: {
- mode: this.getMode(),
- imagesOnly: this._options.imagesOnly
- }
- }
- }
- };
- },
-
- /**
- * Is called if the media manager dialog is shown.
- */
- _dialogShow: function() {
- if (!this._mediaManagerMediaList) {
- var dialog = this.getDialog();
-
- this._mediaManagerMediaList = elByClass('mediaManagerMediaList', dialog)[0];
-
- this._mediaCategorySelect = elBySel('.mediaManagerCategoryList > select', dialog);
- if (this._mediaCategorySelect) {
- this._mediaCategorySelect.addEventListener('change', this._categoryChange.bind(this));
- }
-
- // store list items locally
- var listItems = DomTraverse.childrenByTag(this._mediaManagerMediaList, 'LI');
- for (var i = 0, length = listItems.length; i < length; i++) {
- var listItem = listItems[i];
-
- this._listItems.set(~~elData(listItem, 'object-id'), listItem);
- }
-
- if (Permission.get('admin.content.cms.canManageMedia')) {
- var uploadButton = elByClass('mediaManagerMediaUploadButton', UiDialog.getDialog(this).dialog)[0];
- this._upload = new MediaUpload(DomUtil.identify(uploadButton), DomUtil.identify(this._mediaManagerMediaList), {
- mediaManager: this
- });
-
- var deleteAction = new WCF.Action.Delete('wcf\\data\\media\\MediaAction', '.mediaFile');
- deleteAction._didTriggerEffect = function(element) {
- this.removeMedia(elData(element[0], 'object-id'));
- }.bind(this);
- }
-
- if (Permission.get('admin.content.cms.canManageMedia') || this._forceClipboard) {
- MediaClipboard.init(
- 'menuManagerDialog-' + this.getMode(),
- this._hadInitiallyMarkedItems ? true : false,
- this
- );
- }
- else {
- this._removeClipboardCheckboxes();
- }
-
- this._search = new MediaManagerSearch(this);
-
- if (!listItems.length) {
- this._search.hideSearch();
- }
- }
-
- // only show media clipboard if editor is open
- if (Permission.get('admin.content.cms.canManageMedia') || this._forceClipboard) {
- Clipboard.showEditor('com.woltlab.wcf.media');
- }
- },
-
- /**
- * Opens the media editor for a media file.
- *
- * @param {Event} event event object for clicks on edit icons
- */
- _editMedia: function(event) {
- if (!Permission.get('admin.content.cms.canManageMedia')) {
- throw new Error("You are not allowed to edit media files.");
- }
-
- UiDialog.close(this);
-
- this._mediaEditor.edit(this._media.get(~~elData(event.currentTarget, 'object-id')));
- },
-
- /**
- * Re-opens the manager dialog after closing the editor dialog.
- */
- _editorClose: function() {
- UiDialog.open(this);
- },
-
- /**
- * Re-opens the manager dialog and updates the media data after
- * successfully editing a media file.
- *
- * @param {object} media updated media file data
- * @param {integer} oldCategoryId old category id
- * @param {boolean} closedEditorDialog
- */
- _editorSuccess: function(media, oldCategoryId, closedEditorDialog = true) {
- // if the category changed of media changed and category
- // is selected, check if media list needs to be refreshed
- if (this._mediaCategorySelect) {
- var selectedCategoryId = ~~this._mediaCategorySelect.value;
-
- if (selectedCategoryId) {
- var newCategoryId = ~~media.categoryID;
-
- if (oldCategoryId != newCategoryId && (oldCategoryId == selectedCategoryId || newCategoryId == selectedCategoryId)) {
- this._search.search();
- }
- }
- }
-
- if (closedEditorDialog) {
- UiDialog.open(this);
- }
-
- this._media.set(~~media.mediaID, media);
-
- var listItem = this._listItems.get(~~media.mediaID);
- var p = elByClass('mediaTitle', listItem)[0];
- if (media.isMultilingual) {
- if (media.title && media.title[LANGUAGE_ID]) {
- p.textContent = media.title[LANGUAGE_ID];
- }
- else {
- p.textContent = media.filename;
- }
- }
- else {
- if (media.title && media.title[media.languageID]) {
- p.textContent = media.title[media.languageID];
- }
- else {
- p.textContent = media.filename;
- }
- }
-
- var thumbnail = elByClass('mediaThumbnail', listItem)[0];
- thumbnail.innerHTML = media.elementTag;
- // Bust browser cache by adding additional parameter.
- var imgs = elByTag('img', thumbnail);
- if (imgs.length) {
- imgs[0].src += '&refresh=' + Date.now();
- }
- },
-
- /**
- * Initializes the dialog pagination.
- *
- * @param {integer} pageCount
- * @param {integer} pageNo
- */
- _initPagination: function(pageCount, pageNo) {
- if (pageNo === undefined) pageNo = 1;
-
- if (pageCount > 1) {
- var newPagination = elCreate('div');
- newPagination.className = 'paginationBottom jsPagination';
- DomUtil.replaceElement(elBySel('.jsPagination', UiDialog.getDialog(this).content), newPagination);
-
- this._pagination = new UiPagination(newPagination, {
- activePage: pageNo,
- callbackSwitch: this._search.search.bind(this._search),
- maxPage: pageCount
- });
- }
- else if (this._pagination) {
- elHide(this._pagination.getElement());
- }
- },
-
- /**
- * Removes all media clipboard checkboxes.
- */
- _removeClipboardCheckboxes: function() {
- var checkboxes = elByClass('mediaCheckbox', this._mediaManagerMediaList);
- while (checkboxes.length) {
- elRemove(checkboxes[0]);
- }
- },
-
- /**
- * Opens the media editor after uploading a single file.
- *
- * @param {object} data upload event data
- * @since 5.2
- */
- _openEditorAfterUpload: function(data) {
- if (data.upload === this._upload && !data.isMultiFileUpload && !this._upload.hasPendingUploads()) {
- var keys = Object.keys(data.media);
-
- if (keys.length) {
- UiDialog.close(this);
-
- this._mediaEditor.edit(this._media.get(~~data.media[keys[0]].mediaID));
- }
- }
- },
-
- /**
- * Sets the displayed media (after a search).
- *
- * @param {Dictionary} media media to be set as active
- */
- _setMedia: function(media) {
- if (Core.isPlainObject(media)) {
- this._media = Dictionary.fromObject(media);
- }
- else {
- this._media = media;
- }
-
- var info = DomTraverse.nextByClass(this._mediaManagerMediaList, 'info');
-
- if (this._media.size) {
- if (info) {
- elHide(info);
- }
- }
- else {
- if (info === null) {
- info = elCreate('p');
- info.className = 'info';
- info.textContent = Language.get('wcf.media.search.noResults');
- }
-
- elShow(info);
- DomUtil.insertAfter(info, this._mediaManagerMediaList);
- }
-
- var mediaListItems = DomTraverse.childrenByTag(this._mediaManagerMediaList, 'LI');
- for (var i = 0, length = mediaListItems.length; i < length; i++) {
- var listItem = mediaListItems[i];
-
- if (!this._media.has(elData(listItem, 'object-id'))) {
- elHide(listItem);
- }
- else {
- elShow(listItem);
- }
- }
-
- DomChangeListener.trigger();
-
- if (Permission.get('admin.content.cms.canManageMedia') || this._forceClipboard) {
- Clipboard.reload();
- }
- else {
- this._removeClipboardCheckboxes();
- }
- },
-
- /**
- * Adds a media file to the manager.
- *
- * @param {object} media data of the media file
- * @param {Element} listItem list item representing the file
- */
- addMedia: function(media, listItem) {
- if (!media.languageID) media.isMultilingual = 1;
-
- this._media.set(~~media.mediaID, media);
- this._listItems.set(~~media.mediaID, listItem);
-
- if (this._listItems.size === 1) {
- this._search.showSearch();
- }
- },
-
- /**
- * Is called after the media files with the given ids have been deleted via clipboard.
- *
- * @param {int[]} mediaIds ids of deleted media files
- */
- clipboardDeleteMedia: function(mediaIds) {
- for (var i = 0, length = mediaIds.length; i < length; i++) {
- this.removeMedia(~~mediaIds[i], true);
- }
-
- UiNotification.show();
- },
-
- /**
- * Returns the id of the currently selected category or `0` if no category is selected.
- *
- * @return {integer}
- */
- getCategoryId: function() {
- if (this._mediaCategorySelect) {
- return this._mediaCategorySelect.value;
- }
-
- return 0;
- },
-
- /**
- * Returns the media manager dialog element.
- *
- * @return {Element} media manager dialog
- */
- getDialog: function() {
- return UiDialog.getDialog(this).dialog;
- },
-
- /**
- * Returns the mode of the media manager.
- *
- * @return {string}
- */
- getMode: function() {
- return '';
- },
-
- /**
- * Returns the media manager option with the given name.
- *
- * @param {string} name option name
- * @return {mixed} option value or null
- */
- getOption: function(name) {
- if (this._options[name]) {
- return this._options[name];
- }
-
- return null;
- },
-
- /**
- * Removes a media file.
- *
- * @param {int} mediaId id of the removed media file
- */
- removeMedia: function(mediaId) {
- if (this._listItems.has(mediaId)) {
- // remove list item
- try {
- elRemove(this._listItems.get(mediaId));
- }
- catch (e) {
- // ignore errors if item has already been removed like by WCF.Action.Delete
- }
-
- this._listItems.delete(mediaId);
- this._media.delete(mediaId);
- }
- },
-
- /**
- * Changes the displayed media to the previously displayed media.
- */
- resetMedia: function() {
- // calling WoltLabSuite/Core/Media/Manager/Search.search() reloads the first page of the dialog
- this._search.search();
- },
-
- /**
- * Sets the media files currently displayed.
- *
- * @param {object} media media data
- * @param {string} template
- * @param {object} additionalData
- */
- setMedia: function(media, template, additionalData) {
- var hasMedia = false;
- for (var mediaId in media) {
- if (objOwns(media, mediaId)) {
- hasMedia = true;
- }
- }
-
- var newListItems = [];
- if (hasMedia) {
- var ul = elCreate('ul');
- ul.innerHTML = template;
-
- var listItems = DomTraverse.childrenByTag(ul, 'LI');
- for (var i = 0, length = listItems.length; i < length; i++) {
- var listItem = listItems[i];
- if (!this._listItems.has(~~elData(listItem, 'object-id'))) {
- this._listItems.set(elData(listItem, 'object-id'), listItem);
-
- this._mediaManagerMediaList.appendChild(listItem);
- }
- }
- }
-
- this._initPagination(additionalData.pageCount, additionalData.pageNo);
-
- this._setMedia(media);
- },
-
- /**
- * Sets up a new media element.
- *
- * @param {object} media data of the media file
- * @param {HTMLElement} mediaElement element representing the media file
- */
- setupMediaElement: function(media, mediaElement) {
- var mediaInformation = DomTraverse.childByClass(mediaElement, 'mediaInformation');
-
- var buttonGroupNavigation = elCreate('nav');
- buttonGroupNavigation.className = 'jsMobileNavigation buttonGroupNavigation';
- mediaInformation.parentNode.appendChild(buttonGroupNavigation);
-
- var buttons = elCreate('ul');
- buttons.className = 'buttonList iconList';
- buttonGroupNavigation.appendChild(buttons);
-
- var listItem = elCreate('li');
- listItem.className = 'mediaCheckbox';
- buttons.appendChild(listItem);
-
- var a = elCreate('a');
- listItem.appendChild(a);
-
- var label = elCreate('label');
- a.appendChild(label);
-
- var checkbox = elCreate('input');
- checkbox.className = 'jsClipboardItem';
- elAttr(checkbox, 'type', 'checkbox');
- elData(checkbox, 'object-id', media.mediaID);
- label.appendChild(checkbox);
-
- if (Permission.get('admin.content.cms.canManageMedia')) {
- listItem = elCreate('li');
- listItem.className = 'jsMediaEditButton';
- elData(listItem, 'object-id', media.mediaID);
- buttons.appendChild(listItem);
-
- listItem.innerHTML = '<a><span class="icon icon16 fa-pencil jsTooltip" title="' + Language.get('wcf.global.button.edit') + '"></span> <span class="invisible">' + Language.get('wcf.global.button.edit') + '</span></a>';
-
- listItem = elCreate('li');
- listItem.className = 'jsDeleteButton';
- elData(listItem, 'object-id', media.mediaID);
-
- // use temporary title to not unescape html in filename
- var uuid = Core.getUuid();
- elData(listItem, 'confirm-message-html', StringUtil.unescapeHTML(Language.get('wcf.media.delete.confirmMessage', {
- title: uuid
- })).replace(uuid, StringUtil.escapeHTML(media.filename)));
- buttons.appendChild(listItem);
-
- listItem.innerHTML = '<a><span class="icon icon16 fa-times jsTooltip" title="' + Language.get('wcf.global.button.delete') + '"></span> <span class="invisible">' + Language.get('wcf.global.button.delete') + '</span></a>';
- }
- }
- };
-
- return MediaManagerBase;
+define(["require", "exports", "tslib", "../../Core", "../../Language", "../../Permission", "../../Dom/Change/Listener", "../../Event/Handler", "../../Dom/Traverse", "../../Dom/Util", "../../Ui/Dialog", "../../Controller/Clipboard", "../../Ui/Pagination", "../../Ui/Notification", "../../StringUtil", "./Search", "../Upload", "../Editor", "../Clipboard"], function (require, exports, tslib_1, Core, Language, Permission, DomChangeListener, EventHandler, DomTraverse, DomUtil, UiDialog, Clipboard, Pagination_1, UiNotification, StringUtil, Search_1, Upload_1, Editor_1, MediaClipboard) {
+ "use strict";
+ Core = tslib_1.__importStar(Core);
+ Language = tslib_1.__importStar(Language);
+ Permission = tslib_1.__importStar(Permission);
+ DomChangeListener = tslib_1.__importStar(DomChangeListener);
+ EventHandler = tslib_1.__importStar(EventHandler);
+ DomTraverse = tslib_1.__importStar(DomTraverse);
+ DomUtil = tslib_1.__importStar(DomUtil);
+ UiDialog = tslib_1.__importStar(UiDialog);
+ Clipboard = tslib_1.__importStar(Clipboard);
+ Pagination_1 = tslib_1.__importDefault(Pagination_1);
+ UiNotification = tslib_1.__importStar(UiNotification);
+ StringUtil = tslib_1.__importStar(StringUtil);
+ Search_1 = tslib_1.__importDefault(Search_1);
+ Upload_1 = tslib_1.__importDefault(Upload_1);
+ Editor_1 = tslib_1.__importDefault(Editor_1);
+ MediaClipboard = tslib_1.__importStar(MediaClipboard);
+ let mediaManagerCounter = 0;
+ class MediaManager {
+ constructor(options) {
+ this._forceClipboard = false;
+ this._hadInitiallyMarkedItems = false;
+ this._listItems = new Map();
+ this._media = new Map();
+ this._mediaEditor = null;
+ this._mediaManagerMediaList = null;
+ this._pagination = null;
+ this._search = null;
+ this._upload = null;
+ this._options = Core.extend({
+ dialogTitle: Language.get("wcf.media.manager"),
+ imagesOnly: false,
+ minSearchLength: 3,
+ }, options);
+ this._id = `mediaManager${mediaManagerCounter++}`;
+ if (Permission.get("admin.content.cms.canManageMedia")) {
+ this._mediaEditor = new Editor_1.default(this);
+ }
+ DomChangeListener.add("WoltLabSuite/Core/Media/Manager", () => this._addButtonEventListeners());
+ EventHandler.add("com.woltlab.wcf.media.upload", "success", (data) => this._openEditorAfterUpload(data));
+ }
+ /**
+ * Adds click event listeners to media buttons.
+ */
+ _addButtonEventListeners() {
+ if (!this._mediaManagerMediaList || !Permission.get("admin.content.cms.canManageMedia"))
+ return;
+ DomTraverse.childrenByTag(this._mediaManagerMediaList, "LI").forEach((listItem) => {
+ const editIcon = listItem.querySelector(".jsMediaEditButton");
+ if (editIcon) {
+ editIcon.classList.remove("jsMediaEditButton");
+ editIcon.addEventListener("click", (ev) => this._editMedia(ev));
+ }
+ });
+ }
+ /**
+ * Is called when a new category is selected.
+ */
+ _categoryChange() {
+ this._search.search();
+ }
+ /**
+ * Handles clicks on the media manager button.
+ */
+ _click(event) {
+ event.preventDefault();
+ UiDialog.open(this);
+ }
+ /**
+ * Is called if the media manager dialog is closed.
+ */
+ _dialogClose() {
+ // only show media clipboard if editor is open
+ if (Permission.get("admin.content.cms.canManageMedia") || this._forceClipboard) {
+ Clipboard.hideEditor("com.woltlab.wcf.media");
+ }
+ }
+ /**
+ * Initializes the dialog when first loaded.
+ */
+ _dialogInit(content, data) {
+ // store media data locally
+ Object.entries(data.returnValues.media || {}).forEach(([mediaId, media]) => {
+ this._media.set(~~mediaId, media);
+ });
+ this._initPagination(~~data.returnValues.pageCount);
+ this._hadInitiallyMarkedItems = data.returnValues.hasMarkedItems > 0;
+ }
+ /**
+ * Returns all data to setup the media manager dialog.
+ */
+ _dialogSetup() {
+ return {
+ id: this._id,
+ options: {
+ onClose: () => this._dialogClose(),
+ onShow: () => this._dialogShow(),
+ title: this._options.dialogTitle,
+ },
+ source: {
+ after: (content, data) => this._dialogInit(content, data),
+ data: {
+ actionName: "getManagementDialog",
+ className: "wcf\\data\\media\\MediaAction",
+ parameters: {
+ mode: this.getMode(),
+ imagesOnly: this._options.imagesOnly,
+ },
+ },
+ },
+ };
+ }
+ /**
+ * Is called if the media manager dialog is shown.
+ */
+ _dialogShow() {
+ if (!this._mediaManagerMediaList) {
+ const dialog = this.getDialog();
+ this._mediaManagerMediaList = dialog.querySelector(".mediaManagerMediaList");
+ this._mediaCategorySelect = dialog.querySelector(".mediaManagerCategoryList > select");
+ if (this._mediaCategorySelect) {
+ this._mediaCategorySelect.addEventListener("change", () => this._categoryChange());
+ }
+ // store list items locally
+ const listItems = DomTraverse.childrenByTag(this._mediaManagerMediaList, "LI");
+ listItems.forEach((listItem) => {
+ this._listItems.set(~~listItem.dataset.objectId, listItem);
+ });
+ if (Permission.get("admin.content.cms.canManageMedia")) {
+ const uploadButton = UiDialog.getDialog(this).dialog.querySelector(".mediaManagerMediaUploadButton");
+ this._upload = new Upload_1.default(DomUtil.identify(uploadButton), DomUtil.identify(this._mediaManagerMediaList), {
+ mediaManager: this,
+ });
+ EventHandler.add("WoltLabSuite/Core/Ui/Object/Action", "delete", (data) => this.removeMedia(~~data.objectElement.dataset.objectId));
+ }
+ if (Permission.get("admin.content.cms.canManageMedia") || this._forceClipboard) {
+ MediaClipboard.init("menuManagerDialog-" + this.getMode(), this._hadInitiallyMarkedItems ? true : false, this);
+ }
+ else {
+ this._removeClipboardCheckboxes();
+ }
+ this._search = new Search_1.default(this);
+ if (!listItems.length) {
+ this._search.hideSearch();
+ }
+ }
+ // only show media clipboard if editor is open
+ if (Permission.get("admin.content.cms.canManageMedia") || this._forceClipboard) {
+ Clipboard.showEditor();
+ }
+ }
+ /**
+ * Opens the media editor for a media file.
+ */
+ _editMedia(event) {
+ if (!Permission.get("admin.content.cms.canManageMedia")) {
+ throw new Error("You are not allowed to edit media files.");
+ }
+ UiDialog.close(this);
+ const target = event.currentTarget;
+ this._mediaEditor.edit(this._media.get(~~target.dataset.objectId));
+ }
+ /**
+ * Re-opens the manager dialog after closing the editor dialog.
+ */
+ _editorClose() {
+ UiDialog.open(this);
+ }
+ /**
+ * Re-opens the manager dialog and updates the media data after successfully editing a media file.
+ */
- _editorSuccess(media, oldCategoryId) {
++ _editorSuccess(media, oldCategoryId, closedEditorDialog = true) {
+ // if the category changed of media changed and category
+ // is selected, check if media list needs to be refreshed
+ if (this._mediaCategorySelect) {
+ const selectedCategoryId = ~~this._mediaCategorySelect.value;
+ if (selectedCategoryId) {
+ const newCategoryId = ~~media.categoryID;
+ if (oldCategoryId != newCategoryId &&
+ (oldCategoryId == selectedCategoryId || newCategoryId == selectedCategoryId)) {
+ this._search.search();
+ }
+ }
+ }
- UiDialog.open(this);
++ if (closedEditorDialog) {
++ UiDialog.open(this);
++ }
+ this._media.set(~~media.mediaID, media);
+ const listItem = this._listItems.get(~~media.mediaID);
+ const p = listItem.querySelector(".mediaTitle");
+ if (media.isMultilingual) {
+ if (media.title && media.title[window.LANGUAGE_ID]) {
+ p.textContent = media.title[window.LANGUAGE_ID];
+ }
+ else {
+ p.textContent = media.filename;
+ }
+ }
+ else {
+ if (media.title && media.title[media.languageID]) {
+ p.textContent = media.title[media.languageID];
+ }
+ else {
+ p.textContent = media.filename;
+ }
+ }
+ const thumbnail = listItem.querySelector(".mediaThumbnail");
+ thumbnail.innerHTML = media.elementTag;
+ // Bust browser cache by adding additional parameter.
+ const img = thumbnail.querySelector("img");
+ if (img) {
+ img.src += `&refresh=${Date.now()}`;
+ }
+ }
+ /**
+ * Initializes the dialog pagination.
+ */
+ _initPagination(pageCount, pageNo) {
+ if (pageNo === undefined)
+ pageNo = 1;
+ if (pageCount > 1) {
+ const newPagination = document.createElement("div");
+ newPagination.className = "paginationBottom jsPagination";
+ DomUtil.replaceElement(UiDialog.getDialog(this).content.querySelector(".jsPagination"), newPagination);
+ this._pagination = new Pagination_1.default(newPagination, {
+ activePage: pageNo,
+ callbackSwitch: (pageNo) => this._search.search(pageNo),
+ maxPage: pageCount,
+ });
+ }
+ else if (this._pagination) {
+ DomUtil.hide(this._pagination.getElement());
+ }
+ }
+ /**
+ * Removes all media clipboard checkboxes.
+ */
+ _removeClipboardCheckboxes() {
+ this._mediaManagerMediaList.querySelectorAll(".mediaCheckbox").forEach((el) => el.remove());
+ }
+ /**
+ * Opens the media editor after uploading a single file.
+ *
+ * @since 5.2
+ */
+ _openEditorAfterUpload(data) {
+ if (data.upload === this._upload && !data.isMultiFileUpload && !this._upload.hasPendingUploads()) {
+ const keys = Object.keys(data.media);
+ if (keys.length) {
+ UiDialog.close(this);
+ this._mediaEditor.edit(this._media.get(~~data.media[keys[0]].mediaID));
+ }
+ }
+ }
+ /**
+ * Sets the displayed media (after a search).
+ */
+ _setMedia(media) {
+ this._media = new Map(Object.entries(media).map(([mediaId, media]) => [~~mediaId, media]));
+ let info = DomTraverse.nextByClass(this._mediaManagerMediaList, "info");
+ if (this._media.size) {
+ if (info) {
+ DomUtil.hide(info);
+ }
+ }
+ else {
+ if (info === null) {
+ info = document.createElement("p");
+ info.className = "info";
+ info.textContent = Language.get("wcf.media.search.noResults");
+ }
+ DomUtil.show(info);
+ DomUtil.insertAfter(info, this._mediaManagerMediaList);
+ }
+ DomTraverse.childrenByTag(this._mediaManagerMediaList, "LI").forEach((listItem) => {
+ if (!this._media.has(~~listItem.dataset.objectId)) {
+ DomUtil.hide(listItem);
+ }
+ else {
+ DomUtil.show(listItem);
+ }
+ });
+ DomChangeListener.trigger();
+ if (Permission.get("admin.content.cms.canManageMedia") || this._forceClipboard) {
+ Clipboard.reload();
+ }
+ else {
+ this._removeClipboardCheckboxes();
+ }
+ }
+ /**
+ * Adds a media file to the manager.
+ */
+ addMedia(media, listItem) {
+ if (!media.languageID)
+ media.isMultilingual = 1;
+ this._media.set(~~media.mediaID, media);
+ this._listItems.set(~~media.mediaID, listItem);
+ if (this._listItems.size === 1) {
+ this._search.showSearch();
+ }
+ }
+ /**
+ * Is called after the media files with the given ids have been deleted via clipboard.
+ */
+ clipboardDeleteMedia(mediaIds) {
+ mediaIds.forEach((mediaId) => {
+ this.removeMedia(~~mediaId);
+ });
+ UiNotification.show();
+ }
+ /**
+ * Returns the id of the currently selected category or `0` if no category is selected.
+ */
+ getCategoryId() {
+ if (this._mediaCategorySelect) {
+ return ~~this._mediaCategorySelect.value;
+ }
+ return 0;
+ }
+ /**
+ * Returns the media manager dialog element.
+ */
+ getDialog() {
+ return UiDialog.getDialog(this).dialog;
+ }
+ /**
+ * Returns the mode of the media manager.
+ */
+ getMode() {
+ return "";
+ }
+ /**
+ * Returns the media manager option with the given name.
+ */
+ getOption(name) {
+ if (this._options[name]) {
+ return this._options[name];
+ }
+ return null;
+ }
+ /**
+ * Removes a media file.
+ */
+ removeMedia(mediaId) {
+ if (this._listItems.has(mediaId)) {
+ // remove list item
+ try {
+ this._listItems.get(mediaId).remove();
+ }
+ catch (e) {
+ // ignore errors if item has already been removed by other code
+ }
+ this._listItems.delete(mediaId);
+ this._media.delete(mediaId);
+ }
+ }
+ /**
+ * Changes the displayed media to the previously displayed media.
+ */
+ resetMedia() {
+ // calling WoltLabSuite/Core/Media/Manager/Search.search() reloads the first page of the dialog
+ this._search.search();
+ }
+ /**
+ * Sets the media files currently displayed.
+ */
+ setMedia(media, template, additionalData) {
+ const hasMedia = Object.entries(media).length > 0;
+ if (hasMedia) {
+ const ul = document.createElement("ul");
+ ul.innerHTML = template;
+ DomTraverse.childrenByTag(ul, "LI").forEach((listItem) => {
+ if (!this._listItems.has(~~listItem.dataset.objectId)) {
+ this._listItems.set(~~listItem.dataset.objectId, listItem);
+ this._mediaManagerMediaList.appendChild(listItem);
+ }
+ });
+ }
+ this._initPagination(additionalData.pageCount, additionalData.pageNo);
+ this._setMedia(media);
+ }
+ /**
+ * Sets up a new media element.
+ */
+ setupMediaElement(media, mediaElement) {
+ const mediaInformation = DomTraverse.childByClass(mediaElement, "mediaInformation");
+ const buttonGroupNavigation = document.createElement("nav");
+ buttonGroupNavigation.className = "jsMobileNavigation buttonGroupNavigation";
+ mediaInformation.parentNode.appendChild(buttonGroupNavigation);
+ const buttons = document.createElement("ul");
+ buttons.className = "buttonList iconList";
+ buttonGroupNavigation.appendChild(buttons);
+ const listItem = document.createElement("li");
+ listItem.className = "mediaCheckbox";
+ buttons.appendChild(listItem);
+ const a = document.createElement("a");
+ listItem.appendChild(a);
+ const label = document.createElement("label");
+ a.appendChild(label);
+ const checkbox = document.createElement("input");
+ checkbox.className = "jsClipboardItem";
+ checkbox.type = "checkbox";
+ checkbox.dataset.objectId = media.mediaID.toString();
+ label.appendChild(checkbox);
+ if (Permission.get("admin.content.cms.canManageMedia")) {
+ const editButton = document.createElement("li");
+ editButton.className = "jsMediaEditButton";
+ editButton.dataset.objectId = media.mediaID.toString();
+ buttons.appendChild(editButton);
+ editButton.innerHTML = `
+ <a>
+ <span class="icon icon16 fa-pencil jsTooltip" title="${Language.get("wcf.global.button.edit")}"></span>
+ <span class="invisible">${Language.get("wcf.global.button.edit")}</span>
+ </a>`;
+ const deleteButton = document.createElement("li");
+ deleteButton.classList.add("jsObjectAction");
+ deleteButton.dataset.objectAction = "delete";
+ // use temporary title to not unescape html in filename
+ const uuid = Core.getUuid();
+ deleteButton.dataset.confirmMessage = StringUtil.unescapeHTML(Language.get("wcf.media.delete.confirmMessage", {
+ title: uuid,
+ })).replace(uuid, StringUtil.escapeHTML(media.filename));
+ buttons.appendChild(deleteButton);
+ deleteButton.innerHTML = `
+ <a>
+ <span class="icon icon16 fa-times jsTooltip" title="${Language.get("wcf.global.button.delete")}"></span>
+ <span class="invisible">${Language.get("wcf.global.button.delete")}</span>
+ </a>`;
+ }
+ }
+ }
+ Core.enableLegacyInheritance(MediaManager);
+ return MediaManager;
});
/**
* Executes media file-related actions.
- *
- * @author Matthias Schmidt
- * @copyright 2001-2019 WoltLab GmbH
- * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package WoltLabSuite\Core\Data\Media
- * @since 3.0
- *
- * @method Media create()
- * @method MediaEditor[] getObjects()
- * @method MediaEditor getSingleObject()
+ *
+ * @author Matthias Schmidt
+ * @copyright 2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\Data\Media
+ * @since 3.0
+ *
+ * @method Media create()
+ * @method MediaEditor[] getObjects()
+ * @method MediaEditor getSingleObject()
*/
-class MediaAction extends AbstractDatabaseObjectAction implements ISearchAction, IUploadAction {
- /**
- * number of media files per media manager dialog page
- */
- const ITEMS_PER_MANAGER_DIALOG_PAGE = 50;
-
- /**
- * @inheritDoc
- */
- public function validateUpload() {
- WCF::getSession()->checkPermissions(['admin.content.cms.canManageMedia']);
-
- $this->readBoolean('imagesOnly', true);
- $this->readInteger('categoryID', true);
-
- /** @noinspection PhpUndefinedMethodInspection */
- $this->parameters['__files']->validateFiles(new MediaUploadFileValidationStrategy($this->parameters['imagesOnly']));
-
- if ($this->parameters['categoryID']) {
- $category = CategoryHandler::getInstance()->getCategory($this->parameters['categoryID']);
- if ($category === null || $category->getObjectType()->objectType !== 'com.woltlab.wcf.media.category') {
- throw new UserInputException('categoryID');
- }
- }
- }
-
- /**
- * @inheritDoc
- */
- public function upload() {
- $additionalData = ['username' => WCF::getUser()->username];
- if ($this->parameters['categoryID']) {
- $additionalData['categoryID'] = $this->parameters['categoryID'];
- }
-
- // save files
- $saveStrategy = new DefaultUploadFileSaveStrategy(self::class, [
- 'generateThumbnails' => true,
- 'rotateImages' => true
- ], $additionalData);
-
- /** @noinspection PhpUndefinedMethodInspection */
- $this->parameters['__files']->saveFiles($saveStrategy);
-
- /** @var Media[] $mediaFiles */
- $mediaFiles = $saveStrategy->getObjects();
-
- $result = [
- 'errors' => [],
- 'media' => []
- ];
-
- if (!empty($mediaFiles)) {
- $mediaIDs = $mediaToFileID = [];
- foreach ($mediaFiles as $internalFileID => $media) {
- $mediaIDs[] = $media->mediaID;
- $mediaToFileID[$media->mediaID] = $internalFileID;
- }
-
- // fetch media objects from database
- $mediaList = new ViewableMediaList();
- $mediaList->setObjectIDs($mediaIDs);
- $mediaList->readObjects();
-
- foreach ($mediaList as $media) {
- $result['media'][$mediaToFileID[$media->mediaID]] = $this->getMediaData($media);
- }
- }
-
- /** @var UploadFile[] $files */
- /** @noinspection PhpUndefinedMethodInspection */
- $files = $this->parameters['__files']->getFiles();
- foreach ($files as $file) {
- if ($file->getValidationErrorType()) {
- $result['errors'][$file->getInternalFileID()] = [
- 'filename' => $file->getFilename(),
- 'filesize' => $file->getFilesize(),
- 'errorType' => $file->getValidationErrorType()
- ];
- }
- }
-
- return $result;
- }
-
- /**
- * Generates thumbnails.
- */
- public function generateThumbnails() {
- if (empty($this->objects)) {
- $this->readObjects();
- }
-
- $saveStrategy = new DefaultUploadFileSaveStrategy(self::class);
-
- foreach ($this->getObjects() as $mediaEditor) {
- if ($mediaEditor->getDecoratedObject()->isImage) {
- $saveStrategy->generateThumbnails($mediaEditor->getDecoratedObject());
- }
- }
- }
-
- /**
- * Returns the data of the media file to be returned by AJAX requests.
- *
- * @param Media|ViewableMedia $media media files whose data will be returned
- * @return string[]
- */
- protected function getMediaData($media) {
- return [
- 'altText' => $media instanceof ViewableMedia ? $media->altText : [],
- 'caption' => $media instanceof ViewableMedia ? $media->caption : [],
- 'captionEnableHtml' => $media->captionEnableHtml,
- 'categoryID' => $media->categoryID,
- 'elementTag' => $media instanceof ViewableMedia ? $media->getElementTag($this->parameters['elementTagSize'] ?? 144) : '',
- 'elementTag48' => $media instanceof ViewableMedia ? $media->getElementTag(48) : '',
- 'fileHash' => $media->fileHash,
- 'filename' => $media->filename,
- 'filesize' => $media->filesize,
- 'formattedFilesize' => FileUtil::formatFilesize($media->filesize),
- 'fileType' => $media->fileType,
- 'height' => $media->height,
- 'languageID' => $media->languageID,
- 'imageDimensions' => $media->isImage ? WCF::getLanguage()->getDynamicVariable('wcf.media.imageDimensions.value', [
- 'media' => $media,
- ]) : '',
- 'isImage' => $media->isImage,
- 'isMultilingual' => $media->isMultilingual,
- 'largeThumbnailHeight' => $media->largeThumbnailHeight,
- 'largeThumbnailLink' => $media->largeThumbnailType ? $media->getThumbnailLink('large') : '',
- 'largeThumbnailType' => $media->largeThumbnailType,
- 'largeThumbnailWidth' => $media->largeThumbnailWidth,
- 'link' => $media->getLink(),
- 'mediaID' => $media->mediaID,
- 'mediumThumbnailHeight' => $media->mediumThumbnailHeight,
- 'mediumThumbnailLink' => $media->mediumThumbnailType ? $media->getThumbnailLink('medium') : '',
- 'mediumThumbnailType' => $media->mediumThumbnailType,
- 'mediumThumbnailWidth' => $media->mediumThumbnailWidth,
- 'smallThumbnailHeight' => $media->smallThumbnailHeight,
- 'smallThumbnailLink' => $media->smallThumbnailType ? $media->getThumbnailLink('small') : '',
- 'smallThumbnailTag' => $media->smallThumbnailType ? $media->getThumbnailTag('small') : '',
- 'smallThumbnailType' => $media->smallThumbnailType,
- 'smallThumbnailWidth' => $media->smallThumbnailWidth,
- 'tinyThumbnailHeight' => $media->tinyThumbnailHeight,
- 'tinyThumbnailLink' => $media->tinyThumbnailType ? $media->getThumbnailLink('tiny') : '',
- 'tinyThumbnailType' => $media->tinyThumbnailType,
- 'tinyThumbnailWidth' => $media->tinyThumbnailWidth,
- 'title' => $media instanceof ViewableMedia ? $media->title : [],
- 'uploadTime' => $media->uploadTime,
- 'userID' => $media->userID,
- 'userLink' => $media->userID ? LinkHandler::getInstance()->getLink('User', [
- 'id' => $media->userID,
- 'title' => $media->username
- ]) : '',
- 'userLinkElement' => $media instanceof ViewableMedia ? WCF::getTPL()->fetchString(
- WCF::getTPL()->getCompiler()->compileString('userLink', '{user object=$userProfile}')['template'],
- ['userProfile' => $media->getUserProfile()]
- ) : '',
- 'username' => $media->username,
- 'width' => $media->width
- ];
- }
-
- /**
- * Validates the 'getManagementDialog' action.
- */
- public function validateGetManagementDialog() {
- if (!WCF::getSession()->getPermission('admin.content.cms.canManageMedia') && !WCF::getSession()->getPermission('admin.content.cms.canUseMedia')) {
- throw new PermissionDeniedException();
- }
-
- $this->readBoolean('imagesOnly', true);
-
- $this->readString('mode');
- if ($this->parameters['mode'] != 'editor' && $this->parameters['mode'] != 'select') {
- throw new UserInputException('mode');
- }
- }
-
- /**
- * Returns the dialog to manage media.
- *
- * @return string[]
- */
- public function getManagementDialog() {
- $mediaList = new ViewableMediaList();
- if (WCF::getSession()->getPermission('admin.content.cms.canOnlyAccessOwnMedia')) {
- $mediaList->getConditionBuilder()->add('media.userID = ?', [WCF::getUser()->userID]);
- }
- if ($this->parameters['imagesOnly']) {
- $mediaList->getConditionBuilder()->add('media.isImage = ?', [1]);
- }
- $mediaList->sqlOrderBy = 'media.uploadTime DESC, media.mediaID DESC';
- $mediaList->sqlLimit = static::ITEMS_PER_MANAGER_DIALOG_PAGE;
- $mediaList->readObjects();
-
- $categoryList = (new CategoryNodeTree('com.woltlab.wcf.media.category'))->getIterator();
- $categoryList->setMaxDepth(0);
-
- return [
- 'hasMarkedItems' => ClipboardHandler::getInstance()->hasMarkedItems(ClipboardHandler::getInstance()->getObjectTypeID('com.woltlab.wcf.media')),
- 'media' => $this->getI18nMediaData($mediaList),
- 'pageCount' => ceil($mediaList->countObjects() / static::ITEMS_PER_MANAGER_DIALOG_PAGE),
- 'template' => WCF::getTPL()->fetch('mediaManager', 'wcf', [
- 'categoryList' => $categoryList,
- 'mediaList' => $mediaList,
- 'mode' => $this->parameters['mode']
- ])
- ];
- }
-
- /**
- * Returns the complete i18n data of the media files in the given list.
- *
- * @param MediaList $mediaList
- * @return array
- */
- protected function getI18nMediaData(MediaList $mediaList) {
- if (!count($mediaList)) return [];
-
- $conditionBuilder = new PreparedStatementConditionBuilder();
- $conditionBuilder->add('mediaID IN (?)', [$mediaList->getObjectIDs()]);
-
- $sql = "SELECT *
- FROM wcf".WCF_N."_media_content
- ".$conditionBuilder;
- $statement = WCF::getDB()->prepareStatement($sql);
- $statement->execute($conditionBuilder->getParameters());
-
- $mediaData = [];
- while ($row = $statement->fetchArray()) {
- if (!isset($mediaData[$row['mediaID']])) {
- $mediaData[$row['mediaID']] = [
- 'altText' => [],
- 'caption' => [],
- 'title' => []
- ];
- }
-
- $mediaData[$row['mediaID']]['altText'][intval($row['languageID'])] = $row['altText'];
- $mediaData[$row['mediaID']]['caption'][intval($row['languageID'])] = $row['caption'];
- $mediaData[$row['mediaID']]['title'][intval($row['languageID'])] = $row['title'];
- }
-
- $i18nMediaData = [];
- foreach ($mediaList as $media) {
- if (!isset($mediaData[$media->mediaID])) {
- $mediaData[$media->mediaID] = [];
- }
-
- $i18nMediaData[$media->mediaID] = array_merge($this->getMediaData($media), $mediaData[$media->mediaID]);
- }
-
- return $i18nMediaData;
- }
-
- /**
- * Validates the 'getEditorDialog' action.
- */
- public function validateGetEditorDialog() {
- WCF::getSession()->checkPermissions(['admin.content.cms.canManageMedia']);
-
- $this->getSingleObject();
-
- if (!$this->getSingleObject()->canManage()) {
- throw new PermissionDeniedException();
- }
- }
-
- /**
- * Returns the template for the media editor.
- *
- * @return string[]
- */
- public function getEditorDialog() {
- $mediaList = new ViewableMediaList();
- $mediaList->setObjectIDs([$this->getSingleObject()->mediaID]);
- $mediaList->readObjects();
- $media = $mediaList->search($this->getSingleObject()->mediaID);
-
- I18nHandler::getInstance()->register('title_' . $media->mediaID);
- I18nHandler::getInstance()->register('caption_' . $media->mediaID);
- I18nHandler::getInstance()->register('altText_' . $media->mediaID);
- I18nHandler::getInstance()->assignVariables();
-
- $categoryList = (new CategoryNodeTree('com.woltlab.wcf.media.category'))->getIterator();
- $categoryList->setMaxDepth(0);
-
- return [
- 'availableLanguageCount' => count(LanguageFactory::getInstance()->getLanguages()),
- 'categoryIDs' => array_keys(CategoryHandler::getInstance()->getCategories('com.woltlab.wcf.media.category')),
- 'mediaData' => $this->getI18nMediaData($mediaList)[$this->getSingleObject()->mediaID],
- 'template' => WCF::getTPL()->fetch('mediaEditor', 'wcf', [
- '__aclSimplePrefix' => 'mediaEditor_' . $media->mediaID . '_',
- '__aclInputName' => 'mediaEditor_' . $media->mediaID . '_aclValues',
- '__languageChooserPrefix' => 'mediaEditor_' . $media->mediaID . '_',
- 'aclValues' => SimpleAclHandler::getInstance()->getValues('com.woltlab.wcf.media', $media->mediaID),
- 'availableLanguages' => LanguageFactory::getInstance()->getLanguages(),
- 'categoryList' => $categoryList,
- 'languageID' => WCF::getUser()->languageID,
- 'languages' => LanguageFactory::getInstance()->getLanguages(),
- 'media' => $media
- ])
- ];
- }
-
- /**
- * @inheritDoc
- */
- public function validateUpdate() {
- WCF::getSession()->checkPermissions(['admin.content.cms.canManageMedia']);
-
- if (empty($this->objects)) {
- $this->readObjects();
-
- if (empty($this->objects)) {
- throw new UserInputException('objectIDs');
- }
- }
-
- if (WCF::getSession()->getPermission('admin.content.cms.canOnlyAccessOwnMedia')) {
- foreach ($this->getObjects() as $media) {
- if ($media->userID != WCF::getUser()->userID) {
- throw new PermissionDeniedException();
- }
- }
- }
-
- $this->readInteger('categoryID', true, 'data');
- $this->readInteger('languageID', true, 'data');
- $this->readBoolean('isMultilingual', true, 'data');
- $this->readInteger('captionEnableHtml', true, 'data');
-
- if (count(LanguageFactory::getInstance()->getLanguages()) > 1) {
- // languageID: convert zero to null
- if (!$this->parameters['data']['languageID']) $this->parameters['data']['languageID'] = null;
-
- // isMultilingual: convert boolean to integer
- $this->parameters['data']['isMultilingual'] = intval($this->parameters['data']['isMultilingual']);
- }
- else {
- $this->parameters['data']['isMultilingual'] = 0;
- $this->parameters['data']['languageID'] = WCF::getLanguage()->languageID;
- }
-
- // if data is not multilingual, a language id has to be given
- if (!$this->parameters['data']['isMultilingual'] && !$this->parameters['data']['languageID']) {
- throw new UserInputException('languageID');
- }
-
- // check language id
- if ($this->parameters['data']['languageID'] && !LanguageFactory::getInstance()->getLanguage($this->parameters['data']['languageID'])) {
- throw new UserInputException('languageID');
- }
-
- // check category id
- if ($this->parameters['data']['categoryID']) {
- $category = CategoryHandler::getInstance()->getCategory($this->parameters['data']['categoryID']);
- if ($category === null || $category->getObjectType()->objectType !== 'com.woltlab.wcf.media.category') {
- throw new UserInputException('categoryID');
- }
- }
- }
-
- /**
- * @inheritDoc
- */
- public function update() {
- if (isset($this->parameters['data']['categoryID']) && $this->parameters['data']['categoryID'] === 0) {
- $this->parameters['data']['categoryID'] = null;
- }
-
- if (empty($this->objects)) {
- $this->readObjects();
- }
-
- parent::update();
-
- if (count($this->objects) == 1 && (isset($this->parameters['title']) || isset($this->parameters['caption']) || isset($this->parameters['altText']))) {
- $media = reset($this->objects);
-
- $isMultilingual = $media->isMultilingual;
- if (isset($this->parameters['data']['isMultilingual'])) {
- $isMultilingual = $this->parameters['data']['isMultilingual'];
- }
-
- $sql = "DELETE FROM wcf".WCF_N."_media_content
- WHERE mediaID = ?";
- $statement = WCF::getDB()->prepareStatement($sql);
- $statement->execute([$media->mediaID]);
-
- $sql = "INSERT INTO wcf".WCF_N."_media_content
- (mediaID, languageID, title, caption, altText)
- VALUES (?, ?, ?, ?, ?)";
- $statement = WCF::getDB()->prepareStatement($sql);
-
- if (!$isMultilingual) {
- $languageID = $media->languageID;
- if (isset($this->parameters['data']['languageID'])) {
- $languageID = $this->parameters['data']['languageID'];
- }
- $statement->execute([
- $media->mediaID,
- $languageID,
- isset($this->parameters['title'][$languageID]) ? mb_substr($this->parameters['title'][$languageID], 0, 255) : '',
- isset($this->parameters['caption'][$languageID]) ? $this->parameters['caption'][$languageID] : '',
- isset($this->parameters['altText'][$languageID]) ? mb_substr($this->parameters['altText'][$languageID], 0, 255) : ''
- ]);
- }
- else {
- $languages = LanguageFactory::getInstance()->getLanguages();
- foreach ($languages as $language) {
- $title = $caption = $altText = '';
- foreach (['title', 'caption', 'altText'] as $type) {
- if (isset($this->parameters[$type])) {
- if (is_array($this->parameters[$type])) {
- if (isset($this->parameters[$type][$language->languageID])) {
- /** @noinspection PhpVariableVariableInspection */
- $$type = $this->parameters[$type][$language->languageID];
- }
- }
- else {
- /** @noinspection PhpVariableVariableInspection */
- $$type = $this->parameters[$type];
- }
- }
- }
-
- $statement->execute([
- $media->mediaID,
- $language->languageID,
- mb_substr($title, 0, 255),
- $caption,
- mb_substr($altText, 0, 255)
- ]);
- }
- }
-
- if (!empty($this->parameters['aclValues'])) {
- SimpleAclHandler::getInstance()->setValues('com.woltlab.wcf.media', $media->mediaID, $this->parameters['aclValues']);
- }
- }
- }
-
- /**
- * @inheritDoc
- */
- public function validateGetSearchResultList() {
- if (!WCF::getSession()->getPermission('admin.content.cms.canManageMedia') && !WCF::getSession()->getPermission('admin.content.cms.canUseMedia')) {
- throw new PermissionDeniedException();
- }
-
- $this->readString('searchString', true);
- $this->readInteger('categoryID', true);
-
- $this->readBoolean('imagesOnly', true);
-
- $this->readString('mode');
- if ($this->parameters['mode'] != 'editor' && $this->parameters['mode'] != 'select') {
- throw new UserInputException('mode');
- }
-
- $this->readInteger('pageNo', true);
- if (!$this->parameters['pageNo']) $this->parameters['pageNo'] = 1;
- }
-
- /**
- * @inheritDoc
- */
- public function getSearchResultList() {
- $mediaList = new MediaList();
- $mediaList->addSearchConditions($this->parameters['searchString']);
- if (WCF::getSession()->getPermission('admin.content.cms.canOnlyAccessOwnMedia')) {
- $mediaList->getConditionBuilder()->add('media.userID = ?', [WCF::getUser()->userID]);
- }
- if ($this->parameters['imagesOnly']) {
- $mediaList->getConditionBuilder()->add('media.isImage = ?', [1]);
- }
- if ($this->parameters['categoryID']) {
- $mediaList->getConditionBuilder()->add('media.categoryID = ?', [$this->parameters['categoryID']]);
- }
- $mediaList->sqlOrderBy = 'media.uploadTime DESC, media.mediaID DESC';
- $mediaList->sqlLimit = static::ITEMS_PER_MANAGER_DIALOG_PAGE;
- $mediaList->sqlOffset = ($this->parameters['pageNo'] - 1) * static::ITEMS_PER_MANAGER_DIALOG_PAGE;
- $mediaList->readObjectIDs();
-
- if (empty($mediaList->getObjectIDs())) {
- // check if page is requested that might have existed but does not exist anymore due to deleted
- // media files
- if ($this->parameters['pageNo'] > 1 && $this->parameters['searchString'] === '' && !$this->parameters['categoryID']) {
- // request media dialog page with highest page number
- $parameters = $this->parameters;
- $parameters['pageNo'] = ceil($mediaList->countObjects() / static::ITEMS_PER_MANAGER_DIALOG_PAGE);
-
- return (new MediaAction($this->objects, 'getSearchResultList', $parameters))->executeAction()['returnValues'];
- }
-
- return [
- 'template' => WCF::getLanguage()->getDynamicVariable('wcf.media.search.noResults')
- ];
- }
-
- $viewableMediaList = new ViewableMediaList();
- $viewableMediaList->setObjectIDs($mediaList->getObjectIDs());
- $viewableMediaList->readObjects();
-
- return [
- 'media' => $this->getI18nMediaData($viewableMediaList),
- 'pageCount' => ceil($mediaList->countObjects() / static::ITEMS_PER_MANAGER_DIALOG_PAGE),
- 'pageNo' => $this->parameters['pageNo'],
- 'template' => WCF::getTPL()->fetch('mediaListItems', 'wcf', [
- 'mediaList' => $viewableMediaList,
- 'mode' => $this->parameters['mode']
- ])
- ];
- }
-
- /**
- * @inheritDoc
- */
- public function validateDelete() {
- WCF::getSession()->checkPermissions(['admin.content.cms.canManageMedia']);
-
- if (empty($this->objects)) {
- $this->readObjects();
-
- if (empty($this->objects)) {
- throw new UserInputException('objectIDs');
- }
- }
-
- if (WCF::getSession()->getPermission('admin.content.cms.canOnlyAccessOwnMedia')) {
- foreach ($this->getObjects() as $media) {
- if ($media->userID != WCF::getUser()->userID) {
- throw new PermissionDeniedException();
- }
- }
- }
- }
-
- /**
- * @inheritDoc
- */
- public function delete() {
- if (empty($this->objects)) {
- $this->readObjects();
- }
-
- foreach ($this->getObjects() as $mediaEditor) {
- $mediaEditor->deleteFiles();
- }
-
- parent::delete();
-
- $this->unmarkItems();
- }
-
- /**
- * Unmarks the media files with the given ids. If no media ids are given,
- * all media files currently loaded are unmarked.
- *
- * @param integer[] $mediaIDs ids of the media files to be unmarked
- */
- protected function unmarkItems(array $mediaIDs = []) {
- if (empty($mediaIDs)) {
- foreach ($this->getObjects() as $media) {
- $mediaIDs[] = $media->mediaID;
- }
- }
-
- if (!empty($mediaIDs)) {
- ClipboardHandler::getInstance()->unmark($mediaIDs, ClipboardHandler::getInstance()->getObjectTypeID('com.woltlab.wcf.media'));
- }
- }
-
- /**
- * Validates the `getSetCategoryDialog` action.
- *
- * @throws PermissionDeniedException if user is not allowed to set category of media files
- * @throws IllegalLinkException if no media file categories exist
- */
- public function validateGetSetCategoryDialog() {
- if (!WCF::getSession()->getPermission('admin.content.cms.canManageMedia')) {
- throw new PermissionDeniedException();
- }
-
- if (empty(CategoryHandler::getInstance()->getCategories('com.woltlab.wcf.media.category'))) {
- throw new IllegalLinkException();
- }
- }
-
- /**
- * Returns the dialog to set the category of multiple media files.
- *
- * @return string[]
- */
- public function getSetCategoryDialog() {
- $categoryList = (new CategoryNodeTree('com.woltlab.wcf.media.category'))->getIterator();
- $categoryList->setMaxDepth(0);
-
- return [
- 'template' => WCF::getTPL()->fetch('__mediaSetCategoryDialog', 'wcf', [
- 'categoryList' => $categoryList
- ])
- ];
- }
-
- /**
- * Validates the `setCategory` action.
- *
- * @throws PermissionDeniedException if user is not allowed to edit a requested media file
- * @throws UserInputException if no object ids are given
- */
- public function validateSetCategory() {
- $this->validateGetSetCategoryDialog();
-
- if (empty($this->objects)) {
- $this->readObjects();
-
- if (empty($this->objects)) {
- throw new UserInputException('objectIDs');
- }
- }
-
- if (WCF::getSession()->getPermission('admin.content.cms.canOnlyAccessOwnMedia')) {
- foreach ($this->getObjects() as $media) {
- if ($media->userID != WCF::getUser()->userID) {
- throw new PermissionDeniedException();
- }
- }
- }
-
- $this->readInteger('categoryID', true);
- }
-
- /**
- * Sets the category of multiple media files.
- */
- public function setCategory() {
- $conditionBuilder = new PreparedStatementConditionBuilder();
- $conditionBuilder->add('mediaID IN (?)', [$this->objectIDs]);
-
- $sql = "UPDATE wcf" . WCF_N . "_media
- SET categoryID = ?
- " . $conditionBuilder;
- $statement = WCF::getDB()->prepareStatement($sql);
- $statement->execute(array_merge(
- [$this->parameters['categoryID'] ?: null],
- $conditionBuilder->getParameters()
- ));
-
- $this->unmarkItems();
- }
-
- /**
- * Validates the `replaceFile` action.
- *
- * @since 5.3
- */
- public function validateReplaceFile() {
- WCF::getSession()->checkPermissions(['admin.content.cms.canManageMedia']);
-
- $this->getSingleObject();
-
- /** @noinspection PhpUndefinedMethodInspection */
- $this->parameters['__files']->validateFiles(
- new MediaReplaceUploadFileValidationStrategy($this->getSingleObject()->getDecoratedObject())
- );
- }
-
- /**
- * Replaces the actual file of a media file.
- *
- * @return array
- * @since 5.3
- */
- public function replaceFile() {
- $saveStrategy = new DefaultUploadFileSaveStrategy(static::class, [
- 'action' => 'update',
- 'generateThumbnails' => true,
- 'object' => $this->getSingleObject()->getDecoratedObject(),
- 'rotateImages' => true,
- ], [
- 'fileUpdateTime' => TIME_NOW,
- 'userID' => $this->getSingleObject()->userID,
- 'username' => $this->getSingleObject()->username,
- ]);
-
- /** @noinspection PhpUndefinedMethodInspection */
- $this->parameters['__files']->saveFiles($saveStrategy);
-
- /** @var Media[] $mediaFiles */
- $mediaFiles = $saveStrategy->getObjects();
-
- $result = [
- 'errors' => [],
- 'media' => []
- ];
-
- if (!empty($mediaFiles)) {
- $mediaIDs = $mediaToFileID = [];
- foreach ($mediaFiles as $internalFileID => $media) {
- $mediaIDs[] = $media->mediaID;
- $mediaToFileID[$media->mediaID] = $internalFileID;
- }
-
- // fetch media objects from database
- $mediaList = new ViewableMediaList();
- $mediaList->setObjectIDs($mediaIDs);
- $mediaList->readObjects();
-
- foreach ($mediaList as $media) {
- $result['media'][$mediaToFileID[$media->mediaID]] = $this->getMediaData($media);
- }
- }
-
- /** @var UploadFile[] $files */
- /** @noinspection PhpUndefinedMethodInspection */
- $files = $this->parameters['__files']->getFiles();
- foreach ($files as $file) {
- if ($file->getValidationErrorType()) {
- $result['errors'][$file->getInternalFileID()] = [
- 'filename' => $file->getFilename(),
- 'filesize' => $file->getFilesize(),
- 'errorType' => $file->getValidationErrorType()
- ];
- }
- }
-
- $outdatedMediaFile = $this->getSingleObject();
- $updatedMediaFile = new Media($this->getSingleObject()->mediaID);
-
- // Delete *old* files using the non-updated local media editor object if the new file is
- // stored in a different location.
- if (empty($result['errors']) && $updatedMediaFile->getLocation() !== $outdatedMediaFile->getLocation()) {
- $outdatedMediaFile->deleteFiles();
- }
-
- return $result;
- }
+class MediaAction extends AbstractDatabaseObjectAction implements ISearchAction, IUploadAction
+{
+ /**
+ * number of media files per media manager dialog page
+ */
+ const ITEMS_PER_MANAGER_DIALOG_PAGE = 50;
+
+ /**
+ * @inheritDoc
+ */
+ public function validateUpload()
+ {
+ WCF::getSession()->checkPermissions(['admin.content.cms.canManageMedia']);
+
+ $this->readBoolean('imagesOnly', true);
+ $this->readInteger('categoryID', true);
+
+ /** @noinspection PhpUndefinedMethodInspection */
+ $this->parameters['__files']->validateFiles(new MediaUploadFileValidationStrategy($this->parameters['imagesOnly']));
+
+ if ($this->parameters['categoryID']) {
+ $category = CategoryHandler::getInstance()->getCategory($this->parameters['categoryID']);
+ if ($category === null || $category->getObjectType()->objectType !== 'com.woltlab.wcf.media.category') {
+ throw new UserInputException('categoryID');
+ }
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function upload()
+ {
+ $additionalData = ['username' => WCF::getUser()->username];
+ if ($this->parameters['categoryID']) {
+ $additionalData['categoryID'] = $this->parameters['categoryID'];
+ }
+
+ // save files
+ $saveStrategy = new DefaultUploadFileSaveStrategy(self::class, [
+ 'generateThumbnails' => true,
+ 'rotateImages' => true,
+ ], $additionalData);
+
+ /** @noinspection PhpUndefinedMethodInspection */
+ $this->parameters['__files']->saveFiles($saveStrategy);
+
+ /** @var Media[] $mediaFiles */
+ $mediaFiles = $saveStrategy->getObjects();
+
+ $result = [
+ 'errors' => [],
+ 'media' => [],
+ ];
+
+ if (!empty($mediaFiles)) {
+ $mediaIDs = $mediaToFileID = [];
+ foreach ($mediaFiles as $internalFileID => $media) {
+ $mediaIDs[] = $media->mediaID;
+ $mediaToFileID[$media->mediaID] = $internalFileID;
+ }
+
+ // fetch media objects from database
+ $mediaList = new ViewableMediaList();
+ $mediaList->setObjectIDs($mediaIDs);
+ $mediaList->readObjects();
+
+ foreach ($mediaList as $media) {
+ $result['media'][$mediaToFileID[$media->mediaID]] = $this->getMediaData($media);
+ }
+ }
+
+ /** @var UploadFile[] $files */
+ /** @noinspection PhpUndefinedMethodInspection */
+ $files = $this->parameters['__files']->getFiles();
+ foreach ($files as $file) {
+ if ($file->getValidationErrorType()) {
+ $result['errors'][$file->getInternalFileID()] = [
+ 'filename' => $file->getFilename(),
+ 'filesize' => $file->getFilesize(),
+ 'errorType' => $file->getValidationErrorType(),
+ ];
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Generates thumbnails.
+ */
+ public function generateThumbnails()
+ {
+ if (empty($this->objects)) {
+ $this->readObjects();
+ }
+
+ $saveStrategy = new DefaultUploadFileSaveStrategy(self::class);
+
+ foreach ($this->getObjects() as $mediaEditor) {
+ if ($mediaEditor->getDecoratedObject()->isImage) {
+ $saveStrategy->generateThumbnails($mediaEditor->getDecoratedObject());
+ }
+ }
+ }
+
+ /**
+ * Returns the data of the media file to be returned by AJAX requests.
+ *
+ * @param Media|ViewableMedia $media media files whose data will be returned
+ * @return string[]
+ */
+ protected function getMediaData($media)
+ {
+ return [
+ 'altText' => $media instanceof ViewableMedia ? $media->altText : [],
+ 'caption' => $media instanceof ViewableMedia ? $media->caption : [],
+ 'captionEnableHtml' => $media->captionEnableHtml,
+ 'categoryID' => $media->categoryID,
+ 'elementTag' => $media instanceof ViewableMedia ? $media->getElementTag($this->parameters['elementTagSize'] ?? 144) : '',
+ 'elementTag48' => $media instanceof ViewableMedia ? $media->getElementTag(48) : '',
+ 'fileHash' => $media->fileHash,
+ 'filename' => $media->filename,
+ 'filesize' => $media->filesize,
+ 'formattedFilesize' => FileUtil::formatFilesize($media->filesize),
+ 'fileType' => $media->fileType,
+ 'height' => $media->height,
+ 'languageID' => $media->languageID,
+ 'imageDimensions' => $media->isImage ? WCF::getLanguage()->getDynamicVariable(
+ 'wcf.media.imageDimensions.value',
+ [
+ 'media' => $media,
+ ]
+ ) : '',
+ 'isImage' => $media->isImage,
+ 'isMultilingual' => $media->isMultilingual,
+ 'largeThumbnailHeight' => $media->largeThumbnailHeight,
+ 'largeThumbnailLink' => $media->largeThumbnailType ? $media->getThumbnailLink('large') : '',
+ 'largeThumbnailType' => $media->largeThumbnailType,
+ 'largeThumbnailWidth' => $media->largeThumbnailWidth,
+ 'link' => $media->getLink(),
+ 'mediaID' => $media->mediaID,
+ 'mediumThumbnailHeight' => $media->mediumThumbnailHeight,
+ 'mediumThumbnailLink' => $media->mediumThumbnailType ? $media->getThumbnailLink('medium') : '',
+ 'mediumThumbnailType' => $media->mediumThumbnailType,
+ 'mediumThumbnailWidth' => $media->mediumThumbnailWidth,
+ 'smallThumbnailHeight' => $media->smallThumbnailHeight,
+ 'smallThumbnailLink' => $media->smallThumbnailType ? $media->getThumbnailLink('small') : '',
+ 'smallThumbnailTag' => $media->smallThumbnailType ? $media->getThumbnailTag('small') : '',
+ 'smallThumbnailType' => $media->smallThumbnailType,
+ 'smallThumbnailWidth' => $media->smallThumbnailWidth,
+ 'tinyThumbnailHeight' => $media->tinyThumbnailHeight,
+ 'tinyThumbnailLink' => $media->tinyThumbnailType ? $media->getThumbnailLink('tiny') : '',
+ 'tinyThumbnailType' => $media->tinyThumbnailType,
+ 'tinyThumbnailWidth' => $media->tinyThumbnailWidth,
+ 'title' => $media instanceof ViewableMedia ? $media->title : [],
+ 'uploadTime' => $media->uploadTime,
+ 'userID' => $media->userID,
+ 'userLink' => $media->userID ? LinkHandler::getInstance()->getLink('User', [
+ 'id' => $media->userID,
+ 'title' => $media->username,
+ ]) : '',
+ 'userLinkElement' => $media instanceof ViewableMedia ? WCF::getTPL()->fetchString(
+ WCF::getTPL()->getCompiler()->compileString('userLink', '{user object=$userProfile}')['template'],
+ ['userProfile' => $media->getUserProfile()]
+ ) : '',
+ 'username' => $media->username,
+ 'width' => $media->width,
+ ];
+ }
+
+ /**
+ * Validates the 'getManagementDialog' action.
+ */
+ public function validateGetManagementDialog()
+ {
+ if (!WCF::getSession()->getPermission('admin.content.cms.canManageMedia') && !WCF::getSession()->getPermission('admin.content.cms.canUseMedia')) {
+ throw new PermissionDeniedException();
+ }
+
+ $this->readBoolean('imagesOnly', true);
+
+ $this->readString('mode');
+ if ($this->parameters['mode'] != 'editor' && $this->parameters['mode'] != 'select') {
+ throw new UserInputException('mode');
+ }
+ }
+
+ /**
+ * Returns the dialog to manage media.
+ *
+ * @return string[]
+ */
+ public function getManagementDialog()
+ {
+ $mediaList = new ViewableMediaList();
+ if (WCF::getSession()->getPermission('admin.content.cms.canOnlyAccessOwnMedia')) {
+ $mediaList->getConditionBuilder()->add('media.userID = ?', [WCF::getUser()->userID]);
+ }
+ if ($this->parameters['imagesOnly']) {
+ $mediaList->getConditionBuilder()->add('media.isImage = ?', [1]);
+ }
+ $mediaList->sqlOrderBy = 'media.uploadTime DESC, media.mediaID DESC';
+ $mediaList->sqlLimit = static::ITEMS_PER_MANAGER_DIALOG_PAGE;
+ $mediaList->readObjects();
+
+ $categoryList = (new CategoryNodeTree('com.woltlab.wcf.media.category'))->getIterator();
+ $categoryList->setMaxDepth(0);
+
+ return [
+ 'hasMarkedItems' => ClipboardHandler::getInstance()->hasMarkedItems(ClipboardHandler::getInstance()->getObjectTypeID('com.woltlab.wcf.media')),
+ 'media' => $this->getI18nMediaData($mediaList),
+ 'pageCount' => \ceil($mediaList->countObjects() / static::ITEMS_PER_MANAGER_DIALOG_PAGE),
+ 'template' => WCF::getTPL()->fetch('mediaManager', 'wcf', [
+ 'categoryList' => $categoryList,
+ 'mediaList' => $mediaList,
+ 'mode' => $this->parameters['mode'],
+ ]),
+ ];
+ }
+
+ /**
+ * Returns the complete i18n data of the media files in the given list.
+ *
+ * @param MediaList $mediaList
+ * @return array
+ */
+ protected function getI18nMediaData(MediaList $mediaList)
+ {
+ if (!\count($mediaList)) {
+ return [];
+ }
+
+ $conditionBuilder = new PreparedStatementConditionBuilder();
+ $conditionBuilder->add('mediaID IN (?)', [$mediaList->getObjectIDs()]);
+
+ $sql = "SELECT *
+ FROM wcf" . WCF_N . "_media_content
+ " . $conditionBuilder;
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute($conditionBuilder->getParameters());
+
+ $mediaData = [];
+ while ($row = $statement->fetchArray()) {
+ if (!isset($mediaData[$row['mediaID']])) {
+ $mediaData[$row['mediaID']] = [
+ 'altText' => [],
+ 'caption' => [],
+ 'title' => [],
+ ];
+ }
+
+ $mediaData[$row['mediaID']]['altText'][\intval($row['languageID'])] = $row['altText'];
+ $mediaData[$row['mediaID']]['caption'][\intval($row['languageID'])] = $row['caption'];
+ $mediaData[$row['mediaID']]['title'][\intval($row['languageID'])] = $row['title'];
+ }
+
+ $i18nMediaData = [];
+ foreach ($mediaList as $media) {
+ if (!isset($mediaData[$media->mediaID])) {
+ $mediaData[$media->mediaID] = [];
+ }
+
+ $i18nMediaData[$media->mediaID] = \array_merge($this->getMediaData($media), $mediaData[$media->mediaID]);
+ }
+
+ return $i18nMediaData;
+ }
+
+ /**
+ * Validates the 'getEditorDialog' action.
+ */
+ public function validateGetEditorDialog()
+ {
+ WCF::getSession()->checkPermissions(['admin.content.cms.canManageMedia']);
+
+ $this->getSingleObject();
+
+ if (!$this->getSingleObject()->canManage()) {
+ throw new PermissionDeniedException();
+ }
+ }
+
+ /**
+ * Returns the template for the media editor.
+ *
+ * @return string[]
+ */
+ public function getEditorDialog()
+ {
+ $mediaList = new ViewableMediaList();
+ $mediaList->setObjectIDs([$this->getSingleObject()->mediaID]);
+ $mediaList->readObjects();
+ $media = $mediaList->search($this->getSingleObject()->mediaID);
+
+ I18nHandler::getInstance()->register('title_' . $media->mediaID);
+ I18nHandler::getInstance()->register('caption_' . $media->mediaID);
+ I18nHandler::getInstance()->register('altText_' . $media->mediaID);
+ I18nHandler::getInstance()->assignVariables();
+
+ $categoryList = (new CategoryNodeTree('com.woltlab.wcf.media.category'))->getIterator();
+ $categoryList->setMaxDepth(0);
+
+ return [
+ 'availableLanguageCount' => \count(LanguageFactory::getInstance()->getLanguages()),
+ 'categoryIDs' => \array_keys(CategoryHandler::getInstance()->getCategories('com.woltlab.wcf.media.category')),
+ 'mediaData' => $this->getI18nMediaData($mediaList)[$this->getSingleObject()->mediaID],
+ 'template' => WCF::getTPL()->fetch('mediaEditor', 'wcf', [
+ '__aclSimplePrefix' => 'mediaEditor_' . $media->mediaID . '_',
+ '__aclInputName' => 'mediaEditor_' . $media->mediaID . '_aclValues',
+ '__languageChooserPrefix' => 'mediaEditor_' . $media->mediaID . '_',
+ 'aclValues' => SimpleAclHandler::getInstance()->getValues('com.woltlab.wcf.media', $media->mediaID),
+ 'availableLanguages' => LanguageFactory::getInstance()->getLanguages(),
+ 'categoryList' => $categoryList,
+ 'languageID' => WCF::getUser()->languageID,
+ 'languages' => LanguageFactory::getInstance()->getLanguages(),
+ 'media' => $media,
+ ]),
+ ];
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function validateUpdate()
+ {
+ WCF::getSession()->checkPermissions(['admin.content.cms.canManageMedia']);
+
+ if (empty($this->objects)) {
+ $this->readObjects();
+
+ if (empty($this->objects)) {
+ throw new UserInputException('objectIDs');
+ }
+ }
+
+ if (WCF::getSession()->getPermission('admin.content.cms.canOnlyAccessOwnMedia')) {
+ foreach ($this->getObjects() as $media) {
+ if ($media->userID != WCF::getUser()->userID) {
+ throw new PermissionDeniedException();
+ }
+ }
+ }
+
+ $this->readInteger('categoryID', true, 'data');
+ $this->readInteger('languageID', true, 'data');
+ $this->readBoolean('isMultilingual', true, 'data');
+ $this->readInteger('captionEnableHtml', true, 'data');
+
+ if (\count(LanguageFactory::getInstance()->getLanguages()) > 1) {
+ // languageID: convert zero to null
+ if (!$this->parameters['data']['languageID']) {
+ $this->parameters['data']['languageID'] = null;
+ }
+
+ // isMultilingual: convert boolean to integer
+ $this->parameters['data']['isMultilingual'] = \intval($this->parameters['data']['isMultilingual']);
+ } else {
+ $this->parameters['data']['isMultilingual'] = 0;
+ $this->parameters['data']['languageID'] = WCF::getLanguage()->languageID;
+ }
+
+ // if data is not multilingual, a language id has to be given
+ if (!$this->parameters['data']['isMultilingual'] && !$this->parameters['data']['languageID']) {
+ throw new UserInputException('languageID');
+ }
+
+ // check language id
+ if ($this->parameters['data']['languageID'] && !LanguageFactory::getInstance()->getLanguage($this->parameters['data']['languageID'])) {
+ throw new UserInputException('languageID');
+ }
+
+ // check category id
+ if ($this->parameters['data']['categoryID']) {
+ $category = CategoryHandler::getInstance()->getCategory($this->parameters['data']['categoryID']);
+ if ($category === null || $category->getObjectType()->objectType !== 'com.woltlab.wcf.media.category') {
+ throw new UserInputException('categoryID');
+ }
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function update()
+ {
+ if (isset($this->parameters['data']['categoryID']) && $this->parameters['data']['categoryID'] === 0) {
+ $this->parameters['data']['categoryID'] = null;
+ }
+
+ if (empty($this->objects)) {
+ $this->readObjects();
+ }
+
+ parent::update();
+
+ if (\count($this->objects) == 1 && (isset($this->parameters['title']) || isset($this->parameters['caption']) || isset($this->parameters['altText']))) {
+ $media = \reset($this->objects);
+
+ $isMultilingual = $media->isMultilingual;
+ if (isset($this->parameters['data']['isMultilingual'])) {
+ $isMultilingual = $this->parameters['data']['isMultilingual'];
+ }
+
+ $sql = "DELETE FROM wcf" . WCF_N . "_media_content
+ WHERE mediaID = ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute([$media->mediaID]);
+
+ $sql = "INSERT INTO wcf" . WCF_N . "_media_content
+ (mediaID, languageID, title, caption, altText)
+ VALUES (?, ?, ?, ?, ?)";
+ $statement = WCF::getDB()->prepareStatement($sql);
+
+ if (!$isMultilingual) {
+ $languageID = $media->languageID;
+ if (isset($this->parameters['data']['languageID'])) {
+ $languageID = $this->parameters['data']['languageID'];
+ }
+ $statement->execute([
+ $media->mediaID,
+ $languageID,
+ isset($this->parameters['title'][$languageID]) ? \mb_substr(
+ $this->parameters['title'][$languageID],
+ 0,
+ 255
+ ) : '',
+ $this->parameters['caption'][$languageID] ?? '',
+ isset($this->parameters['altText'][$languageID]) ? \mb_substr(
+ $this->parameters['altText'][$languageID],
+ 0,
+ 255
+ ) : '',
+ ]);
+ } else {
+ $languages = LanguageFactory::getInstance()->getLanguages();
+ foreach ($languages as $language) {
+ $title = $caption = $altText = '';
+ foreach (['title', 'caption', 'altText'] as $type) {
+ if (isset($this->parameters[$type])) {
+ if (\is_array($this->parameters[$type])) {
+ if (isset($this->parameters[$type][$language->languageID])) {
+ /** @noinspection PhpVariableVariableInspection */
+ ${$type} = $this->parameters[$type][$language->languageID];
+ }
+ } else {
+ /** @noinspection PhpVariableVariableInspection */
+ ${$type} = $this->parameters[$type];
+ }
+ }
+ }
+
+ $statement->execute([
+ $media->mediaID,
+ $language->languageID,
+ \mb_substr($title, 0, 255),
+ $caption,
+ \mb_substr($altText, 0, 255),
+ ]);
+ }
+ }
+
+ if (!empty($this->parameters['aclValues'])) {
+ SimpleAclHandler::getInstance()->setValues(
+ 'com.woltlab.wcf.media',
+ $media->mediaID,
+ $this->parameters['aclValues']
+ );
+ }
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function validateGetSearchResultList()
+ {
+ if (!WCF::getSession()->getPermission('admin.content.cms.canManageMedia') && !WCF::getSession()->getPermission('admin.content.cms.canUseMedia')) {
+ throw new PermissionDeniedException();
+ }
+
+ $this->readString('searchString', true);
+ $this->readInteger('categoryID', true);
+
+ $this->readBoolean('imagesOnly', true);
+
+ $this->readString('mode');
+ if ($this->parameters['mode'] != 'editor' && $this->parameters['mode'] != 'select') {
+ throw new UserInputException('mode');
+ }
+
+ $this->readInteger('pageNo', true);
+ if (!$this->parameters['pageNo']) {
+ $this->parameters['pageNo'] = 1;
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getSearchResultList()
+ {
+ $mediaList = new MediaList();
+ $mediaList->addSearchConditions($this->parameters['searchString']);
+ if (WCF::getSession()->getPermission('admin.content.cms.canOnlyAccessOwnMedia')) {
+ $mediaList->getConditionBuilder()->add('media.userID = ?', [WCF::getUser()->userID]);
+ }
+ if ($this->parameters['imagesOnly']) {
+ $mediaList->getConditionBuilder()->add('media.isImage = ?', [1]);
+ }
+ if ($this->parameters['categoryID']) {
+ $mediaList->getConditionBuilder()->add('media.categoryID = ?', [$this->parameters['categoryID']]);
+ }
+ $mediaList->sqlOrderBy = 'media.uploadTime DESC, media.mediaID DESC';
+ $mediaList->sqlLimit = static::ITEMS_PER_MANAGER_DIALOG_PAGE;
+ $mediaList->sqlOffset = ($this->parameters['pageNo'] - 1) * static::ITEMS_PER_MANAGER_DIALOG_PAGE;
+ $mediaList->readObjectIDs();
+
+ if (empty($mediaList->getObjectIDs())) {
+ // check if page is requested that might have existed but does not exist anymore due to deleted
+ // media files
+ if ($this->parameters['pageNo'] > 1 && $this->parameters['searchString'] === '' && !$this->parameters['categoryID']) {
+ // request media dialog page with highest page number
+ $parameters = $this->parameters;
+ $parameters['pageNo'] = \ceil($mediaList->countObjects() / static::ITEMS_PER_MANAGER_DIALOG_PAGE);
+
+ return (new self($this->objects, 'getSearchResultList', $parameters))->executeAction()['returnValues'];
+ }
+
+ return [
+ 'template' => WCF::getLanguage()->getDynamicVariable('wcf.media.search.noResults'),
+ ];
+ }
+
+ $viewableMediaList = new ViewableMediaList();
+ $viewableMediaList->setObjectIDs($mediaList->getObjectIDs());
+ $viewableMediaList->readObjects();
+
+ return [
+ 'media' => $this->getI18nMediaData($viewableMediaList),
+ 'pageCount' => \ceil($mediaList->countObjects() / static::ITEMS_PER_MANAGER_DIALOG_PAGE),
+ 'pageNo' => $this->parameters['pageNo'],
+ 'template' => WCF::getTPL()->fetch('mediaListItems', 'wcf', [
+ 'mediaList' => $viewableMediaList,
+ 'mode' => $this->parameters['mode'],
+ ]),
+ ];
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function validateDelete()
+ {
+ WCF::getSession()->checkPermissions(['admin.content.cms.canManageMedia']);
+
+ if (empty($this->objects)) {
+ $this->readObjects();
+
+ if (empty($this->objects)) {
+ throw new UserInputException('objectIDs');
+ }
+ }
+
+ if (WCF::getSession()->getPermission('admin.content.cms.canOnlyAccessOwnMedia')) {
+ foreach ($this->getObjects() as $media) {
+ if ($media->userID != WCF::getUser()->userID) {
+ throw new PermissionDeniedException();
+ }
+ }
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function delete()
+ {
+ if (empty($this->objects)) {
+ $this->readObjects();
+ }
+
+ foreach ($this->getObjects() as $mediaEditor) {
+ $mediaEditor->deleteFiles();
+ }
+
+ parent::delete();
+
+ $this->unmarkItems();
+ }
+
+ /**
+ * Unmarks the media files with the given ids. If no media ids are given,
+ * all media files currently loaded are unmarked.
+ *
+ * @param int[] $mediaIDs ids of the media files to be unmarked
+ */
+ protected function unmarkItems(array $mediaIDs = [])
+ {
+ if (empty($mediaIDs)) {
+ foreach ($this->getObjects() as $media) {
+ $mediaIDs[] = $media->mediaID;
+ }
+ }
+
+ if (!empty($mediaIDs)) {
+ ClipboardHandler::getInstance()->unmark(
+ $mediaIDs,
+ ClipboardHandler::getInstance()->getObjectTypeID('com.woltlab.wcf.media')
+ );
+ }
+ }
+
+ /**
+ * Validates the `getSetCategoryDialog` action.
+ *
+ * @throws PermissionDeniedException if user is not allowed to set category of media files
+ * @throws IllegalLinkException if no media file categories exist
+ */
+ public function validateGetSetCategoryDialog()
+ {
+ if (!WCF::getSession()->getPermission('admin.content.cms.canManageMedia')) {
+ throw new PermissionDeniedException();
+ }
+
+ if (empty(CategoryHandler::getInstance()->getCategories('com.woltlab.wcf.media.category'))) {
+ throw new IllegalLinkException();
+ }
+ }
+
+ /**
+ * Returns the dialog to set the category of multiple media files.
+ *
+ * @return string[]
+ */
+ public function getSetCategoryDialog()
+ {
+ $categoryList = (new CategoryNodeTree('com.woltlab.wcf.media.category'))->getIterator();
+ $categoryList->setMaxDepth(0);
+
+ return [
+ 'template' => WCF::getTPL()->fetch('__mediaSetCategoryDialog', 'wcf', [
+ 'categoryList' => $categoryList,
+ ]),
+ ];
+ }
+
+ /**
+ * Validates the `setCategory` action.
+ *
+ * @throws PermissionDeniedException if user is not allowed to edit a requested media file
+ * @throws UserInputException if no object ids are given
+ */
+ public function validateSetCategory()
+ {
+ $this->validateGetSetCategoryDialog();
+
+ if (empty($this->objects)) {
+ $this->readObjects();
+
+ if (empty($this->objects)) {
+ throw new UserInputException('objectIDs');
+ }
+ }
+
+ if (WCF::getSession()->getPermission('admin.content.cms.canOnlyAccessOwnMedia')) {
+ foreach ($this->getObjects() as $media) {
+ if ($media->userID != WCF::getUser()->userID) {
+ throw new PermissionDeniedException();
+ }
+ }
+ }
+
+ $this->readInteger('categoryID', true);
+ }
+
+ /**
+ * Sets the category of multiple media files.
+ */
+ public function setCategory()
+ {
+ $conditionBuilder = new PreparedStatementConditionBuilder();
+ $conditionBuilder->add('mediaID IN (?)', [$this->objectIDs]);
+
+ $sql = "UPDATE wcf" . WCF_N . "_media
+ SET categoryID = ?
+ " . $conditionBuilder;
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute(\array_merge(
+ [$this->parameters['categoryID'] ?: null],
+ $conditionBuilder->getParameters()
+ ));
+
+ $this->unmarkItems();
+ }
+
+ /**
+ * Validates the `replaceFile` action.
+ *
+ * @since 5.3
+ */
+ public function validateReplaceFile()
+ {
+ WCF::getSession()->checkPermissions(['admin.content.cms.canManageMedia']);
+
+ $this->getSingleObject();
+
+ /** @noinspection PhpUndefinedMethodInspection */
+ $this->parameters['__files']->validateFiles(
+ new MediaReplaceUploadFileValidationStrategy($this->getSingleObject()->getDecoratedObject())
+ );
+ }
+
+ /**
+ * Replaces the actual file of a media file.
+ *
+ * @return array
+ * @since 5.3
+ */
+ public function replaceFile()
+ {
+ $saveStrategy = new DefaultUploadFileSaveStrategy(static::class, [
+ 'action' => 'update',
+ 'generateThumbnails' => true,
+ 'object' => $this->getSingleObject()->getDecoratedObject(),
+ 'rotateImages' => true,
+ ], [
+ 'fileUpdateTime' => TIME_NOW,
+ 'userID' => $this->getSingleObject()->userID,
+ 'username' => $this->getSingleObject()->username,
+ ]);
+
+ /** @noinspection PhpUndefinedMethodInspection */
+ $this->parameters['__files']->saveFiles($saveStrategy);
+
+ /** @var Media[] $mediaFiles */
+ $mediaFiles = $saveStrategy->getObjects();
+
+ $result = [
+ 'errors' => [],
+ 'media' => [],
+ ];
+
+ if (!empty($mediaFiles)) {
+ $mediaIDs = $mediaToFileID = [];
+ foreach ($mediaFiles as $internalFileID => $media) {
+ $mediaIDs[] = $media->mediaID;
+ $mediaToFileID[$media->mediaID] = $internalFileID;
+ }
+
+ // fetch media objects from database
+ $mediaList = new ViewableMediaList();
+ $mediaList->setObjectIDs($mediaIDs);
+ $mediaList->readObjects();
+
+ foreach ($mediaList as $media) {
+ $result['media'][$mediaToFileID[$media->mediaID]] = $this->getMediaData($media);
+ }
+ }
+
+ /** @var UploadFile[] $files */
+ /** @noinspection PhpUndefinedMethodInspection */
+ $files = $this->parameters['__files']->getFiles();
+ foreach ($files as $file) {
+ if ($file->getValidationErrorType()) {
+ $result['errors'][$file->getInternalFileID()] = [
+ 'filename' => $file->getFilename(),
+ 'filesize' => $file->getFilesize(),
+ 'errorType' => $file->getValidationErrorType(),
+ ];
+ }
+ }
+
- // Delete *old* files using the non-updated local media editor object.
- if (empty($result['errors'])) {
- $this->getSingleObject()->deleteFiles();
++ $outdatedMediaFile = $this->getSingleObject();
++ $updatedMediaFile = new Media($this->getSingleObject()->mediaID);
++
++ // Delete *old* files using the non-updated local media editor object if the new file is
++ // stored in a different location.
++ if (empty($result['errors']) && $updatedMediaFile->getLocation() !== $outdatedMediaFile->getLocation()) {
++ $outdatedMediaFile->deleteFiles();
+ }
+
+ return $result;
+ }
}