Jump to content

Recently Updated Topics

Showing topics posted in for the last 7 days.

This stream auto-updates

  1. Today
  2. Hi @adrian When uploading and inserting an image into a field (e.g., a TinyMCE "body" field) with this module active, any UTF-8 characters (like Greek letters) in other Textarea fields on the same page (e.g., a plain text "seo_description" field) are unexpectedly converted into HTML entities (e.g., "Ξ±" becomes "&alpha;" ) and saved to the database. I asked Gemini to provide a solution. Steps to Reproduce: Create a page with a TinyMCE field (body) and a standard Textarea field (seo_description). Enter UTF-8 characters (e.g., Greek text) into the "seo_description" field and save. Upload and insert an image into the "body" field. Save the page. Check the "seo_description" field or the database; the UTF-8 text has been converted to HTML entities. Root Cause Analysis: The issue stems from the "replaceRteLinks" method, which is triggered to update image URLs after a rename. Scope Logic: The method fetches all fields of type FieldtypeTextarea and FieldtypeTextareaLanguage and processes them using DOMDocument, regardless of whether the field actually contains the image URL. Encoding Handling: DOMDocument::saveHTML() natively converts extended characters into HTML entities. Because it processes unrelated fields, it permanently alters their raw text in the database. Proposed Solution: Updating the "replaceRteLinks" method to strictly check if the field contains the target URL before processing it, and utilizing ProcessWire's native sanitizer to restore entities converted by DOMDocument. Here is the patched method: private function replaceRteLinks($newFilename, $oldFilename) { $textareaFields = $this->wire('fields')->find("type=FieldtypeTextarea|FieldtypeTextareaLanguage"); $fieldsStr = $textareaFields->implode('|', 'name'); $oldRelativeUrl = str_replace($this->wire('config')->paths->root, '', $oldFilename); $oldRelativeUrlSansExt = str_replace(pathinfo($oldFilename, PATHINFO_EXTENSION), '', $oldRelativeUrl); foreach($this->wire('pages')->find("$fieldsStr%=$oldRelativeUrlSansExt, include=all") as $p) { foreach($textareaFields as $taf) { // FIX 1: Only parse the field if it actually contains the image URL. if($p->$taf != '' && strpos((string)$p->$taf, $oldRelativeUrlSansExt) !== false) { // FIX 2: Explicitly declare UTF-8 for the DOMDocument $pagedom = new DOMDocument('1.0', 'UTF-8'); libxml_use_internal_errors(true); // add <cun> as fake root element $pagedom->loadHTML('<?xml encoding="utf-8" ?><cun>' . $p->$taf . '</cun>', LIBXML_HTML_NODEFDTD | LIBXML_HTML_NOIMPLIED | LIBXML_SCHEMA_CREATE); $pagedom = $this->replaceRteLink($pagedom, $newFilename, $oldFilename, 'a', 'href'); $pagedom = $this->replaceRteLink($pagedom, $newFilename, $oldFilename, 'img', 'src'); // remove fake root element $html = str_replace(['<cun>', '</cun>', '<?xml encoding="utf-8" ?>'], '', $pagedom->saveHtml()); // FIX 3: Restore UTF-8 characters converted to entities by DOMDocument $html = $this->wire('sanitizer')->unentities($html); $p->of(false); $p->$taf = $html; libxml_clear_errors(); $p->save($taf); } } } }
  3. Yesterday
  4. The content owner of this video that I randomly chose from the YouTube homepage had marked this particular video content as not-embeddable, so the JSON response from the oembed provider of YouTube responded with "Unauthorized" which the Essence library doesn't really handle (it just returns nothingness, which in this case is kind of appropriate). Once I tracked that down, I tried another YouTube video URL and it worked as expected. 🀦
  5. Hi Folks. I've got a client with an e-commerce jewellery shop. She's been updating the prices a lot in the lasts week or so (price of silver!) but can only go about 30 minutes before the site crashes. I've had a look a the logs and see frequent "SQLSTATE[42000] [1203] User xxxxxx_admin already has more than 'max_user_connections' active connections (/wire/core/WireDatabasePDO.php:556)" notices followed by "SQLSTATE[HY000]: General error: 2006 Server has gone away (/wire/core/WireDatabasePDO.php:1000)" notices (the xxxxx_admin is the database admin account). The site's been in use for about 5 years without issues, she uses the 'live search' feature in the PW backend (upper right corner) to jump from product to product by typing in the product_code (a field I had added via the PageSearch modules' config/settings). She said she often would type a PC like '41-PB-1001' and would often make mistakes, then backup to the second hyphen then that's when things would sometimes freeze. I've checked the cPanel when the site goes down and the CPU uses is always screaming at 99%/100%. They get pretty good traffic on the site, so I've been hesitant to updated anything, but last week I updated php to 8.2, then today I brought PW up to 3.0.255 but it didn't fix it. I can't reproduce it easily, partly because I can't take the time to type searches for 20 or 30 minutes! Anyone ever seen this before? Thanks.
  6. Hey all, MediaHub is ready! Thanks for the kind words and the questions about availability. I've been replying to a few people privately, but here's the latest.. It has been given a thorough kicking on my own site(s) and 2 client sites. I'm opening up a waitlist for those interested in purchasing early access. I want to limit it initially to the first 5-10-ish people. IE small batches so I can make sure the purchase and licensing flow works smoothly hear how the setup goes across different environments provide proper support to each person. If you're interested, DM me here. Early access members will get a discount on the first year of updates as a thank you for feedback. I'll post a walkthrough video here shortly, but the screenshots earlier in the thread should give you a pretty good picture of things. Cheers everyone, Peter
  7. Something I've adopted for my PW sites is putting environment-specific DB credentials and configuration in a separate file, /site/config.local.php: /** * Installer: Database Configuration * */ $config->dbHost = '127.0.0.1'; $config->dbName = 'db'; $config->dbUser = 'default'; $config->dbPass = 'default'; $config->dbPort = '3306'; /** * Installer: HTTP Hosts Whitelist * */ $config->httpHosts = array( 'my-local-domain.test'); And then including that file into /site/config.php: /*** INSTALLER CONFIG ********************************************************************/ // DB Config, Hosts Whitelist include dirname(__FILE__) . '/config.local.php'; I find this makes it easier to push changes when using version control (i.e. git) across different environments, and keep the credentials and other sensitive information excluded from the repo. On some managed hosts like Cloudways, you can pull and push DB changes from staging to/from production applications, as well as branch changes from git. Obviously this doesn't help when you make DB changes locally, though. Otherwise, I will either export/import the entire DB via the Database Backups module, or Pages Import/Export (under Admin > Modules, Core modules) depending on the kind of changes. After using Vercel and Netlify for static-based websites, it does make me wish there was a quicker way to push/pull DB changes, but perhaps that extra bit of friction is good so you don't inadvertently disrupt your production data. Definitely something I can improve in my own workflow! Managing local/remote image assets Occasionally I've added this code snippet to /site/ready.php, that allows for your local installation to pull down the asset from prod, which can save you some time. I'm also generally curious to know more about other folks' deployment strategies – could be a potentially useful thing to collect and make more visible in the PW docs, too.
  8. Last week
  9. psy

    SeoMaestro

    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. $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; });
  10. @jploch @diogo Awesome work, I love the new website!
  11. Most important to me is that I understand everything that goes into the core, and that means that AI is used for ideas and suggestions, but the code is still written by hand. And even then, the suggestions and ideas only end up in the core if they produce a measurable improvement. After testing and benchmarking, sometimes the ideas/suggestions result in an improvement, and just as often they do not. I do the same with pull requests. Coding and re-coding something is how I feel comfortable that I understand it. Perhaps too old-school but I don't think that will ever change. So long as I'm in charge of the core, I need that level of understanding with it. On another project I'm working on with a client (an add-on to their website), we're letting Claude handle the code entirely, with lots of instructions from us, but zero code from us. It's kind of a test and a learning experience, and the client initiated it. We don't know the details of the code, but we do know that the code works quite well. Though I had a peek at its code and found it to be quite solid. What's funny is that in this case, Claude is having me build web services it can pull data from. So I'm giving it instructions, but it's also giving me instructions. πŸ™‚
  12. [Update] v1.0.25 – Multi-Email Account Merge A small but handy addition for real-world scenarios: customers sometimes purchase with different email addresses and end up with split accounts. The new Merge User Accounts tool in the module config lets you consolidate them in seconds. You enter the source email (the old/unwanted account) and the target email (the one the customer wants to keep). The module transfers all purchases from the source to the target and permanently deletes the source account. Before committing, you can run it in test mode to see exactly what would be transferred – no changes are written. Once you're confident, check "Merge now", save, and the merge report confirms what happened. One thing worth noting: the source account is permanently deleted after the merge – so double-check in test mode first. πŸ™‚ Feedback welcome!
  13. @Peter Knight Thanks for the work on this, Peter. Gemini AI actually helped me realize that it should work seamlessly with my PhpStorm + Cline + z.ai setup as well. Since your module includes the local Node.js MCP script, I should just be able to point my local Cline instance to that file via its MCP settings, and it will handle the sync workflow right inside PhpStorm. Looking forward to the release!
  14. Thanks for the feedback! Done in v1.1.7! πŸŽ‰ You can now manually select your CSS framework in module settings: Setup β†’ Modules β†’ Context β†’ Configure β†’ CSS Framework Options: Auto-detect (default) Tailwind CSS Bootstrap UIkit Vanilla CSS / Custom ← your use case None This will generate more accurate code examples and snippets for AI, specifically tailored to your vanilla CSS setup instead of assuming Tailwind.
  15. Thank you both for the feedback! Added a dedicated sitemap-edit permission in v1.0.1. It is created automatically on module install β€” just assign it to the roles you need in Access > Roles. https://github.com/mxmsmnv/Sitemap
  16. On a side note, because of the ProcessWire 'way' of handling images, it helps to think about the purpose of the images your page will have. As teppo flagged. So I usually have 1 x carousel images or a hero image field 1 x general images field i might display in the content 1 x something else The reason for doing this is that you can keep all your images and image field settings appropriate for the image purpose etc. Especially if you have clients adding or removing images. I find having just one image field can be an issue if they accidentally remove something or add an image which should not be part of the hero banner etc. Later on you might want to also look at tagging images and calling them onto your front-end pages https://processwire.com/modules/textformatter-image-tags/ But I'd recommend getting used to the basics first. An example of how I use image tagging might be. I have a product field with a lot of general images one of them in particular is used as a preview thumb on a product overview page so i'll tag that image 'preview' or whatever you want really. Lets us know how it goes
  17. @ai_slop Thanks for sharing your experience! Is it what I made this screenshot of? See: I only had the "Upgrade Plan" button, as you can see. Since I have already deleted the account, I have no way to take any further action, except to write to their support.
  18. Just finding this module now, and I am loving it! First of, belated congratulations are in order, and thanks a lot for making this module available @digitalbricks !
  19. robert

    PromptAI

    Hi @Stefanowitsch, thanks for the heads-up! Pushed a fix to Github to handle wrong values for model or API key a bit more gracefully πŸ˜…
  20. Great! You might want to consider placing the contact form on the homepage too to prompt action, using the terminal-style cursor you used in the Contacts menu.
  1. Load more activity
Γ—
Γ—
  • Create New...