-
Posts
733 -
Joined
-
Last visited
-
Days Won
11
Posts posted by psy
-
-
@Soma So happy to see you back in the PW forums 🙂
Have been using your MarkupSimpleNavigation module for years without a problem, until well on current project. Timely!
Problem was that current pages were not always being recognised as such.
Fix was in function _renderTree line 211 - change:
//$is_current = $child === $page; $is_current = ((int) $child->id === (int) $page->id);
Credit to you that MuSNav has been solid for so long.
Cheers
psy-
3
-
-
@maximus You're on fire 😄
-
3
-
-
7 hours ago, maximus said:
project-summary.md no longer overwritten on re-export Preserves your session history and changes Only created on first export
Thanks @maximus Does it state in the docs that the project-summary.md should be updated at the end of each session via a prompt? Didn't RTFM 🙂
-
5 hours ago, ryan said:
phpstorm integration
@ryan It's configured in PHPStorm settings. I don't have a JetBrains AI Service subscription. Went straight to OpenAI integration. https://www.jetbrains.com/help/ai-assistant/use-custom-models.html
-
4
-
-
15 minutes ago, szabesz said:
Not everyone can afford Claude, including me.
Ditto, I use ChatGPT (Marvin) and Codex (Dex). I have an OpenAI subscription and it's been working for me, including integration into PHPStorm. Both Marvin and Dex know A LOT about PW. @maximus module, Context
has been brilliant with Codex when closing an IDE season then starting a new one while continuing the conversation thread.
-
3
-
-
Have been working with AI for a while, learning on the go. A client presented with me a grand plan presented by Codex for his blog articles. I almost cried. Talked it over with my LLM, Marvin (named in honour of Marvin, the paranoid android of Hitchhikers Guide to the Galaxy fame). His/its response:
There are two very different modes of using AI: 1. Guided use (what you and PW devs are doing) clear constraints known architecture intentional prompts validation after output Result: leverage 2. Unguided use (“vibe coding”) unclear structure shifting goals reliance on memory that doesn’t exist little to no testing Result: drift
Think Marvin nailed it. 🙂
Just asked Marvin to review @ryan's blog article. I think he nailed it again:
The quiet takeaway from the article This is the important bit most people will miss: AI makes good systems better and weak systems worse ProcessWire is a good system because: it’s explicit it’s predictable it avoids hidden complexity That’s why AI fits.
-
4
-
1
-
-
Human in the loop, aka HITL. The latest acronym and IMHO vital for devs.
-
1
-
1
-
-
Untested... and just a starter idea for your use case, but maybe via a hook in site/ready?
if ($input->urlSegments->count > 0 && $input->urlSegment(1) === 'sitemap.xml) { rtrim($input->urlSegmentStr() '/'); };
-
Further to above, there was an issue with the sitemap handling urlsegments with the canonical link. Another hook solved it for me:
Spoiler// update canonical, twitter and OG urls with urlSegments $wire->addHookAfter('SeoMaestro::renderMetatags', function($event) { $name = $event->arguments(0); $group = $event->arguments(1); if(!is_array($name) || !count($name)) return; if(is_null($group)) return; $input = $this->wire('input'); $segments = $input->urlSegments(); if(count($segments) < 1) return; $pageUrl = $this->wire('page')->httpUrl; $urlWithSegs = rtrim($pageUrl, '/') . '/' . $input->urlSegmentStr(); switch($group) { case 'twitter': case 'opengraph': if(!isset($name['url'])) return; $name['url'] = str_ireplace($pageUrl, $urlWithSegs, $name['url']); break; case 'meta': if(!isset($name['canonicalUrl'])) return; $name['canonicalUrl'] = str_ireplace($pageUrl, $urlWithSegs, $name['canonicalUrl']); break; default: break; } $event->return = $name; });
-
19 hours ago, szabesz said:
FI you turn off an option after it has been turned on and files have been generated, you will need to manually delete the files that are no longer needed.
@szabesz I get that except for project-summary.md. That file must stay as is when re-exporting or the change history will be lost
-
1
-
-
This module is great for establishing the rules for AI. Something I needed was a way to continue the narrative between sessions. Here's what my AI suggested as a prompt:
SpoilerCreate a structured project checkpoint summary.
Output MUST follow this exact format and headings. Use short bullet points only. No paragraphs.
## Project
(one line)## Current State
- ...## Decisions Made
- ...## Known Issues
- ...## What We Tried
- ...## Constraints
- ...## Next Steps
1.
2.
3.## Do NOT Do
- ...Rules:
- Be concise and factual
- Do not explain reasoning unless critical
- Do not invent anything not discussed
- Prefer clarity over completenessSave this as:
site/assets/cache/context/prompts/project-summary.mdOverwrite the file.
Do not add any extra commentary outside the file contents.That way I can close an IDE session and use both Context prompts and project-summary.md to bring the AI up to date.
-
1
-
-
Hi @maximus
Hit a red warning when trying to export field definitions for FieldtypeQRCode. This fieldtype uses FieldtypeQRCode.info.php instead of getModuleInfo() inside the module.
Fix in exportFieldDefinitions line 1928:
// 'label' => $field->type->getModuleInfo()['title'] ?? $className, 'label' => method_exists($field->type, 'getModuleInfo') ? $field->type->getModuleInfo()['title'] : $className,
HTH
-
1
-
-
Still using Seomaestro. Discovered a scenario where it missed pages in the sitemap that used urlsegments. In my case, it was the blog module authors page. It should also handle paginated pages but untested. Hope this helps.
Spoiler$wire->addHookAfter('SeoMaestro::sitemapItems', function(HookEvent $event) { $items = $event->return; $pages = wire('pages'); $users = wire('users'); $roles = wire('roles'); $config = wire('config'); $seen = []; foreach($items as $item) { if(!empty($item->loc)) { $seen[rtrim($item->loc, '/')] = true; } } $getSeoSettings = function(Page $page) { foreach($page->template->fieldgroup as $field) { if(!$field->type instanceof FieldtypeSeoMaestro) { continue; } $seo = $page->get($field->name); if(!$seo || !$seo->sitemap->include) { return null; } return [ 'priority' => $seo->sitemap->priority, 'changefreq' => $seo->sitemap->changeFrequency, ]; } return null; }; $appendItem = function($url, $lastmod, array $settings) use (&$items, &$seen) { $key = rtrim($url, '/'); if(isset($seen[$key])) { return; } $item = new \SeoMaestro\SitemapItem(); $item->set('loc', $url); $item->set('lastmod', date('c', (int) $lastmod)); $item->set('priority', $settings['priority']); $item->set('changefreq', $settings['changefreq']); $items[] = $item; $seen[$key] = true; }; $appendPaginatedItems = function(Page $page, $totalItems, $perPage, $lastmod) use ($config, $getSeoSettings, $appendItem) { $settings = $getSeoSettings($page); if(!$settings) { return; } $totalPages = (int) ceil($totalItems / $perPage); if($totalPages < 2) { return; } for($pageNum = 2; $pageNum <= $totalPages; $pageNum++) { $appendItem( $page->httpUrl . $config->pageNumUrlPrefix . $pageNum . '/', $lastmod ?: $page->modified, $settings ); } }; $blogPostsPage = $pages->get('template=blog-posts'); if($blogPostsPage->id) { $latestPost = $pages->get("template=blog-post, sort=-modified"); $appendPaginatedItems( $blogPostsPage, $pages->count("template=blog-post"), 8, $latestPost->id ? $latestPost->modified : $blogPostsPage->modified ); } foreach($pages->find('template=blog-category, include=hidden') as $categoryPage) { $latestPost = $pages->get("template=blog-post, blog_categories=$categoryPage, sort=-modified"); $appendPaginatedItems( $categoryPage, $pages->count("template=blog-post, blog_categories=$categoryPage"), 10, $latestPost->id ? $latestPost->modified : $categoryPage->modified ); } $authorsPage = $pages->get('template=blog-authors'); $authorSettings = $authorsPage->id ? $getSeoSettings($authorsPage) : null; if($authorsPage->id && $authorSettings) { $authorRole = $roles->get('blog-author'); foreach($users->find("roles=$authorRole, sort=title") as $author) { $authorSlug = wire('sanitizer')->pageName($author->title); if(!$authorSlug) { continue; } $postCount = $pages->count("template=blog-post, created_users_id=$author"); if(!$postCount) { continue; } $authorUrl = $authorsPage->httpUrl . $authorSlug . '/'; $latestPost = $pages->get("template=blog-post, created_users_id=$author, sort=-modified"); $lastmod = $latestPost->id ? $latestPost->modified : ($author->modified ?: $authorsPage->modified); $appendItem($authorUrl, $lastmod, $authorSettings); $totalPages = (int) ceil($postCount / 10); if($totalPages < 2) { continue; } for($pageNum = 2; $pageNum <= $totalPages; $pageNum++) { $appendItem( $authorUrl . $config->pageNumUrlPrefix . $pageNum . '/', $lastmod, $authorSettings ); } } } $event->return = $items; });
-
1
-
-
Hi @maximus
Loving your Context module!
One small change request. My sites use custom, nested, vanilla CSS. There's no option in the stack (or I've missed it?). It defaults to 'Tailwind'. My workaround is to override the Tailwind setting in the extra comments. Be nice to have that option in the module config.
Keep up the great work!
-
1
-
-
So many ways to create great websites.
I use Markup Regions too. In _main.php I have regions:
// in <head> <region id="regHeadCSS"> </region> // just above the closing </body> tag <region id="regFooterScripts"> </region>
then on a per template basis I can "pw-append" any CSS or JS specific to an individual template. If no extra CSS or JS needed, no problem.
-
3
-
-
I have never been loyal to tools for the sake of it. If something stops earning its keep, I move on. The reason I have stayed with ProcessWire for close to ten years is simple: it continues to make sense for how I work.
I still look after sites I built many years ago, and most of them just run. No rewrites, no upgrade stress, no feeling that past work is a liability. The API has stayed stable, and when it has changed, it has been deliberate and predictable. That matters when you are responsible for client sites long-term.
What really locked me in early on was the front-end freedom. PW never told me how a site should look or behave. It gave me solid building blocks and allowed me to choose. I can build very different sites without switching platforms or fighting opinionated defaults, and that freedom is something I value.
The forum is another reason I am still here. You, the people in this community, take the time to understand a problem before jumping to solutions. That is very rare. The discussions are thoughtful, practical, and grounded in real experience, and I have learned a lot simply by reading how others approach things.
And finally, trust. I trust ProcessWire not to chase trends simply for attention, and not to trade clarity or performance for fashion. Ten years on, it still feels like a system built by people who actually build websites. For me, that combination has been hard to beat.
-
29
-
-
Thank you @bernhard for all the help and support you've given PW devs, including me, over the years. I did notice drop off in your forum posts recently and wondered what was going on. Appreciate you sharing your thoughts and decisions. Wish you well in your new ventures and hope this isn't the end of your PW connection.
-
1
-
1
-
-
On 1/8/2026 at 9:29 AM, Jonathan Lahijani said:
I'm already what feels like being done with 90% of it. I'm going to finish the remaining 90% (heh)
Feeling it! 🤣
-
On 4/5/2023 at 5:22 AM, HannaP said:
Hello all,
I'm about to make my site fit for php8.1 (it's php8.0 at the moment).
From this ImportPagesCSV module I get deprecation warnings:
Deprecated: auto_detect_line_endings is deprecated in XXX/site/modules/ImportPagesCSV/ImportPagesCSV.module on line 112 Deprecated: strlen(): Passing null to parameter #1 ($string) of type string is deprecated in XXX/site/modules/ImportPagesCSV/ImportPagesCSV.module on line 229What to do?
(PW Version is 3.0.210)
Had the same problems and found the solution. Added to github issues at https://github.com/ryancramerdesign/ImportPagesCSV/issues/29 In short:
Change:
public function init() { parent::init(); ini_set('auto_detect_line_endings', true); }to:
public function init() { parent::init(); // auto_detect_line_endings is deprecated in PHP 8 if (PHP_VERSION_ID < 80000) { ini_set('auto_detect_line_endings', '1'); } }and change:
if(strlen($value)) { $f->attr('value', $value === "\t" ? 2 : 1); } else { $f->attr('value', 1); }to:
if ($value !== null && strlen($value)) { $f->attr('value', $value === "\t" ? 2 : 1); } else { $f->attr('value', 1); } -
Thanks @ryan,
Knew there were different ways to create a PW API variable object. Until I read your blog, I didn't understand the nuances of when, where and which method best suits the context.
Like @Ivan Gretsky,
21 hours ago, Ivan Gretsky said:I really enjoy reading about how to use PW, tips and tricks and such.
-
2
-
-
17 minutes ago, BFD Calendar said:
I do have older PW wire versions on my site
I didn't have the same version of PW locally either. I went to PW github and downloaded the next release version from what was on the site.
-
13 hours ago, matjazp said:
I suspect that restore was not complete, maybe some files are missing or permissions are not correct? Can you roll back to PW 3.0.123?
Had a similar nightmare today. Nothing to do with PW itself. Some files were corrupted during upload to the site.
Fix was to do a manual PW upgrade, ie:
- rename wire dir to .wire-3.0.xxx (whatever version) and do the same for index.php
- upload clean wire directory and index.php
- To be on the safe side, I also re-uploaded site files I knew were clean
Everything then worked 100% as expected.
Hope this helps
-
1
-
After another battle of wills:
AI Agent: Couldn’t agree more, psy — Ryan really thought ahead with those little conveniences.
$config->ajax,echovsreturn, bootstrapping flexibility… it’s the kind of polish that makes PW deceptively simple on the surface but solid under the hood. -
I'm experiencing a similar issue.
The default language is English. The other language is French.
Even when I set the guest user language to French in the user profile, it defaults to English on render.
How do I get PW to recognise the guest user language?
Prompt Manager
in Modules/Plugins
Posted
ProcessPromptManager is a ProcessWire admin module for generating site-aware prompt exports for external AI agents.
It allows an administrator to select a template, define which fields an agent should populate, add instructions, and export a zip containing a markdown prompt and JSON field definitions.
The module does not handle authentication or integration. It provides a controlled starting point for accepting structured input from external agents. Never include credentials or sensitive data in prompts.
Module info: https://github.com/clipmagic/ProcessPromptManager/