Jump to content

Leaderboard

Popular Content

Showing content with the highest reputation on 06/10/2019 in all areas

  1. @kongondo Thanks for your response! You've really helped me out with the string on that line, when my project is finished, I will show it over here! And I'm glad I found a bug so I could help a bit with this module!
    3 points
  2. https://processwire.com/docs/modules/hooks/#how-can-i-add-a-new-method-via-a-hook
    3 points
  3. @psy You can set webpQuality to lower values what will result in smaller filesizes. Examples:
    2 points
  4. Hey everyone searching for this topic — if you're using Let's Encrypt with URL validation, don't forget to replace the part blocking dotfiles location ~ /\. { deny all; } With block denying everything BUT .well-known location ~ /\.(?!well-known).* { deny all; }
    2 points
  5. Just like you‘ve used gif/png/jpeg when appropriate in the past you‘ll likely use different upcoming formats in the future. It‘s unlikely we‘ll get a single general format anytime, which would trump more specialized formats across the board. Also the performance is actually less of an factor besides the bigger hurdle of widespread support. I‘ve at least seen a handful of demos, which trump webp, but those formats seem to never have made it past the initial research/implementation phase.
    2 points
  6. @Tyssen, it looks like the original examples had a small mistake – either that, or I'm missing something as well. You should either store $twig_env in $config in your _init.php file, or (more likely) call it directly with $twig_env in _main.php ?
    2 points
  7. ... except if you're already checking with class_exists(), in which case it's just a tiny but unnecessary overhead ?
    2 points
  8. The only thing what may occure in very rare cases is, that another module in the same PW installation also uses the TwitterOAuth lib, stored in a different place. To avoid conflicts, one can add a conditional check before including it, using the PHP function class_exists(). <?PHP namespace ProcessWire; if(!class_exists("Abraham\TwitterOAuth\TwitterOAuth") { /* load the TwitterOAuth library from my Module folder */ require_once("./twitteroauth/autoload.php"); } use Abraham\TwitterOAuth\TwitterOAuth; Also one may prefer require_once() over require(), if there is a chance that the class / class file is loaded twice.
    2 points
  9. Your method seems fine to me. Something like Composer would be another option, but then you'd have to explain to your module's intended users how to use it, and expect them to have it installed, before they can get started with your module. Depending on the use case that may or may not be worth the hassle. In RedBeanPHP I've bundled the RedBean ORM into a module, and pretty much the only difference is that I'm loading the file and instantiating the class in the init() method – but even then I'm mainly doing it that way because I wanted to provide a config setting for selecting which version of the library to include.
    2 points
  10. I have recently started to integrate Twig in my ProcessWire projects to have a better separation between logic and views as well as have cleaner, smaller template files as opposed to large multi-purpose PHP-templates. Though there is a Twig module, I have opted to initialize twig manually to have more control over the structure and settings of the twig environment. This is certainly not required to get started, but I find that having no "secret sauce" gives the me as a developer more agency over my code structure, and better insights into how the internals of the libraries I'm using work. Framework like Drupal or Craft have their own Twig integration, which comes with some opinionated standards on how to organize your templates. Since Twig is not native to ProcessWire, integrating Twig requires one to build a solid template structure to be able to keep adding pages and partials without repeating oneself or having templates grow to unwieldy proportions. This will be an (opinionated) guide on how to organize your own flexible, extensible template system with ProcessWire and Twig, based on the system I developed for some projects at work. Somehow this post got way too long, so I'm splitting it in two parts. I will cover the following topics: Part 1: Extendible template structures How to initialize a custom twig environment and integrate it into ProcessWire How to build an extendible base template for pages, and overwrite it for different ProcessWire templates with custom layouts and logic How to build custom section templates based on layout regions and Repeater Matrix content sections Part 2: Custom functionality and integrations How to customize and add functionality to the twig environment How to bundle your custom functionality into a reusable library Thoughts on handling translations A drop-in template & functions for responsive images as a bonus However, I will not include a general introduction to the Twig language. If you are unfamiliar with Twig, read the Twig guide for Template Designers and Twig for Developers and then come back to this tutorial. That's a lot of stuff, so let's get started ? Initializing the Twig environment First, we need to install Twig. If you set up your site as described in my tutorial on setting up Composer, you can simply install it as a dependency: composer require "twig/twig:^2.0" I'll initialize the twig environment inside a prependTemplateFile and call the main render function inside the appendTemplateFile. You can use this in your config.php: $config->prependTemplateFile = '_init.php'; $config->appendTemplateFile = '_main.php'; Twig needs two things: A FilesystemLoader to load the templates and an Environment to render them. The FilesystemLoader needs the path to the twig template folder. I'll put my templates inside site/twig: $twig_main_dir = $config->paths->site . 'twig'; $twig_loader = new \Twig\Loader\FilesystemLoader($twig_main_dir); As for the environment, there are a few options to consider: $twig_env = new \Twig\Environment( $twig_loader, [ 'cache' => $config->paths->cache . 'twig', 'debug' => $config->debug, 'auto_reload' => $config->debug, 'strict_variables' => false, 'autoescape' => true, ] ); if ($config->debug) { $twig_env->addExtension(new \Twig\Extension\DebugExtension()); } Make sure to include a cache directory, or twig can't cache the compiled templates. The development settings (debug, auto_reload) will be dependent on the ProcessWire debug mode. I turned strict_variables off, since it's easier to check for non-existing and non-empty fields with some parts of the ProcessWire API. You need to decide on an escaping strategy. You can either use the autoescape function of twig, or use textformatters to filter out HTML tags (you can't use both, as it will double escape entities, which will result in a broken frontend). I'm using twig's inbuilt autoescaping, as it's more secure and I don't have to add the HTML entities filter for every single field. This means pretty much no field should use any entity encoding textformatter. I also added the Debug Extension when $config->debug is active. This way, you can use the dump function, which makes debugging templates much easier. Now, all that's left is to add a few handy global variables that all templates will have access to, and initialize an empty array to hold additional variables defined by the individual ProcessWire templates. // add the most important fuel variables to the environment foreach (['page', 'pages', 'config', 'user', 'languages', 'sanitizer'] as $variable) { $twig_env->addGlobal($variable, wire($variable)); }; $twig_env->addGlobal('homepage', $pages->get('/')); $twig_env->addGlobal('settings', $pages->get('/site-settings/')); // each template can add custom variables to this, it // will be passed down to the page template $variables = []; This includes most common fuel variables from ProcessWire, but not all of them. You could also just iterate over all fuel variables and add them all, but I prefer to include only those I will actually need in my templates. The "settings" variable I'm including is simply one page that holds a couple of common settings specific to the site, such as the site logo and site name. Page templates By default, ProcessWire loads a PHP file in the site/templates folder with the same name of the template of the current page; so for a project template/page, that would be site/templates/project.php. In this setup, those files will only include logic and preprocessing that is required for the current page, while the Twig templates will be responsible for actually rendering the markup. We'll get back to the PHP template file later, but for the moment, it can be empty (it just needs to exist, otherwise ProcessWire won't load the page at all). For our main template, we want to have a base html skeleton that all sites inherit, as well as multiple default regions (header, navigation, content, footer, ...) that each template can overwrite as needed. I'll make heavy use of block inheritance for this, so make sure you understand how that works in twig. For example, here's a simplified version of the html skeleton I used for a recent project: {# site/twig/pages/page.twig #} <!doctype html> <html lang="en" dir="ltr"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>{% block title %}{{ '%s | %s'|format(page.get('title'), homepage.title) }}{% endblock %}</title> <link rel="stylesheet" type="text/css" href="{{ config.urls.site }}css/main.css"> </head> <body class="{{ page.template }}"> {% block navigation %}{{ include("components/navigation.twig") }}{% endblock %} {% block header %}{{ include("components/header.twig") }}{% endblock %} {% block before_content %}{% endblock %} {% block content %} {# Default content #} {% endblock %} {% block after_content %}{% endblock %} {% block footer %}{{ include("components/footer.twig") }}{% endblock %} </body> </html> All layout regions are defined as twig blocks, so each page template can override them individually, without having to touch those it doesn't need. I'll fill the content block with default content soon, but for now this will do. Now, templates for our content types can just extend this base. This is the template for the homepage: {# site/twig/templates/pages/page--home.twig #} {% extends "pages/page.twig" %} {# No pipe-seperated site name on the homepage #} {% block title page.get('title') %} {# The default header isn't used on the homepage #} {% block header %}{% endblock %} {# The homepage has a custom slider instead of the normal header #} {% block before_content %} {{ include('sections/section--homepage-slider.twig', { classes: ['section--homepage-slider'] }) }} {% endblock %} Note that I don't do most of the actual html markup in the page templates, but in individual section templates (e.g. section--homepage-slider.twig) that can be reused across content types. More on this in the next section. We still need to actually render the template. The page template (which will be the entry point for twig) will be loaded in our _main.php, which we defined as the appendTemplateFile earlier, so it will always be included after the template specific PHP file. $template_file = 'pages/page--' . $page->template->name . '.twig'; $twig_template = file_exists($twig_main_dir . '/' . $template_file) ? $template_file : 'pages/page.twig'; echo $twig_env->render($twig_template, $variables); This function checks if a specific template for the current content type exists (e.g. pages/page--home.twig) and falls back to the default page template if it doesn't (e.g. pages/page.twig). This way, if you want a blank slate for a specific content type, you can just write a twig template that doesn't extend page.twig, and you will get a blank page ready to be filled with whatever you want. Note that it passes the $variables we initialized in the _init.php. This way, if you need to do any preprocessing or data crunching for this request, you can do it inside the PHP template and include the results in the $variables array. At this point, you can start building your default components (header, footer, navigation, et c.) and they will be included on every site that extends the base page template. Custom sections Now we've done a great deal of setup but haven't actually written much markup yet. But now that we have a solid foundation, we can add layout components very easily. For most of my projects, I use the brilliant Repeater Matrix module to set up dynamic content sections / blocks (this is not the focus of this tutorial, here's a detailed explanation). The module does have it's own built-in template file structure to render it's blocks, but since it won't work with my Twig setup, I'll create some custom twig templates for this. The approach will be the same as with the page template itself: create a "base" section template that includes some boilerplate HTML for recurring markup (such as a container element to wrap the section content in) and defines sections that can be overwritted by section-specific templates. First, let's create a template that will iterate over our the Repeater Matrix field (here it's called sections) and include the relevant template for each repeater matrix type: {# components/sections.twig #} {% for section in page.sections %} {% set template = 'sections/section--' ~ section.type ~ '.twig' %} {{ include( [template, 'sections/section.twig'], { section: section }, with_context = false ) }} {% endfor %} Note that the array syntax in the include function tells Twig to render the first template that exists. So for a section called downloads, it will look look for the template sections/section--downloads.twig and fallback to the generic sections/section.twig. The generic section template will only include the fields that are common to all sections. In my case, each section will have a headline (section_headline) and a select field to choose a background colour (section_background) : {# sections/section.twig #} {% set section_classes = [ 'section', section.type ? 'section--' ~ section.type, section.section_background.first.value ? 'section--' ~ section.section_background.first.value ] %} <div class="{{ section_classes|join(' ')|trim }}"> <section class="container"> {% block section_headline %} {% if section.section_headline %} <h2 class="section__headline">{{ section.section_headline }}</h2> {% endif %} {% endblock %} {% block section_content %} {{ section.type }} {% endblock %} </section> </div> This section template generates classes based on the section type and background colour (for example: section section--downloads section--green) so that I can add corresponding styling with CSS / SASS. The specific templates for each section will extend this base template and fill the block section_content with their custom markup. For example, for our downloads section (assuming it contains a multivalue files field download_files): {# sections/section--download.twig #} {% extends "sections/section.twig" %} {% block section_content %} <ul class="downloads"> {% for download in section.download_files %} <li class="downloads__row"> <a href="{{ download.file.url }}" download class="downloads__link"> {{ download.description ?: download.basename }} </a> </li> {% endfor %} </ul> {% endblock %} It took some setup, but now every section needs only care about their own unique fields and markup without having to include repetitive boilerplate markup. And it's still completely extensible: If you need, for example, a full-width block, you can just not extend the base section template and get a clean slate. Also, you can always go back to the base template and add more blocks as needed, without having to touch all the other sections. By the way, you can also extend the base section template from everywhere you want, not only from repeater matrix types. Now we only need to include the sections component inside the page template {# pages/page.twig #} {% block content %} {% if page.hasField('sections') and page.sections.count %} {{ include('components/sections.twig' }} {% endif %} {% endblock %} Conclusion This first part was mostly about a clean environment setup and template structure. By now you've probably got a good grasp on how I organize my twig templates into folders, depending on their role and importance: blocks: This contains reusable blocks that will be included many times, such as a responsive image block or a link block. components: This contains special regions such as the header and footer that will probably be only used once per page. sections: This will contain reusable, self-contained sections mostly based on the Repeater Matrix types. They may be used multiple times on one page. pages: High-level templates corresponding to ProcessWire templates. Those contain very little markup, but mostly include the appropriate components and sections. Depending on the size of the project, you could increase or decrease the granularity of this as needed, for example by grouping the sections into different subfolders. But it's a solid start for most medium-sized projects I tackle. Anyway, thanks for reading! I'll post the next part in a couple of days, where I will go over how to add more functionality into your environment in a scalable and reusable way. In the meantime, let me know how you would improve this setup!
    1 point
  11. Hi there. It's hard to provide specific instructions without knowing a bit more about your site – for an example whether you've installed a publicly available site profile or developed a custom one yourself – but you can check the page number in a template file and only show the header if the user is currently viewing page 1: <?php if ($input->pageNum == 1): ?> <!-- your header goes here --> <?php endif; ?> More details here.
    1 point
  12. Also i would give the new page only a title with your text sanitizer and let ProcessWire handle the name of the page (in case the pagename already exists). If you would like to set also the page name then use the $sanitizer->pageName().
    1 point
  13. You should save the page after finishing with your upload. $p->save(); instead of $u->save(); WireUpload doesn't have any save method (that's what the error message tells you)
    1 point
  14. i Would like to thank everyone who gave me tips and advice on this topic. Think i got the gist of it and i will certainly consider everything mentioned. :) /EyeDentify
    1 point
  15. @Tyssen @teppo Yeah that was a mistake, sorry about that. I always add the twig instances to the config to be able to access them globally, but here I didn't include that part. I corrected it in the OP! Yeah, I think setting the output as a variable in the _init.php or _main.php is the best option here, since you can't directly call static methods of classes in twig. Another option, if it's a method you need to access more often, is to write a wrapper twig function and add it to your twig environment. There are some example for this in the second part of the tutorial - let me know if it's not working for you!
    1 point
  16. From @ryan's comment in the blog post https://processwire.com/blog/posts/webp-images-in-pw/: "By the way, if for some reason the webp results in a larger image (which is very rare), calling the $image->webp->url will return the smaller (non-webp) image." Very excited to see webp support in PW core then followed all the instructions. Couldn't get it working, or so I thought. Seems I did. OK, I take the craftsman's approach when adding images: Photoshop image as needed then reduce the image size to close to the max dimensions I need Export for Web Legacy - jpeg high @ 60% quality Drop exported jpeg into the lovely TinyBeest app https://www.tinybeest.com/ (Mac only) Upload TinyBeest-optimised image to PW, often output with MarkupSrcSet https://modules.processwire.com/modules/markup-src-set/ template code Every time I used $image->webp->url I got a jpg file with great quality. Only got webp when I uploaded the original image, often MB's huge. Bit frustrating that my images still get scored down on Google Page Speed Insights for not being webp - but then they'd be bigger and take longer to download ?
    1 point
  17. Update: Blog 2.4.4 Changelog Fixed bug that caused blog settings not to save in blog admin. Thanks @Troost for reporting. Module has been updated in the modules directory.
    1 point
  18. Glad you like it! :-). You do that by passing $options to renderPosts(). I can only apologise that I've never completed the documentation for this module. Please have a look at the options here, in the method processPostsOptions(). All the options that can be passed to renderPosts() are there. For instance, 'post_date' => 1,// display post's date: 0=off,1=top(below post-headline),2=bottom(in post-foot) The string is on this line. Is it not appearing in the translation screen? I've never done translation myself so, I wouldn't know how to help beyond this. If you are still experiencing issues, maybe one of your multilingual members could help out. You caught a bug! Thanks for reporting. I've fixed it in the latest version (2.4.4). I've updated the module in the modules' directory.
    1 point
  19. Actually never mind, set it as a variable in _init.php and then call it in the twig template.
    1 point
  20. Another question: in Twig how would you call modules like AIOM where in PHP it'd be <?=AIOM:CSS(array())?> or <?=AIOM:JS(array())?> ?
    1 point
  21. I've obviously missed something, because I'm getting Call to a member function render() on null from _main.php. My templates are… _init.php <?php $twig_main_dir = $config->paths->site . 'templates/views'; $twig_loader = new \Twig\Loader\FilesystemLoader($twig_main_dir); $twig_env = new \Twig\Environment( $twig_loader, [ 'cache' => $config->paths->cache . 'twig', 'debug' => $config->debug, 'auto_reload' => $config->debug, 'strict_variables' => false, 'autoescape' => true, ] ); if ($config->debug) { $twig_env->addExtension(new \Twig\Extension\DebugExtension()); } foreach (['page', 'pages', 'config', 'user', 'languages', 'sanitizer'] as $variable) { $twig_env->addGlobal($variable, wire($variable)); }; $twig_env->addGlobal('homepage', $pages->get('/')); $twig_env->addGlobal('settings', $pages->get('/site-settings/')); $variables = []; _main.php <?php $template_file = 'views/' . $page->template->name . '.twig'; $twig_template = file_exists($twig_main_dir . '/' . $template_file) ? $template_file : 'views/basic-page.twig'; echo $config->twig_env->render($twig_template, $variables); My .twig templates are set up as yours are and my PW .php templates are just blank at the moment.
    1 point
  22. This approach seems totally fine. You could speed things up using $cache or even ProCache. Depending on the overall setup one thing might work better than the other. To be honest... build your structure, build up your content and when you reach 100-200 news/events/whatever type of content try playing around with different settings. Do it again and again. Always have a DEV environment to test those things. In most cases there is more than one way to get things done in ProcessWire. At first one approach seems to work fine but later on with more content and users another might work way better. I find myself quite often in this situation and rework things in projects after a while because of things and the amount of content changes. My opinion on this: Don't force them. Give them the option to choose what they want. Offer a nice "The last time you watched CITY. Go this way to get all the latest news for CITY." You never know why someone visited a particular city. Maybe just for interest or by accident. Yet again there is no real this is the best option to do this answer. In your case you might want to look at the page reference field and take a look at this post about categorising content and this post about getting references in different ways. You can also try the skyscraper demo @ryan did a while back. He is handling a lot of data and references there as well. This version of Skyscraper profile works with the latest ProcessWire verion.
    1 point
  23. In regards to dev vs live htaccess, you can also use htaccess's "if" conditional for blocks of code you want to selectively enable/disable depending on the site being loaded. By doing so, you don't need two separate htaccess files to maintain. When using ProCache combined with it's htaccess recommendations at the top, I wrap it inside a conditional like this: ################################################################################################# # ProcessWire ProCache recommended .htaccess tweaks for expires and gzip performance settings. # PWPC-3.0 Keep this line here for ProCache identification purposes. ################################################################################################# <If "%{HTTP_HOST} == 'my-live-website.com'"> ...the code here... </If>
    1 point
  24. Whoa, just got bit by this as well. Couple of my sites (on an Ubuntu server) were using SessionHandlerDB, so this happened: > select count(*) from sessions; +----------+ | count(*) | +----------+ | 1561416 | +----------+ 1.5 million rows in the sessions table – "whoops". Should probably add sensible session.gc_probability number for these sites ?
    1 point
  25. Thanks Kongondo for this great offer! I'm currently experimenting with it, I came to the point where I want to change a little bit in the positioning of the blog. Like placing the Date underneath the post instead of above. But I can't find where I can change those settings? Also, the option to show like 200 words and than the text 'view more'. I want to translate this to dutch, but I can neither find this one hehehe. Can u help me out? (Dont know what about this, but when i go to PW admin > blog > settings > I can change whatever I want, but it won't safe. It keeps saying the same text which comes by the installation) I've tested this in Chrome and Safari, same problem. I' on ProcessWire 3.0.132 © 2019
    1 point
  26. This is terrific. Even better would be to build Twig into the core, and allow .twig and .php. I'm a huge Symfony fan, and miss the tools there. It's hard to go back to PHP templates once you've gotten used to Twig. PHPStorm now even support debugging twig templates!
    1 point
×
×
  • Create New...