How do I render a $view with createTemplateObject?

Jaxel

Well-known member
As you know... I have XenPorta... The way it works, is that it collects the module information, and then builds the modules into HTML so that it can be embedded in the portal page...

Code:
<?php

class EWRporta_ViewPublic_Portal extends XenForo_ViewPublic_Base
{
    public function renderHtml()
    {
        $blocks = array('top' => array(), 'bottom' => array(), 'left' => array(), 'center' => array(), 'right' => array());

        foreach ($this->_params['modules'] AS $module)
        {
            switch ($module['module_position'])
            {
                case "top":        $blocks['top'][] = $this->createTemplateObject($module['template'], $module['params']);        break;
                case "bottom":    $blocks['bottom'][] = $this->createTemplateObject($module['template'], $module['params']);    break;
                case "left":    $blocks['left'][] = $this->createTemplateObject($module['template'], $module['params']);    break;
                case "center":    $blocks['center'][] = $this->createTemplateObject($module['template'], $module['params']);    break;
                case "right":    $blocks['right'][] = $this->createTemplateObject($module['template'], $module['params']);    break;
            }
        }

        $this->_params['blocks'] = $blocks;
    }
}

The issue, is that since these createTemplateObject call are embedded into an existing $view, they don't have a $view of their own. While in most cases, this is perfectly fine, it creates issues with my Recent News Module Block and custom BBcode that require templates. For instance, using the QUOTE tag... it uses a !$view fallback, which doesn't look anywhere near as good as the $view version.

How would I get it so that these advanced BBcodes that require templates are processed in full, instead of the fallback?
 
From what I understand, you want the modules to have a separate View class ?
(inheriting from XenForo_ViewPublic_Base)

You'd need a View renderer object for that.
One is available as a protected member field ($_renderer) so you can try reusing it.

PHP:
$content = $this->_renderer->renderView($module['view_class'], $module['params'], $module['template']);
 
Okay... so I tried:
Code:
$object = $this->_renderer->renderView($module['module_name'], $module['params'], $module['template']);
instead of:
Code:
$object = $this->createTemplateObject($module['template'], $module['params']);

Unfortunately, this still doesn't render advanced BBcodes like QUOTE and CODE.
 
Where are you rendering the bbcodes and how? If you are already rendering it from inside a view, no matter which, you can pass the current view object to the bbcode formatter:
PHP:
$bbCodeFormatter = XenForo_BbCode_Formatter_Base::create('Base', array('view' => $this));
$bbCodeParser = new XenForo_BbCode_Parser($bbCodeFormatter);


Btw, could you point out the files from your addon (I haven't installed yet) responsible for rendering a module and a sample module which exhibits this behavior? I could then take a closer look and find a solution; instead of just taking educated guesses without checking the actual source! :)
 
The BBcode is rendered in a model:
Code:
<?php

class EWRporta_Block_RecentNews extends XenForo_Model
{
	public function getModule($options)
	{
		$news = $this->_getDb()->fetchAll("
			SELECT xf_thread.*, xf_user.*, xf_post.message, xf_post.post_id, xf_node.title AS node_title
				FROM xf_thread
				LEFT JOIN xf_user ON (xf_user.user_id = xf_thread.user_id)
				LEFT JOIN xf_post ON (xf_post.post_id = xf_thread.first_post_id)
				LEFT JOIN xf_node ON (xf_node.node_id = xf_thread.node_id)
			WHERE xf_thread.node_id IN (".$options['recentnews_forum'].")
				AND xf_thread.discussion_state = 'visible'
			ORDER BY xf_thread.post_date DESC
			LIMIT ?
		", $options['recentnews_limit']);

		foreach ($news AS &$post)
		{
			$bbCodeParser = new XenForo_BbCode_Parser(XenForo_BbCode_Formatter_Base::create());

			$post = $this->getThumb($post);
			$post['HTML'] = preg_replace('#\n{3,}#', "\n\n", $post['message']);
			$post['HTML'] = XenForo_Helper_String::autoLinkBbCode($post['HTML']);

			if (preg_match('#(.+)\[prebreak\].*?\[/prebreak\]#si', $post['HTML'], $matches))
			{
				$post['HTML'] = trim($matches[1]);
			}
			else
			{
				$post['HTML'] = substr($post['HTML'], 0, $options['recentnews_truncate']);
			}

			$post['HTML'] = $bbCodeParser->render($post['HTML']);
		}

        return $news;
	}

	public function getThumb($post)
	{
		$attachments = $this->getModelFromCache('XenForo_Model_Attachment')->getAttachmentsByContentId('post', $post['post_id']);

		foreach ($attachments AS $attach)
		{
			$attach = $this->getModelFromCache('XenForo_Model_Attachment')->prepareAttachment($attach);

			if ($attach['thumbnailUrl'])
			{
				$post['attach'] = $attach;
				$post['message'] = preg_replace('#\[attach[\w=]*\]'.$attach['attachment_id'].'\[/attach\]#i', '', $post['message']);
				return $post;
			}
		}

		if (preg_match('#\[medio\](\d+)\[/medio\]#i', $post['message'], $matches))
		{
			if ($post['medio'] = $this->getModelFromCache('EWRmedio_Model_Media')->getMediaByID($matches[1]))
			{
				$post['message'] = preg_replace('#\[medio\]'.$matches[1].'\[/medio\]#i', '', $post['message']);
				return $post;
			}
		}

		if (preg_match('#\[img\](.+?)\[/img\]#i', $post['message'], $matches))
		{
			$post['image'] = $matches[1];
			$post['message'] = preg_replace('#\[img\]'.$matches[1].'\[/img\]#i', '', $post['message']);
			return $post;
		}

		return $post;
	}
}
 
Rendering BB code is not a model behavior - it needs to be done in the view, as it is view/display logic. Shadab's explanation is correct and you won't be able to properly render BB code until the code is refactored to that.
 
Then I'm at a complete loss on how to do this... since the modules are modularized, there is no real way to embed them directly into the view...

Code:
<?php

class EWRporta_ViewPublic_Portal extends XenForo_ViewPublic_Base
{
	public function renderHtml()
	{
		$modulesModel = new EWRporta_Model_Modules;
		$settingsModel = new EWRporta_Model_Settings;
		$cacheModel = new EWRporta_Model_Cache;

		$modules = $modulesModel->getModules();
		$settings = $settingsModel->getSettings();
		$blocks = array('top' => array(), 'bottom' => array(), 'left' => array(), 'center' => array(), 'right' => array());

		foreach ($modules AS $module)
		{
			$template = 'EWRporta_Block_'.$module['module_name'];
			$cache = $cacheModel->getCacheByName($module['module_name']);
			$params = array('option' => array());

			if (strtotime($module['module_cache'], $cache['module_date']) < time())
			{
				if (!empty($settings[$module['module_name']]))
				{
					$params['option'] = $settings[$module['module_name']];
				}

				if (XenForo_Application::autoload($template))
				{
					$model = new $template;
					$params[$module['module_name']] = $model->getModule($params['option']);
				}

				$cacheModel->buildModuleCache($module, serialize($params));
			}
			else
			{
				$params = unserialize($cache['module_settings']);
			}

			$object = $this->createTemplateObject($template, $params);

			switch ($module['module_position'])
			{
				case "top":			$blocks['top'][] = $object;			break;
				case "bottom":		$blocks['bottom'][] = $object;		break;
				case "left":		$blocks['left'][] = $object;		break;
				case "center":		$blocks['center'][] = $object;		break;
				case "right":		$blocks['right'][] = $object;		break;
			}
		}

		$this->_params['blocks'] = $blocks;
	}
}
 
I have tried passing the view as $this into the module, but that didnt work because it faisl the XFCP check:
Code:
$params[$module['module_name']] = $model->getModule($params['option'], $this);
 
Okay... I think I came up with a clever solution... in the actual module model, I put the following:
Code:
$options['arrayBB'] = true;
or
Code:
$options['parseBB'] = true;

Then I simply added the following within my view class...
Code:
if (!empty($params['option']['parseBB']))
{
	$bbCodeParser = new XenForo_BbCode_Parser(XenForo_BbCode_Formatter_Base::create('Base', array('view' => $this)));
	$params[$module['module_name']]['HTML'] = new XenForo_BbCode_TextWrapper($params[$module['module_name']]['HTML'], $bbCodeParser);
}
else if (!empty($params['option']['arrayBB']))
{
	foreach ($params[$module['module_name']] AS &$item)
	{
		$bbCodeParser = new XenForo_BbCode_Parser(XenForo_BbCode_Formatter_Base::create('Base', array('view' => $this)));
		$item['HTML'] = new XenForo_BbCode_TextWrapper($item['HTML'], $bbCodeParser);
	}
}

Basically, the mod just tells the view parser whether or not it needs BBcode parsed... it can even handle an array of data if the option is set. Of course, it will only parse data in the HTML index; so it needs to be controlled. But this worked!
 
Hmm... I found an issue...
http://www.8wayrun.com/portal/

Scroll down the last recent news post and you will see the following:
jaxel said:
This and below is a test!
Code:
testing CODE
View attachment 3211
Thats odd isn't it? It parses the QUOTE and the CODE tags without issue; but then it treats the ATTACH as it doesn't have a $view? Is there a reason why this happens? I've determined it has something to do with "$rendererStates", but I have no idea how that stuff works.
 
Look at how it's done in XenForo_ViewPublic_Thread_View::renderHtml()
Check the attachment viewing permission in a controller, and pass it as a bbcode option via "viewAttachments" key.
 
Interesting... I tried the following:
Code:
$bbCodeParser = new XenForo_BbCode_Parser(XenForo_BbCode_Formatter_Base::create('Base', array('view' => $this)));
$bbCodeOptions = array(
	'states' => array(
		'viewAttachments' => true
	)
);
XenForo_ViewPublic_Helper_Message::bbCodeWrapMessages($params[$module['module_name']], $bbCodeParser, $bbCodeOptions);

Still doesnt work... still getting "View Attachment 3211"

the "viewAttachments" state only tells whether a user can view an attachment or not... Its still failing the "attachment" state.
 
I got it... I satisfied the "attachment" state by preparing my attachments in the module itself:
Code:
foreach ($this->getModelFromCache('XenForo_Model_Attachment')->getAttachmentsByContentId('post', $post['post_id']) AS $attachmentId => $attachment)
{
	$post['attachments'][$attachmentId] = $this->getModelFromCache('XenForo_Model_Attachment')->prepareAttachment($attachment);
}
 
Top Bottom