Jump to content

MoritzLost

Members
  • Posts

    354
  • Joined

  • Last visited

  • Days Won

    18

MoritzLost last won the day on July 25 2022

MoritzLost had the most liked content!

3 Followers

Contact Methods

  • Website URL
    https://moritzlost.de

Profile Information

  • Gender
    Male
  • Location
    Cologne, Germany

Recent Profile Visitors

2,403 profile views

MoritzLost's Achievements

Sr. Member

Sr. Member (5/6)

1k

Reputation

  1. It's right here 🙂 Anyway, glad the module is working for you! Let me know if you run into any more issues.
  2. @MarkEWere you viewing the form while logged in as superuser? Since version 2.0.0, the module comes with a permission to bypass hCaptcha. This permission also hides the widget from the frontend. The superuser skips all permission checks, so they never see the widget. Make sure to test the form in a private browser window where you're not logged in.
  3. @thetuningspoon I completely disagree, because that is farther removed from the goal of having a declarative dec config and closer to the territory of migrations. You don't want to give the system a list of instructions on how to build the correct state, you want to have a declarative list of configuration values that describes the correct state. Getting there happens under the hood. Similar to the difference between declarative or functional programming and imperative programming - you only describe what to do, not how to do it. The system can compare the list of fields in the config and in the database, add and remove fields as needed, and update config values. Combined with version control, this allows you to go back seamlessly to any previous state, revert changes to the declarative config etc, which is something that migrations struggle with, as mentioned. I understand the hesitation to have the system outright delete anything that's missing from the config, but that's just a combination of not going 'all-in' on the declarative config, or not embracing some important workflow changes along with it. You want the config to be the single source of truth for the state of the project, independent of any existing installation or database. If you take an existing 'base' state (for example, tracked in version control as a database dump of an existing installation) and only include changes relative to that base state in your config, you're not there all the way. You want the config to include everything, every field, template, setting, installed plugin, etc. This way, a new installation (for example, a staging environment for a specific feature) can ideally be created with a single console command. Once you have that, you don't have to worry about having the system delete fields that aren't in the config, because deleting a field from the config requires the same amount of effort and has the same visibility in your quality control pipeline as adding one. The rest is just a question of workflow. Changes to the config should be tracked in version control, and merging those changes should require an approved pull request (if you're working in a team). Deleting a field is just as much of a change that is visible in the PR as adding one, since you will see the deleted field config in the PR and make sure that this is really what you want to do. Once you have a solid workflow in place, you can confidently delete everything that you no longer need, because you know you this change will go through review and quality control, and you can get it back through version control if you really need it again in the future. Of course, mistakes still happen. Turns out the client still had some vital data in that field you removed? Well, that's what backups are for. The first step in every deployment script should be a backup. Yes, absolutely. Though in a perfect world, changes to the config are only made in development environments, tracked in version control, and then deployed to the live site (after any staging environments in between). Pulling the config and applying it should be done as part of the deployment script. This way, there is rarely a need to have a button in the backend that applies the config (though this is still useful for development). The more you can automate deployments and get rid of manual steps, the better. This allows you to work in smaller iterations and get features out faster and with more confidence. The Phoenix Project is a great read on that subject!
  4. @bernhard You're right, a simple setup like that is unlikely to break. That's also close to the setup my tutorials recommend and that I'm using for all ProcessWire projects. What I had in mind are smaller problems / incompatibilities that crop up between PW and Twig from time to time. We recently did a round of updates to 3.0.200 and ended up with some exceptions because of the way we were accessing fields that may or may not exist on some pages. The way Twig tries to access object properties was causing some unusual errors. If you don't know both ProcessWire and Twig very well, this can be really hard to debug. If you're using an actively maintained plugin that comes with some integrated features, the developer will probably notice those problems as they come up and either fix them or provide guidance on how to avoid them. What I meant above was that with a custom setup, you can end up with a bug that you don't know how to fix and have nobody to turn to for help. But maybe those points apply regardless of how you include Twig, manually or through a plugin … in any case, I wasn't thinking about your upcoming module there, I'm sure it'll be a useful time-saver!
  5. @wbmnfktr Fair enough! For now I've added a disclaimer with a link to this post to the Twig setup tutorial (just below the introduction) so people looking for reasons to use Twig can understand my reasoning. Maybe in the future I'll rework this article a bit and put it all on processwire.dev. Probably not a good idea to just copy it over as is, Google will think I'm a spambot ? That tutorial already exists on processwire.dev though, see the links in my initial post (part one – part two). Those two tutorials go through the complete Twig setup and a basic structure for Twig templates that should fit most use-cases and integrates nicely into the existing PHP templates, as well as some pointers on extending it with custom functionality. Or is there something that you feel is missing there? ? Well, in the end my setup is just one way to integrate Twig into ProcessWire, and there are a couple of different options (like Bernhards upcoming module). And there are definitely some edge-cases that arise from Twig not being "officially" integrated into ProcessWire – like the translation system that can't detect translatable strings in Twig. There are workarounds for those, but you often have to invest a bit more time to get things working. I think Twig is a great benefit and wouldn't want to miss it in any of my projects. But it comes with some strings attached, and the setup might break in unexpected ways with every new ProcessWire update. So a community-provided module might be the 'safer' option if there's an active maintainer behind it who will keep everything up to date and working with new PW versions, and do all the Twig setup and config 'under the hood'.
  6. @wbmnfktr I thought about putting this post on processwire.dev, but it somehow feels like it doesn't belong there. Not sure why – maybe it doesn't provide as much a benefit as (I feel) the other tutorials there do. I've had this topic on my mind because I've been repeatedly reading that Twig doesn't do anything that you can't do in plain PHP. I obviously disagree and needed to get this out of my head and onto a page so I can link to it when it comes up ? But if you don't need convincing to use Twig, the post doesn't provide anything the other two tutorials don't already cover. Maybe I'll integrate this post in processwire.dev at some point …
  7. I've written before about how to use Twig with ProcessWire (see my tutorials on integrating Twig into ProcessWire and extending Twig with custom functionality for ProcessWire). But those posts don't really talk about why I like to use Twig instead of plain PHP templates. For me, this comes down to one killer feature that I'm going to talk about below. But first, let's look at some of the more commonly mentioned advantages of Twig and why I don't actually think they're all that important in the context of ProcessWire: The syntax is nicer. While I personally agree with this, it's entirely subjective (and familiarity is comforting while trying something new is scary). Autoescaping provides security by default. This is true to a degree, but most ProcessWire projects (at least for me) aren't the type of expansive community-driven sites with lots of user-generated content where this would be most relevant. Most of my ProcessWire projects so far have featured a few trusted editors managing content, where you don't really need autoescaping for every template to make sure nobody slips in some malicious code. Twig forces separation of concerns between logic and presentation. Again, this is true, but not relevant to most ProcessWire projects. Most of my ProcessWire projects (and, judging by the showcase, most ProcessWire projects period) are mostly classic brochure sites without a lot of interactivity or app-like behaviour. Those projects are 99% presentation with only some small snippets of logic in between, so separating the two isn't really an issue. With that out of the way, let's talk about the killer feature that makes Twig essential to my work: Inheritance and block-based overwrites. To explain why this is important, I'll start out with a basic template for a header component in PHP and see how it can handle additional content being added to it. Then I'll write the same component in Twig for comparison. If you need a general guide on template inheritance in Twig, read this first: https://twig.symfony.com/doc/3.x/tags/extends.html The reusable header template Here's our basic reusable header template written in PHP: <header class="header"> <h1 class="header__headline"><?= $page->title ?></h1> <?php if ($page->subline): ?> <p class="header__subline"><?= $page->subline ?></p> <?php endif; ?> <?php if ($page->image) echo wireRenderFile('inc/responsive-image.php', ['image' => $page->image]) ?> </header> Sidenote, I'll use wireRenderFile to keep the examples brief, you could also write the image tag inline here. The header component may be included in a page template like this: <?= wireRenderFile('inc/header.php') ?> You have two options for where to do this. Option one is to include this template in every template that needs it (templates/home.php, templates/project.php, templates/news.php). Option two is to use the appendTemplateFile setting to keep the basic page layout in a shared template file that's always included at the end of the request (_main.php). Option one allows you to pass the template different variables depending on context, but it also means you've already started with the code duplication. Option two is probably the more common approach, but with this option you can only pass it one set of variables – those variables might be overwritten by the page template, but this will also lead to some problems as you'll see shortly. Let's introduce our first change request, one particular page needs to display a video instead of an image. No problem, we can just check if the page has a video field and display it conditionally: if ($page->video) { wireRenderFile('inc/video.php', ['video' => $page->video]); } elseif ($page->image) { wireRenderFile('inc/responsive-image.php', ['image' => $page->image]); } This still works fine. But crucially, the logic for the video header is now part of the header template, not part of the template for the page with video headers. This means that every time I want to edit the header template, this little piece of conditional logic is something I have to deal with. But that's fine, multiple pages might need a video header, so having this switch in the header template is acceptable. But then another change request come in: In the page template for some kind of project page, instead of the image, we want to display a list of project data coming from a separate project_data field. Again, we can adjust the template: if ($page->project_data) { wireRenderFile('inc/project-data.php', ['data' => $page->project_data]); } elseif ($page->video) { wireRenderFile('inc/video.php', ['video' => $page->video]); } elseif ($page->image) { wireRenderFile('inc/responsive-image.php', ['image' => $page->image]); } But now some display logic that's specific to one template is part of the global header template, not part of the project.php. This trend will continue: every custom feature required for the header of any page template will inflate the header.php file, and every adjustment requires reading all of it and making sure my change doesn't break any of the other features. This is unsustainable and inherently unscalable. Another example, what if a specific page has both the video and the image fields, but I want to display the image instead of the video? Currently, this is not possible. Now I have to build in some kind of switch: $preferImage = $preferImage ?? false; if ($page->project_data) { wireRenderFile('inc/project-data.php', ['data' => $page->project_data]); } elseif ($page->video && !$preferImage) { wireRenderFile('inc/video.php', ['video' => $page->video]); } elseif ($page->image) { wireRenderFile('inc/responsive-image.php', ['image' => $page->image]); } Again, this solution doesn't scale. Did you notice the subtle bug in there? The noise to signal ratio is becoming worse with every feature. Now you're probably thinking that you would approach those change requests in a different way. Let's look at some of the possible solutions to those problems. Lots of variables You can solve this to a degree by using lots of variables to control what you're template is doing. If we're using a shared _main.php template file that includes the inc/header.php template, the project-specific template (e.g. templates/project.php) is loaded first. So those templates can set some variables that change the content and behaviour of the header component. For example, say you want to do keep the template for the project data block in your project.php so it's easy to find. Let's go back to the original header template and introduce an optional variable that can be used to replace the image with something else: <?= $headerImageContent ?? wireRenderFile('inc/responsive-image.php', ['image' => $page->image]); Now you can set the $headerImageContent variable in your project.php and it will replace the image. But what if I want both the normal image (without duplicating code) AND some custom content? No problem, add even more variables: <?= $headerImageBeforeContent ?? '' ?> <?= $headerImageContent ?? wireRenderFile('inc/responsive-image.php', ['image' => $page->image]); <?= $headerImageAfterContent ?? '' ?> Now repeat that for every part of the header template which might need to be adjusted for some of the page templates (hint: it's all of them). You end up with a template that uses tons of variables, the signal to noise ratio becomes abhorrent. Throw in the fact that those variables are all unscoped, so there's no way to tell where they are being set or overwritten, and variable names have be very specific to avoid collisions. All of this might make sense to you the day you've written it, but what about your colleague that hasn't touched this project yet? What about yourself in six months? Make templates more granular Another solution is to make the templates more granular. I've started this trend above by using wireRenderFile to put little isolated template parts into their own dedicated template – for example, to display a single responsive image or an HTML5 video player. In the same grain, you can split up the header.php into multiple smaller template to mix and match and include include those you want to in each specific context. But this has downsides as well: You end up with a fractal nightmare, a deluge of templates with increasing granularity and decreasing utility, just to be able to include those smaller templates separately from each other. Cohesion and readability is reduced, and there's no way from directory structure alone to tell which templates go together in what ways. Splitting an existing template into two smaller templates is not backwards compatible – you have to make an adjustment in every place the original template was included. Or you keep the original template but change it to just include the two new templates. I said fractal nightmare already, didn't I? Duplicate code You can, of course, just keep separate header templates for each page type. But then you're duplicating the common parts of those templates all over again, and changing those means you have to touch a lot of separate files – definitely not DRY. Most real-life solutions will include a mix of the three approaches. I tried to be fair and write the templates in the leanest and cleanest way possible, but things still got out of hand quickly. Now let's look at the same component written in Twig: Resuable components in Twig Here's the basic header template but written in Twig: {# components/header.twig #} <header class="header"> <h1 class="header__headline">{{ page.title }}</h1> {% if page.subline %} <p class="header__subline">{{ page.subline }}</p> {% endif %} {% block header_image %} {% if page.image %} {{ include('components/responsive-image', { image: page.image, }) }} {% endif %} {% endblock %} </header> One important difference is the block tag defining the header_image block. So far we don't need that, but it will become important in a second. For the page templates, it's common to have a base template that all other templates inherit from: {# html.twig #} <!doctype html> <html lang="en" dir="ltr"> <head> <title>{%- block title -%}{%- endblock -%}</title> {% block seo %} {{ include('components/seo', with_context = false) }} {% endblock %} </head> <body> {% block header %} {{ include('components/header') }} {% endblock %} {% block content %}{% endblock %} {% block footer %} {{ include('components/footer') }} {% endblock %} </body> The base template defines some blocks and includes some default components (seo, header, footer). Now the template for the project page just inherits this: {# project.twig #} {% extends 'html' %} With the PHP template, things got difficult once we wanted to overwrite part of the header template with some content specific to one page template. This is where the header_image block comes in handy: {# project.twig #} {% extends 'html' %} {% block header %} {% embed "components/header" %} {% block header_image %} {# project data template … #} {% endblock %} {% endembed %} {% endblock %} Now the project.twig extends the base html.twig template and overwrites the header block. Then it includes the components/header template and overwrites only the header_image block while keeping the rest. This approach has some major advantages over the plain PHP template: All the code for the project template is in one place – to see what's special about this particular page in comparison to the base template, I just have to look at one template. I didn't have to repeat any of the header template code, so I can still change the header in a central place. The components/header template stays small and manageable, it doesn't know or care what other templates extend it and which parts get overwritten where. As a sidenote, some people may not like the embed syntax. Another approach would be to once again create a custom header template for the project template. But this time, we don't need to repeat any code because we can use inheritance: {# components/project-header.twig #} {% extends "components/header" %} {% block header_image %} {# project data template … #} {% endblock %} I prefer the embed approach because it keeps all the related code together. But both approaches allow for full flexibility with no code duplication. Now what if you want to change other parts of the components/header.twig template in an extending template? In this case, you can always add more blocks: {# components/header.twig #} {% block header_headline %} <h1 class="header__headline">{{ page.title }}</h1> {% endblock %} Adding blocks doesn't change anything about the base template, so it's 100% backwards-compatible. You can always add more blocks without ever having to worry about breaking any existing templates or introducing bugs. Another challenge for the PHP template was to add some additional content to a part of the header template while still keeping the default content. Let's say we want to display a publication date above the headline in a news template, but keep the headline as is. No problem: {# project.twig #} {% block header_headline %} <time>{{ entry.published_date }}<time> {{ parent() }} {% endblock %} The parent() function returns the content of the block in the base template, so you can extend a block without overwriting it completely. Conclusion You can solve all the challenges I posed here in PHP. Most solutions will include a combination of the three approaches mentioned above (making templates more granular, using lots of variables and duplicating code). And a well thought-out mix of those approaches can work reasonably well. The problem is that while those solutions improve reusability and scalability, they usually require lots of boilerplate code and unscoped variables. This reduces the readability and makes the system harder to modify, while making it easier for bugs to creep in. Again, there are solutions for those problems that introduce other problems until the solutions cancel each other out in trade-offs. To me, Twig is a great alternative that requires fewer trade-offs. It allows you to achieve complete freedom and flexibility in your templates all while keeping your templates DRY and keeping code that belongs together in a single file. On top of that, Twig uses a nice, readable syntax (warning: personal opinion) and provides a lot of utility methods and other features to improve your template structure. Some notable caveats to all of this: All of the discussed problems are about scaling a project to a larger scope or team size. For small projects that will never need to scale in this way, this doesn't really matter. ProcessWire's built-in markup regions seem to tackle a lot of the same problems I mentioned in this post. Can't really speak for it as I haven't tried it yet. If this all sounds interesting to you and you want to learn more, you can check out my tutorials on integrating Twig into ProcessWire and extending Twig with custom functionality for ProcessWire.
  8. InputfieldHCaptcha 2.0.0 Version 2.0.0 of InputfieldHCaptcha is now available. Feature: Add a permission to bypass hCaptcha completely. See the documentation for details. If you're upgrading from an earlier version of the module, you may need to add the permission manually. Go to Access -> Permissions -> Add new and add a permission with the name bypass-hcaptcha. Breaking change: After updating, superuser accounts won't see the hCaptcha widget anymore and be allowed to bypass it everywhere. This is a potential breaking change, but mostly relevant to know for development and debugging purposes – make sure to test your forms in a private browser window. --------------------------- @Pete I've gone ahead and released the bypass permission feature in version 2.0.0. Let me know if anything is not working right for you!
  9. @Pete I've added the permission check in a separate branch, would you mind testing it? https://github.com/MoritzLost/InputfieldHCaptcha/tree/bypass-permission For now the permission hides the widget completely, I think this makes the most sense. Server-side verification is skipped as well – for Form Builder entries, the generated field value should indicate this. Let me know if this works for you or if you're encountering any problems! Might have to re-install the module so that the new permission gets added, or add it manually.
  10. @Pete Glad the module is working for you! Yeah, the bypass permission will be a useful addition. I agree that this permission should hide the input field completely. Not sure if I can prevent the input field wrapper markup from showing up altogether, I'll have to give that a try. One thing I'm concerned about is that superusers won't see the hCaptcha input at all, so some site admins might think the module is broken if they add the inputfield to a form and don't see it in the frontend (unless they test in a private browser window). Maybe instead of hiding the inputfield, it should display a static message? Something like The captcha is hidden because you're already a verified user. Or is that too confusing for users? Anyway, I'll try to build this in a feature branch this week for testing purposes!
  11. @bernhard Including the default translations in the code has also fallen a bit out of favour for me, for that exact reason. Since we're using snake-case message IDs (e.g. contact_form_submit_label instead of Submit), it's nice to see the actual translation directly in the code alongside the ID. But if those get changed in the table field, it gets confusing indeed. Usually the message IDs are enough to understand what the message is intended for (and if not, this can be solved by writing better message IDs). Lately I don't include the default translations in the code as it's just as easy to add them in the table field after. I still like that the empty row with the message ID is created whenever a new translation is added, this makes it easy to add a bunch of translatable strings in the code and add the translations later in bulk. Absolutely, this is why I always prefer message IDs over writing translations directly in a fixed source language. This has the nice benefit that you can differentiate between contexts using the message ID - for example, contact_form_submit_label is different from order_form_submit_label. This means you don't have to built context awareness into your custom translation system, making it much easier to implement.
  12. @bernhard Thanks! We can't use the built-in translation system because it can't detect translations in Twig templates, and because the interface is a bit too technical for our average client. So we rolled our own translation system, which is built on a single ProFields Table field located on the global settings page. Doesn't scale super well and doesn't include stuff like pluralization, context translations and parameter replacements, but does the job for simple interface translations on small to medium sites. It's basically the approach outlined in my custom functionality section in my ProcessWire + Twig tutorial. Though it went through a couple of changes since then, most notably I optimized the way the table field is loaded. The current version loads all rows for each call to the translation function, which was terrible for performance. Gotta remember to update that in the tutorial at some point ^^
  13. @Tyssen What do you mean it's not working? Are you getting escaped HTML output or something else? If you turn autoescape off (though I don't recommend that), nothing else should be required. If autoescape is on, you need to use the raw filter to prevent HTML output from being escaped. Maybe you're using ProcessWire's built-in textformatter that escapes HTML?
  14. @Tyssen Hmm, difficult to say. I've had a similar error with the Select Options field. When you use the dot to access object properties / values, Twig tries a lot of things to get the correct value. For example, it will try the name both as a property and as a method, try if there's a method for that property prefixed with get/has/is etc. Sometimes, this unintentionally calls a method that does exist, but is intended for a different purpose or expects some additional parameters. In those cases, using explicit methods instead of the magic get functions usually fixes it. I can't check right now, but isn't there something like get or getField on TableRow objects? Using one of those should work around this issue. Happens to everyone ? Yeah, the browser may change the User-Agent to match the selected device in responsive mode, so if the session component checks for matching User-Agents you're gonna get logged out. Just in case you get this error again and it's not caused by the template caching: Twig compiles all templates to PHP files and only recompiles those automatically if the auto_reload option for the environment is turned on. I usually couple this to $config->debug so I get automatic reloads in development but not in production.
  15. Thanks everyone for the valuable comments! I think it's great that we can return to a good discussion here after the bumpy start (which I'm certainly not innocent of as mentioned above). Hopefully nobody needs to hold grudges now. Thanks to Ryan in particular for the good summary regarding those issues. I think everything has been said regarding the interpersonal stuff now so I'll leave it at that ? @bernhard Given the above code example, I can add everything I want to the selector in the query parameter: https://example.com/?category=5,%20include=all This will result in the following selector: template=news, category=5, include=all. So now I can look at unpublished or hidden pages that the editors may not want me to see. This can be solved by sanitizing the query parameter, in this case with $sanitizer->int() or $sanitizer->selectorValue(). But this is something I have to do manually and remember everytime and everywhere it's used. And while it's simple for integers, it gets a bit more involved with strings. Finally, all of this relies on the implementation of $sanitizer->selectorValue() not having any vulnerabilities or implementation bugs, and no software is bug-free. You can solve all this, but reducing the surface area for issues like this is still really valuable. This isn't unique to ProcessWire, the other day I read a blog post regarding a known vulnerability with filter_var that hasn't been fixed yet … Sounded like Drupal up to the last two sentences ? You're right, having native support for migrations and config schemas doesn't guarantee the system itself will be usable. But on the other hand, you can have the best of both worlds and have an easy to use system with support for all those features.
×
×
  • Create New...