Jump to content

Dealing with member-specific data in theme hooks


Recommended Posts

I'm having some trouble displaying member-specific information on a page. I thought that a theme hook would be appropriate but since those are cached that won't work very well.

I'll describe the problem I'm trying to solve: Groups in my community are tied to external software licenses. I have a (queue-based) task that will regularly check all members' licenses and adjust their groups as appropriate. Members without a valid license are put into a group with limited rights. If that happens I need to inform them about what happened and it was decided to show them banner with an explanation and a link to a page where they can update their licensing info.

Since the banner is displayed on just about every page the JS-based functionality IPS offers is not appropriate; I don't want to shove an alert in the user's face every time they open a page. Inserting additional HTML into the page seems to be the best way to me and that seems to be a job for a theme hook.

What I've tried so far is a theme hook that looks like this (hookData method only):

  public static function hookData()
  {
    $member = \IPS\Member::loggedIn();

    if ($member->member_id === null ||
        $member->member_group_id != \IPS\Settings::i()->member_group_awaiting_activation)
    {
      // No reason to show a banner
      return parent::hookData();
    }

    $content = '<section class="awaiting-activation-banner">'.
      $member->language()->addToStack('awaiting_activation_banner', FALSE, ['sprintf' => [$member->url()]]).
      '</section>';

    return array_merge_recursive(
      [
        'globalTemplate' =>
        [
          // for the only theme we support in production
          [
            'selector' => 'body > div.content-wrapper > div.nav-bar-wrap',
            'type' => 'add_after',
            'content' => $content
          ],
          // for the IPS4 default theme so I can test in dev mode
          [
            'selector' => '#ipsLayout_header',
            'type' => 'add_inside_end',
            'content' => $content
          ],
        ],
      ],
      parent::hookData());
  }

The hook works... once. After that its output is cached and reused on the next page load, which means that a) whether the banner is shown does not depend on whoever is viewing the page but on whoever did when it was first evaluated and b) the language string is not properly translated becausethere is no translation for it on the stack.

I could get around both of these problems if I could keep IPS from caching this specific hook but I'm not sure if that is possible. Of course dev mode forgoes all caching but is obviously not a solution for a live system.

Is there some better way for me to inject HTML into the page? Or can I disable caching for a specific hook?

Link to comment
Share on other sites

Thanks, that did help deal with the translation not working and it provided the clue I needed to fix the caching problem.

The hook only gets evaluated once and is then cached. The hook's output, however, is passed to the template engine, which evaluates its contents every time the page is shown. The solution is thus to put the check for member_id and member_group_id into the output:

  public static function hookData()
  {
    $content = <<<TEMPLATE
{{if member.member_id != NULL && member.member_group_id == \IPS\Settings::i()->member_group_awaiting_activation}}
  <section class="awaiting-activation-banner">
    {lang="awaiting_activation_banner" sprintf="\IPS\Member::loggedIn()->url()"}
  </section>
{{endif}}
TEMPLATE;

    return array_merge_recursive(
      [
        // as above
      ],
      parent::hookData());
  }

This is another one of those times where the IPS4 API is subtly tricky but ultimately makes sense.

Link to comment
Share on other sites

On 27/11/2017 at 11:59 AM, HebRech GmbH & Co. KG said:

After that its output is cached and reused on the next page load, which means that a) whether the banner is shown does not depend on whoever is viewing the page but on whoever did when it was first evaluated and b) the language string is not properly translated becausethere is no translation for it on the stack.

you can delete compiled template when php shutdown the execution, I suggest you to override compileTemplate (it is called after themeHooks):

// Just remember it is static :(
public static function compileTemplate( $content, $functionName, $params='', $isHTML=TRUE, $isCSS=FALSE, $app=null, $location=null, $group=null )
{
	if( $functionName == 'theme_core_front_global_globalTemplate' or ( $functionName == 'globalTemplate' and $app == 'core' and $location == 'front' and $group == 'global' ) )
	{
		$content = static::themeHooks($content,[
			[
				'selector' => 'body > div.content-wrapper > div.nav-bar-wrap',
				'type' => 'add_after',
				'content' => 'yourcontent'
			],
			// for the IPS4 default theme so I can test in dev mode
			[
				'selector' => '#ipsLayout_header',
				'type' => 'add_inside_end',
				'content' => 'yourcontent'
			],
		]);
		register_shutdown_function(function( $app, $location, $group ){
			\IPS\Theme::deleteCompiledTemplate($app,$location,$group);
		}, $app, $location, $group);
	}
	return call_user_func_array( 'parent::compileTemplate', func_get_args() );
}

I have not tested the performance but it works, deleteCompiledTemplate execute many queries if you have more than 1 theme

This solution not work if you would reuse the template in the same instance because getTemplate get a cached template via static::$calledTemplates[ $key ]. Then a "ultimate solution" is to override getTemplate and unset the key.

 

public function getTemplate( $group, $app=NULL, $location=NULL )
{
	$key = \strtolower( 'template_' . $this->id . '_' .static::makeBuiltTemplateLookupHash( 'core', 'front', 'global' ) . '_' . static::cleanGroupName( 'global' ) );
	if ( in_array( $key, array_keys( static::$calledTemplates ) ) )
	{
		unset( \IPS\Data\Store::i()->$key );
		#unset(static::$calledTemplates[$key]); IT DOES NOT WORK BECAUSE THE CLASS COULD BE ALREADY DECLARED PREVIOUSLY \IPS\Theme\class_core_front_global RETURN empty because "if ( @eval( $compiledGroup ) === FALSE )" produce silently a error check with var_dump(eval( $compiledGroup ));exit;
	}
	return call_user_func_array( 'parent::getTemplate', func_get_args() );
}

if you would override also into cms app pay attention to \IPS\cms\Theme::getTemplate

best option is override \IPS\Output::sendOutput if you don't would use params/arguments of template

@bfarber confirm this?

Link to comment
Share on other sites

Archived

This topic is now archived and is closed to further replies.

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...