Page php callback. My first Xenforo coding. Help!

Stuart Wright

Well-known member
Hello folks,
so far I've outsourced all our coding to gurus like Chris Deeming but now I want to have a go myself.

I'm trying to reproduce a page from our vBulletin site which assigns tokens stored in a table to users who visit the page and then creates a link to a retailer website which applies free delivery to their shopping account. Each user is assigned a token and their userid is written into the table.
scan_tokens:
userid int(10) UNSIGNED
token char(64)
assigned timestamp

This is the old code from vBulletin which I wrote:
PHP:
<?php
    $allowed_usergroups = array(6, 2, 7, 5, 11, 24, 25);
    if ($vbulletin->userinfo['userid'] == "") {   // guest
        $HTML .= "<p align='center'>In order to qualify for the SCAN & AVForums loyalty programme you need to login to your AVForums account.<br>Please login (and register first if you need to) <a href='http://www.avforums.com/forums/clogin.php' target='_blank'>here</a>.</p>";
    } elseif (!in_array($vbulletin->userinfo['usergroupid'],$allowed_usergroups)) {
        $HTML .= "<p align='center'>In order to qualify for the SCAN & AVForums loyalty programme you need to be a member of AVForums and logged in to your activated AVForums account.<br>Please login (and register first if you need to) <a href='http://www.avforums.com' target='_blank'>here</a>.</p>";
    } else {
        $HTML .= "<h3 align='center'>Welcome " . $vbulletin->userinfo['username'] . ".</p>";

        $sql = "SELECT token FROM scan_tokens WHERE userid = " . $vbulletin->userinfo['userid'] . " LIMIT 1";
        $result = $db->query_read($sql);
        if (!$result) {
            $HTML .= "<p align='center'>There was a problem finding your token.  Please notify AVForums admin.";
            exit();
        }
        if (mysql_num_rows($result) == 1) {
            extract(mysql_fetch_array($result,MYSQL_ASSOC));   
            if (strlen($token) >= 60) {
                $HTML .= "<P align='center'>Good news - you qualify for the SCAN & AVForums loyalty programme!<br>Click <a href='https://secure.scan.co.uk/aspnet/myaccount/avfreecarriage.aspx?uid=$token' target='_blank'>here</a> to visit the SCAN website and log in/register to qualify for free delivery on all orders of &pound;50 or over (excluding VAT).</p>";               
            }
        } else {  // no token found
            $sql = "SELECT token FROM scan_tokens WHERE userid = 0 LIMIT 1 FOR UPDATE";  //get token and lock record for update
            $result = $db->query_read($sql);
            if (!$result) {
                $HTML .= "<p align='center'>There was a problem assigning the Free Delivery token to your account.  Please notify AVForums admin.";
                exit();
            }
            if (mysql_num_rows($result) == 1) {
                extract(mysql_fetch_array($result,MYSQL_ASSOC));   
                if (strlen($token) >= 60) {
                    $sql = "UPDATE scan_tokens SET userid = " . $vbulletin->userinfo['userid'] . " WHERE token = '$token'";
                    $db->query($sql);   // 
                    $HTML .= "<P align='center'>Good news - you qualify for the SCAN & AVForums loyalty programme!<br>Click <a href='https://secure.scan.co.uk/aspnet/myaccount/avfreecarriage.aspx?uid=$token' target='_blank'>here</a> to visit the SCAN website and log in/register to qualify for free delivery on all orders over &pound;50 (excluding VAT).</p>";               
                }
            }
        }
    }
?>

I'm imaginig that this is dead easy to to with a php callback on a page?
I have looked at a few other examples of Xenforo coding and as you can probably see from my coding above, what I'm used to is very different. Xenforo coding is a bit foreign to me. I have never used classes, for example.
Where do I start please?
 
So basically you want something such as:

avforums.com/token

Where if user has no token, it creates one and then displays it?
 
Well I'm happy to have it as a page as it's not special enough to give a route like that.
I started work on the page already:
http://www.avforums.com/pages/scan_free_delivery_offer/
I already got the hang of conditionals in the page.

But yes, it assigns new tokens to users if they don't already have one. Or gets their token if they do. And then displays a link to the site which incorporates their token.
The scan_tokens table is populated with hundreds of tokens from the retailer. The userid in the unassigned records is zero. When a token is assigned to a user, that token record is updated with that user's userid.

When we run out of tokens the retailer sends us another few thousand to put in the table.
 
Stuart,

There is Waindigo's key generator to read through as a guide. http://xenforo.com/community/resources/random-key-generator-by-waindigo.2145/

Allows member to generate and store a random key, so not far off retrieving a key from a set list and storing it.
This key is added to a column called gen_key in the xf_user table for that user.

The key, once generated can be accessed from any template by adding the following code:
HTML:
{$visitor.gen_key}
 
My suggestion is to make this addon on your own, then hire @Chris Deeming to make the addon for you. I promise you it will look completely different and you will learn a lot from it.

I am not sure where you are on a xenforo programmers level. You could start writing a simple "Hello World" addon that just returns "Hello World". You will need to do that in your addon as your addon also returns different text depending on what happens in your addon.

Then you can add to that page a simple data that you fetch from database, like the users username, or the users latest five posts.

Here is a short tutorial for the use of xen:callback from @AndyB :

http://xenforo.com/community/threads/xen-callback-tag.62124/

Unfortunately, the resouce was removed. There was a great explanation to the resource discussion on how to return your data embedded within xenforos template system.
 
Last edited:
Thanks arms. I'm more interested in finding an example of a php callback in a normal page.

Here is an example of a php call back. This is obviously not my real call back but I wanted a quick example that contains calling a model and send the data to a page template.

PHP:
class IcewindDaleRP_IcewindDale_PageCallback_TrainingHall
{
    public static function trainingHall(XenForo_ControllerPublic_Abstract $controller, XenForo_ControllerResponse_Abstract $response)
    {
        $charData = array();
  
        $visitor = XenForo_Visitor::getInstance()->toArray();
        if ($visitor['user_id'])
        {
            $userCharModel = XenForo_Model::create('IcewindDaleRP_IcewindDale_Model_UserCharacter');
            $charData = $userCharModel->getAllCharDataById($visitor['user_id']);
        }

        $customData = new XenForo_Phrase('hello_world');

        $response->params['custom_data'] = $customData;
        $response->params['char_data'] = $charData;
        $response->templateName = 'iwd_training_hall';
    }
}
 
@Lawrence Do you modify the current $response from the main controller here or do you return a template? I always return a template which is inserted at the point of <xen:callback />. Your use of the callback is interesting.
 
@Lawrence Do you modify the current $response from the main controller here or do you return a template? I always return a template which is inserted at the point of <xen:callback />. Your use of the callback is interesting.

I believe that the example in the post by @Lawrence above is for a page PHP callback :)
 
Here is one of mine

PHP:
class Garage_ControllerPublic_Garage
{
        public static function getPowerBoard(XenForo_ControllerPublic_Abstract $controller, XenForo_ControllerResponse_Abstract &$response)
        {
                $db = XenForo_Application::getDb();

                $power = $db->fetchAll("
                SELECT
                        xf_nflj_showcase_custom_field_value.*,
                        xf_nflj_showcase_item.item_id,
                        xf_nflj_showcase_item.user_id,
                        xf_nflj_showcase_item.item_name,
                        xf_user.user_id,
                        xf_user.username
                FROM xf_nflj_showcase_custom_field_value
                LEFT JOIN xf_nflj_showcase_item ON
                        (xf_nflj_showcase_custom_field_value.item_id = xf_nflj_showcase_item.item_id)
                LEFT JOIN xf_user ON
                        (xf_nflj_showcase_item.user_id = xf_user.user_id)
                WHERE xf_nflj_showcase_custom_field_value.field_id =  'bhp'
                AND xf_nflj_showcase_custom_field_value.field_value !=  ''
                ORDER BY xf_nflj_showcase_custom_field_value.field_value DESC
                ");

                $response->params['power'] = $power;
        }
}

Used here: http://z22se.co.uk/pages/powerboard/
 
I believe that the example in the post by @Lawrence above is for a page PHP callback :)
There are different ways of using the callback, here I return a template:

PHP:
  public static function myCallback($content, $params, \XenForo_Template_Abstract $template)
   {
       $templateParams = array();
       return $template->create('myTemplate', $templateParams);
   }
 
There are different ways of using the callback, here I return a template:

PHP:
  public static function myCallback($content, $params, \XenForo_Template_Abstract $template)
   {
       $templateParams = array();
       return $template->create('myTemplate', $templateParams);
   }

Not the page callback. There isn't a template object passed into the page node callback.
 
Not the page callback. There isn't a template object passed into the page node callback.
I see. For others, here is the description of the page callback in ACP>Applications>Create New Page:
You may optionally specify a PHP callback here in order to fetch more data or alter the controller response for your page.

Callback arguments:
  1. XenForo_ControllerPublic_Abstract $controller
    The controller instance. From this you can inspect the request, response etc.
  2. XenForo_ControllerResponse_Abstract &$response
    The standard response from the page controller.
 
Here is one of mine

PHP:
class Garage_ControllerPublic_Garage
{
        public static function getPowerBoard(XenForo_ControllerPublic_Abstract $controller, XenForo_ControllerResponse_Abstract &$response)
        {
                $db = XenForo_Application::getDb();

                $power = $db->fetchAll("
                SELECT
                        xf_nflj_showcase_custom_field_value.*,
                        xf_nflj_showcase_item.item_id,
                        xf_nflj_showcase_item.user_id,
                        xf_nflj_showcase_item.item_name,
                        xf_user.user_id,
                        xf_user.username
                FROM xf_nflj_showcase_custom_field_value
                LEFT JOIN xf_nflj_showcase_item ON
                        (xf_nflj_showcase_custom_field_value.item_id = xf_nflj_showcase_item.item_id)
                LEFT JOIN xf_user ON
                        (xf_nflj_showcase_item.user_id = xf_user.user_id)
                WHERE xf_nflj_showcase_custom_field_value.field_id =  'bhp'
                AND xf_nflj_showcase_custom_field_value.field_value !=  ''
                ORDER BY xf_nflj_showcase_custom_field_value.field_value DESC
                ");

                $response->params['power'] = $power;
        }
}

Used here: http://z22se.co.uk/pages/powerboard/
Thanks Matt. Can you tell me what you put in the PHP Callback text boxes for Class and Method please?
And then what did you do template wise? I'm going to copy elements of your code, but I'm not sure what to do with the output.
 
Class is the class name (so in Matt's case Garage_ControllerPublic_Garage) and the method is the function name (so in Matt's case getPowerBoard).
 
As @Jeremy said for the Class and Method.

This is the template I'm using:
Code:
<style type="text/css">
.outercontainer {
border: 1px solid #ddd;
border-radius: 4px;
padding: 10px;
margin: 0 auto;
background-color: #f8f8f8 !important;
}
</style>
<script type='text/javascript' src='https://www.google.com/jsapi'></script>
    <script type='text/javascript'>
      google.load('visualization', '1', {packages:['table']});
      google.setOnLoadCallback(drawTable);
      function drawTable() {
        var data = new google.visualization.DataTable();
        data.addColumn('string', 'Member');
        data.addColumn('string', 'Vehicle');
        data.addColumn('string', 'Power');
        data.addRows([
          <xen:foreach loop="$power" value="$power">
          ['<xen:username user="$power" />', '<a href="{xen:link showcase, $power}">{xen:jsescape {xen:raw $power.item_name}, single}</a>', '{$power.field_value} {$power.field_id}'],
          </xen:foreach>
        ]);
        var table = new google.visualization.Table(document.getElementById('table_div'));
        table.draw(data, {showRowNumber: true, allowHtml: true});
      }
    </script>

<div class="outercontainer">
<div class="baseHtml">
<div id='table_div'></div>
</div>
</div>
 
Top Bottom