Leaderboard
Popular Content
Showing content with the highest reputation on 08/02/2022 in all areas
-
There's also: https://processwire.com/api/ref/wire-array/get-values/3 points
-
@wbmnfktr FYI I've just added support for Twig in RockFrontend <?= $rockfrontend->render("sections/header.twig") ?> It will also be easy to add any other template engine: // put this in site/ready.php // it will add support for rendering files with .foo extension // usage: echo $rockfrontend->render("sections/demo.foo") $wire->addHook("RockFrontend::renderFileFoo", function($event) { $file = $event->arguments(0); // implement the renderer here $event->return = "Rendering .foo-file $file"; }); I swear I'll release it very soon ?2 points
-
I feel bad bumping this topic, but I think some people might've missed, or misunderstood the importance of the reasoning for looking towards moving away from CKE4. The codebase of CKE version 4 will essentially be abandoned as of sometime in 2023 by the organization that maintains it. Considering the large amount of security issues that are consistently discovered in heavily used input/output handling JavaScript packages (whether due to custom add-ons or its core), and the fact that Ryan strives for a very secure system... The need to provide an alternative WYSIWYG module is evident. CKE5 seems like the least intrusive option (for users/clients). It appears as though he hopes to make it fairly seamless for site administrators and developers, but the integration changes aren't a 1:1 swap, so he'll have quite a bit to consider. It's likely that you'd still be able to use CKE4 as a module, just like you could continue using TinyMCE when it was removed from core -- as a separately installable module. If you wish to use standard textarea fields or Markdown-powered fields, there are add-ons for those too (already). You will still have choice, and that's one of the excellent things about ProcessWire. The only issue here is that Ryan wants to make sure that you and your clients have a stable, secure, and usable system. As always, you will still have control over your own system(s). ♥2 points
-
If you are OK with linking to posts of this forum, you could just simply include a line or two like: "You can read more about realted topics at 'here' and 'there' etc..."2 points
-
In addition to site development work and ProcessWire core development, this week I upgraded my OS X version (from Mojave to Monterey), my PhpStorm version (from 2018 to 2022), and my Apache/PHP/MySQL environment versions. I tend to wait a bit too long between upgrades on my computer and so I ended up with a lot of things to resolve, and a still few remaining. While waiting for upgrades to install, I randomly came across one of the first sites I ever designed/developed out of college, working for Grafik in the year 2000: https://americanhistory.si.edu/subs/ ... I can't believe it's still there. It looks really dated now (it's 22 years old), but reminded me of how much things have changed in web design/development. While I'd been developing sites for a few years before this, it was just a hobby, and it wasn't until this time that it was my job. Back in 2000 there weren't a lot of people that knew how to create websites and it always felt like you were breaking new ground. Internet Explorer was king (and nobody liked it then either), Google was just a small startup, but AltaVista, InfoSeek and Yahoo were big. Sites were developed in a way that would make you cringe now. I don't think we used CSS hardly at all, but we did use tables for everything layout related. There was no such thing as "mobile" (the iPhone didn't come till 7 years later). There was no such thing as "responsive" layout and accessibility was pretty much an unknown. Most of the time we used images for a lot more than was appropriate (headlines and much more) because HTML and HTML fonts were so limited. It all seems so primitive now but what's the same is that it was fun then and it's fun now, actually it's more fun now. I don't have any point here, just that it's funny to look back at how much as changed. Last week I mentioned that we're likely to upgrade CKEditor 4 to CKEditor 5 sometime in the next year. There were several comments about that and so just wanted to talk a little more about it. First off, I really like CKEditor 4, it's been a great fit for ProcessWire. If the company behind it was going to continue building and supporting version 4 after 2023 then we'd likely stick with it. But the CKEditor 4 end-of-life is sometime in 2023 and I don't think it's an option to stick with it (in the core) after the developer of it is no longer updating or supporting it... at least not long term. While CKEditor 5 is a different animal than CKEditor 4, it's also still the closest upgrade path from CKEditor 4, and I'm hopeful it will be able to serve as a near drop-in replacement from the perspective of users (only). My hope is that by the time we've completed the upgrade to CKE 5, our clients won't necessarily know the difference or have to re-learn anything, unless they want to take advantage of something new exclusive to CKE 5. From my perspective as a developer integrating CKEditor 5 into ProcessWire, the development side is not a drop in replacement for CKE 4 (not even close), as all supporting code will have to be redeveloped. By supporting code, I mean things like the code in the InputfieldCKEditor.module file, the code for our custom CKE plugins (pwimage and pwlink), as well as anything else development related that is referring to CKEditor. There's no doubt it'll be a lot of work. But the rich text editor is one of the most important input types in the ProcessWire admin, so it's fine, it's worth putting a lot of time into. As for CKEditor 5 being bloated relative to CKEditor 4, I very much doubt that's the case. It was a complete rewrite, the folks at CKEditor know what they are doing, and it's safe to assume it's even more optimized and streamlined than CKE 4. In terms of size, the download for CKE 4 and CKE 5 are both 1.7 megabytes. As I understand it, they started with a new codebase for CKEditor 5 in part to start fresh and avoid legacy bloat. So I see this upgrade as being the opposite of bloat. So what happens with CKEditor 4 in ProcessWire when it likely is replaced with CKEditor 5? So long as CKE 5 can be a near drop in replacement for our users, and for the markup it generates, then the CKE 4 module will move out of the core and into an optional module you can install from the modules directory, when/if someone wants it. On the other hand, if the transition is not completely clean between versions then we may end up supporting both in the core for a short period of time. Though I'm hopeful this won't be necessary. There are some other interesting looking editors out there that have been mentioned, and it'd be nice to have more input options available. I see something like Imperavi's Article as a good option for some but not a replacement for our current rich text editor. At least I know my clients would not be happy to have that big of a change occur from a PW version upgrade. Likewise, something like the Easy Markdown Editor is a great option for some, and I'd like to be able to install a module for that and use it in some cases. But folks used to using CKEditor 4 in their work would not be happy to have that big of a change either. CKEditor 4 works really well for what it does, and I think the goal has to be that clients using CKEditor 4 now should be able to continue doing what they are doing with as few changes to their workflow as possible. I'm hopeful we'll be able to get there with CKEditor 5, while also gaining some benefits in the process. Where other input options make a lot of sense is when building a new site where there aren't already users depending on one input method or another. And it may be that at some point (sooner or later) it will make sense for ProcessWire to have another textarea input option that's different enough from CKE to make it worthwhile. But in my opinion, that would be a potential additional option, not a replacement, as CKE is pretty well established and expected in PW.2 points
-
I don't know when I last found a tool for my daily work that had such a huge impact as Latte. The last one was ProcessWire I guess (which had an even greater impact of course, but still). Latte uses PHP syntax so you don't have to learn a new language! It plays extremely well with the PW API and it makes my markup files so much cleaner and easier to read and maintain I can't tell you. From time to time I stumble over situations where I'm once more impressed by latte, so this thread is intended to collect small latte snippets to show how one can use it. If you want use Latte for your ProcessWire projects you can either install it via composer or you wait some more days until I release RockFrontend - a frontend module that can render latte files and comes with several other nice frontend helpers ? ----- Breadcrumbs the Latte+PW way: <ul> <li n:foreach="$page->parents()->add($page) as $item"> <a href="{$item->url}" n:tag-if="$item->id != $page->id"> {$item->title} </a> </li> </ul> The cool thing here is that the last item (the page we are currently viewing) is NOT linked. The <a> tag is only rendered for all other items. Still the label of the link is rendered, because we are using n:tag-if and not n:if ----- Output an image, but only if it exists: <img n:if="{$page->yourimage}" src="{$page->yourimage->maxSize(400,300)->url}" alt="..."> Note that the ->maxSize() call will NOT throw an error even if you didn't upload an image! This is one of my favourite features of latte: Write the markup once and then add simple instructions directly within that tag. No more if/else/foreach chaos ?1 point
-
How to programmatically clear the page cache? I have a page that uses URL Segments. Based on the value of the URL Segment, the content of the page varies. From what I can see, the cache can handle it without a problem. But I have another question. I need to create an API that will clean the cache of selected pages - based on the value of URL Segments or some other value. Is there an API that will allow me to manage the page cache generated by PW?1 point
-
Following up on my July 15th post, I've been working on converting ~1600 reviews from a 3rd party service (BazaarVoice) into ProcessWire for a client's website. That conversion is pretty much done now, here's an example page that has a few reviews (click on the Reviews tab). As you can see, we're using the ProcessWire comments field to store not just the review, but also 15 other details and up to 6 photos, per review. This was all done using the new "meta" features of ProcessWire's comments, mentioned in that post linked above. Though I didn't have an example to show you at the time, so wanted to link it here. Here's another example of how someone can submit a review here. This form is built in FormBuilder and uses an updated FormBuilderFile Inputfield module which will be bundled in the next version of FormBuilder. This new version of InputfieldFormBuilderFile adds a few useful features such as: The ability to show a preview image (when uploading photos). The ability to show just one file input at a time, and reveal additional ones as needed. The ability to include a text or textarea description with each file upload. These are all optional and can be enabled or disabled in the field settings. If none of the options are enabled when the output is exactly the same as before, meaning the upgrade should be seamless for existing forms already using the files input. The way that a submitted review goes from FormBuilder into a comment is automated with a hook to FormBuilder's "saveForm" method. When the form is submitted, it creates a new pending comment, translating most of the fields into 'meta' data in the comment, there's really not much to it. If you ever have a similar need, be sure to let me know and I'm happy to share the hook code that worked for me. In addition to those updates for FormBuilder, this week I've also worked on some related updates to ProcessWire's comments field, especially with improvements to the ProcessCommentsManager module. The module now has a separate dedicated comment editor where you can edit existing comments, and add new comments or replies. Previously you could not add new comments/replies in the comments manager. In the case of the site linked above, they use this particular feature for adding staff replies to reviews. I think this comments stuff is largely done now so next week I'll likely be back to working on other parts of the core and modules. Thanks for reading and have a great weekend!1 point
-
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.1 point
-
I think one way of doing what you're looking for would be to do two finds - one for products and one for other pages and then merge them, because you need to override the 'organic' results somewhat, so you'll need to impose some kind of artificiality otherwise you'll just get what you're getting now.. Say you want 10 results, get 6 products and 4 info pages (or 7 & 3 or whatever) and then rank that combined 10 by relevancy. Another way would be to just always show say 3 products at the top, kind of like Google ads at the top of SERPS, but you wouldn't need to identify them as ads. Yet another way, on wide enough screens, might be to have a column for each.1 point
-
@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'.1 point
-
1 point
-
Hi, this works for me: wire()->addHookAfter("User::changed(user_bookmarks)", function(HookEvent $event) { $user = $event->object; // User $old = $event->arguments(1); // old value $new = $event->arguments(2); // new value wire()->addHookAfter("Pages::savedPageOrField($user)", function(HookEvent $event) use($user, $old, $new) { var_dump($user); var_dump($old); var_dump($new); var_dump($event); }); }); Btw there is also savePageOrFieldReady(). Might be of interest if you want to modify the user.1 point
-
I used Smarty and Twig before, and have to say, that Latte feels much better, and the conditional rendering of elements is a great simplification.1 point
-
No, because $block is a rockmatrix block having an items() method but not having an items property. Same goes for content(). The whole block will only render if there are items using RockFrontend's renderIf() method: $rockfrontend->renderIf("sections/newsitems.latte", $block->items()) Thx for letting me know ? Though personally I don't see any benefit in using another syntax if I can simply use plain PHP (which I already know)...1 point
-
Well... in the past I looked up ways to get Twig up and running in ProcessWire and what-else-where and had to look up so many sources, details, guides and whatever that my urge to get it up and running diminished due to each and every step. Eventually I got it up and running, played with it, yet it was never backed from an "official" or at least "experienced" source. So I never trusted my setup. If I ever had a reliable source in the PW-Community that wrote, talked, spoke about it... I might have invested more time. Yet... there was noone. For whatever reason. Sure there were guides on modules, but not in detail. Or at least not for starters or dummies like me. You on the other hand are experienced in Twig, ProcessWire and therefore would have been a "source of truth" in some kind - if I ever would have had the chance to find a guide by you, I probably had way more fun with Twig and a ProcessWire/Twig combo. I don't wanna talk you into writing such a guide on your site or moving this piece of content as it is onto your site, but... even though it might be unpolished, not that detailed as other guides on your site... it would, could, should, might, whatever... drive people into trying it in a much easier way than struggling through lots of posts, tutorials or whatever. Maybe even Twig-users could find ProcessWire as their PHP-framework in this way. Who knows? Sure there might be more details needed but you know those details and could write at least an outlined article on how to get Twig up and running in ProcessWire. (Sure I know you work fulltime in an agency, with Craft CMS, ... ) Yet... you could. You know ProcessWire way better than me, and Twig as well, so... yes... I'd like to follow your guide or tutorial to get this up and running. You don't have to convince for me anything. My curiosity is already there - like it's by most of us here. On the other hand... I understand your feelings and thoughts about "it doesn't match the existing content" but... hell... it doesn't match most of the time. Wherever you look, whatever you write. Some posts are way more detailed while others are just basics. Yet... It helps. Probably. To make it short: Your inital post already convinced more to look into Twig in ProcessWire than any other post around - as it told me it could be used in a professional way. I thought about it in the past but maybe I gave up too early. So yeah... those were my almost last two cents on this topic.1 point
-
I don't suppose there's any chance of getting these juicy forum board solutions added to related module documentation as Examples, could we? These are great starting points to common scenarios! The downside would be maintaining them for compatibility, but perhaps explicit notices with dates that the examples were created? So often I end up searching the website for answers to questions, but they're all over the place - the blog, documentation, api, forum (and not always the module forum). ? Just a thought. That said, loving these examples you're providing from actual client work you're doing. Thanks, Ryan!1 point
-
You don't know what to use when because ProcessWire does some magic here to make both versions work most of the time. For example the method to get children of the current page is $page->children() (https://processwire.com/api/ref/page/children/) but $page->children will work as well. ProcessWire does that using magic methods (https://www.php.net/manual/en/language.oop5.magic.php) so when you request $page->children it will check if a method "children" exists and will call that method for you even though you are accessing it as a property and not as a method. So requesting the method directly might be slightly more efficient, but in most cases I guess it will not matter. And imho using the "property syntax" like $this->wire->files->render() is cleaner than always using methods. $this->wire()->files()->render()...1 point
-
Sure! Here's a few that might be of use to ProcessWire sites: <?php use Latte\Engine; /** * Latte filter provider * */ final class Filters { /** * Install available filters. */ public static function install(Engine $latte) { foreach ((new static())->provide() as $name => $callback) { $latte->addFilter($name, $callback); } } /** * @return array<string, callable> */ public function provide(): array { return [ // Sanitize values using ProcessWire's sanitizer API variable 'sanitize' => function ($value, $sanitizer, $options = null) { if (!$options) { return sanitizer()->$sanitizer($value); } else { $args = func_get_args(); unset($args[1]); // remove $sanitizer arg return call_user_func_array([sanitizer(), $sanitizer], array_values($args)); } }, // Render FormBuilder form, but allow prepending and appending fields 'form' => function ($form, $options = []) { if (!$form) { return ''; } $output = $form->render(); if ($options['append'] ?? false) { $output = str_ireplace('</form>', $options['append'], $output); } if ($options['prepend'] ?? false) { if (stripos($output, '<form') !== false) { $output = preg_replace('#(<form[^>]*>)#i', '\\1' . $options['prepend'], $output); } else { $output = $output . $options['prepend']; } } return $output; }, // URL slug / page name 'slug' => function ($str, $length = 128) { $str = sanitizer()->unentities($str); $str = sanitizer()->pageNameTranslate($str, $length); return $str; }, // Truncate string 'truncate' => function ($str, $length = 200) { return sanitizer()->truncate($str, $length, ['visible' => true]); }, // Render Markdown 'markdown' => function ($str) { $str = "{$str}"; modules()->get("TextformatterMarkdownExtra")->formatValue(new Page(), new Field(), $str); return $str; }, // Unwrap ProcessWire value objects 'value' => function ($object) { if (is_object($object)) { if ($object instanceof SelectableOptionArray) { return $object->implode(' ', 'value'); } else { return $object->value ?? ''; } } elseif (is_array($object)) { return implode(' ', $object); } else { return (string) $object; } }, // Join a string with a custom separator at the end 'join' => function ($list, $separator = ', ', $lastSeparator = ' & ') { if (count($list) > 1) { $last = array_pop($list); return implode($separator, $list) . $lastSeparator . $last; } else { return implode($separator, $list); } }, // Prettify URL by removing protocol and www 'prettyUrl' => function ($url, $options) { if (null === $url) { return false; } $url = trim($url); if ($options['www'] ?? true) { $url = str_replace('www.', '', $url); } if ($options['http'] ?? true) { $url = str_replace(array('https://', 'http://'), '', $url); } if ($options['slash'] ?? true) { $url = rtrim($url, '/'); } return $url; }, ]; } }1 point
-
@bernhard I can't quite work out what the code would look like there, because if I bd() the inputfields on the page, it only shows the "outer" combo field name, not the subfields. Something else to note using my code is that if you output the field DATA (not the inputfield markup) on the frontend (which in my case is a country) using code like this: echo $user->billing_details->country; it won't show the data as it doesn't know what the key/value pairs are supposed to be when formatting the data. So I got around that by doing: echo $user->getUnformatted('billing_details')->country; Which simply outputs whatever you had stored in the DB. I'm sure there's some way of covering all this in 2 hooks or less using your method bernhard but not sure what it would look like for a combo field. For now my 2 hooks and outputting the unformatted value gets me where I need to be. I just wish I'd remembered about getUnformatted 90 minutes ago ?1 point
-
@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 …1 point
-
Latte is great indeed! I've been using it for all projects since discovering it. Everything is so much more concise and elegant, especially once your start building your own filters and macros. Quick note that you can also use Latte with Wanze's TemplateEngineFactory. I've found that the easiest way to get started; one composer install and you're ready to go ?1 point
-
Nice, thx for sharing! Just played around a little with your code example... This version is using array-syntax ? $find0 = $pages->findIDs("title*=home"); $find1 = $pages->findIDs([ ["title", "*=", "admin"], ["id", "!=", $find0], ]); $find2 = $pages->findIDs([ ["title", "*=", "modules"], ["id", "!=", array_merge($find0,$find1)], ]); $final = $pages->find(["id.sort" => array_merge($find0, $find1, $find2)]);1 point
-
Hey @Greg Lumley I wanted to finish my video about RockFrontend last week or weekend, but it turned out to be a lot more time consuming that I thought ? Yesterday I even realised that some parts of RockFrontend could be improved even more, so I decided to do some important updates before finalising the video. I love those new additions ? To get live reloading for example is now really just installing the module and adding "$config->livereload = 1" to your PW site ? No injecting of scripts whatsoever... Back to topic: Today I had to build an accordion content-element: <ul> <li n:foreach="$block->items() as $item"> <a href="#">{$item->title}</a> <div>{$item->content()|noescape}</div> </li> </ul> IMHO it can't get any cleaner ?1 point
-
There is a lot going on here. You mentioned that visitors (guests or logged-in users?) will be able to subscribe to multiple events at once, but you will likely only want to verify the email once. AFAIK the only way to verify emails is to actually send a secret to the address and have the recipient send it back. So I would do a poor man’s solution somewhat like this: Create one page per recipient that stores the address and a secret token (ProcessWire can generate one for you like this: (new Password())->randomBase64String()) When the user has entered their address and you have created the page, send them a link like https://example.com/verify?mail=myemail@example.com&mytoken=xyIu6yehxXfTelYpqcOdv8 When that link requested, find the recipient and consider them verified if the token matches (i.e. set a checkbox or just delete the token and consider everyone without one verified) Store event subscriptions as multiple page references either on the recipient or on the event, whatever As long as these transactional mails don’t go overboard you should be fine. If you don’t need any personalisation, you might actually send the event reminders as a single email per event, just dumping everyone into BCC. If you’re thinking of growing the business, you will want to go with a dedicated solution such as https://de.sendinblue.com/preise/ (a German company formerly known as Newsletter2Go, should be fine GDPR-wise). Mass mailing is serious business. You don’t want to get blocked by Gmail or something, so above some threshold it’ll be worth shelling out for a service whose business is making sure emails go through. You mentioned you’re on shared hosting. Do you have cron jobs at all? Some shared hosting providers offer something like “cron jobs lite”, where you get a configuration form and the only action you can perform is an HTTP request. In that case you just expose some URL from your site that will send emails upon request.1 point
-
Cross-posting in case any Wireframe user wants to give Latte a try: there's a renderer for it now. Somewhat experimental, since I have a rather miniscule understanding of Latte myself ? Slightly annoying marketing shenanigans aside (I didn't know how to do a single thing in Latte without reading the docs, "Latte is the only system with an effective defense, thanks to context-sensitive escaping" is a silly thing to claim, etc.) Latte does look quite nice. I dig the n:attributes in particular, they remind me a lot of the AngularJS (v1) syntax. Apart from that, Latte seems largely the same as other engines/languages I've used (e.g. Twig, Blade, and Dust). Admittedly I've just scratched the surface, so there's likely a lot more there ? I'm not sure yet whether it's a good thing or not that Latte syntax is so close to plain PHP, just with auto-escaping enabled and <?php ?> replaced with curly brackets. Among other things a) the PHP syntax isn't necessarily the easiest to grasp or nicest to look at, especially for non-developers (though this is admittedly highly opinionated), and b) there's a slight fear in the back of my head that this actually makes it extra tempting for developers to put unnecessarily complex code within their views. So far among the templating languages I've used Blade has been my personal favourite. It's easy to grasp, familiar to PHP users but also for everyone who's ever used another templating language (like Twig), has some very handy shortcuts available (particularly when it comes to loops), the syntax looks nice and clean (in my opinion), and components in particular are just brilliant in terms of both implementation and use. But enough advertising ?1 point
-
I was about to create a similar topic, but why do it if this one already exists))) Not so long ago I started donating to some ProcessWire modules' authors. And each 1st of the month I am looking at my Patreon receipt in my email with a kind of a pride. I think that this thing is my little contribution to the community I love so much and which gave so much to me for free. I do know that Ryan likes to receive his support with pro modules payment. But many modules really shouldn't be paid/pro as they are so essential to many (and so fun to build). But their authors still would be glad to receive some money to sustain their enthusiasm supporting them and creating new features. I am sure the appreciation itself is equally important, but what is an easier way to show it than ???))) The other thing is that I sometimes notice some great improvements that seem to happen to modules as soon as some support comes in. I am writing this to encourage everyone to support the PW ecosystem by donating to module authors. I will list the modules I know that clearly asked for donations. I am sure that is not an extensive list. But at least something to start with. Tracy Debugger Mystique and other modules by ukyo (please don't miss this one @Jonathan Lahijani))) Rock Migrations and a bunch of rock stuff by bernhard All the great stuff from teppo I really like the Patreon/OpenCollective/Github donations, but only 1 of 3 listed used them. Sooooo..... Start throwing money at these fine gentlemen)) Support yourself by making you favorite modules not go away. I wish we could bring back @tpr to support his AOS and wire shell by @marcus and @justb3a. Would they keep supporting their thing is they had some donations coming in? Who knows. And please feel free to list you other module authors that you know asked for support below. Edit 2022-10-27: added teppo's link1 point
-
No, it does prevent a bunch of downstream hooks from firing, but specifically it doesn't include Pages::save and Pages::saveField (and I think it should include those methods). And to avoid confusion it would be good if the documentation explained exactly which hookable methods are affected by the noHooks option when it used in any of Page:save(), Pages::save() and Pages::saveField(). I've opened a GitHub issue so that Ryan is more likely to notice the problem and our discussion of it: https://github.com/processwire/processwire-issues/issues/14051 point
-
This will let you do what you want: http://modules.processwire.com/modules/fieldtype-runtime-markup/1 point