Zeka Posted September 7, 2017 Share Posted September 7, 2017 Hi. I'm using delayed output strategy where in "_init.php" (prepended template) file I define my default regions /fields / etc. For some part of pages, I have to use URL segments and the logic of handling urlSegments is placed in templates files. $p = pages()->findOne("template=knowledge-base-category|knowledge-base-item, name={$pagename}"); if ($p->id) { $p->render(); } else { throw new Wire404Exception(); } In this way when $p-> id statement is true, all circle of _init.php> template file> _main.php repeats. So it takes twice as much time to render one page because a lot of heavy things happen inside _init.php which is called twice. There are two ways that I see how to deal with that: 1. Don't use urlSegments. ))) 2. Move heavy logic from _init.php under segments code, but in this way, I get a lot of duplicated code in my template. How do you handle this cases? Link to comment Share on other sites More sharing options...
abdus Posted September 7, 2017 Share Posted September 7, 2017 During boot, inside /site/config.php $page is not yet determined, so I am limited in determining what to prepend/append. For this reason I disable $config->prependTemplateFile (& append) and use a 'controller' hook like this. wire()->addHookBefore('TemplateFile::render', function (HookEvent $event) { // skip if AJAX if ($event->config->ajax) return; /** @var TemplateFile $templateFile */ $templateFile = $event->object; // Skip admin pages if (strpos($event->input->url, $event->config->urls->admin) === 0) return; // check if this is a partial template, if so, stop $fileDir = pathinfo($templateFile->get('filename'), PATHINFO_DIRNAME); $templatesPath = $event->config->paths->templates; if (realpath($fileDir) !== realpath($templatesPath)) return; $templateName = $templateFile->page->template->name; $prepends = [ 'routes' => $templatesPath . "{$templateName}.routes.php", 'before' => $templatesPath . "_before.php" // like your _init.php ]; // then comes the actual template file $appends = [ 'view' => $templatesPath . "views/{$templateName}.php", 'after' => $templatesPath . "_after.php", 'layout' => $templatesPath . "layouts/main.php" ]; foreach ($prepends as $name => $file) { if (file_exists($file)) $templateFile->setPrependFilename($file); } foreach ($appends as $name => $file) { if (file_exists($file)) $templateFile->setAppendFilename($file); } }); So to adapt this to your setup, you can define your urlSegments inside template.routes.php, and stop further appends with return $this->halt(). This way only template.routes.php is loaded and remaining appends are skipped while still allowing PW to complete its shutdown. 3 Link to comment Share on other sites More sharing options...
szabesz Posted September 7, 2017 Share Posted September 7, 2017 2 hours ago, Zeka said: In this way when $p-> id statement is true, all circle of _init.php> template file> _main.php repeats. How about using a counter or a flag to signal that the page has already been rendered? If you store the rendered output in the first iteration then you can just output it later on without calling render again. Or am I misunderstanding something? 1 Link to comment Share on other sites More sharing options...
Zeka Posted September 7, 2017 Author Share Posted September 7, 2017 @abdus Thank for your example. I have tried to test. It looks like I don't fully understand this approach, would be nice to get clarification. Here is my setup In site/init.php wire()->addHookBefore('TemplateFile::render', function (HookEvent $event) { // skip if AJAX if ($event->config->ajax) return; /** @var TemplateFile $templateFile */ $templateFile = $event->object; // Skip admin pages if (strpos($event->input->url, $event->config->urls->admin) === 0) return; // check if this is a partial template, if so, stop $fileDir = pathinfo($templateFile->get('filename'), PATHINFO_DIRNAME); $templatesPath = $event->config->paths->templates; if (realpath($fileDir) !== realpath($templatesPath)) return; $templateName = $templateFile->page->template->name; bd($templateName); $prepends = [ 'routes' => $templatesPath . "{$templateName}.routes.php", 'before' => $templatesPath . "_init.php" // like your _init.php ]; // then comes the actual template file $appends = [ 'after' => $templatesPath . "_main.php", ]; foreach ($prepends as $name => $file) { if (file_exists($file)) $templateFile->setPrependFilename($file); } foreach ($appends as $name => $file) { if (file_exists($file)) $templateFile->setAppendFilename($file); } }); In templates/categories.routes.php // we are only using 1 URL segment, so send a 404 if there's more than 1 if($input->urlSegment2) throw new Wire404Exception(); // we have 1 URL segment in page URL if ($input->urlSegment(1)) { $pagename = $input->urlSegment(1); $p = pages()->findOne("template=category, name={$pagename}"); if ($p->id) { $p->render(); } else { throw new Wire404Exception(); } } In templates/_init.php // Include helper methods for templates render require_once("./helpers/convenience-methods.php"); require_once("./helpers/functions.php"); region("header", renderGlobal("header")); region("masthead", renderGlobal("masthead", $viewData)); region("footer", renderGlobal("footer")); region("scripts", renderGlobal("scripts")); In templates/categories.php $viewData["categories"] = pages("template=category"); region("content", renderView("categories", $viewData)); In templates/_main.php <?=region("header");?> <?=region("masthead");?> <?=region("content");?> <?=region("footer");?> With this setup with any URL segment1, I got render "categories " page, not "category" from $p->render(); @szabesz Sorry for not being clear, with code examples it would be more clear what I meant. Link to comment Share on other sites More sharing options...
abdus Posted September 7, 2017 Share Posted September 7, 2017 28 minutes ago, Zeka said: With this setup with any URL segment1, I got render "categories " page, not "category" from $p->render(); I'm not getting that behavior. I tired to replicate your setup. This is what I have ended up with (I use jobs instead of categories but it shouldnt matter) <?php namespace ProcessWire; // categories.routes.php if (input()->urlSegment2) throw new Wire404Exception(); if (input()->urlSegment1) { /** @var Pages $pages */ $name = sanitizer()->pageName(input()->urlSegment1); $match = $pages->findOne("template=profession, name=$name"); if (!$match->id) throw new Wire404Exception(); echo $match->render(); return $this->halt(); } This is my _init.php. It gets included to every template (after its routes, if exists) <?php namespace ProcessWire; require_once '_func.php'; $testPages = $pages->find('template=profession, limit=5'); And I use it inside actual page template that renders `category` page <?php namespace ProcessWire; ?> This is: <?= $page->title ?> <p>Other Pages:</p> <?php foreach ($testPages as $p): ?> <?= $p->title ?><br> <?php endforeach; ?> And both pages render correctly So, to me the only thing that seems missing is echo statement before $p->render() and return $this->halt(); after that EDIT: This is the backend structure, I am guessing you have a similar one. You get categories from another parent 1 Link to comment Share on other sites More sharing options...
Zeka Posted September 7, 2017 Author Share Posted September 7, 2017 Thanks, @abdus for so quick response. You are right, I don't get "category" page because of missed "echo"; And this return $this->halt(); prevents render of "categories" page. From your code: $name = sanitizer()->pageName(input()->urlSegment1); https://processwire.com/api/ref/input/url-segment/ Docs say that urlSegments are already sanitized as page names. Thanks once again! 1 Link to comment Share on other sites More sharing options...
abdus Posted September 7, 2017 Share Posted September 7, 2017 You're welcome. I'm glad your problem got solved. 1 hour ago, Zeka said: Docs say that urlSegments are already sanitized as page names. You're right, thanks for pointing that. I was reading the source for urlSegments just today. Gotta love @ryan's humor // ProcessPageView.module /** * Get the requested page and populate it with identified urlSegments or page numbers * * @return Page|null * */ protected function getPage() { $config = $this->wire('config'); $shit = isset($_GET['it']) ? trim($_GET['it']) : "/"; // dirty $it = preg_replace('{[^-_./a-zA-Z0-9]}', '', $shit); // clean unset($_GET['it']); if($shit !== $it && $config->pageNameCharset == 'UTF8') { $it = $this->wire('sanitizer')->pagePathNameUTF8($shit); } // ... while((!$page || !$page->id) && $cnt < $maxSegments) { $it = rtrim($it, '/'); $pos = strrpos($it, '/')+1; $urlSegment = substr($it, $pos); $urlSegments[$cnt] = $urlSegment; $it = substr($it, 0, $pos); // $it no longer includes the urlSegment $selector = "path=" . $this->wire('sanitizer')->selectorValue($it, 2048) . ", status<" . Page::statusMax; $page = $this->pages->get($selector); $cnt++; } // ... } 3 Link to comment Share on other sites More sharing options...
abdus Posted September 14, 2017 Share Posted September 14, 2017 I've found a cleaner way to modify prepend/append files depending on the current page. wire()->addHookBefore('PageRender::renderPage', function (HookEvent $e) { /** @var Page $page */ /** @var HookEvent $event */ /** @var Template $template */ $event = $e->arguments(0); $options = $event->arguments(0); $page = $event->object; $template = $page->template; $options['prependFiles'] = [ "{$template}.routes.php", "_common.php", ]; $options['appendFiles'] = [ "views/{$template}.php", "_after.php", "layouts/main.php", ]; $event->setArgument(0, $options); }); The older way felt too much like a hack to me. To early exit and skip including remaining files during render, use `return $this->halt();` anywhere in your templates. 3 1 Link to comment Share on other sites More sharing options...
szabesz Posted September 14, 2017 Share Posted September 14, 2017 2 hours ago, abdus said: The older way felt too much like a hack to me. Definitely looks easier to grasp I will give this one a try when I have the time to. Thanks for sharing! 1 Link to comment Share on other sites More sharing options...
Robin S Posted September 14, 2017 Share Posted September 14, 2017 2 hours ago, abdus said: I've found a cleaner way to modify prepend/append files depending on the current page. I haven't followed this discussion closely so sorry if I'm missing the point here, but I believe you can override the append/prepend files directly when you render a page with $page->render(). echo $p->render( ['prependFile' => null, 'appendFile' => null] ); 2 Link to comment Share on other sites More sharing options...
Recommended Posts
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 accountSign in
Already have an account? Sign in here.
Sign In Now