XenForo Helper

Robust

Well-known member
I have this helper:
Code:
<?php

class BestAnswer_Helper_VoteStatus
{
    public static function checkVoteStatus($postId, $userId)
    {
        $baPostModel = self::_getBaPostModel();
        $existingBestAnswer = $baPostModel->getCurrentBestAnswerStatusOnPost($postId, $userId);

        return ($existingBestAnswer ? false : true);
    }

    /**
     * @return BestAnswer_Model_Post
     */
    protected function _getBaPostModel()
    {
        return $this->getModelFromCache('BestAnswer_Model_Post');
    }
}
It's used like this I think:
Code:
<xen:if is="<xen:if is="{xen:helper voteStatus, $post['post_id'], $visitor['user_id']}">{xen:phrase bestanswer_vote}<else>{xen:phrase bestanswer_unvote}</xen:if>

Neither Vote or Unvote is shown. the getCurrentBestAnswerStatusOnPost basically does this:

Code:
    /**
     * Gets a best answer content record for a user that has voted on a piece of content.
     *
     * @param integer $postId
     * @param integer $userId
     *
     * @return array|false
     */
    public function getCurrentBestAnswerStatusOnPost($postId, $userId)
    {
        return $this->_getDb()->fetchRow('
            SELECT *
            FROM ba_votes
            WHERE post_id = ?
                AND ba_user_id = ?
        ', array($postId, $userId));
    }

What am I doing wrong?
 
There are a few problems:

Code:
<else>

That's not a valid tag, you'd be looking to use:

Code:
<xen:else />

Also:

Code:
$post['post_id']

This is not a valid way to reference template params, even in helpers it should be:

Code:
{$post.post_id}
 
I have a feeling a helper isn't what I need to use. Basically, I have $voted, which returns true or false based on whether the post has been voted for or not. Similar to how the like feature works. I show the phrase bestanswer_vote if $voted is false, and bestanswer_unvote if $voted is true (user has already voted, so the option is to unvote).

ViewPublic + a helper is what I'm currently using to represent this in the controller class, this is a different template though. I can use $voted there which makes it a lot easier (<xen:if is="$voted">...<xen:else />...</xen:if>). If I could use $voted in the template post somehow I could go about this. I'm not really sure how to do this at this point.
 
A helper isn't completely unreasonable though it depends on exactly what you're trying to do.

If the function is to be frequently used in a template, then clearly it is quite convenient.

You say you now get the "unvote" phrase, I think that's expected because I think this:
Code:
return ($existingBestAnswer ? false : true);
Should be:
Code:
return ($existingBestAnswer ? true : false);
 
A helper isn't completely unreasonable though it depends on exactly what you're trying to do.

If the function is to be frequently used in a template, then clearly it is quite convenient.

You say you now get the "unvote" phrase, I think that's expected because I think this:
Code:
return ($existingBestAnswer ? false : true);
Should be:
Code:
return ($existingBestAnswer ? true : false);
Yeah, I tried that. Same thing. I've tried removing that (debug purposes) and just trying return true; return false;, both give me the unvote phrase, which is strange. It's like it's ignoring the output from the helper.
 
I've made an assumption that you actually have the helper enabled, can you share the rest of the code e.g. where the helper is enabled and initialised?

Also I only just noticed this but there seems to be an additional xen:if here:

Code:
<xen:if is="<xen:if is="{xen:helper voteStatus, $post['post_id'], $visitor['user_id']}">{xen:phrase bestanswer_vote}<else>{xen:phrase bestanswer_unvote}</xen:if>

What does this code look like now you've fixed it?
 
Template Modification, template post:

Find:
Code:
<a href="{xen:link posts/report, $post}" class="OverlayTrigger item control report" data-cacheOverlay="false"><span></span>{xen:phrase report}</a>
                </xen:if>

Replace:
Code:
$0

<xen:if is="{$visitor.permissions.bestAnswerPermissions.markBestAnswer}"><a href="{xen:link posts/bestAnswer, $post}" class="item control <xen:if is="{xen:helper voteStatus, {$post.post_id}, {$visitor.user_id}}">vote<xen:else />unvote</xen:if>"><span></span><xen:if is="{xen:helper voteStatus, {$post.post_id}, {$visitor.user_id}}">{xen:phrase bestanswer_vote}<xen:else />{xen:phrase bestanswer_unvote}</xen:if></a></xen:if>

init_dependencies listener (screenshot attached)
Class:
PHP:
<?php

class BestAnswer_Listener_Helper
{
    public static function init(XenForo_Dependencies_Abstract $dependencies, array $data)
    {
        XenForo_Template_Helper_Core::$helperCallbacks += array(
            'voteStatus' => array('BestAnswer_Helper_VoteStatus', 'checkVoteStatus')
        );
    }
}
 

Attachments

  • Screen Shot 2015-04-25 at 14.38.24.webp
    Screen Shot 2015-04-25 at 14.38.24.webp
    44.1 KB · Views: 7
@Chris D Actually, I don't think the helper is working somehow. I put this in a template:

{xen:helper voteStatus, 6, 1}

post 6, visitor userid 1. It displayed nothing, but it should have displayed 1 or 0 right?
 
There's definitely something not right, but all of the code looks good.

You don't have listeners disabled in config.php or anything do you?

If you want to package it up and PM it to me, I don't mind trying it myself.
 
Okay, I had a similar issue with an add-on I recently made. Try this:

PHP:
<?php

class BestAnswer_Listener_Helper
{
    public static function init(XenForo_Dependencies_Abstract $dependencies, array $data)
    {
        XenForo_Template_Helper_Core::$helperCallbacks += array(
            'votestatus' => array('BestAnswer_Helper_VoteStatus', 'checkVoteStatus')
        );
    }
}

I found out that the helper name needed to be lowercase for some reason so that is what I changed in this bit of code. See if it works.
 
Well spotted - I missed the case.

The reason is this:
PHP:
public static function callHelper($helper, array $args)
{
    $helper = strtolower(strval($helper));
    if (!isset(self::$helperCallbacks[$helper]))
    {
        return '';
    }

    return call_user_func_array(self::$helperCallbacks[$helper], $args);
}
 
Perfect! Thank you both :) Works like a charm now. Looks like I got carried away coding and did a woopsie here too:
Code:
return $this->getModelFromCache('BestAnswer_Model_Post');
1. $this
2. getModelFromCache

Fixed that one too, works like a charm. All actual functionality is done now. It's just styling.
 
@Chris D @NixFifty One other thing. The Like feature doesn't redirect anywhere, it just has that loading thing. Similarly, when posting a reply it doesn't redirect, but it has that box at the top. When someone hits Vote/Unvote, how can I not direct to the controller page but instead just show a message on the same page (similar to when a reply happens) with a message like Your vote has been added / Your vote has been removed.
 
@Chris D So I'd be looking to use an OverlayTrigger? Any classes/templates I can look at for this, I don't really get how to do it.

Also, shouldn't I be verifying the user's XF token somewhere and sending a post request? I'm not sure how exactly the like function works, I managed to replicate it to this point but there's the class post_like and I don't have a clue where that one is loaded

Code:
<xen:title>{xen:helper threadPrefix, $thread, escaped}{$thread.title} - {xen:if $like, '{xen:phrase unlike_post}', '{xen:phrase like_post}'}</xen:title>
<xen:h1>{xen:if $like, '{xen:phrase unlike_post}', '{xen:phrase like_post}'}</xen:h1>

<xen:navigation>
    <xen:breadcrumb source="$nodeBreadCrumbs" />
    <xen:breadcrumb href="{xen:link full:posts, $post}">{xen:helper threadPrefix, $thread}{$thread.title}</xen:breadcrumb>
</xen:navigation>

<xen:container var="$bodyClasses">{xen:helper nodeClasses, $nodeBreadCrumbs, $forum}</xen:container>
<xen:container var="$searchBar.thread"><xen:include template="search_bar_thread_only" /></xen:container>
<xen:container var="$searchBar.forum"><xen:include template="search_bar_forum_only" /></xen:container>

<form action="{xen:link 'posts/like', $post}" method="post" class="xenForm">

    <dl class="ctrlUnit fullWidth surplusLabel">
        <dt></dt>
        <dd>
            <xen:if is="{$like}">
                {xen:phrase you_sure_you_want_to_unlike_this_post}
            <xen:else />
                {xen:phrase you_sure_you_want_to_like_this_post}
            </xen:if>
        </dd>
    </dl>

    <dl class="ctrlUnit submitUnit">
        <dt></dt>
        <dd><input type="submit" value="{xen:if $like, '{xen:phrase unlike_post}', '{xen:phrase like_post}'}" accesskey="s" class="button primary" autofocus="true" /></dd>
    </dl>

    <input type="hidden" name="_xfToken" value="{$visitor.csrf_token_page}" />
</form>

But in the post template it has this:

Code:
                <xen:if is="{$post.canLike}">
                    <a href="{xen:link posts/like, $post}" class="LikeLink item control {xen:if $post.like_date, unlike, like}" data-container="#likes-post-{$post.post_id}"><span></span><span class="LikeLabel">{xen:if $post.like_date, {xen:phrase unlike}, {xen:phrase like}}</span></a>
                </xen:if>
 
As I said, it would require custom JS code. So you would find the JS for the existing Like button in xenforo.js.

As a pointer, typically when a class name on an element begins with a capital letter, that should signify that the class is used as a JS selector more so than being a styling CSS selector.

With that in mind, the class of the like link is "LikeLink" so you can probably find that in xenforo.js somewhere for some pointers.
 
Top Bottom