Jump to content

Best hook for replacing page rendering?


marco
 Share

Recommended Posts

Hi folks,

I recently released the module Template Twig Replace that enables templating using the Twig engine instead of the default php/html templates.

The module hooks into PageRender::___renderPage, replacing the original method and setting the Twig rendering results to the event's return property. I've taken a good amount of information from this post to achieve the results.

Now I've discovered that pages will not be cached if the module is active.

Some digging into the core (/wire/modules/PageRender.module) showed, that the method, I'm hooking not only renders a page but handles page caching (and some form of access control) as well.

As a quick fix I duplicated the code handling cache files in my module to ensure pages a properly cached as usual. The fix is already commited to github and should be available in the module directory soon.

This is would my module currently looks like:

public function init() {

    // ... some more stuff ..

    // replace default page rendering by hooked method
    $this->addHookBefore('PageRender::renderPage', $this, 'renderPageWithTwig');
}
/**
 * Hook callback for PageRender::renderPage
 *
 * Replaces default page rendering entirely.
 *
 * @param HookEvent $event The hook event
 * @throws WirePermissionException Page is not currently viewable.
 */
public function renderPageWithTwig(HookEvent $event) {

    $parentEvent = $event->arguments(0); // grab event provided to PageRender::renderPage

    // don't mess with admin templates
    $page = $parentEvent->object;
    if ($page->template == 'admin') return;

    // double check page's status
    // taken from PageRender::__render()
    if ($page->status >= Page::statusUnpublished && !$page->viewable()) {
        throw new WirePermissionException('Page \'' . $page->url . '\' is not currently viewable.');
    }

    // forced replacing of default page rendering behaviour
    $event->replace = true;

    // look for cached data
    // taken from PageRender::__render()
    $options = count($parentEvent->arguments) ? $parentEvent->arguments[0] : array();
    $defaultOptions = array(
        'forceBuildCache' => false,
    );
    $options = array_merge($defaultOptions, $options);

    $cacheAllowed = wire('modules')->get('PageRender')->isCacheAllowed($page);
    $cacheFile = null;

    if ($cacheAllowed) {
        $cacheFile = wire('modules')->get('PageRender')->getCacheFile($page);
        if(!$options['forceBuildCache'] && ($data = $cacheFile->get()) !== false) {
            $parentEvent->return = $data;
            return;
        }
    }

    // allow page fields to be accessed directly in Twig
    // e.g. {{ page.myfield }} instead of {{ page.get('myfield') }}
    Page::$issetHas = true;

    // render template
    $twigVars = $this->collectVariables($page->output);
    $output = $this->getTwig()->render($page->template->name . '.' . 
                                       wire('config')->templateExtension, $twigVars);

    // cache output if possible
    // taken from PageRender::__render()
    if (!empty($output) && $cacheAllowed && !is_null($cacheFile)) $cacheFile->save($output);

    // manually set return of original event
    $parentEvent->return = $output;
}

But I think, the best option would be finding a hook the only renders a page's content and replacing this method.

Is there a better hook to use that leaves handling page caching and maybe other pre- or post-processing alone and does template rendering only?

Regards,

Marco

  • Like 1
Link to comment
Share on other sites

I'm not seeing through all this but have you seen this http://processwire.com/talk/topic/1421-twig/ ? Maybe something in there helpful?

I'm not sure there's another method like you have now, but then I don't care about template engine anyway :P

On a side note:

"Instead of contaminating your template's and chunk's markup with php code, you would have the Twig templating syntax at hand."

Should read: 

"Instead of using powerful ProcessWire template syntax code in your template's and chunk's markup, you would "contaminate"  your templates with the Twig templating syntax."

Sorry but just had to ;)

  • Like 3
Link to comment
Share on other sites

  • 2 weeks later...

Marco, I think the hook you might be looking for would be TemplateFile::render. The only issue is that this is used by many things, like rendering of partials and such. So you'd probably want to hook into just the instance used by Page::render. Not tested, but I think you could do this by checking for the presence of the $options array (and better yet a property we know would be there--I'll use $options['pageStack']), which would indicate it's the instance you want: 

public function init() {
  $this->addHookAfter('TemplateFile::render', $this, 'hookRender'); 
}

public function hookRender(HookEvent $event) {
  $templateFile = $event->object; 
  $options = $templateFile->options; 
  // checking for $options, which we know is given to page renders
  // this is to make sure it's not some other use of TemplateFile
  if(is_array($options) && array_key_exists('pageStack', $options)) {
    // this is a page render, go ahead and process the output 
   $output = $event->return; 
   $page = $templateFile->page; // I'm assuming you want this
   $output = yourTwigStuff($output $page); // replace with your Twig code
   $event->return = $output; 
  } 
}
 
Link to comment
Share on other sites

  • 2 years later...

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

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