Creating an add-on called Change Date

AndyB

Well-known member
Description:

The purpose of this add-on is the allow superAdmins to move a post within a thread. For example if you want to move post #3 to be after post #5.

To change the date of a post, click the Change Date link located below each post.

pic001.webp
When the superAdmin clicks the Change Date link, the following overlay is displayed:

pic002.webp
Enter the date and time you would like the post to be and click the Submit button.
 
This tutorial covers the following:

1. Create an add-on in Xenforo
2. Create a controller
3. Create and use a Listener
4. Understand a route and access data from it (post_id)
5. Create a custom template and assign it to the add-on
6. Use {xen:link} to create links with the correct route
7. Use {$...} to display a variable in a template
8. Create a <form> in a template to submit data to the Controller
9. Create a Template Modification
10. Use basic security functions (isSuperAdmin, assertPostOnly)
11. How to access functions from other files (classes)
12. Access Xenforo's model functions
13. How to return a custom template from the controller
14. How to create an AJAX Overlay with a custom template
15. Return data from the controller so it can be used in templates
16. How to access data from a <form> field
17. How to get additional data from a datawriter after you saved it ($threadId)
18. Use the datawriter to change an existing database entry
19. How to return a redirect with a message ("Post Date Changed")
 
Understanding the different parts of a link:

http://www.site.com/forums/posts/1234/changedate

1. The /posts/1234/changedate part of the link is called the route.

2. Xenforo always listens to routes. For the route "posts", there is the controller XenForo_ControllerPublic_Post, which is always executed if a link with /posts/ is executed.

3. So to do something with posts like changing a post date, one has to first create a link with this route "/posts/", after the user clicks on this link, the file library/Xenforo/ControllerPublic/Post.php is executed.

4. The Controller has an action for each /posts/1234/this_is_the_action_function. Like actionChangedate or actionLike or actionReply.

5. So if you have a link like /posts/1234/changedate, there is always a function actionChangedate.

6. Adding an action function for changedate is referred to as "extending the controller".

7. Every route is set with a basic variable that is equal to the /1234/ in this example. For posts, it's post_id. For threads, it's thread_id. For forums, it's forum_id and so on.
 
Create your add-on directories and files:

libarary
--Andy
----ChangeDate
------ControllerPublic
--------Post.php
------Listener.php

Initial contents of the /library/ChangeDate/ControllerPublic/Post.php file.

PHP:
<?php

class Andy_ChangeDate_ControllerPublic_Post extends XFCP_Andy_ChangeDate_ControllerPublic_Post
{
   public function actionChangedate()
   {
     $post['post_id'] = $this->_input->filterSingle('post_id', XenForo_Input::UINT);

     $viewParams = array('post' => $post);
   }
}
?>
 
The Listener.php file:

Complete contents of the /library/Andy/ChangeDate/Listener.php file.

PHP:
<?php

class Andy_ChangeDate_Listener
{
   public static function Post($class, array &$extend)
   {
     $extend[] = 'Andy_ChangeDate_ControllerPublic_Post';
   }
}

?>
 
Last edited:
Create the code event listener:

Admin CP -> Development -> Code Event Listeners -> Create New Code Event Listener

pic004.webp
 
Testing what we have done so far:

At this point you are done extending the post controller. Entering in a URL like this into your browser will call your Post.php file:

http://www.site.com/forums/posts/1858732/changedate

You can verify this by adding an echo to the /library/Andy/ChangeDate/ControllerPublic/Post.php file.

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;
     }

     // echo "test" to verify code is working
     echo 'test';
   
     // get post_id from route, example /forums/posts/1859225/changedate
     $postId = $this->_input->filterSingle('post_id', XenForo_Input::UINT);
 
Last edited:
Create a template:

Create a new Master template called andy_changedate as seen in post #1.

Admin CP -> Appearance -> Templates -> Create New Template

Code:
<div class="formOverlay">

<xen:title>Change date for post #{xen:calc '{$post.position} + 1'}</xen:title>

<form action="{xen:link 'posts/changedate-save', $post}" method="post" class="xenForm AutoValidator" data-redirect="on">

<fieldset>

<dl class="ctrlUnit">
  <dt><label>Enter new date:</label></dt>
  <dd><input type="text" name="new_post_date" size="33" value="{$post.formatted_date}" class="textCtrl titleCtrl"></dd>
</dl>

<dl class="ctrlUnit submitUnit">
  <dt></dt>
  <dd><input type="submit" value="Submit" class="button primary" accesskey="s" /></dd>
</dl>
</fieldset>

<input type="hidden" name="_xfToken" value="{$visitor.csrf_token_page}" />
</form>

</div>
 
Template Modification:

The next step is to create a Template Modification for the post template. This will put the Change Date link at the bottom of each post.

Admin CP -> Appearance -> Template Modifications -> Create Template Modification

pic005.webp
 
The complete Post.php file:

Complete contents of the /library/Andy/ChangeDate/ControllerPublic/Post.php file.

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, example /forums/posts/1859225/changedate
     $postId = $this->_input->filterSingle('post_id', XenForo_Input::UINT);

     // get all post data variables
     $post = $this->getModelFromCache('XenForo_Model_Post')->getPostById($postId);

     // format date and time for use in template   input
     $userLanguage = XenForo_Visitor::getInstance()->getLanguage();
     $timeFormat = $userLanguage['date_format'] . ' ' . $userLanguage['time_format'];
     $post['formatted_date'] = date($timeFormat, $post['post_date'] + XenForo_Locale::getTimeZoneOffset());

     // display andy_changedate template
     $viewParams = array('post' => $post);
     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, return
     if (!XenForo_Visitor::getInstance()->isSuperAdmin())
     {
       return;
     }

     // get post_id
     $post['post_id'] = $this->_input->filterSingle('post_id', XenForo_Input::UINT);

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

     // convert $newPostDate to unix timestamp
     $dateline = strtotime($newPostDate ) - XenForo_Locale::getTimeZoneOffset();

     // make sure $dateline has a value
     if (!$dateline)
     {
       return $this->responseError('Incorrect date format entered');
     }

     //########################################
     // start database operations
     //########################################

     // update xf_post table (post_date)
     $dw = XenForo_DataWriter::create('XenForo_DataWriter_DiscussionMessage_Post');
     $dw->setExistingData($post['post_id']);
     $dw->set('post_date', $dateline);
     $dw->save();

     // update xf_post table (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:
20. Extending the class by giving a "hint" (only available from xenforo 1.2.x and above). It's faster than the older method with if/else, switch in the Listener function.
 
Top Bottom