How do I create an add-on that allows a user to click a link and call php code?

To check whether the listener works, you can change the listener to

PHP:
<?php
class Andy_ChangeDate_Listener
{
public static function Post($class, &$extend)
{
  ZEND_DEBUG::dump("This listener is executed");break;
  //$extend[] = 'Andy_ChangeDate_ControllerPublic_Post';
}?>

I tried this, but nothing happens. Should I see This listener is executed" echoed somewhere?
 
I tried this, but nothing happens. Should I see This listener is executed" echoed somewhere?

Take a look at #60. There is an error in both listener files I wrote here. I corrected it in #60.

Yes, "This listener is executed" should be written on top of the page and then it should break with an error. It's just to notice you that the code in the listener is executed. But first it's important to know if the regular listener can be activated without the forum being 100% down :)
 
I updated Listener.php.

PHP:
<?php

class Andy_ChangeDate_Listener
{
   public static function Post($class, array &$extend)
   {
     ZEND_DEBUG::dump("This listener is executed");break;
     //$extend[] = 'Andy_ChangeDate_ControllerPublic_Post';
   }
}

However I'm not seeing the dump to verify it works.
 
But first it's important to know if the regular listener can be activated without the forum being 100% down :)

No problems at all with the regular listener line.

PHP:
<?php

class Andy_ChangeDate_Listener
{
   public static function Post($class, array &$extend)
   {
     //ZEND_DEBUG::dump("This listener is executed");break;
     $extend[] = 'Andy_ChangeDate_ControllerPublic_Post';
   }
}
 
It would only be executed on XenForo_ControllerPublic_Post. So to test this listener, you would have to test a link like "posts/637294/like". Thanks to the "hint" in the addon setup, your listener will only be executed in XenForo_ControllerPublic_Post. And that means, that the route has to be like "/posts/23423234/" So you would have to edit or reply or like a post in the forum, in all these cases, your listener starts!

OK, if you are doing weird stuff in the listener like rewriting "public static" with "ppppublic static", does it throw an error?
 
Interesting, when I went to edit a post I got this error:

Fatal error: Class 'ZEND_DEBUG' not found in /home/*/www/forums/library/Andy/ChangeDate/Listener.php on line 7
 
Okay I changed the Listener.php to this and it works! When I edit a post it echoed on the top "test".

PHP:
<?php

class Andy_ChangeDate_Listener
{
   public static function Post($class, array &$extend)
   {
     //ZEND_DEBUG::dump("This listener is executed");break;
     //$extend[] = 'Andy_ChangeDate_ControllerPublic_Post';
     echo 'test';
   }
}
 
Maybe the ZEND_DEBUG::dump("") only works in the debug mode.

Wonderful ! If your listener works, then now you have to check if this works:

library/Andy/ChangeDate/ControllerPublic/Post.php:
PHP:
<?php
class Andy_ChangeDate_ControllerPublic_Post extends XFCP_Andy_ChangeDate_ControllerPublic_Post
{
   public function actionChangedate()
   {
      echo 'ControllerPublic: Change Date';
   }
}

The link would be like:
posts/637302/changedate
 
Works great! When I call this URL:

posts/637302/changedate

I now get "ControllerPublic: Change Date" on top of my screen.

I also get this below the breadcrumb area:

No controller response from XenForo_ControllerPublic_Post::actionChangedate

  1. XenForo_FrontController->_handleControllerResponse() in XenForo/FrontController.php at line 345
  2. XenForo_FrontController->dispatch() in XenForo/FrontController.php at line 134
  3. XenForo_FrontController->run() in /home/*/www/forums/index.php at line 13
 
Okay I changed the Listener.php to this and it works! When I edit a post it echoed on the top "test".
By the way: this will happen to every action you do on "posts" as the listener is executed at any time. If you like a post, edit a post or anything else, it doesn't matter. So in all situations, "test" will be displayed on top of each page.

With $extend[] = 'Andy_ChangeDate_ControllerPublic_Post'; you show XenForo to also execute all stuff in library/Andy/ChangeDate/ControllerPublic/Post.php whenever the load_class_controller == XenForo_ControllerPublic_Post, so in all situations, when "/posts/...." is executed.

ControllerPublic functions are kind of magic: If the function is named actionAbcdefg, you can call the function in your browser directly by "/posts/abcdefg".

With $postId= $this->_input->filterSingle('post_id', XenForo_Input::UINT); you can get the post_id from /posts/1234/datechange. In this case, it would be 1234. Why does xenforo knowsthat the number after /posts/ is post_id? It could be called also in any other way. Because it is defined in its route. Creating a route is kind of more difficult, when you do that, you not only define "/posts" or "/threads", but also how you name the number that follows that route.

One route is : /posts. The variable is named post_id. /posts/333. post_id = 333
Another route is /threads. The variable is named thread_id. /threads/999. thread_id = 999. Only thread_id, not post_id or any other variable.
 
Here you fetch the post_id:
PHP:
<?php

class Andy_ChangeDate_ControllerPublic_Post extends XFCP_Andy_ChangeDate_ControllerPublic_Post{
public function actionChangedate()
{
$postId= $this->_input->filterSingle('post_id', XenForo_Input::UINT);
echo 'ControllerPublic: Change Date. $postId = ' . $postId;
}
}
 
Post #70 is extremely helpful, thank you Marcus, you're a very good teacher.

Post #71 also works perfect. The errors I mentioned in post #69, why do I get those? I assume because the code is not complete.
 
Last edited:
The error is there, because the actionChangedate should be the function that returns a "View". In Model-View-Controller, you should return a view. What you are doing now, is the "Controller". You do not need the model if you just write "Hello World", but you do need to return a view every time.

Do you want to see your own template if you click on that /posts/1234/changedate link? It's so easy.

1. Add a new template named "andy_changedate". At the bottom of the page, you can assign this template to your addon. There is a list of addons and you can just select yours.
Code:
<h1>Andy Change Date</h1>
postId is {$postId}

2. You can put this at the end of your actionChangedate() function:

PHP:
<?php

class Andy_ChangeDate_ControllerPublic_Post extends XFCP_Andy_ChangeDate_ControllerPublic_Post{
public function actionChangedate()
{$postId= $this->_input->filterSingle('post_id', XenForo_Input::UINT);
echo 'ControllerPublic: Change Date. $postId = ' . $postId;

    $viewParams = array(
         'postId' => $postId,
     );
  
     return $this->responseView(
         'Andy_DateChange_ViewPublic_Post',
         'andy_changedate',
         $viewParams
     );
}

}

$viewParams is a list of all variables you can view in your template. so if you want your custom $postId be accessible like {$postId} in your template, then you have to put it there.

'Andy_DateChange_ViewPublic_Post' is the first parameter in that view, you don't worry about that, because if it is not there, always the regular View will be choosen from xenforo. I only once created my own view, and it is rather complicated. So as a beginner, just do it like that.

'andy_changedate' is the name of the template the View should return. So it's your template.
 
Thank you again, Marcus.

I added the code to Post.php and created the template andy_changedate, it works perfect.

So if I assume correctly, my next step would be to make the template I just created to be a form that will allow the admin to enter a date and time that will ultimately be used to change the post date.
 
To make return $this->responseView() more clear: $this always executes a function within the same class (here the same file). But in your - small - file the function is not there (obviously). But it is also not in the XenForo_ControllerPublic_Post class (and file) you extend. So where is this function? It is in its parent class, the Post Class is extending:

library/Controller.php
PHP:
  public function responseView($viewName = '', $templateName = '', array $params = array(), array $containerParams = array())
   {
     $controllerResponse = new XenForo_ControllerResponse_View();
     $controllerResponse->viewName = $viewName;
     $controllerResponse->templateName = $templateName;
     $controllerResponse->params = $params;
     $controllerResponse->containerParams = $containerParams;

     return $controllerResponse;
   }
 
Thank you again, Marcus.

I added the code to Post.php and created the template andy_changedate, it works perfect.

So if I assume correctly, my next step would be to make the template I just created to be a form that will allow the admin to enter a date and time that will ultimately be used to change the post date.
Yes, you just create a form with only the changedate. I think you have to add one variable there for security, a token. I don't remember.

The form would then execute "actionChangedatesave"

PHP:
public function actionChangedatesave()
   {
     $this->_assertPostOnly();

// if you are not a super admin, you will get an error
    if(!XenForo_Visitor::getInstance()->isSuperAdmin())
     {
       return;
     }

$postId= $this->_input->filterSingle('post_id', XenForo_Input::UINT);
//this is an integer here! I guess the easiest solution would be to use an integer value at first, before you know how you can make use of xenForos calendar for selecting a date. 
$newPostDate = $this->_input->filterSingle('new_post_date', XenForo_Input::UINT);

        $dw = XenForo_DataWriter::create('XenForo_DataWriter_Post');
  
     $dw->setExistingData($postId)
     $dw->set('date', $newPostDate);
  
     $dw->save();
    
     return $this->responseRedirect(
         XenForo_ControllerResponse_Redirect::SUCCESS,
         XenForo_Link::buildPublicLink('index'),
         'Post Date Changed'
     );
   }

Not sure about the XenForo_Link::buildPublicLink('index'), you would have to change that so that it links back to your thread or post. 'Post Date Changed' will be displayed on top for a short moment like you see it in other places here in XenForo.
 
The information you provided in post #75 is extremely helpful. I've always wondered what the $this is doing. Now finally after many years of wondering I now have a small understanding.
 
The actionChangedatesave function does not return a view. Instead it is returning a redirect. You can see that at the end of the function. That should redirect to the post.
PHP:
public function actionChangedatesave()
{
$this->_assertPostOnly();// if you are not a super admin, you will get an error
if(!XenForo_Visitor::getInstance()->isSuperAdmin())
{
return;
}
$postId= $this->_input->filterSingle('post_id', XenForo_Input::UINT);//this is an integer here! I guess the easiest solution would be to use an integer value at first, before you know how you can make use of xenForos calendar for selecting a date.
$newPostDate = $this->_input->filterSingle('new_post_date', XenForo_Input::UINT);
$dw = XenForo_DataWriter::create('XenForo_DataWriter_Post');
$dw->setExistingData($postId);
$dw->set('date', $newPostDate);
$dw->save();

$post['post_id'] = $postId;

return $this->responseRedirect(
XenForo_ControllerResponse_Redirect::SUCCESS,
XenForo_Link::buildPublicLink('post', $post),
'Post Date Changed');
}


I guess $post has to have a 'post_id' array value inside, that's why I named it in $post['post_id'] = $postId; I think just putting $postId in the buildPublicLink would not work.
 
The code you posted in post #76, I assume that is put into the Post.php file.
Yes, there are now the two action functions actionChangedate and actionChangedatesave in library/Andy/ChangeDate/ControllerPublic/Post.php
 
Top Bottom