Recently Updated Topics
Showing topics posted in for the last 7 days.
- Today
-
[WIP/Proposal] Wire2Pdf – A PHP 8 compatible fork of Pages2Pdf
ukyo replied to markus-th's topic in Module/Plugin Development
I see :(, fork and update old module look like best solution for Pages2Pdf module users -
WireNPS is a comprehensive module for collecting and analyzing customer feedback via a modern Net Promoter Score (NPS) popup interface. Key Features: Elegant NPS Widget: A non-intrusive, mobile-friendly popup for ratings (0-10) and text feedback. Real-time Analytics: Dashboard featuring NPS score calculation, score distribution charts, and 30-day trend graphs. Multilingual Support: Built-in support for English, German, French, and Chinese with automatic browser/user language detection. Flexible Access: Support for both logged-in users and guests (Public/Private modes). Data Export: CSV export functionality for external analysis. Privacy Control: Configurable IP/User Agent tracking and cookie management. Requirements: ProcessWire 3.0+ PHP 8.2+ Quick Installation: Download or git clone into /site/modules/WireNPS/. Install via the ProcessWire admin. Create an AJAX handler page using the provided template. GitHub: https://github.com/mxmsmnv/WireNPS
-
- 2
-
-
A quick WireIconifyData class or your name it as IconifyWireData, IconifyValue <?php namespace ProcessWire; class WireIconifyData extends WireData { public function __construct(?array $data = null) { parent::__construct(); // set defaults if (!is_array($data)) { $data = [ 'raw' => null, 'set' => null, 'name' => null, 'path' => null, 'url' => null, 'svg' => null, ]; } $this->setArray($data); } public function __invoke(array $attrs = [], bool $withHeader = false) { return $this->render($attrs, $withHeader); } public function render(array $attrs = [], bool $withHeader = false): string { if (!$this->get('svg')) { return ''; } try { $xml = new \SimpleXMLElement($this->get('svg')); foreach ($attrs as $key => $value) { if (isset($xml->attributes()->{$key})) { $xml->attributes()->{$key} = $value; } else { $xml->addAttribute($key, $value); } } // Return XML without the header declaration for clean HTML embedding $out = $xml->asXML(); return $withHeader ? $out : preg_replace('/^<\?xml[^?]*\?>/i', '', $out); } catch (\Exception $e) { wireLog('error', "SVG icon: {$this->get('name')} error => {$e->getMessage()}"); return ''; } } public function __toString(): string { return $this->render(); } } Usage: <?php // render echo $page->icon_field; // render with attrs echo $page->icon_field->render( attrs: ['width' => 40, 'style' => 'color: red;'] ); // render with attrs and header echo $page->icon_field( attrs: ['width' => 40, 'style' => 'color: red;'], withHeader: true );
- Yesterday
-
@d'Hinnisdaël, I was curious about organising a PageArray into a hierarchy, so here is some hook code for modifying a normal InputfieldCheckboxes (when used with a Page Reference field) that you could experiment with. $wire->addHookBefore('InputfieldCheckboxes::render', function(HookEvent $event) { /** @var InputfieldCheckboxes */ $inputfield = $event->object; $field = $inputfield->hasField; if(!$field || $field->name !== 'YOUR_FIELD_NAME') return; function buildHierarchy($pagearray) { // Organise pages by their parent ID and collect all IDs $grouped = []; $itemsById = []; $allIds = []; foreach($pagearray as $page) { $itemsById[$page->id] = $page; $allIds[$page->id] = true; $grouped[$page->parent->id][] = $page; } // Find orphaned parents - parent IDs that don't exist in the PageArray $orphanedParents = []; foreach($grouped as $parentId => $items) { if($parentId !== null && !isset($allIds[$parentId])) { $orphanedParents[] = $parentId; } } // Recursive function to build children function buildChildren($parentId, &$grouped, &$itemsById) { if(!isset($grouped[$parentId])) { return []; } $children = []; foreach($grouped[$parentId] as $item) { $node = clone $item; // Not using "children" as the property name here to avoid clashing with native property $node->nodeChildren = buildChildren($item->id, $grouped, $itemsById); $children[] = $node; } return $children; } // Build trees for all orphaned parents $hierarchy = []; foreach($orphanedParents as $parentId) { $hierarchy = array_merge($hierarchy, buildChildren($parentId, $grouped, $itemsById)); } return $hierarchy; } function renderCheckboxesList($items, $inputfield) { $out = "<div class='nested-checkboxes-list'>"; foreach($items as $item) { $label = $item->getFormatted('title'); $checked = ''; if($inputfield->isOptionSelected($item->id)) $checked = " checked='checked'"; $out .= "<div class='nested-checkboxes-item'><label><input$checked type='checkbox' name='{$inputfield->name}[]' value='{$item->id}' class='uk-checkbox'><span class='pw-no-select'>$label</span></label>"; if($item->nodeChildren) $out .= renderCheckboxesList($item->nodeChildren, $inputfield); $out .= "</div>"; } $out .= "</div>"; return $out; } $options = $inputfield->getOptions(); $optionIdsString = implode('|', array_keys($options)); $selectable = $event->wire()->pages->find("id=$optionIdsString, sort=parent.sort, sort=sort"); $hierarchy = buildHierarchy($selectable); $out = renderCheckboxesList($hierarchy, $inputfield); $out .= <<<EOT <style> .nested-checkboxes-list:not(.InputfieldCheckboxes > .nested-checkboxes-list) { padding-left:25px; } .nested-checkboxes-item input { margin-right:0.5em; } </style> EOT; $event->replace = true; $event->return = $out; }); Before: After: There's no JavaScript (I'll leave that to you), but in any case you would likely need to use PHP logic to set the selection state of parent items on Pages::saveReady() or else the field value would get out of whack any time an option was set via the API rather than via the inputfield. That's why having the parents/grandparents in the field value is something that can't really be handled by a module that is an inputfield only and is probably best done in custom code that's specific to your project.
- 5 replies
-
- inputfield
- checkboxes
-
(and 2 more)
Tagged with:
-
@hellomoto Not sure, sorry. Since you're outputting the token with ID `contact` and also checking the same one in your code, in theory it should work. Maybe the form validates its own token somewhere else? But yes, it might be a good idea to just have a placeholder for the entire form and render the form in the callback. That way, the form can manage its own CSRF token (and also other state like error messages, etc).
-
Hi everyone, For those of you running into compatibility issues with PHP 8 or needing a newer version of the underlying mPDF library, I wanted to let you know that I am working on a modernized successor to this module. To avoid clogging up this thread, I have started a separate discussion about the new project (currently named Wire2Pdf) here: If you are interested in a maintained version with PHP 8 support and new features, please join the conversation over there. Thanks!
-
New version, added Stats & Page Structure Hierarchy. How to install: Create template (like lego.php) with name lego Create page /lego_1234/ with select template Open your path in browser lego.php Demo:
-
Hi everyone! You’ve probably seen embedded product cards directly in articles on sites like Allegro, PriceRunner? I was using Hanna Code but found its capabilities limiting for my needs, so I decided to fork it and create Embedr - a more feature-rich version: Key Features: 🎯 Dynamic content blocks via ProcessWire selectors (not just static code) 🔄 Live preview directly in admin interface 🎨 Built-in visual card builder (UIKit-based) - no PHP required! 📝 Custom PHP templates for full control when needed 🏷️ Reusable embed types system 🔍 Debug mode with comprehensive logging ✅ Guest-safe - works for all users out of the box Quick Example: Create an embed with a selector: Name: featured-products Selector: template=product, featured=1, limit=6 Type: products Insert in text: ((featured-products)) Done! The module automatically finds pages and renders product cards. Need custom design? Just create a PHP template at /site/templates/components/products.php GitHub: https://github.com/mxmsmnv/Embedr Give it a try and let me know what you think! 🚀
-
- 6
-
-
-
Please try manually making changes to the module code in the relevant lines and, if possible, let us know whether it works or not.
- Last week
-
@ryan, could you please take another look at this breaking change before releasing the next master: https://github.com/processwire/processwire-issues/issues/2157 I have modules that break when updating to the most recent PW version.
- 1 reply
-
- 6
-
-
-
Gemini replied the following after examining things a little bit: The Logic of Date Overlaps The reason your current selector fails is that it only checks if the existing booking completely "swallows" your new request. To catch any overlap (partial or full), you need to use the following logic: A booking overlaps if: The End of the existing booking is after the Start of the new request. AND the Start of the existing booking is before the End of the new request. The Correct ProcessWire Selector Assuming $id->datum is your new Start and $id->datumbis is your new End: // Define your requested range $newStart = $id->datum; $newEnd = $id->datumbis; // Find any page that overlaps // Logic: Existing End > Requested Start AND Existing Start < Requested End $overlap = $pages->find("template=booking, datumbis>$newStart, datum<$newEnd"); if($overlap->count > 0) { // Room is occupied echo "This room is already booked by " . $overlap->count . " existing reservation(s)."; } else { // Room is available echo "The room is available for this period!"; } Why this solves your problems: Existing Booking: 13.08.2025 to 04.12.2025 Problem 1 (01.07.2025 – 03.12.2025): * Is existing end (04.12) > new start (01.07)? Yes. Is existing start (13.08) < new end (03.12)? Yes. Result: Overlap detected. Problem 2 (01.10.2025 – 01.01.2026): Is existing end (04.12) > new start (01.10)? Yes. Is existing start (13.08) < new end (01.01)? Yes. Result: Overlap detected. Pro-Tip: Changeover Days If your system allows a guest to check in on the same day someone else checks out (e.g., Check-out at 11:00, Check-in at 15:00), use strictly "greater than" (>) and "less than" (<). If you use >= or <=, the system will flag the changeover day as a conflict. Using strict operators allows the dates to touch without overlapping. Data Types Ensure that $newStart and $newEnd are in the same format as stored in the database (usually Unix Timestamps or YYYY-MM-DD). If you are using ProcessWire's native Date fields, comparing them as Timestamps is the most reliable method.
- 1 reply
-
- 1
-
-
link to parent in backend leads to list overview instead of actual parent
joe_g replied to joe_g's topic in General Support
fantastic! thanks both! -
Can I insert a soft hyphen in the CKEditor?
virtualgadjo replied to Alf S.'s topic in Getting Started
Hi, sorry to be a bit late to answer this but those are two very different things when it comes to the wbr tag, it's pretty easy, in your site/modules/InputfieldCKEditor folder add a config js file which will allow you to solve both "problems", an exemple of one i have for an "old" website made with pw when it was using ckeditor 4 as its rich editor module CKEDITOR.editorConfig = function( config ) { CKEDITOR.config.fontSize_sizes = '8/.5rem;10/0.625rem;11/0.6875rem;12/0.75rem;14/0.875rem;16/1rem;18/1.125rem;20/1.25rem;22/1.375rem;24/1.5;28/1.75rem'; CKEDITOR.config.extraAllowedContent = 'section[id,class] wbr'; CKEDITOR.config.entities_additional = 'shy'; }; as you can see, i define some option for the font-size dropdown but here the thing we are interested in are the two other lines the extraAllowedContent tells cke not to delete those tags and allow section with id and class attribute and... the wbr tag (which, nomatter how you insert it will be transformed into <wbr /> but works the same way this being done, you can simply create a plugin to insert a wbr tag wherever you need when it comes to ­ or its html equivalent ­ its the config.entities_additional line that tells cke not to remove them, well actuelly only ­ in my case, i don't need both... as you can see, as cke docs says no & nor ; in the list, i could have written 'shy,#173' to make it work for both entities now, the problem is... it works!!! but depending on your browser you may not see it when saving, even if you look at the field content directly in the ddb, it's not a ckeditor issue at all but just a browser behaviour easy to check saving your content/page and viewing it in the browser, using its responsive viewer tool, playing with the page width, you'll see the soft hyphen in action where you've inserted them 🙂 now, like for the wbr tag, you just have to create simple plugin to insert the sofh hyphen in both case, i prefer writing my plugins using icons in the toolbar rather than just keyboard shortcuts as this way i'm sure it will work whatever os my victims are on, pc, minux, mac, it will work when i'm not sure about keys numbers, i'm sure for enter, space but ten plugins later, i'm going to run out of shortcuts 😀 as simple as this 🙂 in case it helps (just tell me if you need help with this plugin thing) have a nice day Edited to add thnking it may be useful for those who keep using this good old CKEditor 4 in pw, i've added a github repo with the two plugins i'm speaking about https://github.com/virtualgadjo/pw-ckeditor4-plugins always in case it helps have a nice day -
@Frank Schneider Are you rendering this calendar on the front end or in the admin? First thing I noticed was that the closing </script> tag is missing. You can use the native PHP alternate control structure syntax if this is in a mixed markup/PHP file. It's up to your personal preference but it can help readability. <?php if ($page->path == "/avisierungen/kalender/" || $page->path == "/vermietungen/kalender/"): ?> <script> $(function(){ 'use strict' $('#calendar').fullCalendar({ header: { left: 'prev,next today', center: 'title', right: 'month,agendaWeek,agendaDay,listMonth' }, height: 'auto', contentHeight: 'auto', aspectRatio: 2.0, weekNumbers: true, navLinks: true, eventTextColor: 'black', defaultDate: '<?=date("Y-m-d")?>', editable: false, eventLimit: true, // allow "more" link when too many events events: <?=json_encode($calendar_data)?> }); }); </script> <?php endif ?>
-
Thank you very much! It was really quite easy with ProcessAdminCustomPages and a foreach loop! $logentries = $log->getEntries("my-email-log", ["limit"=>"20"]); echo "<ul>"; foreach ($logentries as $logentry) { echo "<li>"; echo $logentry["user"]; echo " <br> "; echo $logentry["date"]; echo " <br> "; echo $logentry["url"]; echo " <br> "; echo $logentry["text"]; echo "</li>"; } echo "</ul>"; With chart.js, I was even able to implement a diagram.
-
I don't know if this would be helpful to anyone but it saved me a lot of work so I thought I would share it. When using Duplicator on Windows (I don't think this is a problem on another platform), the installer.php script would always hang on unzipping the files and never make it to the uploading the database stage. No matter what I changed in terms of time out or memory allocation or anything, it still would never full extract everything, and what it did extract was VERY slow. What I ended up doing in replacing the code where it extracts the files with a native tar extract. Starting at the $zip = new ZipArchive(); text (about line 731), I replaced the code down to the closed brace (about line 750) with this: $zip = new ZipArchive(); $res = $zip->open($this->package); if ($res == true) { if (is_writable($path)){ // ---- START: system tar extraction (replacement) ---- $archive = escapeshellarg($this->package); $dest = escapeshellarg($path); // Laragon ships with bsdtar, which can extract zip files $cmd = "tar -xf $archive -C $dest"; exec($cmd, $output, $code); if ($code !== 0) { $this->err("An error occured while extracting the package."); return false; } } else { $zip->close(); $this->err("The temp folder $path is not writable. Please adjust the permissions and try again."); $this->btn("Check Again", 2, 'refresh', false, true); return false; } if ($zip) $zip->close(); $this->ok("The package has been extracted."); } else { $this->err("An error occured! Duplicator couldn't open {$this->package}."); } Once I did that, the extract took 10 seconds max for many hundreds of files, whereas before I would be waiting for many minutes only to have it abort. I did get the code from ChatGPT so if it doesn't look quite right, blame it, but it does work. 🙂 I know I could refactor this a bit to remove the $zip variables that aren't really doing anything now, but I didn't feel like redoing more parts of the script, and it doesn't hurt anything. I hope it helps someone. Maybe a real programmer could clean it up and put it in the options when generating the installer.php file?
-
Hi everyone, I wanted to share a small utility module I’ve put together to help keep the /site/modules/ directory tidy. What it does: When updating modules ProcessWire renames old module directories by prepending a dot (e.g., .ModuleName). Over time, these "hidden" backup folders can clutter your file system. ProcessModuleCleaner identifies these orphaned directories and allows you to delete them directly from the admin interface. Key Features: Automatic Detection: Scans your site modules folder for any directory starting with a dot. Native UI: Built specifically for the ProcessWire backend using UIkit 3 classes for a seamless look. Interactive Selection: Uses AlpineJS for a fast and responsive "select all" and delete workflow. Safe Deletion: Uses ProcessWire's WireFileTools for reliable recursive directory removal. How to use: Install the module. Navigate to Setup > Module Cleaner. Review the list of found folders. Select the ones you want to remove and click "Delete". Screenshot / UI: The module displays a clean table with the folder name and the last modified date, so you know exactly how old those backups are. GitHub: https://github.com/markusthomas/ProcessModuleCleaner Module Directory: https://processwire.com/modules/process-module-cleaner/ I hope some of you find this helpful for keeping your production or development environments clean! Feedback is always welcome. Cheers!
-
- 14
-
-
-
On my Ubuntu (Gnome) laptop, I often get a popup saying an issue happened when waking up computer. I also sometimes had pain accessing an external drive, plug in, plug out, in, out... and finally it worked. Usually it works immediately, but not this time. Recently I was looking to free space, I have old accounts in my /home (from previous installations of Manjaro KDE, Manjaro Gnome...) and found I had 150 GB in the Download folder of one of this accounts. I opened a video just to check its content, and explorer crashed. I restarted explorer, selected everything in folder, deleted and... explorer crashed again. I tried again and this time it crashed when selecting files... I finally succeeded, but what a pain. I rarely turn off computer, just put it in sleep, I suppose this is why sometimes Firefox freezes for a few seconds after days without restarting, so I restart and everything is OK.
-
Aurelien Barrau is a french physicist and philosopher, I translate: 😅
-
Hi everybody, I'm trying to translate options into already activated languages, via hook: setOptionString works correctly for the default language, but I can't populate the options in other languages. More specifically, changing the user profile language the titles are translated, but the options are overwritten in the "default" language tab. So I have found the setOptionsStringLanguages method, but it seems to break someting (I have an error like: ProcessPageEdit:Multi language not enabled.. of course it's enabled) The scenario is a hook for an InputfieldSelect::render Any suggestion? Thanks in advance.
-
It does but using MarkupCache means that the file doesn't exist on the filesystem so, to my original point, loading /sitemap.xml starts PHP, makes DB calls, and then returns the cached markup via a virtual URL. Yes, it's cached and faster than generating a new sitemap on demand, but not as fast or efficient as writing a file that has a direct URL on the filesystem. Your topic may be better suited for a separate thread about multisite implementations. This thread is for support and discussion of SeoMaestro.
-
OK, got an answer. This $icsgen->events->add($myEvent); $icsgen->events->add($myEvent); does not work. This $icsgen->events->add([ 'summary' => $item->title, 'dtstart' => $item->Date_start, 'dtend' => $item->Date_end, ... ]); does. Why?...
-
Hi @horst, here is the link to the discussion: https://github.com/FortAwesome/Font-Awesome/discussions/21364 Regards, Andreas