* Replaced old user bulk processing with new implementation using the abstract bulk processing system.
* `conditionContainers` template event in template `noticeAdd.tpl` added.
* Use condition system for user search.
+* Image proxy for images included with the image BBCode.
* Overhauled Redactor integration
* Linebreaks mode instead of using paragraphs, works better with the PHP-side parser which works with linebreaks
* Ported the PHP-BBCode parser, massively improves accuracy and ensures validity
\ No newline at end of file
<category name="message.general.edit">
<parent>message.general</parent>
</category>
+ <category name="message.general.image">
+ <parent>message.general</parent>
+ </category>
<category name="message.attachment">
<parent>message</parent>
</option>
<!-- /message.general.share -->
+ <!-- message.general.image -->
+ <option name="module_image_proxy">
+ <categoryname>message.general.image</categoryname>
+ <optiontype>boolean</optiontype>
+ <defaultvalue>0</defaultvalue>
+ <enableoptions><![CDATA[image_proxy_secret]]></enableoptions>
+ </option>
+ <option name="image_proxy_secret">
+ <categoryname>message.general.image</categoryname>
+ <optiontype>text</optiontype>
+ <allowemptyvalue>0</allowemptyvalue>
+ </option>
+ <option name="image_proxy_expiration">
+ <categoryname>message.general.image</categoryname>
+ <optiontype>integer</optiontype>
+ <defaultvalue>14</defaultvalue>
+ <maxvalue>30</maxvalue>
+ <minvalue>7</minvalue>
+ </option>
+ <!-- /message.general.image -->
+
<!-- message.censorship -->
<option name="enable_censorship">
<categoryname>message.censorship</categoryname>
use wcf\system\session\SessionHandler;
use wcf\system\WCF;
use wcf\util\DateUtil;
+use wcf\util\StringUtil;
/**
* @author Marcel Werk
$statement->execute(array($timezone, 'timezone'));
}
}
+
+// set image proxy secret
+$sql = "UPDATE wcf".WCF_N."_option
+ SET optionValue = ?
+ WHERE optionName = ?";
+$statement = WCF::getDB()->prepareStatement($sql);
+$statement->execute([
+ StringUtil::getRandomID(),
+ 'image_proxy_secret'
+]);
+
$images.push({
image: {
- fullURL: $link.prop('href'),
+ fullURL: $thumbnail.data('source') ? $thumbnail.data('source').replace(/\\\//g, '/') : $link.prop('href'),
link: '',
title: $link.prop('title'),
url: $link.prop('href'),
content.innerHTML = html;
}
else if (html instanceof DocumentFragment) {
- if (html.children[0].nodeName !== 'div' || html.childElementCount > 1) {
- content.appendChild(html);
- }
- else {
- content = html.children[0];
- }
+ content.appendChild(html);
}
content.id = id;
--- /dev/null
+<?php
+namespace wcf\action;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\exception\SystemException;
+use wcf\util\FileUtil;
+use wcf\util\HTTPRequest;
+use wcf\util\StringUtil;
+
+/**
+ * Proxies requests for embedded images.
+ *
+ * @author Matthias Schmidt
+ * @copyright 2001-2015 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf
+ * @subpackage action
+ * @category Community Framework
+ */
+class ImageProxyAction extends AbstractAction {
+ /**
+ * hashed image proxy secret and image url
+ * @var string
+ */
+ public $hash = '';
+
+ /**
+ * image url
+ * @var string
+ */
+ public $url = '';
+
+ /**
+ * @see \wcf\action\IAction::readParameters()
+ */
+ public function readParameters() {
+ parent::readParameters();
+
+ if (isset($_REQUEST['url'])) $this->url = urldecode(StringUtil::trim($_REQUEST['url']));
+ if (isset($_REQUEST['hash'])) $this->hash = StringUtil::trim($_REQUEST['hash']);
+ }
+
+ /**
+ * @see \wcf\action\IAction::execute()
+ */
+ public function execute() {
+ parent::execute();
+
+ $hash = sha1(IMAGE_PROXY_SECRET.$this->url);
+ if ($this->hash != $hash) {
+ throw new IllegalLinkException();
+ }
+
+ try {
+ $request = new HTTPRequest($this->url);
+ $request->execute();
+ $reply = $request->getReply();
+
+ $fileExtension = '';
+ if (($position = mb_strrpos($this->url, '.')) !== false) {
+ $fileExtension = mb_strtolower(mb_substr($this->url, $position + 1));
+ }
+
+ // check if requested content is image
+ if (!isset($reply['headers']['Content-Type']) || !StringUtil::startsWith($reply['headers']['Content-Type'], 'image/')) {
+ throw new IllegalLinkException();
+ }
+
+ // save image
+ $fileLocation = WCF_DIR.'images/proxy/'.substr($hash, 0, 2).'/'.$hash.($fileExtension ? '.'.$fileExtension : '');
+ $dir = dirname($fileLocation);
+ if (!@file_exists($dir)) {
+ FileUtil::makePath($dir, 0777);
+ }
+ file_put_contents($fileLocation, $reply['body']);
+
+ // update mtime for correct expiration calculation
+ @touch($fileLocation);
+
+ $this->executed();
+
+ @header('Content-Type: '.$reply['headers']['Content-Type']);
+ @readfile($fileLocation);
+ exit;
+ }
+ catch (SystemException $e) {
+ throw new IllegalLinkException();
+ }
+ }
+}
<?php
namespace wcf\system\bbcode;
use wcf\util\StringUtil;
+use wcf\system\WCF;
+use wcf\system\request\LinkHandler;
/**
* Parses the [img] bbcode tag.
}
if ($parser->getOutputType() == 'text/html') {
+ $dataSource = '';
+ if (MODULE_IMAGE_PROXY) {
+ $dataSource = $src;
+ $src = $this->getProxyLink($src);
+ }
+
$float = '';
if (isset($openingTag['attributes'][1])) {
$float = $openingTag['attributes'][1];
$style .= 'width: ' . $width . 'px;';
}
- return '<img src="'.$src.'" class="jsResizeImage" alt=""'.($style ? ' style="' . $style . '"' : '').' />';
+ return '<img src="'.$src.'" class="jsResizeImage" alt=""'.($style ? ' style="' . $style . '"' : '').($dataSource ? ' data-source="'.StringUtil::encodeJS($dataSource).'"' : '').' />';
}
else if ($parser->getOutputType() == 'text/simplified-html') {
$src = StringUtil::decodeHTML($src);
return '';
}
}
+
+ /**
+ * Returns the link to the cached image (or the link to fetch the image
+ * using the image proxy).
+ *
+ * @param string $link
+ * @return string
+ */
+ protected function getProxyLink($link) {
+ $hash = sha1(IMAGE_PROXY_SECRET.$link);
+ $fileExtension = '';
+ if (($position = mb_strrpos($link, '.')) !== false) {
+ $fileExtension = mb_strtolower(mb_substr($link, $position + 1));
+ }
+
+ $path = 'images/proxy/'.substr($hash, 0, 2).'/'.$hash.($fileExtension ? '.'.$fileExtension : '');
+
+ $fileLocation = WCF_DIR.$path;
+ if (file_exists($fileLocation)) {
+ return WCF::getPath().$path;
+ }
+
+ return LinkHandler::getInstance()->getLink('ImageProxy', [
+ 'hash' => $hash,
+ 'url' => urlencode($link)
+ ]);
+ }
}
}
}
}));
+
+ // clean up proxy images
+ if (MODULE_IMAGE_PROXY) {
+ DirectoryUtil::getInstance(WCF_DIR.'images/proxy/')->executeCallback(new Callback(function($filename, $object) {
+ if ($object->isFile() && $object->getMTime() < TIME_NOW - 86400 * IMAGE_PROXY_EXPIRATION) {
+ @unlink($filename);
+ }
+ }));
+ }
}
}