Jump to content

Leaderboard

Popular Content

Showing content with the highest reputation since 03/16/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/
    15 points
  6. It has been a great meeting yesterday 😎 We are working on something... 🙂
    14 points
  7. 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
  8. 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
  9. The invoices application site profile that I uploaded last week now has a companion blog post: https://processwire.com/blog/posts/invoices-site-profile/ Thanks for reading and have a great weekend!
    9 points
  10. 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
  11. 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
    7 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. 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
  16. 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
  17. 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" 😄
    6 points
  18. 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 🙂
    6 points
  19. @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
  20. 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
  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 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
  24. 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
  25. 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
  26. 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
  27. @szabesz @AndZyk Drill down to per-user activity is now behind basic auth, and the main page table is shorter now.
    4 points
  28. 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
  29. 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
  30. 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
  31. 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
  32. "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
  33. @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
  34. 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
  35. I'd like to introduce you to my newest employee: Devin
    4 points
  36. @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.
    3 points
  37. 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
  38. If anyone sticks to using a version of PHP that old, I think they should also be okay with not upgrading modules.
    3 points
  39. @TomPich I wouldn't recommend caching a field value like that, as I don't think you'll get any performance benefit since the amount of work for PW to do is the same either way there. What I'd recommend doing instead is caching the resulting output, which also means only loading the repeaterConsultants when it will be used to generate that cached markup. Whatever markup you generate, use that as your cache value, for example: echo $cache->get('consultants', 'hourly', function() use($pages) { $out = ''; $consultants = $pages->get(1027)->repeaterConsultants; foreach ($consultants as $consultant) { $out .= "<li>$consultant->consultantName</li>"; } return "<ul>$out</ul>" ; });
    3 points
  40. @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
  41. Thank you for the site profile and especially the blog post and extensive comments throughout the code. It's extremely helpful for overall architecture examples, which is something hard to find by just reading documentation or searching forum, because recommended practices have changed over the years. Especially love learning how you do things in the admin side for customizing the page edit screens, etc.
    3 points
  42. @protro you could try to avoid FOUC by loading you oswald.css earlier or even one step durther and inlining the webfont css part into your head part
    3 points
  43. Really great writeup @artfulrobot You might find that TracyDebugger helps with this to some degree - its "Processwire Logs" panel combines all logs into one ordered reverse chronologically. It also highlights when you have new errors / notices since you last viewed the site.
    3 points
  44. What I really love about AdminStyleRock is that you simply set one color and the PW backend immediately looks like it was built only for this client: But I'm not a Designer, so I just kept it very much like the default uikit theme. What I don't like is that several parts, especially the important page edit screen and all the inputfields have very low contrast and as I'm using dark mode now I think all the white is quite heavy on the eyes. What I don't like on the reno theme though is that it has totally unique and opinionated colors that never ever fit any of my client's CI. It's really easy to adjust everything of the style using simple css/less, but unfortunately several years passed by and nobody did improve this foundation. I wonder if some designers are still afraid of messing around with the css/less, so I want to ask if we have any designers here that want to work in improving the style in collaboration with me? We can do video calls or I can try to work from figma designs. Maybe we could even think of adding a light/dark mode toggle as we are working on it. Another thing that is not ideal is that it seems to be impossible to find all existing admin styles in the modules directory at the moment? Using the search I found these: In terms of design I think @Noel Boss AdminThemeBoss is very nice, but I don't like that it's an AdminTheme and not just a Style. And I'm not sure if he is still active in the PW world? If anybody has opinions to share or ideas on how to improve the situation please go ahead!
    3 points
  45. 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
  46. Any plans to switch to the maintained fork: https://github.com/adminerevo/adminerevo ?
    3 points
  47. “Gentlemen you had my curiosity ... but now you have my attention.” ― Quentin Tarantino, Django Unchained
    3 points
  48. InputfieldText (and other inputfield types that extend InputfieldText such as InputfieldTextarea) has a "noTrim" setting that is false by default, which is what causes leading and trailing whitespace to be trimmed out of the field value. This setting isn't included in the config options for text fields (not sure why, perhaps because it's rarely needed) but you can add a config option for it with a hook: $wire->addHookAfter('InputfieldText::getConfigInputfields', function(HookEvent $event) { /** @var InputfieldText */ $inputfield = $event->object; /** @var InputfieldWrapper $wrapper */ $wrapper = $event->return; $field = $inputfield->hasField; // Only for inputfields that are associated with a Field object if(!$field) return; // Add checkbox field to config to control noTrim setting /** @var InputfieldCheckbox $f */ $f = $event->wire()->modules->get('InputfieldCheckbox'); $f->name = 'noTrim'; $f->label = 'No trim'; $f->label2 = 'Do not trim whitespace from the field value'; $f->checked($field->noTrim); if(!$field->noTrim) $f->collapsed = Inputfield::collapsedYes; $wrapper->add($f); }); This will add a "No trim" checkbox to text fields, and if you tick the checkbox the field value won't be trimmed. Result:
    3 points
×
×
  • Create New...