Template modifications - programmatically?

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:
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.
 
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.

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). :)
 
Is is possible to use conditional statements in the 'Find' box of the Template Modifications tool? (No, I think.)
 
Last edited:
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.
 
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>
 
This is how I tried to do it using traditional template hooks.

PHP:
public static function extendNavigation($hookName, &$contents, array $hookParams, XenForo_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];
    }
}
 
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.webp

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, $position, 0); // Does the replacement.
        }
    
        return $foundString; // No replacement has happened, change nothing.
    }
}
 
Last edited:
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.
 
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.
 
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($template, true);
        }
 
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.
 
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.
 
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? :)
 
This code:
PHP:
        $templateModel = $this->_getTemplateModel();

        $templates = $templateModel->getTemplatesByTitles(array('navigation'));
        foreach ($templates AS $template)
        {
            $templateModel->reparseTemplate($template, true);
        }
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.
 
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($template, true);
            }
        }

        return parent::actionList();
    }
}
 
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.
 
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>
 
Top Bottom