XF 2.0 Grab Navigation Externally

LPH

Well-known member
Hi

I'm tinkering today to clear my mind of unrelated things and realized my understanding of how to grab certain portions of XenForo 2 are very weak.

To recreate the navigation menu, I was looking at pulling from the Navigation Repository, using the createNavigationTree and stuffing the appropriate information into the templater()

This is my code so far:

PHP:
/** @var \XF\Repository\Navigation $navRepo */
$navRepo      = \XF::app()->repository( 'XF:Navigation' );
$navCacheData = $navRepo->createNavigationTree();

\XF::dump($navCacheData);

// $navTitle = \XF::app()->templater()->fn()

Looking at the $navCacheData shows what I need BUT choosing the appropriate information for templater is where I get lost.

Does anyone have any suggestions on how to move forward?
 
Let me see if I understand this properly.

The above returns a collection. I can use get to pull data. There are two options: getData() and getAllData();

The getData() requires that I know the navigation_id. Simply putting the _default as an identifier returns that information in a dump.

However, it looks like I need to know the navigation_id for each in order to build the Navigation.

Another option was to try to render the Navigation template. This work failed, too, because I don't know the navigation_id for all of the items.

PHP:
$navTitle = \XF::app()->templater()->renderTemplate('public:_default', array());

Any suggestions?
 
I can pull the ID now using a foreach loop after the getAllData()

PHP:
/** @var \XF\Repository\Navigation $navRepo */
$navRepo      = \XF::app()->repository( 'XF:Navigation' );

$navCacheData = $navRepo->createNavigationTree()->getAllData();

          foreach ( $navCacheData AS $navItem ) {
              \XF::dump($navItem['navigation_id']);
          }
 
OK. I'm trying a different approach and would certainly appreciate some feedback on how to work with the $this->view so that it matches from external \XF::app()->

PHP:
/** @var \XF\Repository\Navigation $navRepo */
$navRepo      = \XF::app()->repository( 'XF:Navigation' );

$navCacheData = $navRepo->createNavigationTree();

$viewParams = [
   'tree' => $navCacheData,
   'defaultNavigationId' => \XF::app()->get('defaultNavigationId')
];

return $this->view('XF:Navigation\Listing', 'navigation_list', $viewParams);

Which I realize is the same as:

PHP:
$viewParams = [
        
   'tree' => \XF::app()->repository( 'XF:Navigation' )->createNavigationTree(),
   'defaultNavigationId' => \XF::app()->get('defaultNavigationId')
];

return $this->view('XF:Navigation\Listing', 'navigation_list', $viewParams);

But it is the $this-view that I'm trying to understand.
 
Thank you for the suggestion @S Thomas --- I'll take a look.

The class view within /Mvc/View looks to have a few methods which render templates. In other words, I'm right back to the templater().
 
Last edited:
Yeah, but there are different View adaptions depending on what you are working with. The code above seems to be a snippet from a controller, hence my suggestion to use the controller response view. What are you trying to achieve? It might be worth looking into code event listeners, there's one for navigation setup.
 
Yeah, but there are different View adaptions depending on what you are working with. The code above seems to be a snippet from a controller, hence my suggestion to use the controller response view. What are you trying to achieve? It might be worth looking into code event listeners, there's one for navigation setup.

I'm trying to learn about the templater, fn, view, and how navigation is built. Ultimately, when I started playing, it was "Can I build the navigation externally? Because I can use renderWidget to pull Widgets externally -- well -- except the links are incorrect.
 
In this case, it's like every other controller response with rendering: view just redirects your parameters to the template navigation_list. The templater renders that given template. What you get back is the HTML for the navigation. So literally everything view does is to create the view. If your goal is to create this part of the view, then go ahead and have a look at the template itself. It's an admin style, so you have to search for it.
Understanding how the templater parses the XF template syntax and converts that to HTML is a bit more complex, so I guess that's not what you're looking for.
 
Here is the thing ... if I simply wanted the admin navigation list then it makes sense to use:

PHP:
$viewParams = [

   'tree' => \XF::app()->repository( 'XF:Navigation' )->createNavigationTree(),
   'defaultNavigationId' => \XF::app()->get('defaultNavigationId')
];

echo \XF::app()->templater()->renderTemplate('admin:navigation_list', $viewParams);

This will return a beautiful navigation that matches the admin for the public navigation. However, I don't see an equivalent template for building the public navigation. To my knowledge (weak as it is), there isn't a template for navigation. It's built a different way.

This is what I'm trying to understand.
 
Last edited:
Well, that's why I said look into code event listeners. There's a navigation_setup event which get's triggered just once (XF/Pub/App.php -> getNavigation). You can track the code down from there.
 
And the template you're looking for is the macro nav_entry and all the following macros in PAGE_CONTAINER. Probably some other parts in PAGE_CONTAINER aswell.
 
The trick is that getNavigation is protected. However, I haven't looked at nav_entry. Thank you.
 
You could extend the App to access getNavigation. Or just copy the code and change $this to \XF::app()

I copied the code, did a dump, and it's exactly what I was getting at the start of the "quick" project. It would be nice if there was a way to renderTemplate just like the admin.
 
Now we're getting to a point.
PHP:
<?php

$dir = __dir__;
require($dir . '/src/XF.php');
XF::start($dir);
$app = \XF::setupApp('XF\Pub\App');
$app->start();

function getNavigation(array $params, $selectedNav = '')
{
    $navigation = null;
    $file = "C:/xampp/htdocs/xf2/internal_data/code_cache/navigation_cache.php"; // use your path
    if (file_exists($file))
    {
        $closure = include($file);
        if ($closure)
        {
            $navigation = \XF::app()->templater()->renderNavigationClosure($closure, $selectedNav, $params);
        }
    }

    if (!$navigation || !isset($navigation['tree']))
    {
        $navigation = [
            'tree' => [],
            'flat' => []
        ];
    }
    return $navigation;
}
var_dump(getNavigation([], "")); // OUTPUT: array(2) {  ["tree"]=>  array(5) {    ["quick"]=>    &array(4) {      ["title"]=>      object(XF\Phrase)#250 (5) {        ["language":protected]=>        object(XF\Language)#202 (10) {          ["id":protected]=>          int(6)          ["options":protected]=>          array(17) {            ["title"]=>            string(7) "English"            ["language_code"]=>            string(5) "en-US"            ["date_fo [...]

Or, as I said, just extend App so you don't need to copy paste the code. After that you could fireup renderMacro from templater with the nav macros from above.
 
In my view, this is too messy. I can render a widget. I can render templates. I can render the admin navigation. There should be a quick easy way to render public navigation. I'm not interested in piecing it all together. And that's disappointing.

As an aside, it's time I learn more about extending the app() class. It looks like there needs to be a container passed when the new extended class is called.

Oh well. Too messy.
 
This is literally the same as rendering a template. To render a template you have to pass over the template params aswell. Here you have to grab the nav data and pass it to the templater for the macro renderer. That's what the process behind the navigation building is.
You could create your own navigation template and feed that with the data from createNavigationTree() or shape the output of that function in a way that the already existing macros can handle that, if necessary. Either way, if you want to re-use the existing navigation, you will have to go through the macros.
 
This is exactly what I'm looking to do as well.

Or, as I said, just extend App so you don't need to copy paste the code. After that you could fireup renderMacro from templater with the nav macros from above.
Using your code, I was able to get a lot of raw data. However, I'm unable to figure how to fetch nav macros from that (assuming have to do some looping) and then pass to renderMacro. Any help please?

This will return a beautiful navigation that matches the admin for the public navigation
Just to observe what is being returned, I use your code verbatim and I got blank/empty response from echo. Just wondering what was returned in your case. Was it HTML or object?
 
Back
Top Bottom