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

Hi Jeremy,

Thank you for reviewing my code.

When I omit that line of code the post date is off by seven hours. Is there a better solution?

Try:

PHP:
  // convert to unix timestamp
  //date_default_timezone_set('America/Los_Angeles');
  $date = $newPostDate . ' ' . $newPostTime;
  $dateline = strtotime($date) + XenForo_Locale::getTimeZoneOffset();

Possibly needs to be - instead of +
 
Thank you, Luke. That works perfect.

PHP:
    // convert to unix timestamp
     $date = $newPostDate . ' ' . $newPostTime;
     $dateline = strtotime($date) - XenForo_Locale::getTimeZoneOffset();
 
One minor improvement I would like to make is to have the overlay title show the post number (position) not the post_id.

So I added this to the Post.php code:

PHP:
    $post['post_id'] = $this->_input->filterSingle('post_id', XenForo_Input::UINT);
    // added this to try and get the position value
     $post['position'] = $this->_input->filterSingle('position', XenForo_Input::UINT);

I also added for testing purposes the following to the andy_changedate template:

PHP:
{xen:helper dump, $post}

But when I click the Change Date link the overlay shows this:

pic001.webp

Notice the ["position"] variable always show int[0] no matter which Change Date link I click. What am I doing wrong?
 
You can only get post_id from the route. Not any other value. You need to get all the data from the post_id. Here you can use XenForos function for getting all post data. You can access then the preferred data {$post.position} in the template
PHP:
public function actionChangedate()
 {$postId = $this->_input->filterSingle('post_id', XenForo_Input::UINT);
$post = $this->getModelFromCache('XenForo_Model_Post')->getPostById($postId);
$viewParams = array('post' => $post);

 if (XenForo_Visitor::getInstance()->isSuperAdmin())
 {
 return $this->responseView('Andy_DateChange_ViewPublic_Post','andy_changedate',$viewParams);
 }
 }
 
Thank you, Marcus.

I added the following to Post.php

PHP:
    // get all post data
     $post = $this->getModelFromCache('XenForo_Model_Post')->getPostById($post['post_id']);

and made the following change in the andy_changedate template

PHP:
<xen:title>Change Date for post #{$post.position}</xen:title>

It now shows the position in the overlay title, but I need to add 1 to {$post.position} so it corresponds with the post number shown below each post.
 
you can use xen:calc to add 1 to the position. You have to figure out the syntax.

What would be definitely nice is to have the original date and time already in the <input> boxes.
 
Adding the current post date to the overlay makes it a bit messy. As an admin one has to know in advance what date and time they want to change the post to.

Here's how the overlay looks now.

pic001.webp
 
The Post.php file currently looks like this:

PHP:
<?php

class Andy_ChangeDate_ControllerPublic_Post extends XFCP_Andy_ChangeDate_ControllerPublic_Post
{
   public function actionChangedate()
   {
     // get post_id from route
     $post['post_id'] = $this->_input->filterSingle('post_id', XenForo_Input::UINT);
     
     // get all post data
     $post = $this->getModelFromCache('XenForo_Model_Post')->getPostById($post['post_id']);
     
     $viewParams = array('post' => $post);
     
     if (XenForo_Visitor::getInstance()->isSuperAdmin())
     {
       return $this->responseView('Andy_DateChange_ViewPublic_Post','andy_changedate',$viewParams);
     }
   }
   
   public function actionChangedatesave()
   {
     // make sure data comes from $_POST
     $this->_assertPostOnly();
     
     // if you are not a super admin, you will get an error
     if (!XenForo_Visitor::getInstance()->isSuperAdmin())
     {
       return;
     }
     
     $post['post_id'] = $this->_input->filterSingle('post_id', XenForo_Input::UINT);
     
     // get input text from template
     $newPostDate = $this->_input->filterSingle('new_post_date', XenForo_Input::STRING);
     
     if ($newPostDate == '')
     {
       return $this->responseError(new XenForo_Phrase('date_missing'));
     }
     
     // get input text from template
     $newPostTime = $this->_input->filterSingle('new_post_time', XenForo_Input::STRING);
     
     if ($newPostTime == '')
     {
       return $this->responseError(new XenForo_Phrase('time_missing'));
     }     
     
     // convert to unix timestamp
     $date = $newPostDate . ' ' . $newPostTime;
     $dateline = strtotime($date) - XenForo_Locale::getTimeZoneOffset();
     
     if ($dateline == '')
     {
       return $this->responseError(new XenForo_Phrase('date_or_time_format_incorrect'));
     }
     
     //########################################
     // start database operations
     //########################################
     
     // update the xf_post.post_date
     $dw = XenForo_DataWriter::create('XenForo_DataWriter_DiscussionMessage_Post');
     $dw->setExistingData($post['post_id']);
     $dw->set('post_date', $dateline);
     $dw->save();
     
     // update the xf_post.position for each post in the thread
     // also updates other tables with first post and last post information
     $threadId = $dw->get('thread_id');
     $dw = XenForo_DataWriter::create('XenForo_DataWriter_Discussion_Thread');
     $dw->setExistingData($threadId);
     $dw->rebuildDiscussion();
     $dw->save();
     
     //########################################
     // return with response redirect
     //########################################
         
     return $this->responseRedirect(
     XenForo_ControllerResponse_Redirect::SUCCESS,
     XenForo_Link::buildPublicLink('posts', $post),'Post Date Changed');       
   }
}

?>
 
You could write
PHP:
if (!$newPostTime)
or
PHP:
 if (!($newPostTime = $this->_input->filterSingle('new_post_time', XenForo_Input::STRING)))

I would change
PHP:
// get post_id from route
$post['post_id'] =
to
PHP:
// get post_id from route
$postId =
Usually when you retrieve just an integer value, you don't open up an array and assign the simple integer to it. $postId is better because it indicates just one Id is inside the variable. You do that only in the second function ($post['post_id']) just so that XenForos route is working.
 
Last edited:
Thank you, Marcus. I'm learning so much from you and the other kind members who have posted in this thread.

Post.php as it stands now.

PHP:
<?php

class Andy_ChangeDate_ControllerPublic_Post extends XFCP_Andy_ChangeDate_ControllerPublic_Post
{
   public function actionChangedate()
   {
     // get post_id from route
     $postId = $this->_input->filterSingle('post_id', XenForo_Input::UINT);
     
     // get all post data
     $post = $this->getModelFromCache('XenForo_Model_Post')->getPostById($postId);
     
     $viewParams = array('post' => $post);
     
     if (XenForo_Visitor::getInstance()->isSuperAdmin())
     {
       return $this->responseView('Andy_DateChange_ViewPublic_Post','andy_changedate',$viewParams);
     }
   }
   
   public function actionChangedatesave()
   {
     // make sure data comes from $_POST
     $this->_assertPostOnly();
     
     // if you are not a super admin, you will get an error
     if (!XenForo_Visitor::getInstance()->isSuperAdmin())
     {
       return;
     }
     
     $post['post_id'] = $this->_input->filterSingle('post_id', XenForo_Input::UINT);
     
     // get input text from template
     $newPostDate = $this->_input->filterSingle('new_post_date', XenForo_Input::STRING);
     
     if (!$newPostDate)
     {
       return $this->responseError(new XenForo_Phrase('date_missing'));
     }
     
     // get input text from template
     $newPostTime = $this->_input->filterSingle('new_post_time', XenForo_Input::STRING);
     
     if (!$newPostTime)
     {
       return $this->responseError(new XenForo_Phrase('time_missing'));
     }     
     
     // convert to unix timestamp
     $date = $newPostDate . ' ' . $newPostTime;
     $dateline = strtotime($date) - XenForo_Locale::getTimeZoneOffset();
     
     if (!$dateline)
     {
       return $this->responseError(new XenForo_Phrase('date_or_time_format_incorrect'));
     }
     
     //########################################
     // start database operations
     //########################################
     
     // update the xf_post.post_date
     $dw = XenForo_DataWriter::create('XenForo_DataWriter_DiscussionMessage_Post');
     $dw->setExistingData($post['post_id']);
     $dw->set('post_date', $dateline);
     $dw->save();
     
     // update the xf_post.position for each post in the thread
     // also updates other tables with first post and last post information
     $threadId = $dw->get('thread_id');
     $dw = XenForo_DataWriter::create('XenForo_DataWriter_Discussion_Thread');
     $dw->setExistingData($threadId);
     $dw->rebuildDiscussion();
     $dw->save();
     
     //########################################
     // return with response redirect
     //########################################
         
     return $this->responseRedirect(
     XenForo_ControllerResponse_Redirect::SUCCESS,
     XenForo_Link::buildPublicLink('posts', $post),'Post Date Changed');       
   }
}

?>
 
Honestly I would just give one error message to make the code cleaner and only have one phrase, like "You have to enter both date and time in the correct formats". The addon is better now from a user view, but if you want to make changes in the future, the less code and phrases - it is always easier to handle.
 
Change
PHP:
  if (XenForo_Visitor::getInstance()->isSuperAdmin())
{
return $this->responseView('Andy_DateChange_ViewPublic_Post','andy_changedate',$viewParams);
}
to either the beginning of the function (this is common practise in XenForo, as it is easier to read: you do not have to follow the } until the end of the function)
PHP:
  if (!XenForo_Visitor::getInstance()->isSuperAdmin())
{
return;
}
... or put it around the whole function like

PHP:
  if (XenForo_Visitor::getInstance()->isSuperAdmin())
{
   $postId = $this->_input->filterSingle('post_id', XenForo_Input::UINT);
// get all post data$post = $this->getModelFromCache('XenForo_Model_Post')->getPostById($postId);
$viewParams = array('post' => $post);

return $this->responseView('Andy_DateChange_ViewPublic_Post','andy_changedate',$viewParams);
}
Because the program already knows about if there is a super admin doing that stuff anyway, so the program can check that first. If someone is not allowed to do something, it is best to stop the program at the earliest point and not let the program run some "harmless" stuff and only check later when it is going to be "serious".
 
You can also put 'Post Date Changed' into a phrase, having then two: one error phrase and one redirect phrase.

Anyway I think that addon is perfect as it is, ready for going live:)
 
one more thing: If you really have to include both date and time, maybe one simple input box would be better:

dd/mm/yy 8:00am

It is easier to enter the date then, as you do not have to go to the next tab. Also it's obvious to the super admin that you can not just change the day, or the time. You have to change both. So you also only need one error message, and the routine for turning the date into the unix timestamp is one step easier.

I would definitely put in the original date (not the current date, but the date the post was created) in the box, so the admin understands the format, and can also just change one value like the day or the time, the rest ist already there in the input box.
 
Both excellent suggestion (post #176).

Here's the latest Post.php

PHP:
<?php

class Andy_ChangeDate_ControllerPublic_Post extends XFCP_Andy_ChangeDate_ControllerPublic_Post
{
   public function actionChangedate()
   {
     // if you are not a super admin, return
     if (!XenForo_Visitor::getInstance()->isSuperAdmin())
     {
       return;
     }
       
     // get post_id from route
     $postId = $this->_input->filterSingle('post_id', XenForo_Input::UINT);
   
     // get all post data
     $post = $this->getModelFromCache('XenForo_Model_Post')->getPostById($postId);
   
     $viewParams = array('post' => $post);
   
     // display andy_changedate template overlay
     return $this->responseView('Andy_DateChange_ViewPublic_Post','andy_changedate',$viewParams);

   }
 
   public function actionChangedatesave()
   {
     // make sure data comes from $_POST
     $this->_assertPostOnly();
   
     // if you are not a super admin, you will get an error
     if (!XenForo_Visitor::getInstance()->isSuperAdmin())
     {
       return;
     }
   
     $post['post_id'] = $this->_input->filterSingle('post_id', XenForo_Input::UINT);
   
     // get input text from template
     $newPostDate = $this->_input->filterSingle('new_post_date', XenForo_Input::STRING);
   
     if (!$newPostDate)
     {
       return $this->responseError(new XenForo_Phrase('you_have_to_enter_both_date_and_time_in_the_correct_formats'));
     }
   
     // get input text from template
     $newPostTime = $this->_input->filterSingle('new_post_time', XenForo_Input::STRING);
   
     if (!$newPostTime)
     {
       return $this->responseError(new XenForo_Phrase('you_have_to_enter_both_date_and_time_in_the_correct_formats'));
     }   
   
     // convert to unix timestamp
     $date = $newPostDate . ' ' . $newPostTime;
     $dateline = strtotime($date) - XenForo_Locale::getTimeZoneOffset();
   
     if (!$dateline)
     {
       return $this->responseError(new XenForo_Phrase('you_have_to_enter_both_date_and_time_in_the_correct_formats'));
     }
   
     //########################################
     // start database operations
     //########################################
   
     // update the xf_post.post_date
     $dw = XenForo_DataWriter::create('XenForo_DataWriter_DiscussionMessage_Post');
     $dw->setExistingData($post['post_id']);
     $dw->set('post_date', $dateline);
     $dw->save();
   
     // update the xf_post.position for each post in the thread
     // also updates other tables with first post and last post information
     $threadId = $dw->get('thread_id');
     $dw = XenForo_DataWriter::create('XenForo_DataWriter_Discussion_Thread');
     $dw->setExistingData($threadId);
     $dw->rebuildDiscussion();
     $dw->save();
   
     //########################################
     // return with response redirect
     //########################################
       
     return $this->responseRedirect(
     XenForo_ControllerResponse_Redirect::SUCCESS,
     XenForo_Link::buildPublicLink('posts', $post),'Post Date Changed');     
   }
}

?>
 
Last edited:
I would just use one input box (#178). OK it is possible some admins just enter the date. In that case it would be maybe nice to fetch the original posts time and add the time to the date. So if the former post was from 8/8/13 10:40am and the admin enters 4/8/13, the program would set it to 4/8/13 10:40am. But admins are using this addon, they should know how to use it :)

If you want to have two, I would replace this

PHP:
// get input text from template$newPostDate = $this->_input->filterSingle('new_post_date', XenForo_Input::STRING);

if (!$newPostDate)
{
return $this->responseError(new XenForo_Phrase('you_have_to_enter_both_date_and_time_in_the_correct_formats'));
}
// get input text from template$newPostTime = $this->_input->filterSingle('new_post_time', XenForo_Input::STRING);

if (!$newPostTime)
{
return $this->responseError(new XenForo_Phrase('you_have_to_enter_both_date_and_time_in_the_correct_formats'));
}
// convert to unix timestamp$date = $newPostDate . ' ' . $newPostTime;$dateline = strtotime($date) - XenForo_Locale::getTimeZoneOffset();

if (!$dateline)
{
return $this->responseError(new XenForo_Phrase('you_have_to_enter_both_date_and_time_in_the_correct_formats'));
}

with this as there is no danger to let PHP still working with missing or wrong strings with the strototime() function.
PHP:
$newPostDate = $this->_input->filterSingle('new_post_date', XenForo_Input::STRING);
$newPostTime = $this->_input->filterSingle('new_post_time', XenForo_Input::STRING);

$date = $newPostDate . ' ' . $newPostTime;
$dateline = strtotime($date) - XenForo_Locale::getTimeZoneOffset();

if (!$dateline)
{
return $this->responseError(new XenForo_Phrase('you_have_to_enter_both_date_and_time_in_the_correct_formats'));
}

Of course this would be easier:
PHP:
$newPostDate = $this->_input->filterSingle('new_post_date', XenForo_Input::STRING);
$dateline = strtotime($newPostDate ) - XenForo_Locale::getTimeZoneOffset();

if (!$dateline)
{
return $this->responseError(new XenForo_Phrase('you_have_to_enter_both_date_and_time_in_the_correct_formats'));
}
 
Back
Top Bottom