Jump to content

Leaderboard

Popular Content

Showing content with the highest reputation since 03/20/2024 in all areas

  1. This week there is a new version of the Site Profile Exporter module released (ProcessExportProfile). This is the module that was used to export last week's Invoices Application Site Profile. While this module has been around for a decade now, this latest version makes some nice improvements, which I'll cover below. @bernhard pointed out to me that he's using the module to make a new site profile, but he found it cumbersome to enter all the profile information every time he wanted to export the profile. So this new version now lets you update an existing profile with 1-click, making it much easier to re-export a profile. This version also adds a preview of what your profile will look like in the ProcessWire installer (the part where the user would select it for installation). Lastly, this version has several other small improvements and fixes as well. If you've ever thought of building a site profile, this module now makes it that much easier. This week I've also been starting to focus on the next long term client project, which is kind of a different and unique one that I look forward to. That's in part because I expect it will also involve a lot of improvements to the ProcessWire core and ProFields modules as part of it. Some of my favorite ProcessWire improvements have accompanied projects like this. There just isn't any substitute for real-life, large-scale projects when it comes to improving and optimizing the core and modules. Next week will be a shortened week here, so I'll likely post the usual weekly update Thursday rather than Friday. Thanks for reading and have a great weekend!
    22 points
  2. On the dev branch this week are a few issue fixes, but also some new features. The "Parent" field in the page editor (Settings tab) now is contextual to the page's template family settings. Now it will just show you the allowed parents, if you've configured them in the template. Previously it would show you a page tree to select from anywhere in the site. This saves you time, as well as the hassle of getting an error message after save, should you select a parent that isn't allowed by the page's template family settings. Next, the page tree "Move" actions (that you see when hovering a page in the tree) are now a little smarter. Previously, these Move actions would appear for any page that was either sortable or moveable. But now it also checks how many other allowed parents there are, per template family settings. If there aren't yet any other potential parent pages existing in the site, the page is no longer considered moveable, and thus won't show a Move action. This useful addition was added per Bernhard's request, as was the addition to a couple new classes used in the page tree to better differentiate between public vs non-public pages... something that I understand Bernhard's admin style may soon demonstrate. This week a question came up through email about how to make multi-language "required" fields require a value in all languages the page is active in. Currently multi-language required fields only require a value to be present in the default language. If you want to make them require all active languages, you can do so with a hook in /site/ready.php. I thought it was pretty useful so thought I'd share it here. Though maybe we'll have to add it as a feature. if($page->process == 'ProcessPageEdit') { $wire->addHookAfter('Inputfield(required=1,useLanguages=1)::processInput', function($event) { $inputfield = $event->object; $page = $inputfield->hasPage; if(!$page) return; foreach($event->wire()->languages as $language) { if($language->isDefault()) continue; if(!$page->get("status$language")) continue; // skip languages not active on page $value = $inputfield->get("value$language"); if(empty($value)) { $inputfield->error("Value required for language " . $language->get('title|name')); } } }); }
    18 points
  3. This week we have a new core version on the dev branch. Relative to the previous dev branch version, the new 3.0.237 version has 33 commits containing 20 issue fixes, 6 feature requests, and more. See the core updates section of ProcessWire Weekly 511, 512, 514 and 515 for more details. It's been about a month and a half since 3.0.236, which is a little longer than usual between version numbers, but that's largely because if the new Invoices site profile (see blog post). I'm off work tomorrow (Friday), so writing the usual weekly post a day early. As always, thanks for reading and I hope you have a great weekend!
    18 points
  4. Here's a copy of my blog with some reflections on building my first site with ProcessWire as someone coming from Drupal: peopleandplanet.org ProcessWire is an open source CMS Content Management System or Framework (CMS / CMF respectively) using PHP and MariaDB/MySQL. It's been around a while, humbly gathering users and sites. I spent a good while reviewing lots of open source CMSes recently as I have previously relied on Drupal 7 (excellent) and didn't like Drupal 8+ nor BackDrop (a fork of Drupal 7). WordPress? not with Gutenberg/React and all those plugin ads, thanks. Turns out I'm quite picky about CMSes! This is because my role is developer, trainer, implementer, themer, discusser of problems and solutions and dreams. I have personal relationships with my clients and am here to help. So I need a system that makes it simple for them to do as much as possible without me, while also being flexible enough for me to rapidly develop new features. So I was shopping for me and my clients, not just one of those parties. ProcessWire seemed a good balance, and once I started developing with it I was hooked. I've now launched my first site with it: peopleandplanet.org and my clients are pretty pleased with it, and I have another job in the pipeline. Templates and pages In ProcessWire, everything (even users!) is a Page, and every Page has a Template. So in Drupal-speak, it's kinda like Page = Content/Entity and Template = Content/Entity Type. A Template is configured with lots of options and describes what fields apply. Templates don't all need to be renderable, but typically are, so generally have an accompanying Template File. Key implementation decisions I made There are many ways to generate and theme page HTML in ProcessWire and I went through them all! Here's what I settled on: Use Page classes: these are PHP classes which add/bend functionality for a particular page/template. Doing pre-processing of data this way seemed the cleanest way to organise things, using inheritance to share code between similar templates. I used the excellent Latte templating engine instead of plain PHP or other options like Blade/Smarty/... Latte is context-aware which makes templates cleaner and clearer to look at and safer because it knows to escape content in one way as HTML text and another as an attribute, for example. The final clincher is that it uses the same syntax as PHP itself, so as a developer hopping between PHP and Latte, there's much less brain strain. Use PageTableNext. This is similar to Drupal's Paragraphs or Gutenberg's Blocks (ish). It allows a page to be built from slices/sections of various different templates, e.g. I have things like "text" and "text-over-image" and "animated stats" etc. These let authors make interesting pages, applying different preset styles to things, giving a good mix of creative control and theme adherence. What I liked Beyond the above features, I liked these things: Fairly unopinionated: the core is quite small and everything is a module, so when you write your own module, you have similar level of access. e.g. I was able to make a core module behave differently by using hooks rather than having to maintain a patch of a core code file. The selector API is a domain-specific query language for fetching page data that makes a lot of common things you need to do very easy and clear to read. I like readable code a lot. A lot of basic CMS features are implemented really nicely; thought has gone into them. e.g. Drupal has a redirect module that can add redirects from old URLs when you update the 'path alias' for a page - great - but ProcessWire's implementation (a) prevents you making circular redirects which is a quick way to kill a Drupal site by accident that's happened more than once, and (b) has some useful rules like let's only do this if the page has been in existence for a little while - because typically while first composing a page you may change the title quite a few times before you publish. e.g. when you save a page that has links to other pages in it, it stores the page IDs of those other pages too, and when the page is fetched it will check that the URLs exist and still match the ID, fixing them if needed. Images - have 'focus' built in as well as resizing etc. so if a crop is needed you can ensure the important content of the image is still present. Booting is flexible; it enabled me to boot ProcessWire from within Drupal7 and thereby write a migration script for a lot of content. There's a good community on the forum. A forum feels a bit old fashioned, but it's good because you can have long form discussions; it sort of doubles as a blog, and a lot of new features are announced in forum posts by Ryan and others. The Tracy debugger is mind-blowingly good, just wow. Modules - third party or core - are typically very focussed and often tiny. This is a testament to what can be achieved throught the flexible and well-designed APIs. Weekly updates on core development from both the lead developer on the blog and the community, both with RSS feeds so it's easy to keep updated. What I don't like Logging is weird and non-standard. I prefer one chronological log of well categorised events, but instead we have lots of separate files. This is a bit weird. One thing it is opinionated on is that there should be a strict hierarchy between pages and URLs. I like that level of order, but in real life, it's not what I needed. e.g. People & Planet has three main campaigns and it wanted those at /campaign-name not /campaigns/campaign-name. And we wanted news at /news/2024-03-25/title-of-news-article, but we don't want a page at /news/2024-03-25/ and we want the news index to be under an Info page in the menu. This meant I had to write a custom menu implementation to implement a different hierarchy to display what we wanted it to look (3 campaigns under a Campaigns menu, the news index under an Info menu). There was a page hook for having news articles describe their URLs with the publish date, but this took quite a bit of figuring out. Ryan Cramer leads/owns the project and other contributors are sparse. He seems like a lovely person, and I'm super grateful for his work, but there's only one of him, so this is a bit of a risk. Also, the code has no phpunit tests. Gulp. There have been various initiatives, but this sort of thing needs to come from a core team, and it's not a priority for Ryan. I love tests, they help avoid regressions, help document designed behaviour, etc. Likewise, there's a styleguide, but it's not adhered to, so... Right decision? I'm very happy with it, and it seems a great fit for my clients' needs, and they're very happy. All software has risks and I got burned twice with Drupal 8/9 - once backing out after months of development; another project that went to completion I regret and dislike administering/maintaining now. But so far, so good, and I intend to use ProcessWire for other projects (including replacing this website) very soon. Contributions I have two ProcessWire modules that are there for anyone who needs them. TagHerder provides a page at Setup » Tag Herder that lists every tag and every page tagged with that tag, and provides three options: Rename the tag; Trash the tag; Replace the tag with another. Useful for cleaning up tags that have gotten out of control! EditablePublishedDate lets you edit the published date of a page. Useful for entering historical page information or such.
    17 points
  5. This week we have some updates for the ProFields table field (FieldtypeTable). These updates are primarily focused on adding new tools for the editor to facilitate input and management of content in a table field. All the details can be found in the new blog post with an accompanying screencast video— https://processwire.com/blog/posts/table-field-with-actions-support/
    16 points
  6. This week we have ProcessWire 3.0.238 on the dev branch. This version contains 17 commits containing new features (some via feature requests) as well as issue fixes. Recent updates have been nicely covered in ProcessWire weekly issues #517 and #518. Also added this week are the following: Improvements to ProcessPageClone, which now supports some new options such as the ability to configure it to always use the full clone form (a requested feature), and an option to specify whether cloned children should be unpublished or not. New $datetime->strtodate($date, $format) method that accepts any PHP recognized date string and reformats it using the given format. It also supports date strings that PHP doesn't recognize if you give it a 3rd argument ($options array) with an `inputFormat`. The existing $datetime->strtotime() method now supports an `inputFormat` option as well. The Inputfield class now has new methods: $inputfield->setLanguageValue($language, $value) and $inputfield->getLanguageValue($language), added by the LanguageSupport module. Previously there were no dedicated methods for this, so you had to manage them with custom keys containing language IDs if you wanted to programmatically use an Inputfield in multi-language mode. The ProcessWire.alert() JS method has been updated with an auto-close option (`expire` argument) that automatically closes the alert box after a specified number of seconds. InputfieldDatetime has been updated with 7 new interactively configurable settings for the jQuery UI date picker. See the screenshot below for an example of a few. Plus, its API has been updated with the ability for you to specify or override any jQuery UI datepicker option, either from PHP or JS, as requested by Toutouwai/Robin S. See the new datepickerOptions() method and phpdoc for details. Next week we've also got some more updates to InputfieldTable that take the newly added actions even further and make them easier to use. Thanks for reading and have a great weekend!
    15 points
  7. It has been a great meeting yesterday 😎 We are working on something... 🙂
    14 points
  8. PHP is slow... well, maybe not that slow at all. Interesting read. https://dev.to/realflowcontrol/processing-one-billion-rows-in-php-3eg0
    13 points
  9. Following the advice of some PW-forum members @gebeer @dotnetic @bernhard @sebibu, I would like to share my first Processwire module with all members. QuickSave: My first attempt at developing a PW module to quickly save a page. What can the module QuickSave do: Quickly save a page edit in the admin and return to the last activity. Adds an extra save button AND shortcuts CMD+s and CTRL+s. QuickSave includes an additional plugin for TinyMCE. The plugin for TinyMCE must be assigned and activated specifically for these textarea fields. (Keyboard input shortcut does not yet work in an iFrame - RTE/iFrame, CKE, etc.) PW-QuickSave-1024-v16.mp4 Installation: To install the module, it simply needs to be copied into the Processwire module directory and then activated in the Processwire admin. Optional: Anyone who uses TinyMCE can activate the keyboard input shortcuts via the configuration as a plugin for TinyMCE. Afterwards it has to be activated for the field (e.g. body). This is the path for the configuration in TinyMCE: /site/modules/QuickSave/tinymce/quicksavetinymce.js QuickSave-TinyMCE-inst.mp4 I have been using the module only as a save button version for some time in a long Processwire page with a strong content and a four-fold nested repeater matrix and it works. The keyboard shortcuts were added because of a request. I was only able to test these on the Mac and hope they also work with Windows browsers. Due to another request, the short notification after saving was added. I hope you like the module and that it helps the moderators to keep a better eye on the areas they are currently working on. Especially with long pages and, for example, a table with many rows, it is very helpful for me not to lose the row when saving. (see the video) Note: The module does not intervene in the Processwire saving process, only the original saving button function is triggered. Please note that the keyborad shortcodes do not work in iframes or CKEditor. UPDATE: New version 0.1.7 added on April 13, 2024. A new combined version without jQuery has been created. Thanks to @dotnetic and all others 🤗. It is now more optimized and further optimizations are planned. Module 0.1.7 Download: QuickSave.zip Feedback on whether the keyboard shortcuts ctrl+s work under MS Windows would be great... 🤗 Thanks and Greetings Chris
    9 points
  10. Hi guys. Check out my latest project Casa Douro Guesthouses. As the name implies, Casa Douro offers a few guesthouses in historic Porto and on the Douro Region. They came to us looking for a well built website that would fit their marketing efforts and grab direct reservations, instead of relying only on Booking.com and Airbnb. Since their channel manager (CM) software only offers a cookie-cutter website and no API, ours does all the presentation and jumps straight to the booking system when the user decides to make a reservation. As usual on my sites, pages are built using a blocks system (Profields Repeater Matrix), allowing the admin to change things up a little bit between the different units. Plus the usual WireMailSMTP, SEOMestro, and Rockfrontend because I love using Latte templates. The frontend is a mix of Tailwind + SCSS on more complicated components, and JS over Vite which allows me to keep it as vanilla as possible. We already have plans for new sections, and maybe upgrade the booking system in the future.
    9 points
  11. That sucks, but I'm guessing they might make exceptions for some projects, so I'll have to ask them. Still, not nearly as much of an issue as what CKEditor did. But if it stays GPL and they don't make an exception for any projects, then most likely we couldn't include TinyMCE 7 with the core. In that case, we'd develop it was a non-core module, and folks would have to install it as a 3rd party module (in /site/modules/). ProcessWire's core is completely separate from what people develop or what they might add-on their site, so they don't have to share the same license. PW is built so that modules are independent of ProcessWire in the same way WP and PW are separate applications that can run on the same webserver, or a website is independent of the webserver that's delivering it, or a browser is independent of the HTML it renders or JS it executes. https://processwire.com/about/license/3rd-party-files/
    9 points
  12. Customized templates and fields Each and every content type only has the fields it really needs. Books, companies, recipes - it doesn't matter what kind of data my clients or I have to deal with. The templates and fields will reflect that. Therefore clients don't even have to learn anything in regards to creating or editing data. Super easy. It's typesafe (by my definition) We can discuss the meaning of 'typesafe' here but... I think ProcessWire is somewhat typesafe because I define each field, template, relationship, and almost everything else. I know where to expect what kind of data and know what data is allowed in which field. No guessing, no errors. (Sure this depends on your setup and your will to invest some time.) Works perfectly fine for non-developers I won't call myself a coder or programmer - I just tinker around with code and have fun. When I started using ProcessWire, getting around was super easy, and learning the fundamentals took only a day or two. From there on, it was easy-going. It's impressive what you can achieve with only some if/foreach/echo in PHP/ProcessWire. I said it a few years back and still stand behind it: ProcessWire seems to be the easiest way to learn and work with PHP. Low maintenance There are ProcessWire projects of mine that haven't been updated in the last 5+ years and still work without any PHP or security issues. The moment a project is finished and works without flaws it will do so for a very long time. There is no real need to update a project. Small footprint, high performance A ProcessWire website doesn't need that much of a big hosting package. The moment you start using Core cache functionalities or even ProCache most websites are fine and ready for an average amount of traffic. Maybe not ready for a slashdot/reddit/ProductHunt-peak but that's a totally different story. I can get so much out of ProcessWire compared to WordPress (and others I used/tested in the past). ZIP downloads and no real need for a package manager What I really love and enjoy is that you can get everything as a ZIP file, unpack those, move them around and do whatever you want or need with them. Not needing NPM or composer to get started - like in the good old days - is just perfect. In the last 1-2 years I did a lot with NPM due to 11ty and Astro, yet an old-school ZIP file has its very own charme. For comparison: Installing CraftCMS feels good and really nice, yet I absolute don't know what's happening, what is needed, and so on. It's like a blackbox. It works but I don't know why. I hate that.
    7 points
  13. Just officially released RockForms: https://www.baumrock.com/processwire/module/rockforms/ A project that has been in the making for quite some time, with its first git commit dating back to a year ago. However, the roots of RockForms stretch even deeper, originating from another repository, showcasing its rich history and evolution. RockForms has already been battle-tested in several production projects, proving its reliability and efficiency. It comes with extensive Docs with several interactive forms that you can try out yourself to see how it looks and feels: https://www.baumrock.com/en/processwire/modules/rockforms/docs/ Furthermore, Jens, known in the community as @dotnetic, has also implemented RockForms in his production environment, contributing significantly to the refinement of this release. Thank you Jens! 😎💪 We believe that RockForms will not only enhance your web development experience but also elevate the quality of your projects.
    7 points
  14. Greetings PW forum, I have completed a revamp of the artist collective The Teaching Machine's website, which was once built on Cargo Collective, then Wordpress, and is now using ProcessWire with the Rockfrontend Module, Duplicator Module, and the Latte template engine (among some others). The site codebase can be found here. (I don't understand why GitHub thinks it's 99% CSS … that will be for another day). The site menu is right-justified, with a simple navigation and dark-mode toggle. Individual project pages can contain galleries (also right-justified), media embeds, and will display similar items in the same category as well as site-wide items. A Zones page displays overarching categories, and randomly selects new projects each time the page is visited. The Home page displays the latest projects in descending chronological order. The Teaching Machine is an independent arts laboratory devising strategies for amalgamated practices within the cinematic imaginary, musical zeitgeist, and networked hyperstructures. Founded by hypermedia artist Strangeloop in 2011, with members in Los Angeles, Barcelona, and Singapore.
    7 points
  15. Ok, sorry, clickbait 😄 Hooks are great! But sometimes, there are even better solutions: I'm cleaning up RockForms to finally release it 🙂 I have some pages that are only for storing data (like form entries and such), so I don't want them to be editable, not even for superusers, as I control them solely via code in my module. -- Solution 1 -- With a regular hook that would look like this: <?php // site/ready.php $wire->addHookAfter("Page::editable", function($event) { $page = $event->object; if($page instanceof \RockForms\Root) $event->return = false; }); That's quite nice, but this approach has some drawbacks: First, sooner or later you might end up with hook-hell in ready.php; That's not ideal and really hard to debug on more complex projects. Second, as we are defining the hook with a callback in a non-OOP style these hooks get a LOT harder to debug! Have a look at tracy's debug panel: The second highlighted hook is the one coming from ready.php and it does not show any helpful information whereas the first one does show clearly that the hook is attached in RockForms\Root in the method "hookUneditEntries" (it should be hookUneditRoot, but I made a mistake when copy-pasting, sorry 🙂 ). -- Solution 2 -- So the next best solution IMHO is using custom page classes! Then you get OOP style and a lot better structure for your project with really very little effort! Just create a file in /site/classes and that's it. Now to attach hooks directly in custom page classes you have to do one additional step. You can watch my video about this if you are interested. If not, head over to solution number 3 which is even simpler 🙂 This solution might look something like this: <?php namespace RockForms; use ProcessWire\HookEvent; use ProcessWire\Page; use RockMigrations\MagicPage; use function ProcessWire\wire; class Root extends Page { use MagicPage; public function init() { wire()->addHookAfter("Page::editable", $this, "hookUneditRoot"); } protected function hookUneditRoot(HookEvent $event): void { $page = $event->object; if (!$page instanceof self) return; $event->return = false; } } This might look like a lot more code, but it's a lot better in the long run in my opinion as things that are related solely to the root page are inside the Root.php file of my module/project. -- Solution 3 -- But then I remembered: As our "Root"-page is a custom page class and PW checks if the page is editable or not by calling $page->editable() we can simply override this method like so: <?php namespace RockForms; use ProcessWire\Page; class Root extends Page { public function editable() { return false; } } You don't even need to make it a "MagicPage" because you don't need an init() method to attach any hooks. Now it's only very little additional code compared to a hook in ready.php but with a lot cleaner setup 😎 It's not a new invention, but I thought I'd share it nevertheless. Maybe it's helpful for some and maybe it's a good reminder for others, that even hooks are sometimes "overkill" 😄
    7 points
  16. Just launched my first public Module 🎉 This module allows ProcessWire to send transactional emails via Brevo. Download the latest version: https://github.com/ttttim0709/WireMailBrevo Installation Copy the WireMailBrevo directory into your site/modules/ directory. In the ProcessWire admin, go to Modules > Refresh. Click "Install" next to the WireMailBrevo module. Usage Example usage: $email = $mail->new(); $email->to = 'recipient@somedomain.com'; $email->subject = 'Test #1'; $email->body = 'An example email'; $email->send(); Use of Versions: Check the Brevo dev section for more information about Message versions $email->version([ [ 'to' => [ [ 'email' => 'bob@example.com', 'name' => 'Bob Anderson' ], [ 'email' => 'anne@example.com', 'name' => 'Anne Smith' ], ], 'subject' => 'This is my version subject line', ], [ 'to' => [ [ 'email' => 'jim@example.com', 'name' => 'Jim Stevens' ] ], 'htmlContent' => "<!DOCTYPE html><html><body><h1>Modified header!</h1><p>This is still a paragraph</p></body></html>", ], ]); Configuration After installing the module, you can configure it by going to the module settings page. You need to provide the following configuration options: Brevo API Key: Obtain this key from your Brevo account settings. Sender Email Address: The email address to be used as the sender. Sender Name: The name associated with the sender's email address.
    6 points
  17. If you're on PHP 8 and a new-ish version of Latte, you should be able to use the nullsafe operator to safely get the value: <body n:class="$page?->category?->value"> On PHP 7, you can still use the similar nullish coalesce operator, which in this case behaves identically: <body n:class="$page->category->value ?? ''">
    6 points
  18. @ryan great to see this module updated. Would you consider adding this feature to the next version: https://github.com/ryancramerdesign/ProcessExportProfile/issues/15
    6 points
  19. I've added a new sub-project to my PWGeeks site that turns the ephemeral activities shown in the PW forum's Online Users & All Activity pages into a live-updating, unified, timeline of events - both what users are replying to - and what they are viewing. Whilst it is not built with Processwire, it is related and was an interesting little project to build. I thought I'd post about it here in case it's of use to anyone else. You can find it here: https://activity.pwgeeks.com/ Clicking on a username drills down into their activity, but more on that later. Unlike the usual All Activity view on the forum, this integrates consumption activities (viewing stuff) with production activities (posting/replying.) Unfortunately, due to a limitation on what the forum lets non-logged in readers see, reactions to posts cannot be tracked at this time. I initially thought this might be useful just to make the ephemeral viewing activity more obvious, but I'm now hoping that it can be turned into a tool to more easily help forum moderators deal with spammers (new joiners who quickly start posting). But that's yet to be proven. If you don't wish your read/write activities to be traceable, you can login to the forum in anonymous mode. Architecture The architecture is split into a long-running ReactPHP Watcher Service, and the Index Generator code which creates the page you just viewed with the help of Caddy, PHP8.2 and PHP-FPM. It all runs on a cheap Contabo VPS. Both Caddy and the watcher process are defined as systemd services that are automatically restarted if they fail, or the box is rebooted. Pusher is used to provide a Pub-Sub channel for immediate communication of changes found by the watcher service to anyone who is viewing the index pages - allowing the index to be updated as forum activity is detected by the watcher. Pusher takes care of any fan-out needed between the Watcher Service publishing the events, and any browsers subscribed, via Pusher-JS. I used Pusher's free tier and created an "application" to get my channel and the needed credentials, which went in a .env file. I also turned on subscription counting on the channel within Pusher's dashboard to allow simple console logging of the number of clients connected to the channel at any one time. All detected activity is also stored in a local SQLite DB which allows the Index Generator to build the initial table of activity shown in your browser. Once the page is loaded, JS events take over and continue populating the table in (almost) real time as they come in from Pusher. The Watcher Service ReactPHP is used to create a long running server process, in PHP, that has run for more than 2 months at a time with rock-solid (no, that's not a bernhard module) memory use at 10MB once all the user data is loaded from the DB. I am sure this process would have run indefinitely, but I recently restarted the service to add detection of write-activities on the forum. The major Composer packages used are pusher/pusher-php-server (to publish to my app's event channel) vlucas/phpdotenv (to read the pusher credentials from an .env file) react/event-loop (to run the PHP app indefinitely) fabprot/goutte (for scraping the 2 forum pages) symfony/console (for CLI output formatting and logging) I would probably choose a different scraping library if I were to do this again, but goutte works just fine for now. The watcher only uses two public forum pages; the Online Users page, with the logged in filter applied, and the All Activity page. It is worth noting that the service has no log-in details, so sees these pages as a logged-out visitor to the forums would - which means significantly less information is available to it than to you if you view those pages when you are logged in to the forum. All Activities Page Differences When logged in, the All Activities page shows user reactions to posts (2 below). These are not available to guests, so cannot be tracked by the Watcher Service. If you have purchased Pro modules and have access to some of the VIP forums, then new posts or replies to posts in those forums are also shown (1) Guests have no access to either of these - so VIP forum activities are not tracked. The Watcher Service does not have any login credentials, so this is the view it sees... All activities listed on this page have a UTC timestamp in their HTML attributes that can be used to record the actual time of Joins, New topics and replies being posted. I don't bother recording any "user started following..." activities. Online Users (logged in) You might not be aware of this page on the forum, but it's how the Watcher Service can tell who's viewing forums & posts, creating new topics, or using the personal messaging service. If you have never visited this page on the forum, you want (1) browse, 2(Online Users) and then use the Filter-By dropdown to view logged-in users. The activities listed (3) do not have a timestamp in the HTML - so the watcher limits itself to anything that happened "Just Now" that has not yet been recorded for that user and uses the server's time() to record the event as having occurred. When users view a VIP forum or post, or visit the All Activity page, the user list page does not show what the user is viewing - it just shows their activity as a blank string (See netcarver's activity in the above screenshot.) Other Limitations The main loop of the service runs several times a minute and scrapes and de-duplicates activities from the forum. Any activity that happens on the forum between these samples are undetectable. So, if you visit a forum and then quickly click into a topic, and then back out, your activity will not be traceable. The Event Loop Using ReactPHP is conceptually quite simple, but there are a few things to keep note of. Here's the basics of the Watcher Service... <?php declare(strict_types=1); namespace Netcarver\ForumActivityMonitor; require_once 'vendor/autoload.php'; use React\EventLoop\Factory; ... use Pusher\Pusher; use Dotenv\Dotenv; require_once __DIR__ . '/.format.php'; // output formatting helpers require_once __DIR__ . '/.storage.php'; // storage class $dotenv = Dotenv::createImmutable(__DIR__); $dotenv->load(); // Create pusher publication connection $pusher = new Pusher( $_ENV['PUSHER_KEY'], ... ); $sample_period_seconds = 20; $started_ts = time(); $output = new ConsoleOutput(); $output->write("\n>>> ProcessWire Forum Activity Monitor (sampling every $sample_period_seconds seconds) <<<\n\n"); // Open the local SQLite DB for storage layer... $db = new \PDO('sqlite:path/to/database.sqlite'); $storage = new Storage($db); $loop = Factory::create(); $loop->addPeriodicTimer(1, function () use (&$storage, $started_ts, $sample_period_seconds, &$pusher, &$output) { $now = time(); $can_access_pw_forum = ($now % $sample_period_seconds === 0); $elapsed_time_seconds = $now - $started_ts; showStatusLine($output, $can_access_pw_forum, $storage->userCount(), $elapsed_time_seconds); if ($can_access_pw_forum) { $client = new Client(); try { // Scrape, dedupe & store events ... // publish events via pusher... if (!empty($event_timeline)) { ksort($event_timeline); $table = new Table($output); $table->setHeaders(['#', 'Time', 'UID', 'Username', 'Activity']); $mem_use = memory_get_usage(true); $runtime_str = formatElapsedTime($elapsed_time_seconds); $pusher_events = []; foreach ($event_timeline as $events_at_time) { foreach ($events_at_time as $event) { $uid = $event['uid']; $user_activity_count = $storage->getUserActivityCount($uid); $table->addRow([$user_activity_count, $event['time'], $uid, $event['user'], $event['activity']]); $pusher_events[$event['time']][] = [ 'uid' => $uid, 'url' => $event['url'], 'user' => $event['user'], 'act' => $event['activity'], 'mem' => $mem_use, 'uptime' => $runtime_str, 'type' => $event['type'], ]; } } $pusher->trigger('activities', 'update', $pusher_events); $table->render(); $output->write("\n"); } } catch (\Throwable $e) { $output->write("\nCaught Throwable: ".$e->getMessage()."\n\n"); } } }); $loop->run(); Note that Pusher, the storage instance, and the console are all passed into the loop closure by reference so state can be maintained between each scheduled call to the loop function. The loop closure uses a try {} catch (\Throwable) {} block to ensure it keeps running without systemd having to restart it in case of a PHP error. The catch block does occasionally run - so far if DNS resolution fails when scraping the forum. I've omitted the scraper and de-dupe code from the above as they are still a work in progress, but they populate an $event_timeline array if anything new is detected. The unified array of events (if any) is published via Pusher and each entry includes information about the Service uptime and memory usage. The Index page simply console logs this meta-data from the first event in the array of activities it receives, so you can use your browser's console to track these (along with the number of subscribers to the channel)... I made the event loop run every second, even if it's not time to sample the forum, so the CLI output can be updated regularly. This was especially useful when initially running the Watcher from the command line and I could probably drop the status updates now things are run via Systemd. Running from the CLI on the server, or tailing the log file, gives a nicely formatted table of events as they occur thanks to Symfony's console library and table helper. Systemd Integration To allow automatic restart of the watcher when the VPS is restarted, I added this service definition file to /etc/systemd/system/forumwatch.service that runs the watcher as an unprivilaged user... [Unit] Description=ReactPHP Processwire Forum Watcher [Service] ExecStart=/usr/bin/php8.2 /home/pwfw/activity.pwgeeks.com/watcher/react.php WorkingDirectory=/home/pwfw/activity.pwgeeks.com/watcher/ StandardOutput=file:/var/log/pwforumwatch.log StandardError=file:/var/log/pwforumwatch.log Restart=always User=pwfw Group=pwfw [Install] WantedBy=multi-user.target A quick sudo sytemctl enable forumwatch.service && sudo systemctl start forumwatch.service is then all that's needed to get things running. As the output is logged to /var/log/pwforumwatch.log, I also gave it a logrotate.conf file to keep things under control. The Index Generator This is a single index.php file that takes care of reading the most recent user activities from the SQLite DB and generating the table of events from it for that point in time. JS is included (pusher-js) that subscribes to the application's activity channel. Pusher's free tier allows up to 100 simultaneous connections to the event activity channel, and you can see how many users are connected via your browser's console. NB: The following feature is now behind a basic auth login. If you click on a user's name, you'll reload the page with a filter that lists only that user's activities. Here are Bernhard's as he has posted recently as I write this up. Click on the User's name or avatar (1) to be taken to their page on the forum, or click on the "Everyone" or PW logo (2) to be taken back to the all-inclusive index. Trying It Out I recommend opening the activity tracker in one browser on one side of your screen, then opening the Forum (and logging in) in another browser on the other side. As you (and anyone else) visits pages on the forum, you should see things update in the tracker. Also open up the console on the tracker page to see uptime/memory and viewing user count data as they come in. If you read this far, thank you for your time, and I hope this was of some interest or use to you.
    5 points
  20. Hello ProcessWire community, it's been a while since I last shared a project showcase with you all 🙂 Today, I'm excited to present a recent project we've been working on: The website of the Austrian artist Tanja Boukal - www.boukal.at This project has been an interesting journey, and I'm excited to highlight some of the features and solutions we implemented: First off, I had to make the project run on my local development computer. That was quick and easy thanks to DDEV, where you can easily define the setup in a simple yaml file (eg php7.4, mariadb 10.2, etc) and then update the setup to a current one and see what breaks and then apply all updates 😄 Then, we tackled the challenge of cleaning up everything from the old ProcessWire website (not done by me). The page was quite a mess. I'm not blaming anybody for that, but I guess we all know the problem: The developer has some structure in his/her had and it works great at the beginning. But then the real world kicks in and slightly different needs pop up here and there and quickly the initially planned structure is not sufficient any more. We need a gallery on a page we didn't plan upfront, or we need some additional text above or below some other elements where we don't have Inputfields... So the client ended up creating several pages on the root level to be able to input the desired content and then link wildly to those hidden pages. Actually I think she did a great job, because she got things done without needing help from a developer (which costs money as we all know). During that process and thanks to RockPageBuilder we got rid of many unnecessary templates while providing the client with a lot more flexibility than before 😎 Before the relaunch: After the relaunch (with AdminStyleRock for styling the backend in the client's colors): Not only was the content on the old PW site structured completely different than on the new one, we also had two WordPress blogs that had been around that we wanted to integrate into the new website. Both RockShell and RockFrontend's DOM-Tools where extremely helpful in that process! We even used @FireWire great Fluency module to translate imported blog posts on the fly! This command is simply put into /site/modules/Site/RockShell/Commands/ImportAegean.php and will then instantly be available to RockShell as import:aegean command 😎 And then you can run "rockshell import:aegean", watch it do its work and enjoy 😎 Another pain for the client was that many people in the arts industry still rely on printed information. So she wanted to provide all the information about her work not only on her website but also as downloadable PDFs. On the old website this process was all done manually and whenever she had a new work/catalogue/project to share she had several things to update. Now she only updates that information on one place and RockPdf creates an updated PDF for her - with all entries sorted automatically by date 🚀 As mentioned RockPageBuilder adds a lot of flexibility to the website and makes editing content easier than ever before: But that's not a one-way-road! Where necessary we can still provide a more rigid structure and add custom fields that show up at dedicated places not movable by the client - for example date, cover-picture and teaser-text that should show up on all blog pages at the very top and at the exact same place: After that identical header section the client is free to choose from all available content elements like regular text, downloads or youtube videos (fully gdpr compliant without the client thinking about that). The work section showcases her artworks, projects, exhibitions and catalogues. All are linked to each other with the great ConnectPageFields module. So for example the https://www.boukal.at/work/projects/the-aegean-project/ has several other pages connected and also has its own blog! Ah, every aspect of the website is multilingual, which is also cool and where ProcessWire shines once more - especially with one-click-translations thx to Fluency! Another nice feature is that the page shows indicators for external links: This is a CSS-only solution and quite easy to implement (using LESS syntax): // style external links with icon body > *:not(#tracy-debug):not(.no-icon) { a[href^="http://"]:not([href*="www.boukal.at"]):not(.no-icon):after, a[href^="https://"]:not([href*="www.boukal.at"]):not(.no-icon)::after { content: url("/site/templates/images/external-link.svg"); display: inline-block; position: relative; top: 3px; margin-left: 5px; } } The site has top-notch performance thanks to the brilliant ProCache module and we did do some basic lighthouse optimisations! Hosting is done by me as well and for quality assurance we are monitoring all services with uptime kuma including a monthly report built again with RockPdf 🙂 I find it quite funny that these 6 spikes show loading times of around one second - that's less than the loading time of an average website! All other checks finished within < 100ms (from the same data center). The spikes happen when content is updated and ProCache has to rebuild the static copy of the homepage showing how much of a difference this treasure makes thx to Ryan 🙂 Site statistics are collected using Matomo to provide a great user experience without an annoying cookie banner. Consent for Youtube videos is requested on demand when a video is clicked on. Last but not least all the code is under version control and changes are pushed to the live server simply by doing a git commit (using RockMigrations deployment tools): So once the client requests a change and I'm done with the update I simply do a "git push" and GitHub does the rest and two minutes later the changes are live 😎🧘‍♂️ I hope you enjoyed reading and I hope you like the site as much as we do 🙂 I'm happy to hear what you think and if you find something to improve please let us know! 🤓 PS: If you like what you see and want to push your next project to the next level I'm happy to do consulting on an hourly basis so that you can efficiently pull my 10 years of ProcessWire knowledge into your work 🚀 Let's meet at cal.baumrock.com - always happy to see real faces instead of avatars 🙂
    5 points
  21. Hey all! Fluency 1.0.8 has been released. Now available for upgrade in ProcessWire admin and in the modules directory. For the Composer fans out there, Fluency has been added to Packagist and can now be installed by running `composer require firewire/fluency` This version contains a new feature where you can now disable translation on a per-field basis. Just check the "Disable translation for this field" checkbox under the 'Details' tab and Fluency will not be added to that field. This is helpful where fields may contain different content for different languages but may not be suitable or desired for translation- email addresses, phone numbers, full string URLs, etc. It can help clean up the UI when editing pages and prevent unnecessary translations. Also includes the following: Activity overlay text has been resized and reformatted, now looks better where the size of the field is smaller. Translation cache is now enabled by default as stated in the README file Performs some polite cleanup when uninstalling the module. Fix issue where CKEditor fields in collapsed Repeater and RepeaterMatrix fields may fail to initialize. Credit to @ryangorley for finding/reporting. Fix issue where fields with combinations of languages that are and are not configured in Fluency are properly handled. Languages that aren't configured in Fluency are now indicated where others have the translation button present. Credit to @ryangorley for finding/reporting. Fix for issue where the way that Fluency stored module configuration data may cause ModSecurity false positives depending on how rules were configured on that server. Credit to @update AG for finding/reporting. Various minor fixes and code cleanup. Please share any bugs here or, if possible, create an issue in the Fluency Github repo. Thanks!
    5 points
  22. This exact module helped me to build and maintain all my starters for clients and sideprojects for a very long time now. So this update is highly appreciated! 😃
    5 points
  23. I had to do this just the other week. We had an old CMSMS site that we'd moved to PW. We had a db table with the old user names and passwords in which we stuck on the new site, and then just created our own login page which first checked to see if we had the user in PW already. If the user isn't already in PW then check the password against the old table; If it matches then use that information to add a new user to PW. The users don't really notice that anything has changed. Here's the code I used for that - you'll have to adapt to your circumstances of course but it worked well for our move. if ($input->post('ml_name')) { // when processing form (POST request), check to see if token is present // (we have a CSRF field on our login form) if ($session->CSRF->hasValidToken()) { // form submission is valid // okay to process } else { // form submission is NOT valid throw new WireException('CSRF check failed!'); } $ml_name = $sanitizer->name($input->post('ml_name')); $ml_pass = $input->post('ml_pass'); // dont allow the guest username if ($ml_name === 'guest') { throw new WireException('Invalid username'); }; // do we have this user in PW already? $u_exists = $users->get("$ml_name"); if ($u_exists->id) { // user exists // lets try and log them in try { if ($session->login($ml_name, $ml_pass)) { $session->redirect('/'); } else { $ml_feedback = 'Unknown details.'; } } catch (WireException $e) { // show some error messages: // $e->getMessage(); } } else { // ok - well do we have this user in the old CMS table? $query = $this->database->prepare("SELECT * FROM `cms_module_feusers_users` WHERE username = :login_name LIMIT 1;"); try { $query->execute(['login_name' => $ml_name]); } catch (PDOException $e) { die ('Error querying table : ' . $e->getMessage()); } // we've got a user from the old table if($query && $row=$query->fetch()) { $ml_feedback='Is in old table'; $hash=$row['password']; // handily the old auth just uses password_verify if(password_verify($ml_pass, $hash)){ // Add this user to the PW users $new_user=$users->add($ml_name); if($new_user){ $new_user->pass=$ml_pass; $new_user->addRole('members'); $new_user->save(); $log->save("new_members_site", $ml_name . " added as user"); $u = $session->login($ml_name, $ml_pass); if($u) { $session->redirect('/'); } else { die("Error in logging in. Please contact admin."); } $ml_feedback='new user added to PW'; }else{ $ml_feedback='Unable to add new user. Please let admin know'; } }else{ $ml_feedback='No matching records found.'; } }else{ $ml_feedback='No record found.'; } } } and this is the login form that the we had on our new login page - this and the above was all in a single template. <form id="ml_login_form" class="ml_login_form" method="POST"> <?php echo $session->CSRF->renderInput(); ?> <label for="ml_name">Username</label> <input id="ml_name" name="ml_name" type="text" value="<?= $ml_name ?>" required> <label for="ml_pass">Password</label> <input id="ml_pass" name="ml_pass" type="password" value="<?= $ml_pass ?>" required> <div style="display: none;"> <label for="ml_pass_bear">Password</label> <input id="ml_pass_bear" name="ml_pass_bear" type="text" value=""> </div> <button type="submit" name="ml_submit" class="butt">Submit</button> <div class="ml_feedback"><?= $ml_feedback ?></div> </form>
    4 points
  24. Hi @DrQuincy I think you can do this by hooking in to PW's login flow. Here's how I'd attempt to do this - untested - just to give you some inspiration... Create a new field on the User template for their hashed password from your old system. Write a script to populate this field where there's an email match between the old system and the new one. If the new system doesn't have users in it yet, then great, you'll be creating user records for everyone from the old system. Make sure you create a long, random, password for the initial processwire password on any new users you create and leave existing user passwords untouched. Store the hash from your system in the new field on the matching user and save any changes. You need a before hook on the session::login method. Use the name param (in $event->argument(0)) to match on emails if that's how you did it in the old system, if you have exactly one match, and this user has a non-empty value for your old password hash, use your existing pwd checking algorithm to authenticate them from the pass ($event->arguments(1)) parameter. If that works, the plaintext password they submitted to you is the correct password. Now set the PW password on that account using the plaintext password you just verified is correct AND delete the old password hash value. Now the correct password has been stored in the User's PW pass field, just exit the hook and PW should process the login as normal, or call the forceLogin method to log them in and do your own redirect. Basically, as people log in, they will auto-convert their accounts over to PWs way of doing things. Once they convert, the login process just reverts to using PWs method. Potential gotcha: more than one account in the PW system with the same email (if there are already accounts in there.) After a reasonable period has elapsed, you could then deal with seemingly dormant accounts by emailing non-converted users and asking them to login (or otherwise handle them as appropriate.) Hope that helps.
    4 points
  25. I've been working on this site profile over the last month and we got several updates to the site profile exporter thx to @ryan that make maintaining site profiles a lot smoother. It's not 100% battle tested yet, but if anybody wants to help finding bugs and improving the docs (by asking questions, which indicates where docs are missing), here you go: https://github.com/baumrock/site-rockfrontend It's a quite interesting setup that I came up with on my latest project. I'll share more info another day, if there is interest, but for my latest RockMonthly newsletter I didn't want to hold that information back and also I wanted to have a place where people can discuss.
    4 points
  26. @Christophe Thanks to Christophe for suggestion the PHP Session save path, I set it to my writable folder and now it works, had to do in this in PHP Cpanel settings
    4 points
  27. Hi @HarryWilliam and welcome to the forum! E-Commerce is a huge topic and there are many ways how to do it - unfortunately or luckily ... Option 1: Payment Buttons or Links Very simple solutions are payment buttons that Platforms like Paypal offer. You can see an example here: https://www.boukal.at/work/catalogues/catalogue-do-you-also-have-pretty-motifs/ Another option would be payment links that payment providers like stripe or mollie offer: https://www.mollie.com/gb/products/payment-links Pro: Very easy to implement Con: You have to manage different buttons/links for different products on your own, place them in your markup (for example with a textformatter), keep them up do date and you have very limited possibilities for customisations (like different options etc). Option 2: SaaS Shop integration The next option would be to add one of the SaaS solutions to your PW site. One option would be to use Snipcart, which is very simple to add to your site, at least in theory: https://snipcart.com/blog/processwire-ecommerce-tutorial Pro: Easy setup, working shop out of the box, they maintain and develop the product continuously and long term (hopefully) Con: You usually pay for it per purchase and/or you have a monthly fee. Snipcart for example costs at least 20$ per month if your sales are < 1000$. That's 240$ each year. https://snipcart.com/pricing Con: You need to add products to your SaaS shop. That means you can either not manage products directly from within your ProcessWire backend or you need to develop a bridge that keeps products in sync. Con: Using a SaaS Shop means you are locked to the features that this shop provides. In PW we have this module, but I'm not sure whether it's still maintained or will see any updates in the near future. Option 3: A custom PW shop This is maybe the most advanced solution. The benefits are that you get a fully integrated solution. You can manage all your products, all your users, all your orders etc. directly from within your PW backend. You can add hooks wherever you want and you can customise everything to make it work 100% the way you or your client wants. Imagination and your skills are the limit. The con is that it will likely be a more complex setup, as you need to understand the basic workflows of E-Commerce and you need to setup everything the way you want. As far as I know we only have Padloper 2 by @kongondo at the moment. I don't know the price, though, because the shop is down at the moment. I'm developing RockCommerce at the moment and it will hopefully be released during this year. The basic version is already done and you can see it in action on my website baumrock.com where I use it to sell my commercial modules, for example RockForms. As you can see on my website it can already be used to sell single products. It comes with a checkout (live example) but it has no cart functionality at the moment. But it already has a nice Dashboard with filters and charts, at least 🙂 Also, it can already create fully automated and 100% customisable invoices directly from within PW/PHP (using RockPdf) - which is something that not all shopping carts do for you, keep that in mind when evaluating those products! Thanks to the integration into the PW system it can send 100% custom emails to your customers, where you can make sure that you comply to your local legislation (for example in Austria we need to attach terms of service to that email): In this example it's a nice looking mail because I use RockMails, which is another module in the pipeline 🙂 But as it is 100% ProcessWire you can simply send an email with some lines of code as well: $m = new WireMail(); $m->from('office@your-company.com'); $m->to('your@client.at'); $m->subject('Thank you for your oder!'); $m->body('Congrats, you are now a ProcessWire hero!'); $m->send(); So while it is not yet 100% it can already be already a great option! If you want to get notified about the release you can subscribe to my monthly developer newsletter: https://www.baumrock.com/en/rock-monthly/ I'm quite sure there are 100 more options, but I tried to give an overview 🙂
    4 points
  28. It now also works on Mac with Firefox. Some other changes are also included. The new optimized and vanilla-js QuickSave version 0.1.7 is available as a new download in the first post.
    4 points
  29. @Chris-PW Welcome to the plugin-author's club :) Would you be able to create a github account and create a repository for this? It's the next step to getting this published in the PW modules DB.
    4 points
  30. I don't know if this will help you in your case, but I will take your partner example here to demonstrate my way of doing similar things. Partners wouldn't be content on its own. I see them more like data I can re-use. Therefore I need two templates: partners (parent for all partner entries - just to group the partner pages) Fields: title Template file: none partner (single partner page with all the data, content, images) Fields: title, desc, image, www, phone, email, ... Template file: none With this setup I can create a RepeaterMatrix block called partners and would add a PageReference field there that allows multiple entries. Probably AsmSelect to be able to sort those entries. Or a selector field to look up (template=partner, sort=random, limit=5) Now whereever I want to display a set of partners I add that partners block, select my partners and it's done. There could be multiple blocks for partners in RepeaterMatrix - so the layout can change easily. partnersLogo - just the logo partnersLogoLink - the logo linked to the partners website partnersLogoDesc - partner name and the description partnersCard - full card with logo, name, description, link to website With this setup I create those partner pages somewhere in the backend and reference those. Would even work in case your partners would be pages on their own. In terms of images I stopped worrying too much. There are several ways to handle images. For example: Tags. Tag images with hero, gallery, avatar, or whatever and select images based on your needs. While this works I started using multiple image fields. It's easier (for me) and I can upload optimized images for each use case. AND I can check how many partners don't have a hero image, og:image or gallery images super easy.
    4 points
  31. Looks like the next level of Business Email Compromise just became unlocked (if it wasn't already.) Lutra Security have a nice write-up on the attack they are calling "Kobold Letters". Basically, email clients change the DOM of the email when they forward it, allowing different CSS to apply between the first recipient viewing an email, and subsequent recipients. Allows an innocent-looking "request to forward to the CFO" phishing email to the CEO to become a "request to send funds/data" when received by the CFO from the CEO. Explict verification of the request contents needed now, not just a "Did you send me a message?" question which only verifies that the message was forwarded. Something to be aware of if you work in a team or are responsible for any kind of staff training on phishing etc.
    4 points
  32. Ran into the exact same problem a few minutes ago. Verified the [home] of composer with this: composer config --list --global And finally added it to my $PATH in .bashrc export PATH="$PATH:~/.config/composer/vendor/bin"
    4 points
  33. You can probably find the executable file from /home/<username>/.config/composer/vendor/bin -folder so running /home/<username>/.config/composer/vendor/bin/wirecli should work. If you want to use it without the path you should add that path to your shell $PATH-variable.
    4 points
  34. @szabesz @AndZyk Drill down to per-user activity is now behind basic auth, and the main page table is shorter now.
    4 points
  35. Just a note to say that I've added a new Page Meta section to the Request Info panel. This displays all meta entries for the page, along with links to open the entries for editing directly in Adminer. Speaking of Adminer - it was recently updated to AdminerEvo and I changed the way links from Tracy to open Adminer work - they now open the bar panel version which I believe is a much nicer experience than the standalone Adminer page. I also added links to edit the values of all fields within the Page Edit interface. If the field has multiple values, it will open Adminer showing all entries instead. This is of course optional, so you can visit the Adminer settings (now merged into Tracy's settings) to turn this off if you prefer. Please note that when you first upgrade, you will likely need to do a modules > refresh to get Adminer to load properly the first time.
    4 points
  36. Yeah that might be more elegant in some situations. Actually you don't even need to put it in a hidden div, you can put it in your markup in a latte comment for example. So those classes will not end up being rendered in your sites markup, but still tailwind will catch them for the final css. I prefer to place markup related things in markup files (*.latte) as for example when working on a "text" block for RockPageBuilder then I want it to contain all necessary markup for that block. So when moving that block into a new project it will still work and I will not have to mess with the tailwind config... But workflows and preferences are different 🙂
    4 points
  37. New docs about the RockPageBuilder API that makes it easy to import content from old websites and convert it to RockPageBuilder blocks 😎 https://www.baumrock.com/en/processwire/modules/rockpagebuilder/docs/api/
    4 points
  38. I'm using uptime kuma for monitoring my websites and it looks like it can do what you want: Oh, and I'm using https://www.statuscake.com/ to monitor my monitor 😄 So as uptime kuma is self hosted and needs some time to setup you'd maybe better of with statuscake which offers 10 monitors for free. I just checked and you can use GET and POST
    4 points
  39. "if ($article->gallery_images)" just works if the field is set to one image only. You have to use "if (count($article->gallery_images))" if you want to check whether one or more images are available. https://processwire.com/docs/fields/images/ -> "How to tell if a page has images present"
    4 points
  40. @Denis Schultz - new version of Tracy now uses AdminerEvo. Thanks again for letting me know about this new fork. Hopefully it has a long life. I had to tweak a few things so if anyone see any issues (should be mostly cosmetic hopefully), please let me know.
    4 points
  41. HA! It's ProCache's markup minification! First test with ProCache on (html minification for guests): Second test with ProCache off: I'll post that into the ProCache forum! Thx for your help guys!
    4 points
  42. Checking all 3 of these will probably do what you want, but I actually never use the second two because I make use of the "Enable Guest Dumps" button from the panel selector. Enable that, do what you need to do as a guest in a private window and then reload the PW admin where you are logged in as a superuser and you'll get the dumps recorded by the guest. Another thing is to make sure you have the "Tracy Exceptions" panel enabled so that if a guest user interaction results in an exception, the bluescreen with full stack trace will be made available for easy viewing. I rely on this for all my sites with Tracy's production mode. Hope that helps.
    3 points
  43. Hey @Neue Rituale. Yes, Padloper is very much still being developed. Version 009 is due for a release this week (I have been dealing with a number of issues, including this one).
    3 points
  44. Here's a small module I wrote a few years ago and was asked to share in the module repo. TextformatterImgDataUri This Textformatter checks all images in the field's markup for images under a certain size and converts those from links to data URLs, i.e. it embeds the image data itself. This can be handy when you cache whole pages and want to cut down on the number of requests. Original post with the module code:
    3 points
  45. @iank I had a look at this and it seems like it can happen if you omit homepage language segments, and are also using the PagePathHistory module. What happens is that PagePathHistory provides a shortcut for finding the page, leaving no language segments for PagePathHistory to detect the language. I don't think PagePathHistory supported multi-language URLs until 3.0.186, so maybe that's when the issue started. Though the entire URL-to-page mapping system has been rewritten since 3.0.165 via the new PagesPathFinder class, so who knows. I think maybe it's not been reported before because just about everyone is using language segments on the homepage, and I always recommend doing so, but didn't intend to require it. I've attempted a fix on the dev branch, it's just one line of code, so maybe it'll work just to modify your copy too. Do you find this fixes it there? https://github.com/processwire/processwire/commit/9e6b89cf9331fccb8f41ae0b1785e21c8c5d62f0
    3 points
  46. Have a great weekend and Happy Easter, @ryan.
    3 points
  47. Hello all- FieldtypeFormSelect does what the post title says it does. Lets you create a field to select a form created by the FormBuilder pro module. This type of field (or some variation of it) has probably been done elsewhere but I put this together with a few extra considerations for flexibility and utility. When creating a form select field you can choose what forms will be present and how their names will be shown. Let's go to the pictures: A form select field: Creating fields to choose from specific forms? You have options. You can also create a field that will only include forms that begin, include, or end with a specific string. This allows you to create a field once, then use form names to help group them together and add/remove them from form select elements without editing the field. This is also a pretty simple way to allow end users to create forms that will be selectable without having to ever edit a field configuration. For example, this field will only allow you to choose forms having names enting with "request", so "customer-support-request" and "consultation-request" will be included, but "newsletter-signup" and "call-to-action" won't. You can also choose how the form names will be presented in the select element. They can be shown as they are originally named, as spaced words, or as capitalized/spaced words. So, how does this field work? Form select fields store the ID of the form you select, but it also has a nice trick for working with forms in your markup too. FieldtypeFormSelect makes use of ProcessWire's built-in field rendering to keep things simple. Let's go to the code. <?php $page->select_a_form; // => Outputs the form ID, or null if no form has been selected // You could do this to output your form markup echo $forms->render($page->select_a_form); // Or you can do it this way. If a form has been selected the markup will be output to the page, if no form is selected, the output will be null. echo $page->render('select_a_form'); The fields you create will always be up-to-date with the forms that have been created in FormBuilder. This module also keeps things tidy- if a form is deleted that has been selected in one or more fields, on one or more pages, the values for those fields will be set to null so you won't experience any reference errors to form IDs that no longer exist. My primary use is to have a form select field available for blocks created in the RockPageBuilder module by @bernhard. I wanted each section on the page to contain an option to include a call to action button that can be a link to a page, a link to another URL, or that can open a modal with a call to action form to capture leads and visitor contact information. It's a great way to easily add flexibility and give some extra power to the end user when considering what they want visitors to do when browsing their website. RockPageBuilder is not required, but makes for a useful example! Protip for website designers- in my experience and empirical study of conversion analytics for sites that I've built, buttons located within sections of content on the page captured more leads and outperformed a call-to-action button in the page header, the call-to-action form at the bottom of the page, and forms located on a "Contact Us" page- by far. The true purpose of this field is to get the right forms in the right places quickly and easily without any need to work around markup output strategies or short codes. Contributions on Github are welcome if there's some extra functionality anyone wants to add that makes sense. Please let me know if you run into any bugs as well, when there's some extra usage and testing I'll submit it to the modules directory. Hope you find it useful! https://github.com/SkyLundy/FieldtypeFormSelect
    3 points
  48. Bad news for all of us: https://github.com/tinymce/tinymce/discussions/9496 "....we have decided to release TinyMCE 7 under the GNU General Public License Version 2 or later, abbreviated as GPLv2+..." To see why this is pretty bad news, read: https://github.com/BookStackApp/BookStack/issues/4908 BTW, I am a happy admin user of BookStack which is highly recommended! Now BookStack and ProcessWire share the pain of TinyMCE moving away from MIT :(
    3 points
  49. “Gentlemen you had my curiosity ... but now you have my attention.” ― Quentin Tarantino, Django Unchained
    3 points
×
×
  • Create New...