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

XF 2.0 Looking for XF2 equivalent of _assertCanReplyToThread

AndyB

Well-known member
#1
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.
 

Chris D

XenForo developer
Staff member
#2
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?
 

AndyB

Well-known member
#3
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.
 

Chris D

XenForo developer
Staff member
#4
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.
 

AndyB

Well-known member
#5
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.
 

Chris D

XenForo developer
Staff member
#6
No, the Editor service is for editing posts.

I misspoke slightly, it's the "Thread replier" service you're after.
 

AndyB

Well-known member
#7
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;
	}
 

AndyB

Well-known member
#8
I tried extending the XF/Service/Thread/Replier.php like this:

1507575352539.png

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.png

What am I doing incorrectly?
 

AndyB

Well-known member
#11
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:

Chris D

XenForo developer
Staff member
#12
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.
 

AndyB

Well-known member
#13
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;	
	}
}
 

arms

Active member
#14
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.png

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:

AndyB

Well-known member
#16
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;	
	}
}
 

Chris D

XenForo developer
Staff member
#17
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');
 

AndyB

Well-known member
#18
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;	
	}
}