• This site uses cookies. By continuing to use this site, you are agreeing to our use of cookies. Learn more.

XF 2.0 Switching thread option

#1
Hello!

First of all, I have to say that it is really hard for my to get into all this xF core and addon structure, relations, models and etc. but I am trying my best to understand this. I used to create Wordpress plugins before and that process was fairly easy. So, let's get to the point.

I am trying to create an addon wich allow users to create question-threads. These threads are just like the old ones but they can be solved or not solved (by default). From official dev tutorial I created a new column in xf_thread called questionthreads__is_question. I also created a table xf_questionthreads_thread which has 3 columns: thread_id, is_solved and best_post_id. I extended thread entity structure so it now knows about questionthreads__is_question column:
PHP:
public static function threadEntityStructure(\XF\Mvc\Entity\Manager $em, \XF\Mvc\Entity\Structure &$structure) {
    $structure->columns['questionthreads__is_question'] = ['type' => Entity::BOOL, 'default' => false];

    $structure->relations['QuestionThread'] = [
        'entity' =>     'QuestionThreads:QuestionThread',
        'type' =>       Entity::TO_ONE,
        'conditions' =>  'thread_id',
        'primary' =>    true
    ];
}
... and did all other steps. Although I am not sure I understand everything that is written in dev tutorial, I can create question-threads.

Now the only thing I need to do is to add a button "Mark as solved" in quick_reply_macros template (or somewhere else). This button must somehow change the is_solved column value from 0 to 1 for current thread. I am completely confused now in all this entities, routings, contorllers ans so on. I don't quite understand what steps should I perform now...

How can I create such a button (what to write in <xf:button ...? How to change the column value?
 
Last edited:

Chris D

XenForo developer
Staff member
#2
Perhaps the best example I can give of a relatively simple state change for a thread is to point you towards something like the "Undelete" button which appears at the top of a thread when you are viewing a soft deleted thread.

The button is just actually a link styled to look like a button. In the case of undeleting a thread, it calls the threads/undelete action and you can see the code for that in the Thread controller (src/XF/Pub/Controller/Thread.php look for the actionUndelete() method).

This should at least get you familiar with how that typical flow works.

The main difference between this code and yours is that you need to set a value which is not directly in the thread table (thread entity) itself, but in another table (your QuestionThread entity). If I quickly hacked how that should look it would be something like:
PHP:
public function actionSolve(ParameterBag $params)
{
    $thread = $this->assertViewableThread($params->thread_id);

    // check if thread can be solved (e.g. is it a question thread? is it already solved?)

    if ($this->isPost())
    {
        $questionThread = $thread->getRelationOrDefault('QuestionThread');
        $questionThread->is_solved = true;
        $questionThread->save();
        
        return $this->redirect($this->buildLink('threads', $thread));
    }
    else
    {
        $viewParams = [
            'thread' => $thread
        ];
        return $this->view('YourAddOn:ViewClass', 'your_addon_template', $viewParams);
    }
}
You would get your actionSolve() method into our Thread controller by adding a class extension to extend it (I think that bit is covered in the docs).
 
#3
Perhaps the best example I can give of a relatively simple state change for a thread is to point you towards something like the "Undelete" button which appears at the top of a thread when you are viewing a soft deleted thread.

The button is just actually a link styled to look like a button. In the case of undeleting a thread, it calls the threads/undelete action and you can see the code for that in the Thread controller (src/XF/Pub/Controller/Thread.php look for the actionUndelete() method).

This should at least get you familiar with how that typical flow works.

The main difference between this code and yours is that you need to set a value which is not directly in the thread table (thread entity) itself, but in another table (your QuestionThread entity). If I quickly hacked how that should look it would be something like:
PHP:
public function actionSolve(ParameterBag $params)
{
    $thread = $this->assertViewableThread($params->thread_id);

    // check if thread can be solved (e.g. is it a question thread? is it already solved?)

    if ($this->isPost())
    {
        $questionThread = $thread->getRelationOrDefault('QuestionThread');
        $questionThread->is_solved = true;
        $questionThread->save();
   
        return $this->redirect($this->buildLink('threads', $thread));
    }
    else
    {
        $viewParams = [
            'thread' => $thread
        ];
        return $this->view('YourAddOn:ViewClass', 'your_addon_template', $viewParams);
    }
}
You would get your actionSolve() method into our Thread controller by adding a class extension to extend it (I think that bit is covered in the docs).
Thank you for your reply. It really helped me to understand the flow better. I managed to create "Mark as solved" button and added actionSolve to extended Thread class. I have one more question:

In actionSolve we use $questionThread->save(); method. I guess that I am to extend this method so I can manually update my custom xf_questionthreads_thread table and set column is_solved to 1. From dev tutorial I extended Creator class (to automatically set a new thread in forum as question thread) but this class has nothing to do when updating (or undeleting) the thread that already exists...

How to extend this 'save' method?
 
Last edited:

Chris D

XenForo developer
Staff member
#4
You don't need to do anything else.

This sets the field and saves it:

PHP:
        $questionThread = $thread->getRelationOrDefault('QuestionThread');
        $questionThread->is_solved = true;
        $questionThread->save();
 
#5
You don't need to do anything else.

This sets the field and saves it:

PHP:
        $questionThread = $thread->getRelationOrDefault('QuestionThread');
        $questionThread->is_solved = true;
        $questionThread->save();
The problem was that I manually added the public variable is_solved in my QuestionThread class. Now, after deleting it "Mark as solved" button really works! I guess the getRelationOrDefault method does the real magic here. Is it adding properties to $questionThread according to database record? Because my PhpStorm says there is no porperty is_solved in my class but I am setting a value for that nonexisting property...

Anyway, thank you for help!
 
Last edited:

Chris D

XenForo developer
Staff member
#6
Our entity system implements certain "magic" methods (http://php.net/manual/en/language.oop5.magic.php). Particularly __get and __set which means that when you do $entity->some_field = true; if there's no existing class property that matches some_field, then it will instead call the __set() method on that object. That's then where our code kicks in and does things like encode/decode the value to the correct type, checks it's valid, etc. So just by setting up your entity in the standard way, we take care of the rest.