Jump to content


  • Posts

  • Joined

  • Last visited

  • Days Won


FireWire last won the day on June 24

FireWire had the most liked content!

1 Follower

About FireWire

  • Birthday January 1

Profile Information

  • Location
  • Interests
    Writing code. Writing more code. Refactoring. Writing code.

Recent Profile Visitors

4,208 profile views

FireWire's Achievements

Sr. Member

Sr. Member (5/6)



  1. Would be great to see .env support. I think this base change would level up ProcessWire and make it an increasingly viable application framework that fits into modern workflows. I think this would be a great idea. It would greatly help module development where modifying or appending behavior to existing elements is needed. I could see that having been useful when developing my Fluency module. I'll throw a thought in about multi-site capability after considering multi-tenancy for a Laravel project using a module. May be a nice feature but if there's a tradeoff for future features and development speed due to maintenance overhead, both for ProcessWire core and modules, it may not pencil out. I know it's not apples to apples, but creating two sites and a private API between them has worked for me in the past.
  2. @bernhard Wish I had more information but the only step was installing the module. I did some digging but am still coming up short. I removed the custom page classes and still got the error, so we can rule that out. Here's more detailed output after attempting to install again: The site is set up with language support, so that missing method error should not exist. I'm struggling to locate where exactly this issue is coming from because $page->localPath() is working everywhere else. Maybe this is an issue with language support installed before RockSettings is installed? I noticed this on lines 398-400 in SettingsPage.php <?php // make redirects be single-language $tpl = $rm->getRepeaterTemplate(self::field_redirects); $rm->setTemplateData($tpl, ['noLang' => true]); Shot in the dark, having trouble narrowing down where to look to find more information.
  3. @bernhard Well, I hate to be that guy today (haha) but one more issue and I think it's related to custom page classes(?) Seems odd because DefaultPage extends Page. ProcessWire version 3.0.229 and RockSettings version 2.2.1
  4. @bernhard It's the strangest thing... I am trying to set it to 'Hidden'. Which works when editing in template context. But after a modules refresh, it comes back with the original visibility. That's the only steps I can come up with 😆 All of the fields under that 'Media' fieldset keep popping back up after a modules refresh.
  5. @bernhard I am hiding some fields using the template context settings, but only the fields I mentioned aren't keeping those settings. All of the other fields keep the visibility settings.
  6. Hey @bernhard Everything is working great, just having an issue with fields having their Visibility re-set to 'Open' after refreshing modules. The only affected fields are Logo, Favicon, og:image, Images, and Files. So only the fields under the 'Media' fieldset. All of the other fields keep the visibility as configured in the template settings. Using version 2.0.1 Thanks!
  7. @bernhard Your modules have so many features I'm still getting to know them all haha. Awesome, will keep that in mind as well. I think I'm going to need these features a lot more on the project I'm getting started on.
  8. I meant to mention that I didn't know about that method before your comment. Now that I know about it though I'll probably end up using it. So thank you for the tip! There was one instance where it may have come in handy. I created a PageClass method that analyzes the current page request to determine if it was an AJAX request for a specific purpose, but decided to just call $page->whateverMethdName() at the top of the template before anything rendered. The loaded() method is the way I'll go with that next time. I learned something new today 😎 Bookmarking this. Keeping hooks closer to the location/context that makes sense is a good idea.
  9. Well, funny story, I have no reasoning. I just misremembered between reading that and hacking out this post. Jumping between projects in and outside of ProcessWire didn't help haha. I've used DefaultPage in the past, it was just an oversight this time around. Ryan's is of course the correct way. I generally always have a custom page class for each of my templates so I've never had an issue where DefaultPage was loaded on it's own. Appreciate you mentioning it, I've updated the post to use DefaultPage. My first reaction is that unexpected behaviors may indicate that there may be too much abstraction. My DefaultPage classes have traditionally been lightweight and stick to only containing what can be reliably counted on as valuable across all pages. One of the criticisms of OOP is that classes and inheritance can make for brittle code so it's something that I try to be mindful of. It may also be good to try an keep anything that executes automatically or hooks into the Page lifecycle to a minimum (maybe none at all) in DefaultPage since it spans the entire application. I use names like these myself for the same reason. Really helps keep a sane number of fields and reduce redundancy. I haven't taken this approach to reassigning values to properties like this but it's an interesting idea. I reviewed the code for the Page class and there is a good amount of logic in Page::set() that can be useful when working with values but may not be needed within the context of a page class. At first blush though I would just assign the value of the field to an instance variable directly rather than call Page::set() because at time of authorship- you know what field you'll be working with, it's data type, and intended usage within the context of that template/page. <?php public $introTitle; public $introText; // set custom properties in loaded public function loaded() { // load custom properties only for allowed statuses if (!empty(array_intersect(array_keys($this->status(true)), self::excludeStatuses))) return; $this->introTitle = $this->text; $this->introText = nl2br($this->textarea); } I may be missing the added value of using Page::set() in your use case though. My other thought though is that if these fields have consistent and wide enough usage through all templates, enough to reassign them to alternate property names, they might be a candidate for dedicated fields rather than repurposing generalized fields. Both introTitle and introText seem like they have wide enough usage, so I would create those fields and skip any extra logic needed here in DefaultPage. I actually haven't set a lot of instance variables in page classes. I've focused on methods that can abstract logic out of templates, or perform computationally expensive operations only on demand. Here's a DefaultPage from a production site: <?php namespace ProcessWire; use Ramsey\Uuid\Uuid as Uuid; use \DateTime; class DefaultPage extends Page { /** * Paths for rendering methods */ const COMPONENTS_PATH = '/../templates/components/'; const FORMS_PATH = '/../templates/components/forms/'; const PARTIALS_PATH = '/../templates/partials/'; const SVG_PATH = '/../templates/img/svg/'; /** * Stub method that may be overridded by inheriting classes where needed */ public function renderStructuredData(): ?string { return null; } /** * Outputs full path and filename for CSS files */ public function css(string $file): string { return wire('config')->urls->templates . "dist/styles/{$file}"; } /** * Outputs full path and filename for JavaScript files */ public function js(string $file): string { return wire('config')->urls->templates . "dist/scripts/{$file}"; } /** * Returns contents of an SVG file with unique IDs for title/description attributes for accessibility */ final public function renderSvg(string $file, array $variables = []): string { $variables = array_merge($variables, [ '{ARIA_TITLE_ID}' => Uuid::uuid4()->toString(), '{ARIA_DESC_ID}' => Uuid::uuid4()->toString(), '{CLASSES}' => $variables['classes'] ?? null, ]); return strtr(file_get_contents(__DIR__ . self::SVG_PATH . $file), $variables); } /** * Renders a file in /templates/partials/ */ final public function renderPartial(string $file, array $variables = []): string { return wire('files')->render($file, $variables, [ 'defaultPath' => __DIR__ . self::PARTIALS_PATH ]); } /** * Renders file located in templates/components/ */ final public function renderComponent(string $file, array $variables = []): string { return wire('files')->render($file, $variables, [ 'defaultPath' => __DIR__ . self::COMPONENTS_PATH ]); } /** * Renders a form */ final public function renderForm(string $file, array $variables = []): string { return wire('files')->render($file, $variables, [ 'defaultPath' => __DIR__ . self::FORMS_PATH ]); } /** * Gets the time period of the day based on the hour */ final public function getPeriodOfDay(): string { $hour = (new DateTime())->format('H'); switch (true) { case $hour < 12: return 'morning'; case $hour >= 12 && $hour <= 17: return 'afternoon'; default: return 'evening'; } } /** * Returns all top level pages for the main navigation */ final public function topLevelPages(): PageArray { $pages = wire('pages'); $selector = 'parent=1'; $excludedIds = $pages->get('template=admin_website_settings') ->nav_main_excluded_pages ->explode('id'); // Check for excluded pages from nav defined in the "Website Settings" page count($excludedIds) && $selector .= ',id!=' . implode('|', $excludedIds); $homePage = $pages->get(1); $topLevelPages = $pages->find($selector); $topLevelPages->prepend($homePage); return $topLevelPages; } } All of the other magic goes into traits or inheriting page classes. My soft guidelines when it comes to code organization is: Save abstraction until something is repeated 3 times, consistency should be maintained, or complexity warrant it for the sake of the surrounding code. Abstract to a trait or enum that can be selectively used where needed. Consider abstracting to a parent class last, there has to be a case for why it should be shared between all classes because it's more than ubiquity, it comes down to rock solid shared behavior and purpose
  10. This is incredibly useful and could not come at a better time!
  11. I'd be happy to provide something like that. I'm really short on free time right now but I may be able to adapt a real life example once I get enough done on one of my projects. That way I can avoid double work and have a good example. Will keep it in mind and bring that info back here as a demo repository when I can do it right. I've implemented the exact same method on sites before and that's a great example. I like to create a jsonld() method which returns null in my BasePage class that exists specifically to be overwritten by inheriting pages. That way I can call $page->jsonld() in a partial like site_header.php without any logic, it renders something or it doesn't.
  12. I second this wholeheartedly. It's truly too much to cover in a comment, but this is what I mean when I said "thinking with objects". It requires starting to work with encapsulation and breaking down/dividing your code into behavioral chunks. I'm pretty sure at some point I read the book that @da² recommended. The Head First series really does explain things in ways that I have seldom seen elsewhere. Also +1 for real life books, I find that they let me "unplug" from the problem at hand and focus on the content presented. In my closing list of items I mentioned "single responsibility", "composition over inheritance", "does not expose how it calculates", and an example of how properties and methods can each have their purposes and roles. You're going to immediately be introduced to these fundamental concepts as soon as you start down the OOP path, I peppered them in to contextualize them in ProcessWire. I've referenced this site before and it's a good next step. You'll write OOP code and sometimes think "there's got to be a better way to do this", and these are the abstract concepts that you'll be able to take into consideration. Just be mindful of "premature optimization" and get comfortable with OOP to the point where before you even start coding, your mental model will naturally begin with OOP. Approach LLMs with caution while learning. They require that you ask a question the right way and may not tell you why what you asked maybe isn't the right question to ask in the way a human can. You also have to be familiar enough with the concepts to recognize when they give you the wrong answer. LLMs require large scale data ingestion and that means their models are indiscriminately built with code examples whether they're good or bad. You'll get there but they're wrong, a lot, so maybe consider this one a little further down the road. Just my two cents.
  13. Very happy to hear you enjoyed it!. Writing a course on anything hasn't ever crossed my mind haha. I just put this together to share a great ProcessWire feature that has really boosted my workflow and thought maybe sharing some examples might be useful to others. I hadn't thought about this post as a walkthrough on OOP specifically, but I did want to be descriptive in a way that would be useful to as many people as possible- whether you are familiar with OOP or not, and without any assumptions. All said, I'm happy to try and answer questions if it would help anyone ?
  14. If anyone's interested, I posted a deeper dive into page classes with more real world examples. I like page classes so much I had to write a love letter.
  • Create New...