XF 2.0 xf:foreach not iterating through PHP callback array

I'm having trouble iterating through an array given by a PHP callback.

I've set up an HTML widget above my nodes to display one thread in my forum. There's not much to it but it's not iterating through any metadata I put in the array through the callback

Here is the thread I'm trying to get ahold of:
Screenshot_2.png

PHP Setup:

PHP:
<?php

namespace Madsen;

class News {

    public static function getHtml($content, $params) {

        // I will find a use for $params later

        $finder = \XF::finder('XF:Thread');
        $threads = $finder->limit(3)->where('node_id', 4)->fetchOne();

        // For now I just need one, but three will come to functionality later.

        $post = \XF::finder('XF:Post')->where('post_id', $threads->first_post_id)->fetchOne();

        $threadlib = array(
            $post->user_id,
            $post->message,
            $threads->title,
            $threads->discussion_open,
            $threads->reply_count,
            $threads->first_post_likes
        );

        return $threadlib;
    }
}

HTML Setup:

XML:
<xf:set var="$threadmeta">
    <xf:callback class="\Madsen\News" method="getHtml" params="['testparam']"></xf:callback>
</xf:set>

{$threadmeta}

<xf:foreach loop="$threadmeta" value="$data" >
    <p><b>{$data}</b></p>
</xf:foreach>

It doesn't iterate.
Result:
support1.png
Dumped:
Screenshot_5.png

As a workaround/alternative, I tried to run this setup:

PHP:

PHP:
<?php

namespace Madsen;

class News {

    public static function getHtml($content, $params) {

        // I will find a use for $params later

        $finder = \XF::finder('XF:Thread');
        $threads = $finder->limit(3)->where('node_id', 4)->fetchOne();

        // For now I just need one, but three will come to functionality later.

        $post = \XF::finder('XF:Post')->where('post_id', $threads->first_post_id)->fetchOne();

        $threadlib = self::compressArray(array(
            $post->user_id,
            $post->message,
            $threads->title,
            $threads->discussion_open,
            $threads->reply_count,
            $threads->first_post_likes
        ), false);

        return $threadlib;
    }

    private static function compressArray($array, bool $hasKeys) {

        $strng = '{{[';

        if ($hasKeys === false) {

            $i = 0;

            foreach($array as $element) {
                $strng .= '\'' . $element . '\'';
                if ($i != (count($array) - 1)) {
                    $strng .= ',';
                }
                $i++;
            }
        } else {
            // Functionality will arrive later
        }

        $strng .= "]}}";

        return $strng;

    }
}
[compressArray] is a simple function that parses it in a way I thought XenForo would recognize from straight string

HTML:

XML:
<xf:set var="$threadmeta">
    <xf:callback class="\Madsen\News" method="getHtml" params="['testparam']"></xf:callback>
</xf:set>

<p>This prints just fine</p>
<b>{$threadmeta}</b>

<xf:set var="$threadmeta2" value="{$threadmeta}" />

<p>This also prints just fine, so "value" field accepts variables enclosed in var brackets</p>
<b>{$threadmeta2}</b>


<xf:set var="$names" value="{{ ['Patrick', 'Theresa', 'Kimball', 'Wayne', 'Grace'] }}" />
<p>XenForo provided example for iteration ({$names})</p>
<xf:foreach loop="$names" key="$key" value="$name" i="$i">
    <p><b>Hello there, {$name}. This is name number {$i}. Array key of this element: {$key}</b></p>
</xf:foreach>

<p>My iteration of threadmeta2 ({$threadmeta2})</p>
<xf:foreach loop="{$threadmeta2}" value="$data" >
    <p><b>Unkeyed info {$data}</b></p>
</xf:foreach>

Result:
Screenshot_1.png

Is it a possible XenForo bug that upgrading my XenForo version would fix? Or is there some miniscule syntax problem that's preventing iteration? I'm about lost at solutions
 
Last edited:

Chris D

XenForo developer
Staff member
What you're trying to do just simply isn't within the scope of what template callbacks allow.

They are designed to return the HTML content which is returned when the template is rendered. It is a literal string and it cannot be processed as an array or template code.
 
What you're trying to do just simply isn't within the scope of what template callbacks allow.

They are designed to return the HTML content which is returned when the template is rendered. It is a literal string and it cannot be processed as an array or template code.
Thank you for such an incredibly quick response. Does this mean i'm best just hardcoding the HTML in the PHP?
 

Pawn Studios

Active member
Thank you for such an incredibly quick response. Does this mean i'm best just hardcoding the HTML in the PHP?

I found this thread while searching for solutions for implementing my feature. I just wanted to add it's not necessary to hard code the HTML in the PHP.

I have successfully gathered some data in PHP then returned it to the template as JSON using PHP's built in JSON encoder.

Since JSON is javascript code for an object, it's possible to send the callback output directly into a javascript variable in the page HTML code like this:


HTML:
<script>
var dataFromCallback = <xf:callback class="MyAddon\MyCallBackClass" method="getMyCallBackDataJSON" params="[]"></xf:callback>;
</script>

And the data can be used without any conversions.

An example php file is as follows:

PHP:
<?php
namespace MyAddon;

use XF;

class MyCallBackClass {
    public static function getMyCallBackDataJSON(string $argument, array $arguments, $templater): string {
        $someData = (object) [
                    'name' => 'XenForo',
                    'description' => 'Example'
                ];
        return \GuzzleHttp\json_encode($someData);
    }
}

Javascript will be need to be used consume your data and present it in the page.

[Could be useful for others finding this thread, searching for solutions.]
 

Jeremy P

XenForo developer
Staff member
You can also use the templater to render another template, using whatever template syntax you'd like:

PHP:
public static function getFoo(
    string $argument,
    array $arguments,
    \XF\Template\Templater $templater
): string {
    $params = [
        'name' => 'XenForo',
        'description' => 'Example',
    ];
    return $templater->renderTemplate('public:addon_foo', $params);
}
 
Top