XF 2.0 Looking for XF2 equivalent of _assertCanReplyToThread

AndyB

Well-known member
In XF1 I extended the following:

PHP:
<?php

class Andy_FirstPost_ControllerPublic_Thread extends XFCP_Andy_FirstPost_ControllerPublic_Thread
{
	public function _assertCanReplyToThread(array $thread, array $forum)
	{
		// get parent
		$parent = parent::_assertCanReplyToThread($thread, $forum);

What function is equivalent code be in XF2?

Thank you.
 
There's not really a direct replacement for that.

We check the "can reply" method on the entity and then if you can't we return an error.

What are you actually trying to do?
 
What are you actually trying to do?

The add-on called First post will make sure new members post in a specified forum as their first post. If they try to reply to an existing thread not located in the designated introductions forum I would like to show an error phrase. Here's what I have so far and it works fine to show the error phrase, but the code appears to ignore the error and allow the post to be created, I wish for the new reply to be aborted.

PHP:
<?php

namespace Andy\FirstPost\XF\Pub\Controller;

use XF\Mvc\ParameterBag;

class Thread extends XFCP_Thread
{
	public function actionAddReply(ParameterBag $params)
	{
		// get parent
		$parent = parent::actionAddReply($params);
		
		// get visitor
		$visitor = \XF::visitor();
		
		// get messageCount
		$messageCount = $visitor['message_count'];
		
		// continue if first post
		if ($messageCount == 0)
		{
			// get threadId
			$threadId = $params->thread_id;

			// get db
			$db = \XF::db();		

			// get nodeId
			$nodeId = $db->fetchOne("
				SELECT node_id
				FROM xf_thread
				WHERE thread_id = ?
			", $threadId);

			// get options
			$options = \XF::options();			

			// get options from Admin CP -> Options -> First post -> Introductory forum
			$introductoryForum = $options->firstPostIntroductoryForum;

			// get options from Admin CP -> Options -> First post -> Exclude forum
			$exludeForumArray = $options->firstPostExcludeForum;

			// check if current nodeId is excluded
			if (in_array($nodeId, $exludeForumArray))
			{
				// return parent
				return $parent;
			}

			// return error
			if ($nodeId != $introductoryForum)
			{
				return $this->error(\XF::phrase('firstpost_this_forum_requires_new_member_introductions'));
			}
		}
		
		// return parent
		return $parent;	
	}
}

Note that I wish to continue to show the quick reply and reply link and only abort the creation of the new post.
 
That's because of this:
PHP:
$parent = parent::actionAddReply($params);
You've already called the parent, so the code to insert the post has already happened. If you were doing it like that in XF1 then I'd just tell you to not call the parent until after you've checked the various node IDs / exclusions etc.

But, this isn't XF1, so I'd instead tell you that you should be looking to hook into the Post replier servce.

Specifically, you'd just extend the _validate() method there. If that method returns an array containing any errors, then the reply will not happen, and the errors will be shown to the user.
 
you should be looking to hook into the Post replier servce.

Thank you for your help, Chris.

Are you referring to this function?

Service/Post/Editor.php

PHP:
	protected function _validate()
	{
		$this->finalSetup();

		$this->post->preSave();
		return $this->post->getErrors();
	}

I tried putting in a exit() as a test into that function, but it doesn't appear to be called when saving a new post with the quick editor.
 
No, the Editor service is for editing posts.

I misspoke slightly, it's the "Thread replier" service you're after.
 
Thank you, Chris. Now I'm off to see how to extend this function.

XF/Service/Thread/Replier.php

PHP:
	protected function _validate()
	{
		$this->finalSetup();

		$post = $this->post;

		if (!$post->user_id)
		{
			/** @var \XF\Validator\Username $validator */
			$validator = $this->app->validator('Username');
			$post->username = $validator->coerceValue($post->username);
			if ($this->performValidations && !$validator->isValid($post->username, $error))
			{
				return [$validator->getPrintableErrorValue($error)];
			}
		}

		$post->preSave();
		$errors = $post->getErrors();

		return $errors;
	}
 
I tried extending the XF/Service/Thread/Replier.php like this:

1507575352539.webp

PHP:
<?php

namespace XF\Service\Thread;

class Replier extends XFCP_Replier
{
    protected function _validate()
    {
        // get parent
        $parent = parent::_validate();
        
        // get visitor
        $visitor = \XF::visitor();
        
        // get messageCount
        $messageCount = $visitor['message_count'];
        
        // continue if first post
        if ($messageCount == 0)
        {
            // get threadId
            $threadId = $params->thread_id;

            // get db
            $db = \XF::db();        

            // get nodeId
            $nodeId = $db->fetchOne("
                SELECT node_id
                FROM xf_thread
                WHERE thread_id = ?
            ", $threadId);

            // get options
            $options = \XF::options();            

            // get options from Admin CP -> Options -> First post -> Introductory forum
            $introductoryForum = $options->firstPostIntroductoryForum;

            // get options from Admin CP -> Options -> First post -> Exclude forum
            $exludeForumArray = $options->firstPostExcludeForum;

            // check if current nodeId is excluded
            if (in_array($nodeId, $exludeForumArray))
            {
                // return parent
                return $parent;
            }

            // return error
            if ($nodeId != $introductoryForum)
            {
                return $this->error(\XF::phrase('firstpost_this_forum_requires_new_member_introductions'));
            }
        }
        
        // return parent
        return $parent;    
    }
}

but I get the following error:

1507575463130.webp

What am I doing incorrectly?
 
Thank you, Chris. It's working perfectly.

PHP:
<?php

namespace Andy\Firstpost\XF\Service\Thread; 

use XF\Entity\Post;

class Replier extends XFCP_Replier
{
	protected function _validate()
	{
		// get parent
		$parent = parent::_validate();
		
		// get visitor
		$visitor = \XF::visitor();
		
		// get messageCount
		$messageCount = $visitor['message_count'];
		
		// continue if first post
		if ($messageCount == 0)
		{
			// get post
			$post = $this->post;
			
			// get thread
			$thread = $post->Thread;
			
			// get nodeId
			$nodeId = $thread->node_id;

			// get options
			$options = \XF::options();			

			// get options from Admin CP -> Options -> First post -> Introductory forum
			$introductoryForum = $options->firstPostIntroductoryForum;

			// get options from Admin CP -> Options -> First post -> Exclude forum
			$exludeForumArray = $options->firstPostExcludeForum;

			// check if current nodeId is excluded
			if (in_array($nodeId, $exludeForumArray))
			{
				// return parent
				return $parent;
			}

			// check condition
			if ($nodeId != $introductoryForum)
			{
				// get error
				$errors = array(\XF::phrase('firstpost_this_forum_requires_new_member_introductions'));
				
				// return error
				return $errors;
			}
		}
		
		// return parent
		return $parent;	
	}
}
 
Last edited:
We tended to use $parent quite a lot in XF1. Of course that variable can be called whatever you like. Though I’d actually suggest naming it after whatever the parent function returns, which helps make the code more clear. So if you extend a controller action, they will return a Reply object so I’d use $reply there, rather than $parent.

It will help your own comprehension of the code when you come back to it at a later date. It requires you to think about what is being returned by the parent.

In this particular case the method you are extending returns an array of errors so I’d suggest using the variable name $errors rather than $parent.

And of course it’s true just as much as anything else that you should always be returning whatever the parent returned originally. In your case you are creating a new array called errors and returning that. What if the parent call already returned some errors in its array? You’ve just overwritten them.

So, instead of creating a new $errors array you should be adding your new error to the existing one and returning that.
 
Thank you, Chris.

Does this look correct?

PHP:
<?php

namespace Andy\Firstpost\XF\Service\Thread; 

use XF\Entity\Post;

class Replier extends XFCP_Replier
{
	protected function _validate()
	{
		// get errors
		$errors = parent::_validate();
		
		// get visitor
		$visitor = \XF::visitor();
		
		// get messageCount
		$messageCount = $visitor['message_count'];
		
		// continue if first post
		if ($messageCount == 0)
		{
			// get post
			$post = $this->post;
			
			// get thread
			$thread = $post->Thread;
			
			// get nodeId
			$nodeId = $thread->node_id;

			// get options
			$options = \XF::options();

			// get options from Admin CP -> Options -> First post -> Exclude forum
			$exludeForumArray = $options->firstPostExcludeForum;

			// check if current nodeId is excluded
			if (in_array($nodeId, $exludeForumArray))
			{
				// return errors
				return $errors;
			}

			// get errors
			$errors += array(\XF::phrase('firstpost_this_forum_requires_new_member_introductions'));

			// return errors
			return $errors;
		}
		
		// return errors
		return $errors;	
	}
}
 
Think it would be:

PHP:
$errors[] = \XF::phrase('firstpost_this_forum_requires_new_member_introductions');

Just use your ide to search on _validate and you'll get lots of examples.

1507592025988.webp

Andy,

Only getting involved as I've just extended the _validate for Creator service and so wanting to share what I've learnt.
 
Last edited:
Final code.

PHP:
<?php

namespace Andy\Firstpost\XF\Service\Thread; 

use XF\Entity\Post;

class Replier extends XFCP_Replier
{
	protected function _validate()
	{
		// get errors
		$errors = parent::_validate();
		
		// get visitor
		$visitor = \XF::visitor();
		
		// get messageCount
		$messageCount = $visitor['message_count'];
		
		// continue if first post
		if ($messageCount == 0)
		{
			// get post
			$post = $this->post;
			
			// get thread
			$thread = $post->Thread;
			
			// get nodeId
			$nodeId = $thread->node_id;

			// get options
			$options = \XF::options();

			// get options from Admin CP -> Options -> First post -> Exclude forum
			$exludeForumArray = $options->firstPostExcludeForum;

			// check if current nodeId is excluded
			if (in_array($nodeId, $exludeForumArray))
			{
				// return errors
				return $errors;
			}

			// get errors
			$errors[] = array(\XF::phrase('firstpost_this_forum_requires_new_member_introductions'));

			// return errors
			return $errors;
		}
		
		// return errors
		return $errors;	
	}
}
 
Not quite right.
You need to change:
PHP:
$errors[] = array(\XF::phrase('firstpost_this_forum_requires_new_member_introductions'));
To:
PHP:
$errors[] = \XF::phrase('firstpost_this_forum_requires_new_member_introductions');
 
Thank you, Chris.

Final final code. :)

PHP:
<?php

namespace Andy\Firstpost\XF\Service\Thread; 

use XF\Entity\Post;

class Replier extends XFCP_Replier
{
	protected function _validate()
	{
		// get errors
		$errors = parent::_validate();
		
		// get visitor
		$visitor = \XF::visitor();
		
		// get messageCount
		$messageCount = $visitor['message_count'];
		
		// continue if first post
		if ($messageCount == 0)
		{
			// get post
			$post = $this->post;
			
			// get thread
			$thread = $post->Thread;
			
			// get nodeId
			$nodeId = $thread->node_id;

			// get options
			$options = \XF::options();

			// get options from Admin CP -> Options -> First post -> Exclude forum
			$exludeForumArray = $options->firstPostExcludeForum;

			// check if current nodeId is excluded
			if (in_array($nodeId, $exludeForumArray))
			{
				// return errors
				return $errors;
			}

			// get errors
			$errors[] = \XF::phrase('firstpost_this_forum_requires_new_member_introductions');

			// return errors
			return $errors;
		}
		
		// return errors
		return $errors;	
	}
}
 
Top Bottom