Leaderboard
Popular Content
Showing content with the highest reputation on 11/12/2023 in all areas
-
This tutorial is about making the Latte template engine available in ProcessWire template files via the use of ProcessWires Custom Page Classes. Based on some inspiring posts from @bernhard, I wanted to use custom page classes and Latte templates in my own projects. I know there are modules like RockFrontend and others available to integrate template engines like Latte or Twig . However I wanted to start with a small, flexible and reusable code base as playground for my next projects without module dependencies as starting point. Please note: The template part of this tutorial assumes you are using the Site profile Default profile (standard/intermediate edition), which can be downloaded from the ProcessWire Github repository. Here a technique called delayed output strategy is used. I also posted an alternative Latte implementation some posts below, using a normal class file for implementing Latte via your template _init.php file. Step 1: Enable ProcessWire Custom Page Classes PageClasses were added with ProcessWire 3.0.152. To use page classes open site/config.php and enable the feature if needed via $config->usePageClasses = true;. After that create the folder site/classes in your site installation and add the PHP file DefaultPage.php inside with the following content for now. <?php namespace ProcessWire; class DefaultPage extends Page {} The DefaultPage class is used whenever a ProcessWire template like site/templates/your-template.php has no page class associated with it. In other words, if no site/classes/YourTemplatePage.php exists, the default page class site/classes/DefaultPage.php is used as fallback. Step 2: Install PHP Dependency Manager Composer As we are using the Latte Composer package, we first need to install Composer on our computer if not yet available. Visit the Composer webiste and install Composer on your system. On Windows just grab the Composer-Setup.exe file and execute it. Once Composer is installed, open a new terminal and type the command composer. You should see some Composer output in your console. If so, proceed with Step 3. If an error occurs, please read and follow the Docs on the Composer website to get Composer installed on your system before proceeding with this tutorial. Step 3: Install Latte Composer Package In the next step, we install the Latte template engine files as Composer package in our site folder. Open a terminal and switch into your local site/classes folder created in Step 1. From inside the site/classes folder type the following command into your terminal composer require latte/latte. Please note: If your installation has a composer.json file in the root, you will get asked if you want to use the composer.json in the root. As this tutorial assumes to install the composer files inside our site/classes folder, you MUST answer no. Otherwise the Latte files will be installed inside /root/vendor not /root/site/classes/vendor as expected in this tutorial. Once done, your site classes folder should contain a vendor folder with the Latte composer files, the two Composer files composer.json and composer.lock and our DefaultPage.php file created in Step 1. site/ classes/ vendor/ bin/ composer/ latte/ autoload.php composer.json composer.lock DefaultPage.php (implements Latte logic inherited by other PageClasses) EventsPage.php (implements logic for /templates/events.php) // Note this part is setup and explained later in this tutorial templates/ views/ events.latte your-template.php (associated with fallback classes/DefaultPage.php) events.php (associated with classes/EventsPage.php) Please note: The files located under site/templates are explained later in this tutorial. I just thought it would be a good idea to show the relevant file structure at one spot. Step 4: Make Latte available from PageClasses Now we need to make Latte available in our custom PageClasses. Open site/classes/DefaultPage.php and change the code as follows: <?php namespace ProcessWire; /** * The DefaultPage class implements methods to render Latte templates as string. This page class serves as * fallback for PW templates inside site/templates not implementing their own page class inside site/classes. */ class DefaultPage extends Page { protected static \Latte\Engine|null $latte = null; private array $latteConfig = []; // Constructor. public function __construct(Template $tpl = null) { parent::__construct($tpl); $this->setupLatteEngine(); $this->setLatteConfig(); } // Set or update Latte configuration. public function setLatteConfig(array $config = []): void { if (!isset(self::$latte)) return; // Remove keys from user config not available in Latte defaults, except for 'pwAPI'. $config = array_intersect_key($config, $this->getLatteDefaultConfig()); // Merge user config with Latte defaults. $this->latteConfig = array_merge($this->getLatteDefaultConfig(), $config); // Apply updated configuration to actual Latte instance. self::$latte->setAutoRefresh($this->latteConfig['autoRefresh']); self::$latte->setTempDirectory($this->latteConfig['cacheFolder']); } // Render given Latte template as string, injecting ProcessWire API and $data. public function renderTemplate(string $template, array $data = []): string { if (!isset(self::$latte) || !$this->latteConfig) return ''; // Ensure Latte template exists within specified template folder. $latteTemplateFolder = realpath($this->latteConfig['latteTemplateFolder']); $templatePath = realpath($latteTemplateFolder . '/' . $template); if ($templatePath === false || stripos($templatePath, $latteTemplateFolder) !== 0) { throw new \Exception("Latte template '$template' not found or outside 'latteTemplateFolder'."); } // Render Latte template and inject ProcessWire API and $data. return self::$latte->renderToString($templatePath, array_merge($data, $this->latteConfig['pwAPI'])); } // Returns associative array with Latte default configuration. protected function getLatteDefaultConfig(): array { return [ 'autoRefresh' => true, 'cacheFolder' => $this->wire('config')->paths->cache . 'Latte/', 'latteTemplateFolder' => $this->wire('config')->paths->templates, 'pwAPI' => [ 'page' => $this, 'pages' => $this->wire('pages'), 'config' => $this->wire('config'), ], ]; } // Initiate Latte template engine. private function setupLatteEngine(): void { if (isset(self::$latte)) return; require_once "vendor/autoload.php"; self::$latte = new \Latte\Engine; } } The DefaultPage class assumes the Latte template files to be stored inside path site/templates/. By default we can access the PW-API $page, $pages and $config inside our Latte templates. You can add more ProcessWire APIs like $sanitizer or template specific data via the setLatteConfig() method or via the optional associative array $data of the renderTemplate() method. Assume we have another PageClass in site/classes/EventsPage.php, which implements the complex method hello(). To make the Latte render methods available in the EventsPage class, we need to extend from our DefaultPage class instead of the ProcessWire Page class. This way we can create as many page classes with logic for specific templates as needed, while keeping code structured and organized. <?php namespace ProcessWire; class EventsPage extends DefaultPage { public function hello() { return "Hello World!"; } } Step 5: Add Latte template (acting as View) Next we add a Latte template file to site/templates/views/events.latte with the following content. <div> <h2>{$page->title|capitalize}</h2> <p>PageId: {$page->id}</p> <p n:if="$config->useFunctionsAPI"> Using Functions API and Latte is fun. </p> {* This will be translated based on the default PW language or users language preference *} <p>{$lang['Please select a date']}</p> <ul n:if="$page"> <li n:foreach="$page->getFields() as $field"> $page->{$field}: {$page->$field} </li> </ul> </div> Step 6: Render Latte template from ProcessWire templates (acting as Controller) The code below shows how custom methods and the Latte render method can be invoked within normal ProcessWire templates like site/templates/events.php. The first code line appends the output from the method hello() implemented in the associated PageClass site/classes/EventsPage.php to our template $output variable. Please note: In this example the ProcessWire template file uses a technique called Delayed Output Strategy. You can read more about this strategy in the linked ProcessWire blog post from Ryan. <?php namespace ProcessWire; /** * Template: "events.php" * Using Processwire delayed output strategy * https://processwire.com/docs/tutorials/how-to-structure-your-template-files/ */ // Append output from method hello() implemented in `site/classes/EventPage.php`. $content .= $page->hello(); // Add needed translations here. You can access them via $lang inside your Latte template. // The translation for 'Please select a date' can be added via the ProcessWire backend. // For details see https://processwire.com/docs/multi-language-support/code-i18n/. $data = ['lang' => [ 'Please select a date' => __('Please select a date!'), ], ]; // Append output of rendered Latte template located in `site/templates/views/events.latte`. $content .= $page->renderTemplate('views/events.latte', $data); At the bottom of our template file site/templates/events.php we call the render method implemented in site/classes/DefaultPage.php to render the specified Latte template in site/templates/views/events.latte. The ProcessWire API $page, $pages, $config is available in all Latte templates by default. As the EventsPage class extends the DefaultPage class, it inherents it's render methods This way we need to implement the Latte template code once, but can use it in all our custom PageClasses and default ProcessWire template files located in /site/templates. Note: If you are using the output strategy Markup regions, the Events template file would look as follows: <?php namespace ProcessWire; /** * Template: "events.php" * Using Processwire delayed output strategy * https://processwire.com/docs/tutorials/how-to-structure-your-template-files/ */ // Append output from method hello() implemented in `site/classes/EventPage.php`. // Add needed translations here. You can access them via $lang inside your Latte template. // The translation for 'Please select a date' can be added via the ProcessWire backend. // For details see https://processwire.com/docs/multi-language-support/code-i18n/. $data = [ 'lang' => [ 'Please select a date' => __('Please select a date!'), ], ]; ?> <div id="content"> <?= $page->hello(); ?> <?= $page->renderTemplate('views/events.latte', $data); ?> </div> Have fun. cwsoft Please note: Edited the initial post to include the code suggestions from member @d'Hinnisdaël some posts below. Thanks for your suggestions.4 points
-
Delayed Image Variations Delays the creation of image variations until their individual URLs are loaded. Image variations being generated one-by-one: Background Normally when you create new image variations in a template file using any of the ProcessWire image resizing methods, all the new variations that are needed on a page will be created from the original Pageimage the next time the page is loaded in a browser. If there are a lot of images on the page then this could take a while, and in some cases the page rendering might exceed your max_execution_time setting for PHP. So you might like to have image variations be generated individually when they are requested rather than all at once. That way the page will render more quickly and the risk of a timeout is all but eliminated. But there can be problems with some implementations of this approach, such as with the (in)famous TimThumb script: It's not ideal to have PHP be involved in serving every image as this is needlessly inefficient compared to serving static assets. It's not good to allow arbitrary image sizes to be generated by varying URL parameters because you might want to restrict the maximum resolution an image is available at (e.g. for copyrighted images). If images are generated from URL parameters a malicious user could potentially generate thousands of images of slightly different dimensions and fill up all your disk space. The Delayed Image Variations module avoids these problems - it creates variations when their URLs are loaded but only allows the specific dimensions you have defined in your code. It does this by saving the settings (width, height and ImageSizer options) of every new Pageimage::size() call to a queue. The module intercepts 404s and if the request is to an image variation that doesn't exist yet but is in the queue it generates the variation and returns the image data. This only happens the first time the image is requested - after that the image exists on disk and gets loaded statically without PHP. Usage In most cases usage is as simple as installing the module, and you don't need to change anything in your existing code. However, there might be some cases where you don't want the creation of a particular image variation to be delayed. For example, if you created a variation in your code and then immediately afterwards you wanted to get information about the variation such as dimensions or filesize. $resized = $page->image->width(600); echo $resized->height; echo $resized->filesize; This wouldn't work because the actual creation of the resized image hasn't happened yet and so that information won't be available. So in these cases you can set a noDelay option to true in your ImageSizer options and Delayed Image Variations will skip over that particular resizing operation. $resized = $page->image->width(600, ['noDelay' => true]); echo $resized->height; echo $resized->filesize; For advanced cases there is also a hookable method that you can return false for if you don't want a delayed variation for any particular resizing operation. Example: $wire->addHookAfter('DelayedImageVariations::allowDelayedVariation', function(HookEvent $event) { /** @var Pageimage $pageimage */ $pageimage = $event->arguments(0); // The Pageimage to be resized $width = $event->arguments(1); // The width supplied to Pageimage::size() $height = $event->arguments(2); // The height supplied to Pageimage::size() $options = $event->arguments(3); // The options supplied to Pageimage::size() // Don't delay variations if the Pageimage belongs to a page with the product template if($pageimage->page->template == 'product') $event->return = false; }); 404 handling For Delayed Image Variations to work your .htaccess file needs to be configured so that ProcessWire handles 404s. This is the default configuration so for most sites no change will be needed. # ----------------------------------------------------------------------------------------------- # 2. ErrorDocument settings: Have ProcessWire handle 404s # # For options and optimizations (O) see: # https://processwire.com/blog/posts/optimizing-404s-in-processwire/ # ----------------------------------------------------------------------------------------------- ErrorDocument 404 /index.php ProCache If you are using ProCache then make sure it is not configured to cache the 404 page or else PHP will not execute on 404s and queued image variations will not be generated. Generate queued variations Before launching a new website you might want to pre-generate all needed image variations, so no visitor will have to experience a delay while a variation is generated. To queue up the image variations needed for your site you will need to visit each page of the website one way or another. You could do this manually for a small site but for larger sites you'll probably want to use a site crawler tool such as Xenu's Link Sleuth. This may generate some image variations but it's likely that some other variations (e.g. within srcset attributes) will not be requested and so will remain queued. To generate all currently queued variations there is a button in the module config: This will search the /site/assets/files/ directory for queue files and render the variations. https://github.com/Toutouwai/DelayedImageVariations https://processwire.com/modules/delayed-image-variations/2 points
-
Hi Bernhard, thanks for your feedback. Fully agree, custom page classes and Latte templates are really awesome features and help to keep custom projects structured and clean. Regarding your suggestion about ProcessWire translate functions. So far all my custom projects are German only sites, so I had no need to implement this feature yet. However a great suggestion as usual, which may find it‘s way into a follow up writing. Wished I knew about the potential of page classes an Latte already in my first project. However in future projects I will make use of page classes and Latte right from the beginning. And if I find some spare time, maybe I will switch to RockFrontend mid to long term. Regards cwsoft2 points
-
Great to see a tutorial about Latte ? And always great to get some promotion of using custom page classes. ?? One thing to add might be that when using RockFrontend you can use __('Some text') translations directly in your Latte files! That's not possible with your approach. Also the next version of RockFrontend will have automatic layout loading for latte, which is really cool and comes from an input of @dotnetic who had this need and sponsored that addition ? And not to forget live reloading ? But of course Latte is great even without RockFrontend ?2 points
-
2 points
-
Just in case it might be useful to you, there's a $sanitizer->truncate() method which will trim a block of text to the nearest word / sentence etc. It's very handy for creating neat summaries.2 points
-
I was struggling, trying permissions, searching in PW source code and the web, thought about a bug... and while taking 1 minute of pause I saw this thread in forum activity tab and thought "OK let's give a try with ChatGPT, I have no idea anyway right now". Question: (I already used template access control, but for some reason I thought user access control was managed by permissions (user-view, user-admin...)). Answer (in bold what I was looking for):2 points
-
Hi, if it‘s save depends on your setup and user permissions. The secret would be contained in the PHP file and on save in the PW database and may find it‘s way into Github or server/user backups some time. If users have access to FTP they can extract the secret from files on your server. Users with access to templates fields may add a template exposing your secret using your API etc. So I would say it depends on your setup and use case.1 point
-
Fantastic(!) Such a rapid reply, and it worked! Looks like i have yet to learn much myself to be any good at php & PW APIs to deal with :). ThankYou, love the processwire community1 point
-
Hello, Error message says there's no property "field" on Page class. There are also some more mistakes. This should be better: public function ready() { $this->pages->addHookBefore('saveReady', function($event) { // Replaced saved with saveReady $page = $event->arguments(0); // Replaced [0] with (0) $summaryField = $page->summary; // Removed ->field('name') $bodyField = $page->body; // Removed ->field('name') if (empty($summaryField)) { // Removed ->value $page->summary = strip_tags(substr($bodyField, 0, 192)); // replaced $summaryField->value with $page->summary, $bodyField->value with $bodyField } }); } Note that "save" hook is executed after the page is saved, so it's too late. Documentation : Page and Pages hooks EDIT : Note that you can also do that without creating a module, just by adding the hook ($this->pages->addHookBefore...) in site/templates/admin.php1 point
-
@Richard Jedlička & @bernhard, just want to let you know, I did a pull request for fixing issues on non label fields and on fields "not closable". https://github.com/uiii/AdminHelperLinks/pull/6#issue-19880090381 point
-
So... it seems my SQL is a bit rusty. I fired up ChatGPT, entered my question, along with the mySQL export of all relevant tables (incl. content), and hey: Got a working query back in no time, with nice explanations.1 point
-
The first version of FrontendLoginRegister was released a long time ago, and the module in its latest version (2.1.8) is now available in the Processwire modules directory. After a long development period, I have decided to add this module to the modules' directory. This will make it much easier to update the module in the future, and I hope that a larger community will test the module and report problems. The module has BETA status. I have tested it and have not found any problems, so it should work as expected (but I probably cannot test all possible scenarios). Therefore, I would be happy if many people would test the module and help me to reach the stable status after a while. Best regards1 point