XF 2.1 Display a thread post in xenforo's page

Neoblizz

Member
I shared this code in another thread, I have a working implementation but have a question regarding how to improve it:

Location: src/addons/Pages/Providers.php
PHP:
<?php

/*
* This file is part of a XenForo add-on.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Pages;

/**
* Static methods for the Providers page.
*/
class Providers
{

    /**
     * @param \XF\Pub\Controller\AbstractController $controller
     * @param \XF\Mvc\Reply\AbstractReply           &$reply
     */
    public static function getData(
        \XF\Pub\Controller\AbstractController $controller,
        \XF\Mvc\Reply\AbstractReply &$reply
    ) {
        $active = 1;
        if ($reply instanceof \XF\Mvc\Reply\View) {
                $finder = \XF::finder('XF:Thread');
                $thread = $finder->where('thread_id', $active)->fetchOne();
                $firstPost = \XF::app()->finder('XF:Post')->where('post_id', $thread['first_post_id'])->fetchOne();
                $viewParams = [
                    'title' => $thread['title'],
                    'message' => $firstPost['message']
                ];
                $reply->setParams($viewParams);
        }
    }
}

And in the nodes, I created a page and using the php callback provided within the page node I have: Pages\Providers as my class and getData as my method. The following screenshot shows the setup:

Annotation 2019-04-29 162153.png

Now, what I am stuck at (as you can probably tell from the code) is that $active is a static variable set to be thread_id=1, so I can only display first post of thread 1 in the page. What I would like is to reuse the code again but only "send" the thread_id within the xenforo page to automatically get the post I really wanna display.

So, lets say I create a new page, I want that new page to have $active=2 and show the first post of thread_id=2. Is this possible using the PHP callback provided within pages?
 

Jeremy P

XenForo developer
Staff member
You can capture input using the filter method on the controller: $active = $controller->filter('thread_id', 'uint');

Then you can change the thread ID using a URL parameter: ?thread_id=1. Just make sure to handle when the parameter isn't set ($active will be 0).
 

Neoblizz

Member
You can capture input using the filter method on the controller: $active = $controller->filter('thread_id', 'uint');

Then you can change the thread ID using a URL parameter: ?thread_id=1. Just make sure to handle when the parameter isn't set ($active will be 0).
Thank you! This worked. I am wondering if I can do this without URL parameter? Maybe through xf variable set?
 

Jeremy P

XenForo developer
Staff member
I'm not sure what you mean by through a variable set. Where are you trying to store/retrieve the thread ID from?
 

Neoblizz

Member
I'm not sure what you mean by through a variable set. Where are you trying to store/retrieve the thread ID from?
Within the Xenforo Node (Page). URL worked pretty nice actually, but it will be useful to know if I can store this variable (say in a template or a page) and then retrieve it in a function later on.
 

Wildcat Media

Well-known member
A bump. This is almost what I was looking for.

I'm not sure what you mean by through a variable set. Where are you trying to store/retrieve the thread ID from?
I think he meant a way to store the thread ID in a variable within the template. Which would be like <xf:set var="$my_thread_id" value="333" />. Where "333" would be the thread ID we'd enter manually for the page we're creating.

So that's easy enough. But, what would your code above need to be altered to? You offered this to retrieve the ID from a URL: $active = $controller->filter('thread_id', 'uint'); . Similar code to retrieve the variable from the template and not the URL, in other words.
 

Wildcat Media

Well-known member
You can't, as the callback is executed prior to the template being rendered.
OK, that makes sense.

Yet, entering the URL has me confused. I can't put = into "URL portion:" when I create the page.

1586570622099.png

And manually adding it to a URL does nothing but kick me back to our forum home.

This is so close--I can get the post to display if I manually enter the thread ID into Providers.php . I'm just struggling to pass this thread ID along.
 

Jeremy P

XenForo developer
Staff member
The code above allows you to link to the page with an appended thread_id. You would leave your URL portion at display-post-test (or whatever you like) and then if you wanted to link to it somewhere you would include it manually:

{{ link('display-post-test', null, {'thread_id': 40325}) }}
 

Jeremy P

XenForo developer
Staff member
Also the code in the OP could be improved upon:

PHP:
    public static function getData(
        \XF\Pub\Controller\AbstractController $controller,
        \XF\Mvc\Reply\AbstractReply &$reply
    ) {
        if ($reply instanceof \XF\Mvc\Reply\View) {
            $threadId = $controller->filter('thread_id', 'uint');
            if (!$threadId) {
                return;
            }

            $thread = \XF::app()->find('XF:Thread', $threadId, ['FirstPost']);
            $post = $thread->FirstPost;

            $reply->setParams([
                'thread' => $thread,
                'post' => $post,
            ]);
        }
    }

HTML:
<xf:if is="$post">
    {{ bb_code($post.message, 'post', $post) }}
</xf:if>

This will fetch the thread from the cache if available, use a relation to eager-load the first post to avoid an extra query, and properly pass the post entity into the bb_code function. Untested so YMMV.
 

Wildcat Media

Well-known member
Untested so YMMV.
I just tested--no errors, but no change in behavior. ;)

I included {{ link('display-post-test', null, {'thread_id': 40325}) }} and that does create a link on my test page:

1586571356014.png

If I paste that link where it belongs (https://ourtestforum.com/index.php?display-post-test&thread_id=40325)

1586571431626.png


I'm thinking that for what I'm after, it just may not be possible to do as I want with this code, unless I'm creating a separate PHP to retrieve each page, with the thread number hard-coded into each one. Luckily, we only need to create maybe a half dozen pages, so it's not completely out of the question. I would have thought there was a way to retrieve a single post and display it in a page easily, using the page node hierarchy. Unless I am totally missing something here (which is not surprising--I haven't written code in several years).
 

Wildcat Media

Well-known member
Hmmm...what about the XF callback function? Can I use that in the template instead of calling it out in the page node setup? Borrowed from another thread:

<xf:callback class="\MyOwnPage\Index" method="getHtml"></xf:callback>

EDIT: That should have a params= in it.

<xf:callback class="\MyOwnPage\Index" method="getHtml" params="[params_here]"></xf:callback>
 
Last edited:

Jeremy P

XenForo developer
Staff member
I included {{ link('display-post-test', null, {'thread_id': 40325}) }} and that does create a link on my test page:
It will only generate a URL, you'd need to wrap it in the proper HTML to create a link. Nevertheless I was a bit off anyways:
HTML:
<a href="{{ link('pages', {'node_name': 'display-post-test'}, {'thread_id': 40325}) }}">Page</a>


I'm thinking that for what I'm after, it just may not be possible to do as I want with this code, unless I'm creating a separate PHP to retrieve each page,
You can use the same callback for multiple pages. You could create a map of page node IDs to thread IDs and fetch the page entity in the callback to select the pertinent thread:
PHP:
// page node ID => thread ID
$threadMap = [
     1 => 40325
];

/** @var \XF\Entity\Page $page */
$page = $reply->getParam('page');
$threadId = isset($threadMap[$page->node_id]) ? $threadMap[$page->node_id] : null;
if (!$threadId) {
    return;
}

// ...


Hmmm...what about the XF callback function?
That's another option. You can pass params from the template to the callback. I'm not sure if you can capture the result in a variable though, so you might wind up having to do the BB code rendering in PHP.
 

Wildcat Media

Well-known member
You can use the same callback for multiple pages. You could create a map of page node IDs to thread IDs and fetch the page entity in the callback to select the pertinent thread:
That certainly could work. I will test that out in the meantime.
That's another option. You can pass params from the template to the callback. I'm not sure if you can capture the result in a variable though, so you might wind up having to do the BB code rendering in PHP.
That's a maybe...? I've been reading through numerous threads here with code examples to see what is possible. I do know the PHP code would need to be altered to accept the 'params' being passed. Wouldn't that depend more on the callback function itself?
 

Wildcat Media

Well-known member
I must have pasted the code into the wrong place. No errors, but no output either. I'll hit this tomorrow when my mind is fresh. 😐
Pasted the code properly. But I forgot to change the template to read $post.message. Amazing what four hours of sleep will do. So between that and an incorrect node ID, I do have it working this way now.

I'm still curious about the <xf:callback > function:
That's another option. You can pass params from the template to the callback. I'm not sure if you can capture the result in a variable though, so you might wind up having to do the BB code rendering in PHP.
Maybe @Chris D can fill us in on how that works. It certainly would be easier to do this right in the template through the <xf:callback> vs. having to edit my Providers.php file whenever we need to add a page.
 

Wildcat Media

Well-known member
Well, I'm back at it again. It seems that callbacks within the template will return variables, as I've seen a few code examples posted here that do the same.

But here's where I'm stuck. How do I get the params into the PHP script? I've tried several things. This is in the template:

<xf:callback class="xxxx\newProviders" method="getData" params="['42307']"></xf:callback>

This is the most recent (just a section of the code):

PHP:
    public static function getData(
        array $params,
        \XF\Pub\Controller\AbstractController $controller,
        \XF\Mvc\Reply\AbstractReply &$reply
    ) {
       
       if ($reply instanceof \XF\Mvc\Reply\View) {
            $threadId = $params[0];
            if (!$threadId) {
                return;
            }

            $thread = \XF::app()->find('XF:Thread', $threadId, ['FirstPost']);
            $post = $thread->FirstPost;

            $reply->setParams([
                'thread' => $thread,
                'post' => $post,
            ]);
        }
    }

Error:

An exception occurred: [TypeError] Argument 1 passed to xxxx\newProviders::getData() must be of the type array, string given in src/addons/xxxx/newProviders.php on line 22
 
Last edited:

Jeremy P

XenForo developer
Staff member
The method signature for a callback is: $contents, array $params, \XF\Template\Templater $templater. They return whatever you want to be output in the place they are called.
 

Wildcat Media

Well-known member
The method signature for a callback is: $contents, array $params, \XF\Template\Templater $templater. They return whatever you want to be output in the place they are called.
That worked perfectly--thanks much for the help! That will be much easier when creating a new page to display a post.

False alarm...no output. Back to troubleshooting. (I still had the page doing the callback.)
 
Last edited:

Jeremy P

XenForo developer
Staff member
The code would need to be adapted further for use in a callback, it won't just work as-is. You need to return what you want the function to render.

PHP:
$threadId = $params[0];
if (!$threadId) {
	return;
}

$thread = \XF::app()->find('XF:Thread', $threadId, ['FirstPost']);
$post = $thread->FirstPost;

return $templater->renderTemplate('public:custom_post_template', [
	'thread' => $thread,
	'post' => $post
]);

Then you could create a custom_post_template template containing:

HTML:
<xf:if is="$post">
    {{ bb_code($post.message, 'post', $post) }}
</xf:if>
 
Top