<definitionname>com.woltlab.wcf.message.embeddedObject</definitionname>
<classname><![CDATA[wcf\system\message\embedded\object\AttachmentMessageEmbeddedObjectHandler]]></classname>
</type>
+ <type>
+ <name>com.woltlab.wcf.page</name>
+ <definitionname>com.woltlab.wcf.message.embeddedObject</definitionname>
+ <classname><![CDATA[wcf\system\message\embedded\object\PageMessageEmbeddedObjectHandler]]></classname>
+ </type>
<!-- embedded object handlers -->
<type>
{@$content->getFormattedContent()}
</section>
{elseif $page->pageType == 'html'}
- {@$content->content}
+ {@$content->getParsedContent()}
{elseif $page->pageType == 'tpl'}
- {include file=$page->getTplName($contentLanguageID)}
+ {@$page->getParsedTemplate($content)}
{/if}
{/if}
*/
public function getLink() {
if ($this->controller) {
- // todo
+ // todo: use a unified method for this
$controllerParts = explode('\\', $this->controller);
$controllerName = $controllerParts[count($controllerParts) - 1];
$controllerName = preg_replace('/(page|action|form)$/i', '', $controllerName);
return $this->boxIDs;
}
+ /**
+ * Returns the parsed template.
+ *
+ * @param PageContent $pageContent page content
+ * @return string parsed template
+ */
+ public function getParsedTemplate(PageContent $pageContent) {
+ return $pageContent->getParsedTemplate($this->getTplName($pageContent->languageID));
+ }
+
/**
* Returns the template name of this page.
*
use wcf\data\IToggleAction;
use wcf\system\exception\PermissionDeniedException;
use wcf\system\exception\UserInputException;
+use wcf\system\html\simple\HtmlSimpleParser;
use wcf\system\message\embedded\object\MessageEmbeddedObjectManager;
use wcf\system\page\handler\ILookupPageHandler;
use wcf\system\WCF;
$pageContentEditor->update(['hasEmbeddedObjects' => 1]);
}
}
+ else if ($page->pageType == 'html' || $page->pageType == 'tpl') {
+ HtmlSimpleParser::getInstance()->parse('com.woltlab.wcf.page.content', $pageContent->pageContentID, $pageContent->content);
+ }
}
}
$pageContentEditor->update(['hasEmbeddedObjects' => ($pageContent->hasEmbeddedObjects ? 0 : 1)]);
}
}
+ else if ($page->pageType == 'html' || $page->pageType == 'tpl') {
+ HtmlSimpleParser::getInstance()->parse('com.woltlab.wcf.page.content', $pageContent->pageContentID, $pageContent->content);
+ }
}
// save template
namespace wcf\data\page\content;
use wcf\data\DatabaseObject;
use wcf\system\html\output\HtmlOutputProcessor;
+use wcf\system\html\simple\HtmlSimpleParser;
use wcf\system\message\embedded\object\MessageEmbeddedObjectManager;
+use wcf\system\template\plugin\SimpledEmbeddedObjectPrefilterTemplatePlugin;
use wcf\system\WCF;
/**
return $processor->getHtml();
}
+ /**
+ * Parses simple placeholders embedded in raw html.
+ *
+ * @return string parsed content
+ */
+ public function getParsedContent() {
+ MessageEmbeddedObjectManager::getInstance()->loadObjects('com.woltlab.wcf.page.content', [$this->pageContentID]);
+
+ return HtmlSimpleParser::getInstance()->replaceTags('com.woltlab.wcf.page.content', $this->pageContentID, $this->content);
+ }
+
+ /**
+ * Parses simple placeholders embedded in HTML with template scripting.
+ *
+ * @param string $templateName content template name
+ * @return string parsed template
+ */
+ public function getParsedTemplate($templateName) {
+ MessageEmbeddedObjectManager::getInstance()->loadObjects('com.woltlab.wcf.page.content', [$this->pageContentID]);
+ HtmlSimpleParser::getInstance()->setContext('com.woltlab.wcf.page.content', $this->pageContentID);
+
+ WCF::getTPL()->registerPrefilter(['simpleEmbeddedObject']);
+
+ $returnValue = WCF::getTPL()->fetch($templateName);
+
+ WCF::getTPL()->removePrefilter('simpleEmbeddedObject');
+
+ return $returnValue;
+ }
+
/**
* Returns a certain page content.
*
use wcf\data\page\content\PageContent;
use wcf\data\page\Page;
use wcf\system\exception\IllegalLinkException;
+use wcf\system\html\simple\HtmlSimpleParser;
use wcf\system\language\LanguageFactory;
use wcf\system\request\LinkHandler;
use wcf\system\request\RequestHandler;
--- /dev/null
+<?php
+namespace wcf\system\html\simple;
+use wcf\system\message\embedded\object\ISimpleMessageEmbeddedObjectHandler;
+use wcf\system\message\embedded\object\MessageEmbeddedObjectManager;
+use wcf\system\SingletonFactory;
+
+/**
+ * Parses content for simple placeholders.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2016 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\System\Message\Embedded\Object
+ * @since 3.0
+ */
+class HtmlSimpleParser extends SingletonFactory {
+ /**
+ * embedded object context
+ * @var array
+ */
+ protected $context = [
+ 'objectType' => '',
+ 'objectID' => 0
+ ];
+ /**
+ * @var ISimpleMessageEmbeddedObjectHandler[]
+ */
+ protected $handlers = [];
+
+ /**
+ * regex for simple placeholders
+ * @var string
+ */
+ protected $regexHandlers = '~{{\ ((?:[a-z][a-zA-Z]+="(?:\\"|[^"])+?"\ ?)*)\ }}~';
+
+ /**
+ * @inheritDoc
+ */
+ protected function init() {
+ $this->handlers = MessageEmbeddedObjectManager::getInstance()->getSimpleMessageEmbeddedObjectHandlers();
+ }
+
+ /**
+ * Sets the embedded object context.
+ *
+ * @param string $objectType object type identifier
+ * @param integer $objectID object id
+ */
+ public function setContext($objectType, $objectID) {
+ MessageEmbeddedObjectManager::getInstance()->setActiveMessage($objectType, $objectID);
+
+ $this->context = [
+ 'objectType' => $objectType,
+ 'objectID' => $objectID
+ ];
+ }
+
+ /**
+ * Parses a message to identify any embedded content using simple placeholders.
+ *
+ * @param string $objectType object type identifier
+ * @param integer $objectID object id
+ * @param string $message message content
+ * @return boolean true if there is at least one embedded content found
+ */
+ public function parse($objectType, $objectID, $message) {
+ preg_match_all($this->regexHandlers, $message, $matches);
+
+ $data = [];
+ foreach ($matches[1] as $attributesString) {
+ $attributes = $this->parseAttributes($attributesString);
+ $handler = $attributes['handler'];
+
+ if (!isset($this->handlers[$handler])) {
+ // unknown handler, ignore
+ continue;
+ }
+
+ if (!isset($data[$handler])) {
+ $data[$handler] = [];
+ }
+
+ $data[$handler][] = $attributes['value'];
+ }
+
+ $embeddedContent = [];
+ foreach ($data as $handler => $values) {
+ $values = $this->handlers[$handler]->validateValues($objectType, $objectID, $values);
+ if (!empty($values)) {
+ $embeddedContent[$this->handlers[$handler]->objectTypeID] = $values;
+ }
+ }
+
+ return MessageEmbeddedObjectManager::getInstance()->registerSimpleObjects($objectType, $objectID, $embeddedContent);
+ }
+
+ /**
+ * Replaces simple placeholders with embedded content data.
+ *
+ * @param string $objectType object type identifier
+ * @param integer $objectID object id
+ * @param string $message message content
+ * @return string parsed and replaced string
+ */
+ public function replaceTags($objectType, $objectID, $message) {
+ MessageEmbeddedObjectManager::getInstance()->setActiveMessage($objectType, $objectID);
+ $this->setContext($objectType, $objectID);
+
+ return preg_replace_callback($this->regexHandlers, function ($matches) use ($objectType, $objectID) {
+ $data = $this->parseAttributes($matches[1]);
+
+ return $this->replaceTag($data);
+ }, $message);
+ }
+
+ /**
+ * Replaces a placeholder.
+ *
+ * @param array $data placeholder data
+ * @return string placeholder replacement
+ */
+ public function replaceTag(array $data) {
+ $handler = $data['handler'];
+
+ if (!isset($this->handlers[$handler])) {
+ // unknown handler, return raw value
+ return $data['raw'];
+ }
+
+ $value = $this->handlers[$handler]->replaceSimple($this->context['objectType'], $this->context['objectID'], $data['value'], $data['attributes']);
+ if ($value === null) {
+ // invalid value
+ return $data['raw'];
+ }
+
+ return $value;
+ }
+
+ /**
+ * Parses the template by replacing the simple embedded object syntax
+ * with a custom template plugin. This step ensures proper replacement
+ * without causing conflicts with existing syntax.
+ *
+ * @param string $template template content
+ * @return string template content with custom template plugin
+ */
+ public function parseTemplate($template) {
+ return preg_replace_callback($this->regexHandlers, function ($matches) {
+ $data = $this->parseAttributes($matches[1]);
+ $handler = $data['handler'];
+
+ if (!isset($this->handlers[$handler])) {
+ // unknown handler, return raw value
+ return $matches[0];
+ }
+
+ return '{embeddedObject}' . base64_encode(serialize($data)) . '{/embeddedObject}';
+ }, $template);
+ }
+
+ /**
+ * Parses the attribute string and return individual components.
+ *
+ * @param string $attributesString attributes string, e.g. `foo="1" bar="baz"`
+ * @return array list of individual components
+ */
+ protected function parseAttributes($attributesString) {
+ preg_match_all('~([a-z][a-zA-Z]+)="((?:\\\\"|[^"])+?)"~', $attributesString, $attributes);
+
+ $additionalAttributes = [];
+ for ($i = 1, $length = count($attributes[0]); $i < $length; $i++) {
+ $additionalAttributes[$attributes[1][$i]] = $attributes[2][$i];
+ }
+
+ return [
+ 'attributes' => $additionalAttributes,
+ 'handler' => $attributes[1][0],
+ 'raw' => '{{ ' . $attributesString . ' }}',
+ 'value' => $attributes[2][0]
+ ];
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\message\embedded\object;
+use wcf\system\html\input\HtmlInputProcessor;
+
+/**
+ * Provides default implementations for simple message embedded object handlers.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2016 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\System\Message\Embedded\Object
+ */
+abstract class AbstractSimpleMessageEmbeddedObjectHandler extends AbstractMessageEmbeddedObjectHandler implements ISimpleMessageEmbeddedObjectHandler {
+ /**
+ * @inheritDoc
+ */
+ public function parse(HtmlInputProcessor $htmlInputProcessor, array $embeddedData) {
+ // this default implementation allows for embedded object handlers that
+ // only handle the simplified syntax
+ return [];
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\message\embedded\object;
+
+/**
+ * Default interface of simple embedded object handler.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2016 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\System\Message\Embedded\Object
+ */
+interface ISimpleMessageEmbeddedObjectHandler extends IMessageEmbeddedObjectHandler {
+ /**
+ * Validates the provided values for existence and returns the filtered list.
+ *
+ * @param string $objectType object type identifier
+ * @param integer $objectID object id
+ * @param integer[] $values list of value ids
+ * @return integer[] filtered list
+ */
+ public function validateValues($objectType, $objectID, array $values);
+
+ /**
+ * Returns replacement string for simple placeholders. Must return `null`
+ * if no replacement should be performed due to invalid or missing arguments.
+ *
+ * @param string $objectType object type identifier
+ * @param integer $objectID object id
+ * @param integer $value value id
+ * @param array $attributes list of additional attributes
+ * @return string|null replacement string or null if value id is unknown
+ */
+ public function replaceSimple($objectType, $objectID, $value, array $attributes);
+}
return $returnValue;
}
+ /**
+ * Registers the embedded objects found in a message using the simplified syntax.
+ *
+ * @param string $messageObjectType object type identifier
+ * @param integer $messageID object id
+ * @param integer[][] $embeddedContent list of object ids for embedded objects by object type id
+ * @return boolean true if at least one embedded object was found
+ */
+ public function registerSimpleObjects($messageObjectType, $messageID, array $embeddedContent) {
+ $messageObjectTypeID = ObjectTypeCache::getInstance()->getObjectTypeIDByName('com.woltlab.wcf.message', $messageObjectType);
+
+ // delete existing assignments
+ $this->removeObjects($messageObjectType, [$messageID]);
+
+ if (empty($embeddedContent)) {
+ return false;
+ }
+
+ // prepare statement
+ $sql = "INSERT INTO wcf".WCF_N."_message_embedded_object
+ (messageObjectTypeID, messageID, embeddedObjectTypeID, embeddedObjectID)
+ VALUES (?, ?, ?, ?)";
+ $statement = WCF::getDB()->prepareStatement($sql);
+
+ // call embedded object handlers
+ WCF::getDB()->beginTransaction();
+ foreach ($embeddedContent as $objectTypeID => $objectIDs) {
+ foreach ($objectIDs as $objectID) {
+ $statement->execute([$messageObjectTypeID, $messageID, $objectTypeID, $objectID]);
+ }
+ }
+ WCF::getDB()->commitTransaction();
+
+ return true;
+ }
+
/**
* Removes embedded object assigments for given messages.
*
* @param integer[] $messageIDs
*/
public function loadObjects($messageObjectType, array $messageIDs) {
+ $messageObjectTypeID = ObjectTypeCache::getInstance()->getObjectTypeIDByName('com.woltlab.wcf.message', $messageObjectType);
+ if ($messageObjectTypeID === null) {
+ throw new \UnexpectedValueException("Expected a valid object type for definition 'com.woltlab.wcf.message'.");
+ }
+
$conditionBuilder = new PreparedStatementConditionBuilder();
- $conditionBuilder->add('messageObjectTypeID = ?', [ObjectTypeCache::getInstance()->getObjectTypeIDByName('com.woltlab.wcf.message', $messageObjectType)]);
+ $conditionBuilder->add('messageObjectTypeID = ?', [$messageObjectTypeID]);
$conditionBuilder->add('messageID IN (?)', [$messageIDs]);
// get object ids
}
}
+ /**
+ * @return ISimpleMessageEmbeddedObjectHandler[];
+ */
+ public function getSimpleMessageEmbeddedObjectHandlers() {
+ $handlers = [];
+ foreach ($this->getEmbeddedObjectHandlers() as $handler) {
+ if ($handler instanceof ISimpleMessageEmbeddedObjectHandler) {
+ $name = lcfirst(preg_replace('~^.*\\\\([A-Z][a-zA-Z]+)MessageEmbeddedObjectHandler$~', '$1', get_class($handler)));
+ $handlers[$name] = $handler;
+ }
+ }
+
+ return $handlers;
+ }
+
/**
* Returns all embedded object handlers.
*
- * @return array
+ * @return IMessageEmbeddedObjectHandler[]
*/
protected function getEmbeddedObjectHandlers() {
if ($this->embeddedObjectHandlers === null) {
* Returns a specific embedded object handler.
*
* @param integer $objectTypeID
- * @return object
+ * @return IMessageEmbeddedObjectHandler
*/
protected function getEmbeddedObjectHandler($objectTypeID) {
$this->getEmbeddedObjectHandlers();
--- /dev/null
+<?php
+namespace wcf\system\message\embedded\object;
+use wcf\data\page\Page;
+use wcf\data\page\PageCache;
+
+/**
+ * Parses embedded pages and outputs their link or title.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2016 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\System\Message\Embedded\Object
+ */
+class PageMessageEmbeddedObjectHandler extends AbstractSimpleMessageEmbeddedObjectHandler {
+ /**
+ * @inheritDoc
+ */
+ public function loadObjects(array $objectIDs) {
+ $pages = [];
+
+ foreach ($objectIDs as $objectID) {
+ $page = PageCache::getInstance()->getPage($objectID);
+ if ($page !== null) {
+ $pages[$objectID] = $page;
+ }
+ }
+
+ return $pages;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function validateValues($objectType, $objectID, array $values) {
+ return array_filter($values, function($value) {
+ return (PageCache::getInstance()->getPage($value) !== null);
+ });
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function replaceSimple($objectType, $objectID, $value, array $attributes) {
+ /** @var Page $page */
+ $page = MessageEmbeddedObjectManager::getInstance()->getObject('com.woltlab.wcf.page', $value);
+ if ($page === null) {
+ return null;
+ }
+
+ $return = (!empty($attributes['return'])) ? $attributes['return'] : 'link';
+ switch ($return) {
+ case 'title':
+ return $page->getTitle();
+ break;
+
+ case 'link':
+ default:
+ return $page->getLink();
+ break;
+ }
+ }
+}
}
}
+ /**
+ * Removes a prefilter by its internal name.
+ *
+ * @param string $name internal prefilter identifier
+ */
+ public function removePrefilter($name) {
+ unset($this->prefilters[$name]);
+ }
+
/**
* Sets the dir for the compiled templates.
*
--- /dev/null
+<?php
+namespace wcf\system\template\plugin;
+use wcf\system\html\simple\HtmlSimpleParser;
+use wcf\system\template\TemplateEngine;
+
+/**
+ * Template block plugin handling embedded object data.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2016 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\System\Template\Plugin
+ * @since 3.0
+ */
+class EmbeddedObjectBlockTemplatePlugin implements IBlockTemplatePlugin {
+ /**
+ * internal loop counter
+ * @var integer
+ */
+ protected $counter = 0;
+
+ /**
+ * @inheritDoc
+ */
+ public function execute($tagArgs, $blockContent, TemplateEngine $tplObj) {
+ $data = unserialize(base64_decode($blockContent));
+
+ return HtmlSimpleParser::getInstance()->replaceTag($data);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function init($tagArgs, TemplateEngine $tplObj) {
+ $this->counter = 0;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function next(TemplateEngine $tplObj) {
+ if ($this->counter == 0) {
+ $this->counter++;
+ return true;
+ }
+
+ return false;
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\template\plugin;
+use wcf\system\html\simple\HtmlSimpleParser;
+use wcf\system\template\TemplateScriptingCompiler;
+
+/**
+ * Template prefiler plugin that replaces simple embedded object placeholders. Not to be meant for
+ * regular use, is currently only utilized in `wcf\data\page\content\PageContent::getParsedTemplate()`.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2016 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\System\Template\Plugin
+ * @since 3.0
+ */
+class SimpleEmbeddedObjectPrefilterTemplatePlugin implements IPrefilterTemplatePlugin {
+ /**
+ * @inheritDoc
+ */
+ public function execute($templateName, $sourceContent, TemplateScriptingCompiler $compiler) {
+ return HtmlSimpleParser::getInstance()->parseTemplate($sourceContent);
+ }
+}