Jump to content

Leaderboard

Popular Content

Showing content with the highest reputation on 04/12/2026 in all areas

  1. Hello, A small utility I built for my own workflow — export any page directly from the editor as a clean Markdown file. Useful for documentation, content migration, and feeding page content to AI tools. GitHub: https://github.com/mxmsmnv/PageMarkdown What it does: Adds an Export to Markdown button to the page edit form Smart HTML conversion — CKEditor content (tables, lists, headings, links, bold/italic) → standard Markdown Supports ProFields: Table, Combo, Repeater Matrix (with type labels and nested structure) Images and files render as Markdown image/link syntax Page references render as links or titles MapMarker, Email, URL, Color fields all handled Configurable: toggle field labels as headings, ignore lists per field/type, datetime format, empty HTML cleanup Requirements: ProcessWire 3.0+, PHP 8.0+ MIT License.
    3 points
  2. Hi everyone, Here in an updated version of the module It is now called NativeAnalytics. This should be treated as a **new module release**, not just a small update of the earlier test versions. "Important:" if you previously tested "PW Native Analytics", please **uninstall the old module first** and then install "NativeAnalytics" as a fresh install. I did "not" add a migration path from the old module to the new one, because the module name and structure changed during development. A clean install is the safer option. The main idea behind the module is simple: to provide a useful analytics dashboard directly inside ProcessWire, without relying on external analytics platforms, third-party scripts, or external APIs. Everything is handled natively inside the CMS, which makes it a good fit for projects where you want a simpler, more self-contained analytics solution. The module currently tracks and displays things like: page views unique visitors sessions current visitors top pages referrers devices and browsers 404 hits engagement events such as form submits, downloads, tel/mail clicks, outbound clicks, and custom CTA events It also includes: charts and trend views comparison between periods custom date range filtering page-level analytics inside the page edit screen exports to CSV, PDF, and DOCX helper examples and a small snippet generator for custom event tracking The reason I built it was that I wanted something that feels natural inside ProcessWire itself, instead of just embedding another analytics service into the admin. For many sites, it can be useful to have core traffic and engagement data available right where content is managed, with no need for external integrations. If you want, I can also make you a slightly shorter and more forum-friendly version with a stronger opening line like “Please uninstall the old PW Native Analytics test version before installing NativeAnalytics.” Download it Here: NativeAnalytics_1_0_8.zip Enjoy!
    2 points
  3. Useful utility indeed, thanks for sharing! Are you planning to add TinyMCE support as well? Given that CKEditor has been replaced by TinyMCE as the default RTE, on new sites I always use TinyMCE.
    2 points
  4. Hüttenzauber - The magic of the Swiss Alps. Eat, celebrate and sleep in the most beautiful places in the mountains. Today, I am presenting to you a very cool and challenging project we tackled and successfully finished last summer. Obviously, Fruitcake is 100% a ProcessWire agency at this point but still, this project especially proves again and again that ProcessWire’s flexibility and unopinionated structure just works for us every time. Gone are the days where we are breaking and bending other CMSs to work the way we need it to work. “Hüttenzauber” is a well-known brand in the Bernese Alps skiing and hiking destinations. Lately, they expanded into other regions of the Swiss Alps and accumulated a variety of locations they are both managing and running from their central offices at the birthplace of the enterprise, Lenk im Simmental. Coding one of our latest projects to date was a cool but also daring challenge. We set out to replace a few dozen single websites for each of the different locations with one big website. The general goal was to streamline all the information and present a concise yet still quite independent experience to the website’s visitors. In addition to the independent experiences, the website features a plethora of central features like a search map, an illustrative blog, cool events and a web shop whose contents however, are again compiled together from blog articles written for or events happening at the different locations. The website was conceptualized, designed and programmed 100% in-house by us. It features tons of content which is completely available in both German and English (with a small JavaScript language detection function). ProcessWire admin: have exactly one source of truth I think we can be proud of the challenge we set out to achieve: Have every information only ever written down once. This is most beneficial for the client since they can for example change the hotel’s address once and it is then displayed at many different locations automatically. The client factually only needs to work with our database we created in the ProcessWire admin area and the website presents that information in a variety of places automatically. One example of that in action is restaurants. There are two types of restaurants: locations which actually are restaurants but also restaurants inside other locations, e.g. hotels. The client can easily a new restaurant inside a hotel (in PW terms that is just add a “restaurant” as a child of a “hotel”) and just set up all of it’s information like descriptions, menus, booking links and images. This entry will automatically display on the search map as part of the hotel, be added to the “book a table” buttons everywhere across the website and also have it’s information and download links be displayed on the hotel’s detail page. And by the way, the client also can (and does!) add hotels inside hotels, e.g. a small resort with independent booking but which factually is part of a bigger hotel complex. They add, press save and “it just works!” 🤯 To achieve this goal, we made extensive use of the beloved “addHookProperty” method to for example output a list of all the “book-a-table” links for any specific page which makes programming the front end of the website a whole lot easier! 🥳 The culmination of all this is a simple and easy tree structure in the admin area like this (this is just part of it): All the information one might add about a restaurant or hotel is entered in each entry’s fields. Every coordinate, address and image is only ever entered once. All of this for example results in the search map and floating booking buttons completely automatically: Not only there, but also in the menus: Content «Page Builder» In addition to all the meta information, all of the pages should allow to have a completely independent experience for a visitor. That is why any restaurant and hotel gets it’s own landing page which acts as a mini landing page. There, the information is broken down: Booking links show only for the location itself (remember, there still might be multiple 😉), events are automatically filtered by location and sub-restaurants are displayed automatically. Yet still, all of the content feels dynamic because we make heavy use of @ryan’s Repeater Matrix module. For some of the blocks, the information is entered directly but for others, the information is grabbed from the events catalog or the blog entries and automatically filtered as appropriate for the page where the block is displayed on. For example, the events block on a hotel page only displays events for that specific location whereas if the events block is used on the homepage, everything is displayed. Here is a few of the blocks the client can use on any page: Webshop with Print@Home vouchers To finish up this showcase, now for the most interesting part for all developers here, the webshop and all it’s interfaces to external services. From the very beginning, we knew, we needed to use something which will offload the cart and checkout parts of the shop completely because we don’t have the capacity to create a full webshop application for this project and there was nothing around we could build upon (this is only partly true, there is @Gadgetto's SnipWire which was a big inspiration). Obviously, nowadays there are alternatives around the corner like @bernhard’s RockCommerce which might just be the on-page solution for cart and checkout ProcessWire needs. Although the shop might seem small and unimpressive when looking at it from the user’s perspective, a lot has to happen in the background. Part of it is that we had to combine two types of products and part of it is the actual technologies we ended up using. The first product type is your standard product which gets shipped to the buyers. These products are easy compared to the second type - a streamlined experience starting in the shop where the user picks a value and a greeting for a print-at-home voucher, pays for it and together with the order confirmation is sent the voucher as a PDF ready to be printed. This lead us on an adventure where we came across asynchronous payment confirmation, custom payment processors for SnipCart and a small translation layer mapping one API to the other. In the end, we built a system of three modules for ProcessWire like this: The heart of the system is our «Snipart Integration» module. Think of it as a baby-SnipWire. It adds webhook handling, a custom payment provider API and JSON product info endpoints for SnipCart to work on the website. Building on the custom payment provider API, we have the «Wallee interface» which acts as a translation layer from the asynchronous API Wallee speaks to the synchronous one SnipCart uses. Further, we have the «Boncard interface» which adds webhook handlers to reach out to the print-at-home provider to generate and fetch the PDFs and finally sending them to the client, using our fourth and last external provider: SendGrid. Since SnipCart already uses SendGrid, this one was easy to decide. Also, there is a very good integration available with WireMailSendGrid. All of the modules have been built with reusability and modularity in mind: all of them have a configuration screen to add API secrets amongst other settings. They can be used as a package or in parts. This is useful if for example, you don’t need Wallee as a payment provider or do not have Boncard’s print-at-home vouchers in your webshop. Also, more custom payment providers are easily added using the main module’s API and webhook handlers. Conclusion There is still much to tell especially about the shop and custom payment providers’ implementations and challenges we faced. If you guys are interested, I can start working on a case study. Let me know! I will leave you with a few links for you to look at and/or get more information: https://huettenzauber.ch/ our main subject https://www.fruitcake.ch/projekte/huettenzauber/ our portfolio entry about the project COMING SOON link to page on ProcessWire Showcase Also, I don't want you to miss out on what's running behind the scenes: ProFields: Combo ProFields: Repeater Matrix Seo Maestro ProcessRedirects Tracy Debugger WireMailSendGrid All of this rocks on ProcessWire v227.
    1 point
  5. Small update on PW Native Analytics I made a few refinements to improve usability and setup: cleaned up and improved the module settings added short text descriptions to make technical options easier to understand improved the date format setting so it now offers cleaner and more useful format choices made important beginner-friendly options enabled by default on install, such as: Enable tracking Enable event tracking Respect Do Not Track Ignore query strings in stored paths This makes the module easier to understand and ready to use immediately after installation, especially for less technical users. thx to matjazp for feedback 😉 I updated the file in first post! Cheers
    1 point
  6. 1) 37 2) 31 3) 21 (but regularly there are not more then 8 - 12 templates in my websites)
    1 point
  7. 211 71 46 Keep in mind that most of these are the parent/child templates for page reference "tag" fields.
    1 point
  8. Thanks. That’s quite a volume. Appreciate the info. More soon….
    1 point
  9. For my management execution system web application which was built with PW, 50+ templates and another 50+ repeater fields which are technically templates.
    1 point
  10. @Soma So happy to see you back in the PW forums 🙂 Have been using your MarkupSimpleNavigation module for years without a problem, until well on current project. Timely! Problem was that current pages were not always being recognised as such. Fix was in function _renderTree line 211 - change: //$is_current = $child === $page; $is_current = ((int) $child->id === (int) $page->id); Credit to you that MuSNav has been solid for so long. Cheers psy
    1 point
  11. @Marty Walker, I don't want to complicate what this module does, but you can achieve your objective with a bit of custom JavaScript added to the PW admin. Custom JavaScript file in /site/templates/scripts/admin-custom.js $(function() { // Copy a string to the clipboard function copyToClipboard(string) { // HTTPS websites if(navigator && navigator.clipboard && navigator.clipboard.writeText) { const clipboardItem = new ClipboardItem({ 'text/html': new Blob([string], {type: 'text/html'}), 'text/plain': new Blob([string], {type: 'text/plain'}) }); navigator.clipboard.write([clipboardItem]); } // Old browsers or non-HTTPS websites else { const $input = $('<input type="text" value="' + string + '">'); $('body').append($input); $input.select(); document.execCommand('copy'); $input.remove(); } } // When an image thumbnail is Alt-clicked, copy the URL of the corresponding original image document.addEventListener('click', function(event) { if(event.altKey) { const $el = $(event.target.closest('.gridImage__edit, .gridImage__overflow')); if($el.length) { // Prevent any other click handlers from running event.stopImmediatePropagation(); event.preventDefault(); // Copy the image URL const $img = $el.closest('.gridImage').find('img'); const url = window.location.origin + $img.data('original'); copyToClipboard(url); // Highlight the clicked element to show that something happened $el.effect('highlight', {}, 500); } } }, true); }); Add the custom JS file to the PW admin by adding the following line to /site/templates/admin.php ... $config->scripts->add($config->versionUrl($config->urls->templates . 'scripts/admin-custom.js', true)); ...immediately before the existing line... require($config->paths->core . "admin.php"); Now when you Alt-click an image thumbnail the URL of the corresponding original image will be copied to the clipboard.
    1 point
  12. Thanks! Yeah I had deleted my first account after I got 100k followers... don't ask why. Then on my second account @somartist2 I tried to leave 2-3 times but then reactivated the account. And finally I did it. I'm on bluesky now, and still on Instagram and other platforms. No I didn't realize, but yeah I was very active for many years answering and helping many. It's great to hear tho. 🙂 Oh @ryan, thanks for the warm welcome back. Yeah I went on a long journey, glad you been following me. Great to see you're still running this thing haha. And now even using llm's! Yeah OMG so many names that I almost forgot about. I'm an old man now hehe.
    1 point
  13. @Joachim Exactly, when you use wireRenderFile() (or include), the variable $page remains the current page being viewed in the browser—in this case, the Home page. Since your Repeater fields live on the child pages (the modules), $page->introimg_field is looking at the Home page, finding nothing, and returning an empty object. When you loop through your child pages to render them as components, pass the specific child page into the options array of wireRenderFile. foreach($page->children() as $component) { // We pass the $component page object into the file echo wireRenderFile("components/{$component->template->name}.php", [ "item" => $component ]); } Inside the child (module) template: <section id="<?php echo $item->name; ?>" class="module"> <div class="landing_images"> <?php foreach($item->introimg_field as $intro_image): ?> <a href="<?php echo $intro_image->page_picker->url; ?>"> <h3><?php echo $intro_image->title; ?></h3> <img src="<?php echo $intro_image->image->url; ?>" alt="<?php echo $intro_image->description; ?>"> </a> <?php endforeach; ?> </div> </section> This should give you the correct array.
    1 point
  14. If you love the simplicity of ProcessWire's API but want the reactive, SPA-like feel of modern frontend frameworks without writing complex JavaScript, this module bridges that gap. It brings native Server-Side Component state hydration, Out-Of-Band (OOB) swaps, and strict security to your ProcessWire application using HTMX. 🚀 What does it do? This module transforms how you write frontend components in ProcessWire: True Stateful Backend Components: It introduces Component and Ui base classes. Your PHP components automatically rehydrate their state (variables, dependencies, $page assignments) between HTMX AJAX requests! No need to manually parse POST payloads. Auto-Discovery: Just place your components in your site directories. The module automatically discovers and securely namespaces them (Htmx\Component and Htmx\Ui). Zero-Javascript Reactivity: You can handle form submissions, counters, validation, and multi-field updating dependencies directly from PHP using HTMX attributes. Cryptographic Security: The module uses strict HMAC-SHA256 signatures with TTL (Time-To-Live). This guarantees that bad actors cannot modify state payloads or trigger invalid endpoint logic in the browser. WebSockets & SSE Ready: It has built-in helpers to easily hook Server-Sent Events (SSE) and WebSockets onto your templates without exhausting PHP-FPM pools globally. 🛠 How it looks in your code You simply create a PHP component class, define some public properties, and write an endpoint action: <?php namespace Htmx\Component; use Totoglu\Htmx\Component; class ClickCounter extends Component { public int $count = 0; public function increase() { $this->count++; } } Then, you can render it anywhere in your site with a single line: /** @var Htmx $htmx */ echo $htmx->renderComponent(ClickCounter::class); View the documentation and examples on Github Feel free to try it out, run the tests included in the repo, and let me know your thoughts or feedback! htmx.mp4
    1 point
  15. 1 point
  16. The video uses the HEVC codec (h.265). Firefox on Windows doesn't support h.265 due to licensing issues. A workaround is to download the video and open it with whatever video viewer is associated with mp4 files.
    1 point
  17. Just created a new test project and added the module. One thing I noticed immediately was that in my DDEV environment I needed to prefix the commands in order to run them in the actual container. // non-DDEV environment php index.php --at-eval 'echo wire()->pages->count() . " pages\n";' // DDEV environment ddev exec php index.php --at-eval 'echo wire()->pages->count() . " pages\n";'
    1 point
  18. Hey everyone, on a recent client project we had to deal with a large number of Markdown files that needed to end up as regular HTML content on ProcessWire pages. Converting them manually or piping them through external tools wasn't an option – too many files, too tedious, and the content had to be stored as actual HTML in rich textfields, not just formatted at runtime. So we built a small module that handles this directly inside ProcessWire. How it works The module creates a file upload field (md_import_files) and a Repeater field (md_import_items) with a standard title field and a richtext body field (md_import_body) inside. The body field automatically uses TinyMCE if installed, otherwise CKEditor. You add both fields (md_import_files,md_import_items) to any template, upload your .md files, hit save – each file gets converted to HTML via PW's core TextformatterMarkdownExtra and stored as a separate Repeater item. The source filename goes into the items title, processed files are removed from the upload automatically. Template output The Repeater items are regular PW pages, so output is straightforward: foreach ($page->md_import_items as $item) { echo "<section>"; echo "<h2>{$item->title}</h2>"; echo "<div>{$item->md_import_body}</div>"; echo "</section>"; } Tag mappings One thing we needed right away: control over how certain Markdown elements end up in HTML. For example, #headings in Markdown become <h1> – but on most websites <h1> is reserved for the page title. The module has a simple config (Modules → Configure → Markdown Importer) where you define tag mappings, one per line: h1:h2 h2:h3 strong:b blockquote:aside hr:br This performs a simple 1:1 tag replacement after conversion, preserving all attributes. Works well for standalone or equivalent elements like headings, inline formatting, blockquotes, or void elements like hr:br. Note that it doesn't handle nested structures – mapping table:ul for example would only replace the outer <table> tag while leaving thead, tr, td etc. untouched. Requirements ProcessWire 3.0.0+ FieldtypeRepeater (core) TextformatterMarkdownExtra (core) GitHub: github.com/frameless-at/MarkdownImporter Modules Directory: https://processwire.com/modules/markdown-importer/ Happy to hear if anyone finds this useful or has suggestions for improvements. Cheers, Mike
    1 point
  19. Hi Roych, a nice module, well done! Just one suggestion: In your display the seats are looking downwards (the stage is at the bottom). Wouldn't it be more intuitive to make it all upside down? (That's usually the case in seating displays of airline services during the reservation process.) The advantage is a more natural orientation when booking : The seat to the left of yours also appears to the left in the display. Best wishes ottogal
    1 point
  20. @Noel Boss For some reason this doesn't work with your change from using add() to using $page->set() (PW 3.0.178). I got it to work and figured out a way to clear the orphans message: $field->setOrphans(new PageArray()); $page->{$field->name}->add($page->children('include=all, check_access=0')); I also added include=all and check_access=0 to make sure all children get added. Here's the resulting full hook method: /** * Fill pagetable fields with children before editing…. * * @param HookEvent $event */ public function addChildrenToPageTableFieldsHook(HookEvent $event) { $field = $event->object; // on ajax, the first hook has no fieldname if (!$field->name) { return; } // get the edited backend page $editID = $this->wire('input')->get->int('id'); if (!$editID && $this->wire('process') instanceof WirePageEditor) { $editID = $this->wire('process')->getPage()->id; } $page = wire('pages')->get($editID); // disable output formating – without this, the ajax request will not populate the field $page->of(false); // you could also insert a check to only do this with sepcific field names… $field->setOrphans(new PageArray()); // Clear out the "orphans" notice $page->{$field->name}->add($page->children('include=all, check_access=0')); }
    1 point
  21. Not quite.... Nope. Not really.... The most important thing to remember about repeaters is that behind the scenes, they are actually real pages (see /admin/repeaters). Not unlike the recommendation to be cautious with $page->children, if you have too many repeaters (and especially with lots of fields), you will be loading all of them into memory when you call the repeater. With $page->children, you can at least do $page->children('limit=20'); So, the issue is about memory/efficiency. Nothing to do with MySQL (which can easily handle millions of rows - of course, we don't load all of these into memory at once ). In conclusion, repeaters do not scale infinitely...
    1 point
×
×
  • Create New...