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

Extending DataWriter/_preSave but it executes elsewhere too

Discussion in 'XenForo Development Discussions' started by Marcel, Apr 6, 2014.

  1. Marcel

    Marcel Active Member

    I've written an addon to add the ability to archive threads.
    It adds two colunns/options to the xf_forum table for the Forum Editor so we can set each one to archive to on a per-forum basis.

    Here is my DataWriter Code

    PHP:
    <?php
    class MaB_Arch_DataWriter_Forum extends XFCP_MaB_Arch_DataWriter_Forum
    {
        protected function 
    _getFields()
        {
            
    $fields parent::_getFields();
            
    $fields['xf_forum']['mab_arch_archive_enabled'] = array(
                
    'type' => self::TYPE_UINT'default' => 0
            
    );
            
    $fields['xf_forum']['mab_arch_archive_forumid'] = array(
                
    'type' => self::TYPE_UINT'default' => 0
            
    );
            return 
    $fields;
        }

        protected function 
    _preSave()
        {
            
    $parent parent::_preSave();
            
    // only run this conditional if we are editing a forum
            
    if ($GLOBALS['fc']->route()->getControllerName() === 'XenForo_ControllerAdmin_Forum')
                {
                
    // is the 'enabled' checkbox ticked?
                
    if(isset($_POST['mab_arch_archive_enabled']))
                {
                    
    // if so store the entered archive forum id into a variable and pass it
                    // to the datawriter and set the enabled flag to 1
                    
    $mab_arch_archive_forumid $_POST['mab_arch_archive_forumid'];
                    
    $this->set('mab_arch_archive_enabled'1);
                    
    $this->set('mab_arch_archive_forumid'$mab_arch_archive_forumid);
                }
                else
                {
                    
    // if not then ignore what was typed in and just set the archive forum id to 0
                    
    $this->set('mab_arch_archive_enabled'0);
                    
    $this->set('mab_arch_archive_forumid'NULL);
                }}
            return 
    $parent;
         
        }
    }
    The problem is the conditional. The code works fine without it, it presents the forum options which I set OK.
    As soon as I then archive a thread...the forum options are removed.
    I'm assuming because when it's rebuilding the discussion after the move, it's also using the Forum DataWriter, and running through the _PreSave code again...and because there's no forum options in the $POST, it's assuming I've set them as Disabled / 0.

    So I put in the conditional, using the discussion from digitalpoint and Mike/Kier here

    This works, but the conditional throws an error with TapaTalk, and tbh I'd rather work around it at my end.
     
  2. tyteen4a03

    tyteen4a03 Well-Known Member

    Why are you extending the DataWriter instead of the Controller? This doesn't belong in the DataWriter.
     
  3. Marcel

    Marcel Active Member

    Damn, I had a feeling it should have been the other way round.
    I didn't know how to add per-forum options, so with a mix of searching, googling, and trying to understand how other addons did it, I did it this way. I came across both ways while looking it up.

    I'm learning all this as I'm going along. Adding new functionality is fine, I'm just not sure on how to extend current functionality (IE adding two more fields to be dealt with via ControllerAdmin_Forum).
     
  4. Marcel

    Marcel Active Member

    Forgot to say, I did think of adding a new listener, triggering on ControllerAdmin_Forum, and in that setting a variable.
    Then using a simple variable check in the DataWriter.

    Although that seemed a wasteful way of doing it.
     
  5. tyteen4a03

    tyteen4a03 Well-Known Member

    Extend ControllAdmin_Forum::actionSave() so it is saved ($dw->set(things)), use template modification system to add the option to the forum edit template.
     
  6. Marcel

    Marcel Active Member

    Thanks for the reply :)

    Right, I've been squirrelling away all afternoon and I just couldn't figure out how to get it from the preSave in the DW to the actionSave of of the Forum Admin Controller.
    The best I could do was similar to this guy
    http://xenforo.com/community/threads/datawriter-not-working-with-my-action-it-save-blank.63154/

    However, I *was* getting errors. The rest of the fields (Forum Name etc) were throwing up unnecessary errors.
    Then I saw Chris D's reply about having to put the code in the DataWriter/_preSave instead, so that'll do for me lol

    I've moved the conditional into the controlleradmin, then populated GLOBALS with the info, so I can pass those back to the datawriter, which then does it's original job.


    This is my ControllerAdmin_Forum
    PHP:
    class MaB_Arch_ControllerAdmin_Forum extends XFCP_MaB_Arch_ControllerAdmin_Forum
    {
        public function 
    actionSave()
        {
            
    // is the 'enabled' checkbox ticked?
            
    if(isset($_POST['mab_arch_archive_enabled']))
            {
                
    // if so store the entered archive forum id into a variable and pass it
                // to the datawriter and set the enabled flag to 1
                
    $mab_arch_archive_forumid $_POST['mab_arch_archive_forumid'];
                
    $GLOBALS['mab_arch_archive_enabled'] = 1;
                
    $GLOBALS['mab_arch_archive_forumid'] = $mab_arch_archive_forumid;
            }
            else
            {
                
    // if not then ignore what was typed in and just set the archive forum id to 0
                
    $GLOBALS['mab_arch_archive_enabled'] = 0;
                
    $GLOBALS['mab_arch_archive_forumid'] = NULL;
            }
            return 
    parent::actionSave();
        }
    }
    and my DataWriter_Forum
    PHP:
        protected function _getFields()
        {
            
    $fields parent::_getFields();
            
    $fields['xf_forum']['mab_arch_archive_enabled'] = array(
                
    'type' => self::TYPE_UINT'default' => 0
            
    );
            
    $fields['xf_forum']['mab_arch_archive_forumid'] = array(
                
    'type' => self::TYPE_UINT'default' => 0
            
    );
            return 
    $fields;
        }

        protected function 
    _preSave()
        {
            
    $parent parent::_preSave();
            
    $this->set('mab_arch_archive_enabled'$GLOBALS['mab_arch_archive_enabled']);
            
    $this->set('mab_arch_archive_forumid'$GLOBALS['mab_arch_archive_forumid']);
            return 
    $parent;
        }
     
  7. tyteen4a03

    tyteen4a03 Well-Known Member

    Why are you using globals?
     
  8. Marcel

    Marcel Active Member

    I need to pass the two variables from the controller to the datawriter. Is there a better way of doing it?
     
  9. tyteen4a03

    tyteen4a03 Well-Known Member

    XenForo_DataWriter::set()

    /me demands @Marcel to learn how to DataWriter
     
  10. Marcel

    Marcel Active Member

    I'm trying! :p
    So you're saying I can just pull the input direct from within the DataWriter?
    That's fine....but then we're back to the problem I had at first....I need this to only run on ControllerAdmin_Forum, or else other things fail.
     
  11. Chris D

    Chris D XenForo Developer Staff Member

    @tyteen4a03, there is actually a long standing issue involved in extending controller actions that save data using the DataWriter.

    It's not possible to set data after preSave has been called, (so you can't just $parent = parent::actionSave(); /* blah */ return $parent;) and if you call the DataWriter, set your data, you can't save because you need the parent to do that, but then return parent::actionSave() will create a new DataWriter instance overwriting your own.

    So what @Marcel is doing is actually correct.

    It's discussed in great detail here:

    http://xenforo.com/community/threads/more-modular-code.29444/
     
    Xon and tyteen4a03 like this.
  12. Chris D

    Chris D XenForo Developer Staff Member

    And Mike pretty much confirms that, as it stands right now, the "hacky" workarounds are what is needed:

     
  13. Marcel

    Marcel Active Member

    Thanks Chris, I thought I was going insane for a moment then.
    I've now modified the code, and got it all working.
    Using this in the Controller
    PHP:
        public function actionSave()
        {
            
    $GLOBALS['mab_arch_controlleradmin_forum'] = $this;
            return 
    parent::actionSave();
        }
    and this in the DataWriter
    PHP:
        protected function _preSave()
        {
            
    $parent parent::_preSave();
            if (isset(
    $GLOBALS['mab_arch_controlleradmin_forum']))
            {
                
    $this->set('mab_arch_archive_enabled'$GLOBALS['mab_arch_controlleradmin_forum']->getInput()->filterSingle('mab_arch_archive_enabled'XenForo_Input::UINT));
                
    $this->set('mab_arch_archive_forumid'$GLOBALS['mab_arch_controlleradmin_forum']->getInput()->filterSingle('mab_arch_archive_forumid'XenForo_Input::UINT));
                }
            return 
    $parent;
        }
     
  14. Chris D

    Chris D XenForo Developer Staff Member

    I'll dig out my most recent, preferred workaround.

    You can actually see this in my Hide Poll Results add-on:

    PHP:
    <?php

    class HidePollResults_ControllerPublic_Forum extends XFCP_HidePollResults_ControllerPublic_Forum
    {
        
    /**
        * Inserts a new thread into this forum.
        *
        * @return XenForo_ControllerResponse_Abstract
        */
        
    public function actionAddThread()
        {
            
    $session false;
            if (
    XenForo_Application::isRegistered('session'))
            {
                
    /** @var $session XenForo_Session */
                
    $session XenForo_Application::get('session');
            }

            
    $hidePollResultsFormShown $this->_input->filterSingle('hide_poll_results_form'XenForo_Input::UINT);
            if (!
    $hidePollResultsFormShown || !$session)
            {
                if (
    $session)
                {
                    
    $session->remove('hidePollResults');
                }

                return 
    parent::actionAddThread();
            }

            
    $inputData $this->_input->filter(array(
                
    'hide_results' => XenForo_Input::BOOLEAN,
                
    'until_close' => XenForo_Input::BOOLEAN
            
    ));

            
    $session->set('hidePollResults'$inputData);
               
            return 
    parent::actionAddThread();
        }
    }
    So, before the parent is called, get the input data and save it to the user's session.

    PHP:
    <?php

    class HidePollResults_DataWriter_Poll extends XFCP_HidePollResults_DataWriter_Poll
    {
        
    /**
        * Gets the fields that are defined for the table. See parent for explanation.
        *
        * @return array
        */
        
    protected function _getFields()
        {
            
    $parent parent::_getFields();
           
            
    $parent['xf_poll']['hide_results'] = array('type' => self::TYPE_BOOLEAN'default' => 0);
            
    $parent['xf_poll']['until_close'] =    array('type' => self::TYPE_BOOLEAN,    'default' => 0);
           
            return 
    $parent;
        }
       
        
    /**
        * Pre-save handling.
        */
        
    protected function _preSave()
        {
            
    $session false;
            if (
    XenForo_Application::isRegistered('session'))
            {
                
    /** @var $session XenForo_Session */
                
    $session XenForo_Application::get('session');
            }

            if (
    $session && $session->get('hidePollResults'))
            {
                
    $this->bulkSet($session->get('hidePollResults'));

                
    $session->remove('hidePollResults');
            }

            return 
    parent::_preSave();
        }
    }
    Then, in preSave in the DataWriter, we can grab that data from the session, set it in the DataWriter and then remove the data from the session.
     
    lsxforo and NixFifty like this.
  15. tyteen4a03

    tyteen4a03 Well-Known Member

    TIL.

    To-fix in XF2 it seems.
     
  16. Marcel

    Marcel Active Member

    TIL too :)

    Thanks Chris. I'll work that way of doing it, into the next update. Much appreciated :)
     
  17. AlexT

    AlexT Well-Known Member

    Hi Chris, I remember the discussion from here, and I understand you're using the session object now to temporarily store the data as your preferred workaround.

    Question: Is there any practical reason why you're storing into the session rather than defining a custom key in the xenforo registry object?
     
  18. Chris D

    Chris D XenForo Developer Staff Member

    It depends what it's for I guess. I tend to only use the session now because I know it works and for most tasks I can just copy and paste my existing code.

    I started using the session where actually using the session made more sense.

    Specifically I had something that was run by users in the front end. I guess there was potential here for there to be a race condition where one user's data could overwrite another. To mitigate that I guess you could store it by user ID in the registry but then by the time you've done that you might as well just be using storage designed to be individual to each user. Also as the session data is temporary in nature there wasn't any risk of old or orphaned data being left in the registry in case of some error.

    Overall it works and works well so I'm going to stick to it.

    Roll on XF 2.0 where hopefully this is going to be fixed :)
     
    Daniel Hood likes this.
  19. Marcel

    Marcel Active Member

    Is it bad that I got the updated thread notification, read through the thread and have very little recollection of it? I've even looked through the code I've provided and I'm looking at my screen with a blank look :D

    Aaaah, gotta love those caffeine and thumping music-fuelled nights of development :D
     

Share This Page