Jump to content

Leaderboard

Popular Content

Showing content with the highest reputation since 09/23/2025 in all areas

  1. We've long wanted a way to utilise a CDN to offload images/files/videos from ProcessWire sites without losing all the native greatness of ProcessWire image and file field types. Having read various discussions on here about ways to approach this that never seemed to reach conclusion, I've thrown myself into creating a module that allows offloading of files to Bunny.net CDN as we need a solution for a specific project. I think this would be easily adaptable to any S3 compatible CDN but I've only tested on Bunny. ⚠️ This is still very beta! Use at your own risk! I've been conducting basic testing and so far, so good but there's bound to be holes or things that others may suggest better ways of doing. But I'm now at a stage where the insight/experience of the PW community might add value to the project - so I'm sharing now! Full disclosure: Once past the initial project scaffolding I've been using AI/careful prompting to write some of the code so that I can arrive at a prototype as quickly as possible. This seems to have worked well, although some of the code looks a little verbose and could probably be refactored later on. Also not security/pen-tested yet. https://github.com/warp-design/WireBunnyCdn/ Features: Automatically uploads images to Bunny storage on page save, including all variants and mirrors assets folder structure for simple merging back to local at a later date if needed. Automatically cleanses deleted files (or files from deleted pages) from your CDN. Option to mirror files to CDN or delete local copies (this is the main aim for me, otherwise we could just use ProCache). Handles (basic currently) image sizing - either using standard ProcessWire `$image->size(X,X)` methods or by implementing Bunny Optimizer for sizing using URL params. Rewrites image paths via CDN so that you can use standard `$page->imageField->url` calls with the output being a Bunny path rather than local PW path. Also handles the image previews in admin view. Roadmap: Support for video uploads (with optional separate CDN endpoint for Bunny Stream buckets). Support for front-end video output to templates using Bunny stream players/optimisation etc. Implement chunked/background uploads for large files. Support for other size() method options, like cropping etc and mapping to Bunny Optimizer equivalents. Anyway - look forward to hearing any advice/feedback/bug reports... I'm sure there's many!
    17 points
  2. Hi all, if you log in since Friday and click the Notifications (bell) icon in the forums then you should see this message which allows the forums to send alerts for messages and topics you are subscribed to via browser notifications. There is also PWA (Progressive Web App) feature that I've enabled so you may also see "Install to Home Screen" as an option for both Android and iOS. If the PWA works as intended, you will have an app icon on your device and, I think, notification badges should appear there too, so if you're like me and have had email notifications switched on for followed content you can edit your notification settings and change this to Notification List I think. Apologies for the vagueness but the forum docs are a little unclear in places. It is all optional and if you don't want to use either feature you don't have to of course.
    8 points
  3. Hi, everyone! We recently built a small add-on module for our StripePaymentLinks setup and thought it might be useful to share here. We’ve been working with StripePaymentLinks quite a lot lately, and one thing the clients always wanted was a simple way to get purchases synced into Mailchimp without relying on external paid add-ons. So we built a small ProcessWire module to handle exactly that. The idea is simple: every time a customer makes a purchase via StripePaymentLinks, their details (name, email) plus the purchased products (as tags) get synced directly into Mailchimp. That means you can instantly segment, automate, and follow up with buyers without any manual exports. We built this because we didn’t want to rely on a separate paid Stripe → Mailchimp connector. With this add-on, it’s all handled natively inside ProcessWire — lightweight, minimal, and no extra subscription fees. What it does right now: Hooks into the creation of purchase repeater items (repeater_spl_purchases) Pulls customer name + email from the User created by StripePaymentLinks Extracts product names either from the expanded Stripe session line_items or as fallback from the purchase_lines field Pushes everything to Mailchimp, creating the subscriber if you allow it in the config Assigns the product titles as Mailchimp tags Config is super simple: just drop in your Mailchimp API key, Audience ID, and decide if you want to auto-create subscribers or only update existing ones. We’re keeping this intentionally minimal — one module, no extra steps, no fuss. Install, configure, and you’re done. We’ve been running it in production for some clients and it’s working reliably. If you’re already using StripePaymentLinks, this could save you the cost of external integrations while keeping everything in one place. Get it here: ProcessWire: https://processwire.com/modules/stripe-pl-mailchimp-sync/ Github: https://github.com/frameless-at/StripePlMailchimpSync Happy to hear your feedback or ideas for tweaks. Cheers, Mike from frameless Media
    4 points
  4. Wow, I missed that. That is the beauty of the friendly ProcessWire Community. You always learn something new. Thank you!
    3 points
  5. You can set custom icons for your Repeater Matrix elements in the item header. 😉 https://processwire.com/blog/posts/new-repeater-and-repeater-matrix-features/#custom-icons-for-repeater-types
    3 points
  6. Based on driving myself completely insane with noHooks for the last 2 years, and based on what Ryan specifically said here: ... I completely agree with Ryan. noHooks option should be absolutely avoided. Seriously, if you use it in advanced cases like I have been doing, you will hit every WTF issue known to man. It is not made for developer use, even though it gives off that vibe. I think that is a mistake. I will write more about this in depth soon, but at least in my situation, my goal was to ultimately update a page and all of its descendant repeaters (repeaters within repeaters) only after the page is its descendant repeaters have been saved completely first without hook interference. Using noHooks basically fucks up everything up (saying it's been frustrating dealing with it is an understatement) and there are unintended consequences everywhere! The correct way to do what I described is to do something like this, which took forever to figure out (every line has a specific reasoning behind it): // // /site/classes/OrderPage.php // class OrderPage extends Page { public function getAggregateRootPage() { return $this; } public function finalize() { // your code to finalize the page, such as populating an order_total field, etc. } } // // /site/classes/OrderLineItemsRepeaterPage.php // class OrderLineItemsRepeaterPage extends RepeaterPage { public function getAggregateRootPage() { return $this->getForPage(); } } // // /site/init.php or /site/ready.php (doesn't matter) // wire()->set('finishedPages', new PageArray()); // hook 1: use Pages::saved only to build list of pages to finalize wire()->set('finishedPagesHook', wire()->addHookAfter('Pages::saved', function(HookEvent $event) { $page = $event->arguments('page'); if(!method_exists($page, 'getAggregateRootPage')) return; wire('finishedPages')->add($page->getAggregateRootPage()); // duplicated pages won't get stored }, [ 'priority' => 1000 ]); // hook 2: use ProcessWire::finished to finalize the pages 🤌 wire()->addHookBefore('ProcessWire::finished', function(HookEvent $event) { wire()->removeHook(wire('finishedPagesHook')); foreach(wire('finishedPages') as $finishedPage) { $finishedPage->finalize(); } }, [ 'priority' => 1001 ]); When I demo my system one day which is way more complicated than the example code above, it will become clear. // TLDR: don't use noHooks when saving a page. DON'T! Instead, create a log of what pages need to be finalized, and act on those pages in ProcessWire::finished hook, which is when you can be absolutely sure the coast is clear. I wish I knew about that hook earlier. If you follow those simple rules, you don't have to think about CLI vs non-cli, ajax vs. non-ajax, whether the current page process implements WirePageEditor, uncache, getFresh, saved vs. saveReady, before vs. after hook, hook priority, editing a repeater on a page vs. editing the repeater "directly", where the hook should go (it should be in init.php/ready.php or in the init()/ready() method of a module), etc. Note: there's a whole other aspect to this in terms of locking the a page to prevent multiple saves (like if a page was being saved by an automated script and the same page was being saved by an editor in the GUI).
    3 points
  7. In case you are using this module and want to sync customers/purchases to Mailchimp – we just released an add-on for this module:
    2 points
  8. Is there a way to remove sites from the ProcessWire Sites directory, that are no longer powered by ProcessWire or does this happen automatic? https://processwire.com/sites/ Sadly, I have a few websites, that are no longer powered by ProcessWire anymore. 🙁
    2 points
  9. Awesome. I use BunnyCDN as well (previously KeyCDN) configured with ProCache, mainly because they publish the list of IPs they use if their edge-servers need to pull assets (and then serve) from your website. While this post isn't directly related to your module, it matters if you are using WireRequestBlocker for the reasons I explained here (post only viewable if you have access). Long story short, if a CDN makes a request to a URL on your site that matches a blocking rule in WireRequestBlocker (rare, but it's bound to happen by accident) then the IP of that particular BunnyCDN edge-server will get blocked. Then if a visitor on your site is being sent assets from that edge server, then it will error because the CDN was never able to obtain it due to the edge-server being blocked. This is why the site might look fine in California, but broken in New York for example, as the user is being sent assets from different edge-servers. To prevent this from happening, I have a cronjob set up (runs every 24 hours) to grab the list of BunnyCDN edge-server IPs and I insert it into WireRequestBlockers "IP addresses to whitelist" field. This is a function that can do what I described above: function bunnycdnWhitelistIps() { if(!wire('modules')->isInstalled('WireRequestBlocker')) return false; // Fetch BunnyCDN edge server list $url = 'https://bunnycdn.com/api/system/edgeserverlist'; // Use cURL to fetch the content $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); curl_setopt($ch, CURLOPT_TIMEOUT, 30); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if($httpCode !== 200 || $response === false) { throw new WireException("Error fetching data from BunnyCDN API. HTTP Code: $httpCode\n"); } // Parse the JSON response $data = json_decode($response, true); if(json_last_error() !== JSON_ERROR_NONE) { throw new WireException("Invalid IPs."); } // Extract IP addresses into an array $ipAddresses = []; if(isset($data) && is_array($data)) { foreach($data as $ip) { if(filter_var($ip, FILTER_VALIDATE_IP)) { $ipAddresses[] = $ip; } } } // Remove duplicates and sort $ipAddresses = array_unique($ipAddresses); sort($ipAddresses); $data = wire('modules')->getModuleConfigData('WireRequestBlocker'); $data['goodIps'] = implode("\n", $ipAddresses); wire('modules')->saveModuleConfigData('WireRequestBlocker', $data); }
    2 points
  10. I think having seen Kirby CMS' blocks editor last week that that sort of UI with the JSON storage behind the scenes could be the way to go. I think some mashup of the JSON storage from one of those editors, Kirby's UI and the way the Pro Custom Fields module has its fields configured would be the perfect blend and then get rid of the need for Repeater Matrix for a lot of things. Some limitations would be that you have to store images/files somewhere but it's easy enough I guess to just have a field for each that acts as a bucket and the block editor references them. If the UI was advanced enough they could be there but hidden or something. I'll see if I can track down that other discussion - sounds interesting, thanks Brendon!
    2 points
  11. I think experimenting with a block-style editor would be great. In the very large discussion over WYSIWYG editors (I don't recall of this was a 2025 wishlist, or a discussion of the future of TinyMCE/CKEditor, but it had a LOT of paginated responses), a few people even experimented in creating a proof-of-concept with editor.js, showing their results. I gave it a shot but determined the end result (interface) was too cumbersome for most of my users (and I usually try to aim for a very low bar as a standard; less complaints / support needed). Even in the demo (tried today) I was able to cause a rendering bug in both of the editors mentioned (TipTap, Editor.js) above. That said, if a true module is attempted to be released, since they render and use JSON as the underlying structure, I would recommend using a similar database structure to TinyMCE and/or CKEditor, and save the HTML-rendered output in a field just in case someone wished to switch back to a standard text editor for a particular field. (Unfortunately that means only one-way compatibility, however - and increased storage costs/size...so maybe an option for that in the module.)
    2 points
  12. Hello all, @BFD Calendar - this did actually the trick. If no user is logged in the textblocks render once as expected. Thanks for the hint! I also figured out, that this behaviour depends on frontend editing, which is enabled for this particular field in my case. Once this is disabled, it also works fine even when logged on to processwire's backend. For my scenario I m fine, however, I think it is a small glitch and could be enhanced... any opinions if I should file a bug report for this and how and where? Many thanks!
    1 point
  13. I think it is out of scope for this module, but it sounds like a useful thing to have in a separate module. I'll add it to my list of things to do.
    1 point
  14. Thank you - this is really useful to know. If I’m understanding it correctly I think we’re ok in this instance as we’re only pushing content to Bunny via API rather than the CDN hitting the site itself. It’s definitely early days though and I think there will be lots of edge cases to look out for. We’ve thrown this together for a product MVP that will have some ad traffic sent to it, so there should be plenty of data to look at. The main motivation at the moment is to minimise the data storage burden vs local storage on the VPS where the rest of the site runs rather than performance optimisation. But caching/compression etc will be interesting next steps.
    1 point
  15. Thanks! I'll see what our host (OVH) says and keep you posted.
    1 point
  16. At some point this happened to me as well. However only if I browsed the website while I was logged in as admin in the same browser. So for what it's worth try with a different browser, log out as admin or even use a VPN to test.
    1 point
  17. I'm monitoring the ProcessWire logs to find eventual PHP errors that needed a change by upgrading ProcessWire and PHP. Some old PHP was not recognised anymore, but most of that is done now. Most pages get some content (images) from external websites, usually just one per 'person'/page, and several on queried pages, like ' $features = $pages->find("parent=/events/|/the-eyes/, bfd_day.name=$todayday, bfd_month.name=$todaymonth, sort=bfd_year"); ', giving a list of events for 'today' with an average of 30-40 features. It all went well for over 10 years and without any notable changes the problem occurred and got worse. The problem affects the whole website, same when I call the home page or any other page including admin pages, even the Matomo statistics sometimes give a 500 error. From the hosting error logs I get many '[Wed Sep 24 00:08:51 2025] [X-OVHRequest-Id: 1d8b279cbdb0de4b3c5dad4c408a1f98] [error] [client 34.174.60.244:0] [host www.birthfactdeathcalendar.net] AH10141: FastCGI: comm with server "/homez.863/birthfac/www/index.php" aborted: idle timeout (160 sec)' errors. I filed an error report with them and wait for reply. There are no hooks used anywhere, site modules are up to date and give no errors at all in the logs. With an average of 30 visitors a day it's not extremely busy either. There are however over 33.000 pages.
    1 point
  18. Hello! I want to find out if this plugin is able to take over all mail sending within ProcessWire automatically by simply replacing the default WireMail Class. If so, where can I configure "SMTP via OAuth" like it is described here: https://github.com/PHPMailer/PHPMailer/wiki/Microsoft-Azure-and-XOAUTH2-setup-guide Is that even possible, or do I have to manually go through all my websites forms and send the mail programmatically? Luckily, I do have all the Azure Application-IDs and secret codes right here, but where do I fill them in?
    1 point
  19. I don't get a notice from LRP either. For the backend, I tried both a dash and now an underscore and neither work. I did manage to login via the backend after a few attempts.
    1 point
  20. With new version 1.0.4 it is possible now to set multiple Stripe API Keys in the modules config. This is especially useful for testing. Unfortunatly the module still has the "pending" status, so you will not be able to upgrade via the backend. See:
    1 point
  21. @ryan - I can no longer log into the modules directory. I don't get any notification as to why either, but I do see this JS error (which might be unrelated). Uncaught ReferenceError: Inputfields is not defined <anonymous> https://d1juguve2xwkcy.cloudfront.net/modules/WireRequestBlocker/ProcessRequestBlocker.js?v=4-3.0.251:43 fire https://d1juguve2xwkcy.cloudfront.net/assets/pwpc/pwpc-e1739702c839f9ff94bc214b49a3df07d7cbaa03.js:1 fireWith https://d1juguve2xwkcy.cloudfront.net/assets/pwpc/pwpc-e1739702c839f9ff94bc214b49a3df07d7cbaa03.js:1 ready https://d1juguve2xwkcy.cloudfront.net/assets/pwpc/pwpc-e1739702c839f9ff94bc214b49a3df07d7cbaa03.js:1 completed https://d1juguve2xwkcy.cloudfront.net/assets/pwpc/pwpc-e1739702c839f9ff94bc214b49a3df07d7cbaa03.js:1 ProcessRequestBlocker.js:43:2
    1 point
  22. The largest problem with the JSON-type WYSIWYG implementation of editors is that they all have their own custom implementations and conventions of how they represent the DOM via JSON. Quill was one of the first (that I was aware of) to have a complete and somewhat sane implementation therein. That said, since that seems to be the way editors are moving in general, I think it makes sense to go in that direction as well, but choosing should be made with care, determining as many pros and cons of features and technical decisions for the choice as possible. The topic that covers editor.js is: https://processwire.com/talk/topic/24876-pw-30170-– core-updates/ There was another one (I believe the next update by Ryan in News & Announcements) that partially continued the discussion, but most of it, if I'm remembering correctly, was here. Statamic's Bard uses TipTap, and prior to that used ProseMirror (TipTap uses ProseMirror under-the-hood which is likely why they were able to easily switch to TipTap; the same underlying JSON representation). EDIT: I think @jploch's module, PAGEGRID, originated out of that discussion. 🙂
    1 point
  23. AdminQuickTree This module is great for productivity when editing many pages within the admin, as it gives you direct access to the page tree navigation without having to hover the tree icon first. Download from Github Donwload fom Module Directory For non-superusers the page dropdown currently looks like this (if no bookmarks are defined): With this module it looks like this: For super-users or users with all permissions set it looks like this: These changes are achieved width a small js file that triggers the ajax tree navigation after the dom has been loaded. While I have tested it already a bit, I release this for now as a beta version. I hope others find it useful as well.
    1 point
  24. Good day @ryan! After the launch of the redesigned admin theme and site I had private conversations with some community members about it. And I have to say that they were pretty harsh about it. Some of us are choosing words here in the forums keeping hard thoughts to ourselves. Was it the right move? Was it made with enough consideration? Didn't it cost too much breaking things that worked? Is it possible to rollback somehow? This is what at least some of us here are silently thinking. Ryan, a lot of us here like myself have always relied on your vision and gut. You have managed to deliver exactly what was needed so many times. We want to believe the redesign is the right move too and we are just are too accustomed to the familiar and struggling to change opinions... So let me ask this. You see the google analytics metrics, downloads and stuff. Maybe you can measure something else like forums activity rate. How did it change with the redesign? Did you get what you expected?
    1 point
  25. I've built this over and over in several of my modules (RockDevTools for Livereload, RockCalendar for creating events). Always a lot of work. Always a lot of boilerplate code. Always a lot of issues to fix. I think it's time to bring SSE (Server Sent Events) to everybody. Simple example (Empty Trash): Client-Side: // create stream const stream = ProcessWire.Sse.stream( 'ssedemo-empty-trash', (event) => { const textarea = document.querySelector('textarea[name="empty-trash-status"]'); try { let json = JSON.parse(event.data); stream.prepend(textarea, json.message, 100); } catch (error) { stream.prepend(textarea, event.data); } } ); // update progress bar const progressBar = document.querySelector('#empty-trash-progress'); stream.onProgress((progress) => { progressBar.value = progress.percent; }); // click on start button document.querySelector('#empty-trash').addEventListener( 'click', (e) => { e.preventDefault(); stream.start(); }); // click on stop button document.querySelector('#stop-empty-trash').addEventListener( 'click', (e) => { e.preventDefault(); stream.stop(); }); Server-Side: public function __construct() { parent::__construct(); /** @var Sse $sse */ $sse = wire()->modules->get('Sse'); $sse->addStream('ssedemo-empty-trash', $this, 'emptyTrash'); } public function emptyTrash(Sse $sse, Iterator $iterator) { $user = wire()->user; if (!$user->isSuperuser()) die('no access'); $selector = [ 'parent' => wire()->config->trashPageID, 'include' => 'all', ]; // first run if ($iterator->num === 1) { $iterator->max = wire()->pages->count($selector); } // trash one page at a time $p = wire()->pages->get($selector); if ($p->id) $p->delete(true); else { $sse->send('No more pages to delete'); return $sse->stop(); } // send message and progress info $sse->send( $iterator->num . '/' . $iterator->max . ': deleted ' . $p->name, $iterator ); // no sleep to instantly run next iteration $sse->sleep = 0; } Code + Readme: https://github.com/baumrock/SSE/tree/dev What do you think? Would be nice if you could test it in your environments and let me know if you find any issues!
    1 point
  26. I'm working on RockCalendar which will be released soon if everything goes smoothly 😎 I think it should be stable enough next month. If you want to get notified you can subscribe to the Rock Monthly Newsletter. The module uses https://fullcalendar.io/ for the calendar interface and when clicking an event it will open the page editor in a pw modal: Opening the modal was a bit of a challenge, because PW unfortunately does not offer a good JS API to open modals. It only offers markup-based options to control the behaviour of the modal - at least as far as I could find out. This is a problem, because all events in the calendar get loaded by fullcalendar and I only have the eventClick() callback where I need to fire my action. My solution is to create a fake element in the dom and trigger a click on that element: calendar.on("eventClick", (info) => { // create a fake link element in body let link = document.createElement("a"); let $link = $(link); $link.attr( "href", ProcessWire.config.urls.admin + "page/edit/?id=" + info.event.id ); $link.addClass("pw-modal"); $link.attr("data-autoclose", ""); $link.attr("data-buttons", "button.ui-button[type=submit]"); $link.on("click", pwModalOpenEvent); $(document).on("pw-modal-closed", this.refresh.bind(this)); $link.click(); $link.remove(); }); Maybe that helps anyone else looking for a solution to a similar problem. If anybody knows a better way how to do it, please let me know! PS: If you have input (feature requests) for the development of RockCalendar please let me know: https://processwire.com/talk/topic/30355-request-for-input-what-features-should-a-pw-calendar-module-have/
    1 point
  27. Needed this today while working on RockGrid. Realised that the version above attaches an event listener for every created link but doesn't remove it later. So I improved this by listening on the "pw-modal-closed" event on the $link instead of the $(document). This didn't work at first, but I found out, that the culprit was that I instantly removed the $link after it has been clicked, so obviously there is no element any more to listen for the event. After moving the $link.remove() in the modal closed callback it worked as expected. Here is the updated function that I use in RockCalendar: const openInModal = (href, options = {}) => { // merge options with defaults const defaults = { calendar: false, autoclose: true, buttons: "button.ui-button[type=submit]", }; const opts = { ...defaults, ...options }; // create a fake link element in body let link = document.createElement("a"); let $link = $(link); $link.attr("href", href); $link.addClass("pw-modal rc-link-remove"); if (opts.autoclose) $link.attr("data-autoclose", ""); if (opts.buttons) $link.attr("data-buttons", opts.buttons); $link.on("click", pwModalOpenEvent); $link.on("pw-modal-closed", () => { if (opts.calendar) opts.calendar.refresh(); $link.remove(); }); $link.click(); }; A different approach is to add a second button (save + close) to the modal:
    1 point
  28. I'm a little embarrassed to admit that after several years of working in PW I still don't "get" output formatting in the context of saving pages and fields. I understand that when I get the value of a field it is automatically formatted when output formatting is on, and that I can switch this off to get the unformatted value. What I don't understand is why I need to turn off output formatting when I am saving a new value to a page. It's output formatting, not input formatting, right? Even more befuddling to me is the fact that this often results in a fatal error if I forget to do it, crashing my site. I also have to remember to turn it back on again afterwords, or else I can introduce security flaws into my code. I'm sure Ryan must have a reason for requiring switching output formatting off when doing a save, but if this is so important and is always required, why isn't it just baked right into the save() method? I know the new setAndSave() method now does this for individual fields. It would be great to have a similar method when saving an entire Page.
    1 point
×
×
  • Create New...