HEX
Server: Apache
System: Linux WWW 6.1.0-40-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.153-1 (2025-09-20) x86_64
User: web11 (1011)
PHP: 8.2.29
Disabled: NONE
Upload Files
File: /var/www/intranet.kauko.lt/wp-content/plugins/wise-chat/assets/js/src/utils/html-renderer.jsx
import React from "react";
import ImageViewer from "utils/image-viewer";
import { Base64 } from 'js-base64';

/**
 * Renders plain text with shortcodes to HTML.
 */
export default class HtmlRenderer {

	get PARSERS() {
		const emoticonsConfig = this.configuration.interface.input.emoticons;
		const soundsConfig = this.configuration.interface.input.sounds;

		return [{
				regExp: /\[link src="(.+?)"(\s+name="(.+?)")?]/g,
				callback: (match, i, parserIndex) => {
					let url = match[1];

					if (this.configuration.interface.message.links) {
						let finalUrl = (!url.match(/^https|http|ftp|mailto:/) ? "http://" : '') + url;
						if (match[2]) {
							url = match[3];
						}

						return <a key={ this.currentKey++ } href={ finalUrl } target="_blank" rel="noopener noreferrer nofollow" data-org={ Base64.encode(match[0]) }>{ url }</a>;
					} else {
						return url;
					}
				}
			}, {
				regExp: /\[sound id="(.+?)" src="(.+?)" name-org="(.+?)"]/g,
				callback: (match, i, parserIndex) => {
					let attachmentSrc = match[2];

					if (soundsConfig.enabled) {
						return <audio key={ this.currentKey++ } controls data-org={ Base64.encode(match[0]) } controlsList="nodownload noplaybackrate">
							<source src={ attachmentSrc } type="audio/mpeg" />
							Your browser does not support the audio element.
						</audio>;
					} else {
						return '';
					}
				}
			}, {
				regExp: /\[attachment id="(.+?)" src="(.+?)" name-org="(.+?)"]/g,
				callback: (match, i, parserIndex) => {
					let attachmentSrc = match[2];
					let linkBody = match[3];
					const videoPlayer = this.configuration.interface.message.attachmentsVideoPlayer;
					const soundPlayer = this.configuration.interface.message.attachmentsSoundPlayer;

					if (this.configuration.interface.message.attachments) {
						if (videoPlayer && this.isFile(attachmentSrc, 'mp4')) {
							return <video key={ this.currentKey++ } controls data-org={ Base64.encode(match[0]) } controlsList="nodownload noplaybackrate">
								<source src={attachmentSrc} type="video/mp4"/>
								Your browser does not support the video tag.
							</video>
						} else if (videoPlayer && this.isFile(attachmentSrc, 'webm')) {
							return <video key={ this.currentKey++ } controls data-org={ Base64.encode(match[0]) } controlsList="nodownload noplaybackrate">
								<source src={ attachmentSrc } type="video/webm" />
								Your browser does not support the video tag.
							</video>
						} else if (soundPlayer && this.isFile(attachmentSrc, 'mp3')) {
							return <audio key={ this.currentKey++ } controls data-org={ Base64.encode(match[0]) } controlsList="nodownload noplaybackrate">
								<source src={ attachmentSrc } type="audio/mpeg" />
								Your browser does not support the audio element.
							</audio>;
						} else if (soundPlayer && this.isFile(attachmentSrc, 'wav')) {
							return <audio key={ this.currentKey++ } controls data-org={ Base64.encode(match[0]) } controlsList="nodownload noplaybackrate">
								<source src={ attachmentSrc } type="audio/wav" />
								Your browser does not support the audio element.
							</audio>;
						} else {
							return <a key={this.currentKey++} href={attachmentSrc} target="_blank"
							          rel="noopener noreferrer nofollow"
							          data-org={Base64.encode(match[0])}>{linkBody}</a>;
						}
					} else if (this.configuration.interface.message.links) {
						let finalUrl = (!attachmentSrc.match(/^https|http|ftp:/) ? "http://" : '') + attachmentSrc;

						return <a key={ this.currentKey++ } href={ finalUrl } target="_blank" rel="noopener noreferrer nofollow" data-org={ Base64.encode(match[0]) }>{ linkBody }</a>;
					} else {
						return linkBody;
					}
				}
			}, {
				regExp: /\[img id="(\d+)" src="(.+?)" src-th="(.+?)" src-org="(.+?)"]/g,
				callback: (match, i, parserIndex) => {
					let imageSrc = match[2];
					let imageThumbnailSrc = match[3];
					let imageOrgSrc = match[4];

					if (this.configuration.interface.message.images) {
						const viewer = this.configuration.interface.message.imagesViewer;

						return <a
							key={ this.currentKey++ }
							href={ imageSrc }
							target={ viewer === 'browser_window' ? "_blank" : undefined }
							data-lightbox={ viewer === 'lightbox' ? "wise_chat" : undefined }
							className="wcFunctional"
							rel={ viewer === 'lightbox' ? "lightbox[wise_chat]" : undefined }
							data-org={ Base64.encode(match[0]) }
							onClick={ e => viewer === 'internal' ? this.handleImagePreview(e, imageSrc) : undefined }
						>
							<img src={ imageThumbnailSrc } className="wcImage wcFunctional" alt="Chat image"/>
						</a>;
					} else if (this.configuration.interface.message.links) {
						if (imageOrgSrc === '_') {
							imageOrgSrc = imageSrc;
						}
						let finalUrl = (!imageOrgSrc.match(/^https|http|ftp:/) ? "http://" : '') + imageOrgSrc;

						return <a key={this.currentKey++} href={finalUrl} target="_blank"
						          rel="noopener noreferrer nofollow" data-org={Base64.encode(match[0])}>{imageOrgSrc}</a>;
					} else {
						return imageOrgSrc !== '_' ? imageOrgSrc : imageSrc;
					}
				}
			}, {
				regExp: /\[img src="(.+?)"(\s+className="(.+?)")?]/g,
				callback: (match, i, parserIndex) => {
					let imageSrc = match[1];
					let className = match[2] ? match[3] : '';

					if (this.configuration.interface.message.images) {
						return <img key={ this.currentKey++ } src={ imageSrc } className={ `wcImage wcFunctional ${className}` } alt="Chat image" data-org={ Base64.encode(match[0]) } />;
					} else {
						return imageSrc;
					}
				}
			}, {
				regExp: /\[span className="(.+?)" content="(.*?)"]/g,
				callback: (match, i, parserIndex) => {
					let className = match[1];
					let content = match[2];

					return <span key={ this.currentKey++ } className={ className } data-org={ Base64.encode(match[0]) }>{ content }</span>;
				}
			}, {
				regExp: /\[youtube movie-id="(.+?)" src-org="(.+?)"]/g,
				callback: (match, i, parserIndex) => {
					let movieId = match[1];
					let srcOrg = match[2];

					if (this.configuration.interface.message.yt && movieId.length > 0) {
						return <iframe
									key={ this.currentKey++ }
									width={ this.configuration.interface.message.ytWidth }
									height={ this.configuration.interface.message.ytHeight }
									className="wcVideoPlayer"
									src={ "https://www.youtube.com/embed/" + movieId }
									frameBorder="0" allowFullScreen
									data-org={ Base64.encode(match[0]) }
								/>;
					} else if (this.configuration.interface.message.links && srcOrg.length > 0) {
						let finalUrl = (!srcOrg.match(/^https|http|ftp:/) ? "http://" : '') + srcOrg;

						return <a key={ this.currentKey++ } href={ finalUrl } target="_blank" rel="noopener noreferrer nofollow" data-org={ Base64.encode(match[0]) }>{ srcOrg }</a>;
					} else if (srcOrg.length > 0) {
						return srcOrg;
					}
				}
			}, {
				regExp: /#([^\s#\[\]\\<&;]+)/g,
				callback: (match, i, parserIndex) => {
					let tag = match[1];

					if (this.configuration.interface.message.tt) {
						return <React.Fragment key={ this.currentKey++ }><a href={ 'https://twitter.com/hashtag/' + tag + '?src=hash' } target="_blank" rel="noopener noreferrer nofollow" data-org={ Base64.encode(match[0]) }>#{ tag }</a></React.Fragment>;
					} else {
						return match[0];
					}
				}
			}, {
				regExp: /\[emoticon set="(.+?)" index="(.+?)" size="(\d+)"]/g,
				callback: (match, i, parserIndex) => {
					let setId = match[1];
					let index = match[2];
					let size = match[3];

					if (emoticonsConfig.set) {
						return <img key={ this.currentKey++ }
						            src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
						            alt={'Emoticon set #' + setId + ' ' + size + 'px #' + index }
						            className={'wcFunctional wcEmoticon bg-emot_' + setId + '_' + size + '_' + index }  data-org={ Base64.encode(match[0]) } />;
					} else {
						return <span key={ this.currentKey++ } />;
					}
				}
			}, {
				regExp: /\[emoticon custom="(\d+)"]/g,
				callback: (match, i, parserIndex) => {
					let emoticonId = parseInt(match[1]);

					if (emoticonsConfig.custom) {
						const customEmoticon = emoticonsConfig.custom.find( emoticon => emoticon.id === emoticonId );
						if (customEmoticon) {
							return <img key={ this.currentKey++ }
						            src={ customEmoticon.url }
						            alt={'Emoticon ' + emoticonId }
						            className={'wcFunctional wcEmoticon' }  data-org={ Base64.encode(match[0]) } />;
						} else {
							return <span key={ this.currentKey++ } />;
						}
					} else {
						return <span key={ this.currentKey++ } />;
					}
				}
			}, {
				regExp: /\[video-call channelId="([^"]+)"]/g,
				callback: (match, i, parserIndex) => {
					let channelId = match[1];

					if (this.rendererConfiguration.onShortcodeRender) {
						return this.rendererConfiguration.onShortcodeRender('video-call', { channelId: channelId }, this.currentKey++);
					} else {
						return <span key={ this.currentKey++ } />;
					}
				}
			}, {
				regExp: /\n/g,
				callback: (match, i, parserIndex) => {
					return <br key={ this.currentKey++ } />;
				}
			}
		];
	}

	/**
	 * @param {Object} configuration
	 * @param {Object} rendererConfiguration
	 */
	constructor(configuration, rendererConfiguration = {}) {
		this.configuration = configuration;
		this.rendererConfiguration = rendererConfiguration;
		this.currentKey = 0;
		this.imageViewer = new ImageViewer();
		this.parse = this.parse.bind(this);
		this.handleImagePreview = this.handleImagePreview.bind(this);
	}

	toHtml(mixed) {
		return this.parse(mixed, 0);
	}

	escapeRegExp(string) {
		return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
	}

	/**
	 *
	 * @param {String} src Path or URL to file
	 * @param {String} type File's extension
	 * @returns {Boolean}
	 */
	isFile(src, type) {
		return src.toLowerCase().match(new RegExp('\.' + type + '$')) !== null;
	}

	handleImagePreview(e, data) {
		e.preventDefault();

		this.imageViewer.show(data);
	}

	/**
	 * @param {String} string
	 * @param {Number} parserIndex
	 * @returns Array
	 */
	parseString(string, parserIndex) {
		if (this.PARSERS.length <= parserIndex) {
			return [string];
		}
		if (string === '') {
			return [string];
		}

		const matches = [...string.matchAll(this.PARSERS[parserIndex].regExp)];
		if (matches.length === 0) {
			return this.parseString(string, parserIndex + 1);
		}

		const elements = [];
		let lastIndex = 0;
		matches.forEach((match, i) => {
			// parse preceding text if there is any:
			if (match.index > lastIndex) {
				elements.push(...this.parseString(string.substring(lastIndex, match.index), parserIndex + 1));
			}

			const callBackResult = this.PARSERS[parserIndex].callback(match, i, parserIndex);
			elements.push(callBackResult);

			lastIndex = match.index + match[0].length;
		});

		// parse the remaining text if there is any:
		if (string.length > lastIndex) {
			elements.push(...this.parseString(string.substring(lastIndex), parserIndex + 1));
		}

		return elements;
	}

	parse(children, key) {
		if (typeof children === 'string') {
			return this.parseString(children, 0);
		} else if (React.isValidElement(children) && (children.type !== 'a') && (children.type !== 'button')) {
			return React.cloneElement(children, {key: key}, this.parse(children.props.children));
		} else if (Array.isArray(children)) {
			return children.map((child, i) => this.parse(child, i));
		}

		return children;
	}

}