Add item to navigation menu from a generic plugin

Description of issue or problem I’m having:

I want to add a new page to the primary navigation menu as part of a generic plugin.

Steps I took leading up to the issue:

I searched this forum and did find issues discussing adding a menu item, but not programmatically from a plugin.

Application Version - e.g., OJS 3.1.2:

OJS 3.3.x

Additional information, such as screenshots and error log messages if applicable:

I could override primaryNavMenu.tpl fully, but then I need to make sure to keep up with changes there. Unless there are other recommendations or ways that I’m missing, I might go that route.

I would also be fine with users having to add the menu item manually. For that, they could add an “external page” link. Since it looks easy to mess up the paths there, is there maybe a way to let a plugin add a new “Navigation Menu Type”, so that users just select the page that the plugin provides?

image

Hi @nuest,

load_menu is registered as Smarty plugin in pkp-lib/PKPTemplateManager.inc.php at stable-3_3_0 · pkp/pkp-lib · GitHub. From a generic plugin I think it’s possible to override that behavior using TemplateManager::display hook.

Hi @Vitaliy - thanks for the pointer. I did some searching for TemplateManager::display and load_menu in the source code, but I’m new to Smarty plugins and I’m not sure how to make such an override. Can you sketch the core steps in a few words? THANKS!

Following example in the documentation: https://docs.pkp.sfu.ca/dev/plugin-guide/en/categories#generic, it should be something like

import('lib.pkp.classes.plugins.GenericPlugin');
class ExamplePlugin extends GenericPlugin {

	public function register($category, $path, $mainContextId = NULL) 
    {
		$success = parent::register($category, $path);
		if ($success && $this->getEnabled()) {
			HookRegistry::register('TemplateManager::display', array($this, 'doSomething'));
		}
		return $success;
    }

  public function doSomething($hookName, $args) 
  {
		$smarty = $args[0];
        $smarty->registerPlugin('function', 'load_menu', [$this, 'doSomethingWithMenu']);
		return false;
  }

  public function doSomethingWithMenu($params, $smarty)
  {
    // modify behavior
  }
}

Maybe Smarty expects to unregister load_menu plugin first: https://www.smarty.net/docs/en/api.unregister.plugin.tpl, but in general, I think it should work.

2 Likes

Thanks for that! The unregister is indeed needed, but then the regular menu is of course gone. So I suppose I could call the unregistered function from PKPTemplateManager within my function somehow and then append my page.

Alternatively, might it be more reasonable to directly interact with NavigationMenuDAO ?

Yes. The third parameter passed to the registerPlugin() is a closure where you can pass your own function: https://www.smarty.net/docs/en/api.register.plugin.tpl
Does the example work if overriding the original method: pkp-lib/PKPTemplateManager.inc.php at stable-3_3_0 · pkp/pkp-lib · GitHub in doSomethingWithMenu()?

Depending on a task. For a static menu item I’d probable modify the front-end part. If going this path, the menu item should be added only once, e.g., during plugin installation. But I think in this case it’s less complicated to add the item manually through the dashboard

Yeah, for something that has only to be done once, it might be more reasonable to add documentation instead of code.

Anyway, I can access the rendered menu HTML, so your suggestion does work:

	public function doSomethingWithMenu($params, $smarty)
	{
		$menu = $smarty->smartyLoadNavigationMenuArea($params, $smarty);
		// modify HTML of menu?
		return $menu;
	}

But that’s a bit hacky, of course. Maybe I am missing an idea here? Could there be a way to manipulate before the menu is handed over to the fetch(..) call and is rendered?

But I found yet another way:


Since I already have a child theme because I need to add content to an issue’s page, I added an override to frontend/components/navigationMenu.tpl and added a hook there: add hook to navigation menu · ifgi/optimetaGeoTheme@0369a29 · GitHub

This hook I can pick up in my plugin and have a small template to add an item: use hook in optimetaGeoTheme to add item to navigation menu · nuest/optimetaGeo@aea2e45 · GitHub

The only problem I have, but will ignore for now, is that this way, I’m both changing the main navigation menu and the admin menu at the top right:

image

Anyway, I am not 100% sure that my target users will be able to use the child theme, but until I have a definitive “no” on that I’ll go with this approach. Yet another hook that I might propose to PKP?

@Vitaliy Thank you for your patience and help! I wouldn’t have had any entry point to this problem without you.

What about using menu $id? E.g., in the template:

{if $id === 'navigationPrimary'}
...
{/if}

Or inside main plugin class:

$navigationMenuId = $smarty->getTemplateVars('id')
if ($navigationMenuId === 'navigationPrimary') {
....
}

Yes, there are basically 2 ways here, modify the returned by Smarty fetch() output or completely override the method without calling a smartyLoadNavigationMenuArea().

1 Like

That works like a charm. Thank you!