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

Template modifications - programmatically?

Discussion in 'XenForo Development Discussions' started by Valhalla, Apr 8, 2014.

  1. Valhalla

    Valhalla Well-Known Member

    I think the answer is "no", but is it possible to enable and disable changes in Template Modifications programmatically?

    I feel it would be a much more comfortable option to use that system instead of template hooks, in cases where modifications might not need to be shown.

    EDIT: maybe I could just toggle the specific change on or off through a DB call?
     
    Last edited: Apr 8, 2014
  2. Chris D

    Chris D XenForo Developer Staff Member

    The answer is probably yes, actually.

    But what exactly do you want to do? I don't understand the need to dynamically enable or disable a TM.
     
  3. Valhalla

    Valhalla Well-Known Member

    If one of my settings was to allow the change in position (re-order) of a navigation link.

    But I've just twigged, I don't need a new template modification for each "case" - I'd just need to use the conditional tags based on the option that is selected (within one template only). :)
     
    Chris D likes this.
  4. Valhalla

    Valhalla Well-Known Member

    Is is possible to use conditional statements in the 'Find' box of the Template Modifications tool? (No, I think.)
     
    Last edited: Apr 10, 2014
  5. Chris D

    Chris D XenForo Developer Staff Member

    Correct. That's a no. No harm in multiple TMs though or conditionals in the replacements.
     
    Valhalla likes this.
  6. Valhalla

    Valhalla Well-Known Member

    I'm trying to insert something at a specific point, only with specific conditions (because I suppose, then I don't have to worry about the "execution order", because it all likelihood this wouldn't matter if I only find and replace small specific piece of the template).

    I can only achieve this if I find entire blocks, and replace with entire blocks - but that's not very flexible if other template modifications are being used at the same time. (If most authors leave their execution order at "10", then in a worst case scenario, my find and replace template modifications will always fail because the significant block that I'm trying to change has already been changed, or cannot be found).

    So far, I've tried using template hooks, and two different approaches in the TMS (one is below, the other was to create a new TM for each condition - but that didn't work because they were all active at one time).

    In the TMS, I'm using a Simple Replacement. My goal is to reduce my "Find" to just a single line.

    Thanks, if anyone has any ideas.
     
  7. Valhalla

    Valhalla Well-Known Member

    Find:
    Code:
    <xen:hook name="navigation_tabs_forums">
        <xen:if is="{$visitor.user_id}"><li><a href="{xen:link 'forums/-/mark-read', $forum, 'date={$serverTime}'}" class="OverlayTrigger">{xen:phrase mark_forums_read}</a></li></xen:if>
        <xen:if is="{$canSearch}"><li><a href="{xen:link search, '', 'type=post'}">{xen:phrase search_forums}</a></li></xen:if>
        <xen:if is="{$visitor.user_id}">
            <li><a href="{xen:link 'watched/forums'}">{xen:phrase watched_forums}</a></li>
            <li><a href="{xen:link 'watched/threads'}">{xen:phrase watched_threads}</a></li>
        </xen:if>
        <li><a href="{xen:link 'find-new/posts'}" rel="nofollow">{xen:if $visitor.user_id, {xen:phrase new_posts}, {xen:phrase recent_posts}}</a></li>
    </xen:hook>
    Replace:
    Code:
    <xen:if is="{$xenOptions.multiviewActive} AND {$visitor.permissions.multiview.useMultiview}">
        <xen:comment>Do not display a navigation link</xen:comment>
        <xen:if is="{$xenOptions.multiviewNavigationPosition} == 0">
            <xen:hook name="navigation_tabs_forums">
                <xen:if is="{$visitor.user_id}"><li><a href="{xen:link 'forums/-/mark-read', $forum, 'date={$serverTime}'}" class="OverlayTrigger">{xen:phrase mark_forums_read}</a></li></xen:if>
                <xen:if is="{$canSearch}"><li><a href="{xen:link search, '', 'type=post'}">{xen:phrase search_forums}</a></li></xen:if>
                <xen:if is="{$visitor.user_id}">
                    <li><a href="{xen:link 'watched/forums'}">{xen:phrase watched_forums}</a></li>
                    <li><a href="{xen:link 'watched/threads'}">{xen:phrase watched_threads}</a></li>
                </xen:if>
                <li><a href="{xen:link 'find-new/posts'}" rel="nofollow">{xen:if $visitor.user_id, {xen:phrase new_posts}, {xen:phrase recent_posts}}</a></li>
            </xen:hook>
        <xen:comment>Display prior to "Mark Forums Read"</xen:comment>
        <xen:elseif is="{$xenOptions.multiviewNavigationPosition} == 1" />
            <xen:hook name="navigation_tabs_forums">
                <xen:if is="{$visitor.user_id}"><li><a href="{xen:link multiview}" rel="nofollow">{xen:phrase multiview_title}</a></li></xen:if>
                <xen:if is="{$visitor.user_id}"><li><a href="{xen:link 'forums/-/mark-read', $forum, 'date={$serverTime}'}" class="OverlayTrigger">{xen:phrase mark_forums_read}</a></li></xen:if>
                <xen:if is="{$canSearch}"><li><a href="{xen:link search, '', 'type=post'}">{xen:phrase search_forums}</a></li></xen:if>
                <xen:if is="{$visitor.user_id}">
                    <li><a href="{xen:link 'watched/forums'}">{xen:phrase watched_forums}</a></li>
                    <li><a href="{xen:link 'watched/threads'}">{xen:phrase watched_threads}</a></li>
                </xen:if>
                <li><a href="{xen:link 'find-new/posts'}" rel="nofollow">{xen:if $visitor.user_id, {xen:phrase new_posts}, {xen:phrase recent_posts}}</a></li>
            </xen:hook>
        <xen:comment>Display prior to "Search Forums"</xen:comment>
        <xen:elseif is="{$xenOptions.multiviewNavigationPosition} == 2" />
            <xen:hook name="navigation_tabs_forums">
                <xen:if is="{$visitor.user_id}"><li><a href="{xen:link 'forums/-/mark-read', $forum, 'date={$serverTime}'}" class="OverlayTrigger">{xen:phrase mark_forums_read}</a></li></xen:if>
                <xen:if is="{$visitor.user_id}"><li><a href="{xen:link multiview}" rel="nofollow">{xen:phrase multiview_title}</a></li></xen:if>
                <xen:if is="{$canSearch}"><li><a href="{xen:link search, '', 'type=post'}">{xen:phrase search_forums}</a></li></xen:if>
                <xen:if is="{$visitor.user_id}">
                    <li><a href="{xen:link 'watched/forums'}">{xen:phrase watched_forums}</a></li>
                    <li><a href="{xen:link 'watched/threads'}">{xen:phrase watched_threads}</a></li>
                </xen:if>
                <li><a href="{xen:link 'find-new/posts'}" rel="nofollow">{xen:if $visitor.user_id, {xen:phrase new_posts}, {xen:phrase recent_posts}}</a></li>
            </xen:hook>
        <xen:comment>Display prior to "Watched Forums"</xen:comment>
        <xen:elseif is="{$xenOptions.multiviewNavigationPosition} == 3" />
            <xen:hook name="navigation_tabs_forums">
                <xen:if is="{$visitor.user_id}"><li><a href="{xen:link 'forums/-/mark-read', $forum, 'date={$serverTime}'}" class="OverlayTrigger">{xen:phrase mark_forums_read}</a></li></xen:if>
                <xen:if is="{$canSearch}"><li><a href="{xen:link search, '', 'type=post'}">{xen:phrase search_forums}</a></li></xen:if>
                <xen:if is="{$visitor.user_id}"><li><a href="{xen:link multiview}" rel="nofollow">{xen:phrase multiview_title}</a></li></xen:if>
                <xen:if is="{$visitor.user_id}">
                    <li><a href="{xen:link 'watched/forums'}">{xen:phrase watched_forums}</a></li>
                    <li><a href="{xen:link 'watched/threads'}">{xen:phrase watched_threads}</a></li>
                </xen:if>
                <li><a href="{xen:link 'find-new/posts'}" rel="nofollow">{xen:if $visitor.user_id, {xen:phrase new_posts}, {xen:phrase recent_posts}}</a></li>
            </xen:hook>
        <xen:comment>Display prior to "Watched Threads"</xen:comment>
        <xen:elseif is="{$xenOptions.multiviewNavigationPosition} == 4" />
            <xen:hook name="navigation_tabs_forums">
                <xen:if is="{$visitor.user_id}"><li><a href="{xen:link 'forums/-/mark-read', $forum, 'date={$serverTime}'}" class="OverlayTrigger">{xen:phrase mark_forums_read}</a></li></xen:if>
                <xen:if is="{$canSearch}"><li><a href="{xen:link search, '', 'type=post'}">{xen:phrase search_forums}</a></li></xen:if>
                <xen:if is="{$visitor.user_id}">
                    <li><a href="{xen:link 'watched/forums'}">{xen:phrase watched_forums}</a></li>
                    <xen:if is="{$visitor.user_id}"><li><a href="{xen:link multiview}" rel="nofollow">{xen:phrase multiview_title}</a></li></xen:if>
                    <li><a href="{xen:link 'watched/threads'}">{xen:phrase watched_threads}</a></li>
                </xen:if>
                <li><a href="{xen:link 'find-new/posts'}" rel="nofollow">{xen:if $visitor.user_id, {xen:phrase new_posts}, {xen:phrase recent_posts}}</a></li>
            </xen:hook>
        <xen:comment>Display prior to "New Posts"</xen:comment>
        <xen:elseif is="{$xenOptions.multiviewNavigationPosition} == 5" />
            <xen:hook name="navigation_tabs_forums">
                <xen:if is="{$visitor.user_id}"><li><a href="{xen:link 'forums/-/mark-read', $forum, 'date={$serverTime}'}" class="OverlayTrigger">{xen:phrase mark_forums_read}</a></li></xen:if>
                <xen:if is="{$canSearch}"><li><a href="{xen:link search, '', 'type=post'}">{xen:phrase search_forums}</a></li></xen:if>
                <xen:if is="{$visitor.user_id}">
                    <li><a href="{xen:link 'watched/forums'}">{xen:phrase watched_forums}</a></li>
                    <li><a href="{xen:link 'watched/threads'}">{xen:phrase watched_threads}</a></li>
                </xen:if>
                <xen:if is="{$visitor.user_id}"><li><a href="{xen:link multiview}" rel="nofollow">{xen:phrase multiview_title}</a></li></xen:if>
                <li><a href="{xen:link 'find-new/posts'}" rel="nofollow">{xen:if $visitor.user_id, {xen:phrase new_posts}, {xen:phrase recent_posts}}</a></li>
            </xen:hook>
        <xen:comment>Display following "New Posts"</xen:comment>
        <xen:elseif is="{$xenOptions.multiviewNavigationPosition} == 6" />
            <xen:hook name="navigation_tabs_forums">
                <xen:if is="{$visitor.user_id}"><li><a href="{xen:link 'forums/-/mark-read', $forum, 'date={$serverTime}'}" class="OverlayTrigger">{xen:phrase mark_forums_read}</a></li></xen:if>
                <xen:if is="{$canSearch}"><li><a href="{xen:link search, '', 'type=post'}">{xen:phrase search_forums}</a></li></xen:if>
                <xen:if is="{$visitor.user_id}">
                    <li><a href="{xen:link 'watched/forums'}">{xen:phrase watched_forums}</a></li>
                    <li><a href="{xen:link 'watched/threads'}">{xen:phrase watched_threads}</a></li>
                </xen:if>
                <li><a href="{xen:link 'find-new/posts'}" rel="nofollow">{xen:if $visitor.user_id, {xen:phrase new_posts}, {xen:phrase recent_posts}}</a></li>
                <xen:if is="{$visitor.user_id}"><li><a href="{xen:link multiview}" rel="nofollow">{xen:phrase multiview_title}</a></li></xen:if>
            </xen:hook>
        <xen:else />
        </xen:if>
    <xen:else />
        <xen:hook name="navigation_tabs_forums">
            <xen:if is="{$visitor.user_id}"><li><a href="{xen:link 'forums/-/mark-read', $forum, 'date={$serverTime}'}" class="OverlayTrigger">{xen:phrase mark_forums_read}</a></li></xen:if>
            <xen:if is="{$canSearch}"><li><a href="{xen:link search, '', 'type=post'}">{xen:phrase search_forums}</a></li></xen:if>
            <xen:if is="{$visitor.user_id}">
                <li><a href="{xen:link 'watched/forums'}">{xen:phrase watched_forums}</a></li>
                <li><a href="{xen:link 'watched/threads'}">{xen:phrase watched_threads}</a></li>
            </xen:if>
            <li><a href="{xen:link 'find-new/posts'}" rel="nofollow">{xen:if $visitor.user_id, {xen:phrase new_posts}, {xen:phrase recent_posts}}</a></li>
        </xen:hook>
    </xen:if>
    
     
  8. Valhalla

    Valhalla Well-Known Member

    This is how I tried to do it using traditional template hooks.

    PHP:
    public static function extendNavigation($hookName, &$contents, array $hookParamsXenForo_Template_Abstract $template)
    {
        if (
    $hookName == 'navigation_tabs_forums')
        {
            
    $insert $template->create('my_navigation_entry');
            
    $delimiter  "";
            list(
    $explode[0], $explode[1]) = explode($delimiter $contents);

            
    $contents $explode[0] . $delimiter $insert $explode[1];
        }
    }
     
  9. Chris D

    Chris D XenForo Developer Staff Member

    I think your best bet is to use a PHP Callback on the template modification.

    You would set it up like this:

    upload_2014-4-10_2-1-29.png

    Then the code like this:

    PHP:
    <?php

    class TEST_RegexTM
    {
        public static function 
    getCallback($matches)
        {
            
    $options XenForo_Application::getOptions();

            
    $multiView '<xen:if is="{$visitor.user_id}"><li><a href="{xen:link multiview}" rel="nofollow">{xen:phrase multiview_title}</a></li></xen:if>';

            
    $before '';
            
    $after '';

            
    $foundString $matches[0];
            switch (
    $options->multiviewNavigationPosition)
            {
                case 
    1:

                    
    $before '<xen:if is="{$visitor.user_id}"><li>'// Before Mark Forums Read
                    
    break;

                case 
    2:

                    
    $before '<xen:if is="{$canSearch}">'// Before Search
                    
    break;

                case 
    3:

                    
    $before '<li><a href="{xen:link \'watched/forums\''// Before Watched Forums
                    
    break;

                case 
    4:

                    
    $before '<li><a href="{xen:link \'watched/threads\''// Before Watched Threads
                    
    break;

                case 
    5:

                    
    $before '<li><a href="{xen:link \'find-new/posts\''// Before New Posts
                    
    break;

                case 
    6:

                    
    $after '{xen:phrase recent_posts}}</a></li>'// After New Posts

                
    default: // No option selected. Do nothing.
                    
    break;
            }

            if (
    $before)
            {
                
    $position strpos($foundString$before);
            }
            elseif (
    $after)
            {
                
    $position strpos($foundString$after) + strlen($after);
            }

            if (
    $position)
            {
                return 
    substr_replace($foundString$multiView$position0); // Does the replacement.
            
    }
        
            return 
    $foundString// No replacement has happened, change nothing.
        
    }
    }
     
    Last edited: Apr 10, 2014
    Valhalla likes this.
  10. Chris D

    Chris D XenForo Developer Staff Member

    I just updated the above code a little. There may have been a couple of mistakes and it didn't properly account for the scenario where the option maybe 0 / not enabled.
     
  11. Valhalla

    Valhalla Well-Known Member

    @Chris D, that's really helpful and useful. Thanks a lot (and for the updated code).
     
  12. Valhalla

    Valhalla Well-Known Member

    This method works well for the initial loading of the page, but if I were to update my option value, the changes would not be reflected until I re-built "Master Data" (where the change would then be shown as expected). If I completely empty my callback, the TM is shown regardless (until I "rebuild").

    Ideally, if one of my options has changed, the "navigation" template could be rebuilt (or just emptied?), ready to use the callback when the page is next loaded.
     
  13. Chris D

    Chris D XenForo Developer Staff Member

    Yes, you would be able to trigger some sort of rebuild by using the Option verification call back.

    That'd allow you to run some PHP code when the option value changes.

    The code would be basically this:

    PHP:
            $templateModel $this->_getTemplateModel();

            
    $templates $templateModel->getTemplatesByTitles(array('navigation'));
            foreach (
    $templates AS $template)
            {
                
    $templateModel->reparseTemplate($templatetrue);
            }
     
    Valhalla likes this.
  14. Valhalla

    Valhalla Well-Known Member

    My problem now is, it seems the template is reparsed before the settings are saved (leading to my changes to be one step behind what I would expect). You have to save, then save again with a different option, to get the previous one to take effect.
     
  15. Chris D

    Chris D XenForo Developer Staff Member

    Yeah I can understand that. Effectively the verification runs before save, so you're right: the option doesn't take effect.

    I still think this is the best way to do the conditionals that you want in that template. But then the only way I can think to get around rebuilding the template is:

    1) The verification callback, despite the process of changing the option value is already happening, could actually update the option value using a DB query. Effectively the value will be getting set twice, but if it is set by a query you could immediately afterwards reparse the template. It would work but... a bit messy really.

    2) Forget the verification call back and extend the Option DataWriter. Extend the _postSave function. If the option ID == your option, and option_value is changed, perform the template reparse.
     
  16. Valhalla

    Valhalla Well-Known Member

    Thanks again. I'm using the second option, but I'm still having trouble getting the navigation to update when the options are changed (or, even just when the options are saved to verify it's working). It still takes a second "Save" in order for it to work.

    I'm using the "reparseTemplate", like you suggested, in my extended DataWriter_Option, but I noticed there was a similar function "removeTemplateFromCache", which may (or may not) be more useful. (Maybe they effectively do the same thing?)

    I think it doesn't work until a second "Save" because if I reparse the template in postSave... well, I just think that's no good on it's own. But I think removing the template from the cache would.

    Thoughts? :)
     
  17. Chris D

    Chris D XenForo Developer Staff Member

    This code:
    Is basically the code that executes after a template modification is saved. So I'd have thought that would do it.

    Only thing I can think of is to change the function you're extending in the DW from _postSave to _postSaveAfterTransaction

    That would then reparse the template after the option value change has been committed to the database.
     
    Valhalla likes this.
  18. Valhalla

    Valhalla Well-Known Member

    I got this working by extending actionList() in ControllerAdmin_Option.

    I don't know if it's best practice, but it works, at least for the time being. Thanks for your help with this one. :)

    PHP:
    class Valhalla_Multiview_ControllerAdmin_Option extends XFCP_Valhalla_Multiview_ControllerAdmin_Option
    {
        public function 
    actionList()
        {
            
    $input $this->_input->filter(array(
                
    'group_id' => XenForo_Input::STRING
            
    ));

            if(
    $input['group_id'] === 'multiview')
            {
                
    $templateModel = new XenForo_Model_Template;

                
    $templates $templateModel->getTemplatesByTitles(array('navigation'));
                foreach (
    $templates AS $template)
                {
                    
    $templateModel->reparseTemplate($templatetrue);
                }
            }

            return 
    parent::actionList();
        }
    }
     
  19. Valhalla

    Valhalla Well-Known Member

    Is it possible to use <xen:set> outside the template that you wish to use it in?

    For example, I want to set the variable in a parent template, and be able to access it in the included template.
     
  20. Chris D

    Chris D XenForo Developer Staff Member

    Yes, that should work fine.

    What I should also add, however, is it is common to use such a scenario like this:

    HTML:
    <xen:include template="template_name">
        <xen:set var="$var">{$value}</xen:set>
    </xen:include>
     
    Steve F likes this.

Share This Page