XF 2.0 How to use the thread creator to create a thread

HQCoder

Member
i use creator sevice to post new thread
Code:
                    $creator = \XF::service('XF:Thread\Creator', $forum);
                    $creator->setContent($title, $message);
                    $creator->setUser($user);
                    $creator->setPrefix($forum['default_prefix_id']);
                    $creator->setIsAutomated();
                    $creator->save();
but this show an error
Call to protected method XF\Service\Thread\Creator::setUser() from context

please help me
 
Code:
$creator = \XF::asVisitor($user, function() use($forum)
{
    return $this->service('XF:Thread\Creator', $forum);
});

$creator->setContent($title, $message);
$creator->setPrefix($forum['default_prefix_id']);
$creator->setPerformValidations(false);
$creator->logIp(false);
$creator->save();

$user should be a full user entity.
 
Code:
$creator = \XF::asVisitor($user, function() use($forum)
{
    return $this->service('XF:Thread\Creator', $forum);
});

$creator->setContent($title, $message);
$creator->setPrefix($forum['default_prefix_id']);
$creator->setPerformValidations(false);
$creator->logIp(false);
$creator->save();

$user should be a full user entity.
This isn't inherently wrong (and I know it was based on our own code) but we have changed how that works (see the method mentioned in the previous post) so that the visitor change should apply to the entire service to ensure everything that branches off from the service definitely runs as the intended user.
 
Code:
$creator = \XF::asVisitor($user, function() use($forum)
{
    return $this->service('XF:Thread\Creator', $forum);
});

$creator->setContent($title, $message);
$creator->setPrefix($forum['default_prefix_id']);
$creator->setPerformValidations(false);
$creator->logIp(false);
$creator->save();

$user should be a full user entity.
thank you so much
See XF\Service\Feed\Feeder and the importEntry method.
thank you.
PHP:
protected function setupThreadCreate(array $entry)
    {
        $userRepo = $this->repository('XF:User');
        $strFormatter = $this->app->stringFormatter();

        $feed = $this->feed;
        if ($feed->user_id)
        {
            $user = $feed->User;
        }
        else if ($entry['author'])
        {
            $user = $userRepo->getGuestUser($strFormatter->wholeWordTrim($entry['author'], 50, 0, ''));
        }
        else
        {
            $user = $userRepo->getGuestUser($strFormatter->wholeWordTrim($feed->title, 50, 0, ''));
        }

        /** @var \XF\Service\Thread\Creator $creator */
        $creator = \XF::asVisitor($user, function() use($feed)
        {
            return $this->service('XF:Thread\Creator', $feed->Forum);
        });
        $creator->setIsAutomated();

        $creator->setContent($feed->getEntryTitle($entry), $feed->getEntryMessage($entry));
        $creator->setPrefix($feed->prefix_id);
        $creator->setDiscussionState($feed->discussion_visible ? 'visible' : 'moderated');
        $creator->setDiscussionOpen($feed->discussion_open);
        $creator->setSticky($feed->discussion_sticky);

        return $creator;
    }
 
  • Like
Reactions: LPH
Actually, our code is sort of bugged, funnily enough.

Taking your original code as an example, do this instead:
PHP:
\XF::asVisitor($user, function() use ($forum, $title, $message)
{
    $creator = \XF::service('XF:Thread\Creator', $forum);
    $creator->setContent($title, $message);
    $creator->setPrefix($forum['default_prefix_id']);
    $creator->setIsAutomated();
    $creator->save();
});
 
Actually, our code is sort of bugged, funnily enough.

Taking your original code as an example, do this instead:
PHP:
\XF::asVisitor($user, function() use ($forum, $title, $message)
{
    $creator = \XF::service('XF:Thread\Creator', $forum);
    $creator->setContent($title, $message);
    $creator->setPrefix($forum['default_prefix_id']);
    $creator->setIsAutomated();
    $creator->save();
});
LOL, I was going to say the only real difference is that setIsAutomated is back and usable in the code. ;)
 
Last edited:
Actually, our code is sort of bugged, funnily enough.

Taking your original code as an example, do this instead:
PHP:
\XF::asVisitor($user, function() use ($forum, $title, $message)
{
    $creator = \XF::service('XF:Thread\Creator', $forum);
    $creator->setContent($title, $message);
    $creator->setPrefix($forum['default_prefix_id']);
    $creator->setIsAutomated();
    $creator->save();
});
While this works, if you're running the code inline, need to set or unset certain variables (poll, no poll, moderated, not moderated, etc.) and also return the thread that's been created with $creator->getThread();, everything must be within this code. Which renders any post-processing next to impossible.

This could probably be solved if you return the creator, but then if you're calling this from different areas with different information in the same class, it will become real hairy sorting through the different info being supplied to the function. The "use" section could grow beyond a reasonably manageable size.
 
Last edited:
The asVisitor call returns whatever you want. It can return the thread (from the save call) the creator itself.

Basically it has to be done like this or there may end up being some cross contamination of user contexts. (For example if _postSave on the entity kicks off some stuff based on visitor, if save() is run outside of this asVisitor call then it's going to run as the wrong user).
 
While this works, if you're running the code inline, need to set or unset certain variables (poll, no poll, moderated, not moderated, etc.) and also return the thread that's been created with $creator->getThread();, everything must be within this code. Which renders any post-processing next to impossible.

This could probably be solved if you return the creator, but then if you're calling this from different areas with different information in the same class, it will become real hairy sorting through the different info being supplied to the function. The "use" section could grow beyond a reasonably manageable size.
I increasingly like xenforo 2
many thanks xenfor dev team
 
The asVisitor call returns whatever you want. It can return the thread (from the save call) the creator itself.

Basically it has to be done like this or there may end up being some cross contamination of user contexts. (For example if _postSave on the entity kicks off some stuff based on visitor, if save() is run outside of this asVisitor call then it's going to run as the wrong user).
I understand why it needs to be done that way, but this is what I was taking about....
Code:
protected function createThread($forum, $title, $message, $creation_options, $poster, $user, $info, $attachment_hash, $createPoll = true)

return \XF::asVisitor($poster, function()
    use ($forum, $title, $message, $creation_options, $user, $info, $attachment_hash, $createPoll)
{
    .........
   $thread = $creator->save();
    .........
});

The list of things needed just to process the thread creation part is quite long and all of that is required in my actual use.

$creation_options contains if moderated, thread prefix, poll options and misc other info (pre-set by application specific defaults).
$poster is the user used to actually post the thread
$user is used for application specific processing when a poll is created (the user the thread is about)
$info is also used for application specific processing when a poll is created (dynamically changing info)
$attachment_hash we all know what that's for
$createPoll is to abort creating a poll when normally it would be created by $creation_options (required for other areas that will use the routine)

What is boils down to is I'm not a big fan of a long list of required items for a function to work. It does work, but one or two more items in the list and I'd be looking for a better way to do the same thing.

And I hope nobody minds this type of discussion since it may or may not improve things down the line for everyone. ;)
 
A massive list of function arguments is fairly inflexible anyway (taking the createThread function as an example).

Obviously I don't know the bigger picture of your code, but you may be able to take an approach similar to our services where object properties are set in advance and then accessed later. Or another approach could be that you have a special object which encapsulates the values you need for creating a thread. Or even just a simple array.
PHP:
protected function createThread(array $params)

\XF::asVisitor($params['poster'], function() use ($params)
{
    // etc...
});
At least with something like an array you can add additional items in there without having to add even more arguments and without breaking backwards compatibility in case someone is extending your code.
 
I use this to copy a thread by click.

Code:
        $creator = \XF::asVisitor($visitor, function () use ($prefix, $forum, $title, $message) {
            /** @var \XF\Service\Thread\Creator $creator */
            $creator = $this->service('XF:Thread\Creator', $forum);
            $creator->setPerformValidations(false);
            $creator->setContent($title, $message);
            $creator->setPrefix($prefix);
            $creator->save();
            return $creator;
        });

I does what i should do so far, but i have no idea how to fetch the new thread_id and post_id.

Both are inside $creator->thread and $creator-post.

How can i use them like

newPostId = $creator->thread->thread_id;

please?


And how can i pass a new existing field to the $creator?
 
PHP:
\XF::asVisitor($user, function() use ($forum, $title, $message)
{
    $creator = \XF::service('XF:Thread\Creator', $forum);
    $creator->setContent($title, $message);
    $creator->setPrefix($forum['default_prefix_id']);
    $creator->setIsAutomated();
    $creator->save();
});
What is $forum in the above case? I'm assuming a forum entity based on forum ID. If yes, is it supposed to be fetched in the same way as the $user entity?
 
What is $forum in the above case? I'm assuming a forum entity based on forum ID. If yes, is it supposed to be fetched in the same way as the $user entity?

Yeah, it's a forum entity. If you already have it, you can simply pass it along. If you have the corresponding node, you can do $forum = $node->Forum;, and if you have the forum ID, you can fetch it with $forum = \XF::em()->find('XF:Forum', $yourForumId);.
 
Yeah, it's a forum entity. If you already have it, you can simply pass it along. If you have the corresponding node, you can do $forum = $node->Forum;, and if you have the forum ID, you can fetch it with $forum = \XF::em()->find('XF:Forum', $yourForumId);.
Just a few follow-up questions. Pardon my ignorance.

(1) If I use $forum = \XF::app()->find('XF:Forum', $yourForumId); instead of $forum = \XF::em()->find('XF:Forum', $yourForumId); , it works too. Are the em() and app() interchangeable?

(2) My $message variable contains HTML such as:
PHP:
$message = "<p>A new <strong>event</strong> was created. Click <a href='someurl'>here</p> to view it.</p>";
But the message is not getting parsed as HTML when returning back. Rather it's showing up as string. How can I get the HTML support in content being posted? Is htmlentities() the way to go?
 
(1) If I use $forum = \XF::app()->find('XF:Forum', $yourForumId); instead of $forum = \XF::em()->find('XF:Forum', $yourForumId); , it works too. Are the em() and app() interchangeable?

A good IDE will answer you that question. Here's what the find() method of \XF::app() looks like:
Code:
public function find($entity, $id, $with = [])
{
   return $this->em()->find($entity, $id, $with);
}

(2) My $message variable contains HTML such as:
PHP:
$message = "<p>A new <strong>event</strong> was created. Click <a href='someurl'>here</p> to view it.</p>";
But the message is not getting parsed as HTML when returning back. Rather it's showing up as string. How can I get the HTML support in content being posted? Is htmlentities() the way to go?

You cannot do that. XenForo messages only work with BB Code. You can use the HTML->BB Code parser to create BB Code that corresponds to your HTML, but that does not support full HTML.
 
Something equal and solved while copying the first post of a thread to a new thread.

Code:
        $creator = $this->service('XF:Thread\Creator', $forum);
        $creator->setPerformValidations(false);
        $creator->setContent($title, $message);
        $creator->setPrefix($thread->prefix_id);

        //$creator->setUser($user);

        $thread2 = $creator->save();
        $creator->sendNotifications();

and later change all users for thread, post, forum (last one)

Code:
        $data = [
            'user_id' => $thread->user_id,
            'last_post_user_id' => $thread->user_id,
            'last_post_username' => $thread->username,
        ];

        // update xf_thread
        $thread2->fastUpdate($data);

...

        $data = [
            'user_id' => $thread->user_id,
        ];

        // update xf_thread
        $post2->fastUpdate($data);

...

        $data = [
            'last_post_user_id' => $thread->user_id,
        ];
        $forum ->fastUpdate($data);


I just dont know how to use:

\XF::asVisitor($user, function() use ($forum, $title, $message)

and call the create with the $user i want instead of $visitor;
just then it is not needed to update post, thread, forum
 
Last edited:
Top Bottom