Search the Community
Showing results for 'render('.
-
PW: 3.0.255 PHP: 8.3.3 Sadly I now have the same issue and I can't find a way around it. Duplicate sites, create a new page on one then try to export the page to the duplicate site and get this error: Error: Call to a member function getLanguageValue() on null in wire/modules/LanguageSupport/FieldtypeTextareaLanguage.module:126 #0 [internal function]: FieldtypeTextareaLanguage->___importValue() #1 wire/core/Wire.php (425): call_user_func_array() #2 wire/core/WireHooks.php (1018): Wire->_callMethod() #3 wire/core/Wire.php (484): WireHooks->runHooks() #4 wire/core/PagesExportImport.php (998): Wire->__call() #5 wire/core/PagesExportImport.php (685): PagesExportImport->importFieldValue() #6 wire/core/PagesExportImport.php (535): PagesExportImport->arrayToPage() #7 wire/modules/Fieldtype/FieldtypeRepeater/FieldtypeRepeaterPorter.php (255): PagesExportImport->arrayToPages() #8 wire/modules/Fieldtype/FieldtypeRepeater/FieldtypeRepeater.module (1006): FieldtypeRepeaterPorter->importValue() #9 [internal function]: FieldtypeRepeater->___importValue() #10 wire/core/Wire.php (425): call_user_func_array() #11 wire/core/WireHooks.php (1018): Wire->_callMethod() #12 wire/core/Wire.php (484): WireHooks->runHooks() #13 wire/core/PagesExportImport.php (998): Wire->__call() #14 wire/core/PagesExportImport.php (685): PagesExportImport->importFieldValue() #15 wire/modules/Process/ProcessPagesExportImport/ProcessPagesExportImport.module (619): PagesExportImport->arrayToPage() #16 wire/modules/Process/ProcessPagesExportImport/ProcessPagesExportImport.module (320): ProcessPagesExportImport->processImportItemToPage() #17 wire/modules/Process/ProcessPagesExportImport/ProcessPagesExportImport.module (242): ProcessPagesExportImport->processImportSubmit() #18 wire/modules/Process/ProcessPagesExportImport/ProcessPagesExportImport.module (76): ProcessPagesExportImport->processImport() #19 wire/core/Wire.php (413): ProcessPagesExportImport->___execute() #20 wire/core/WireHooks.php (1018): Wire->_callMethod() #21 wire/core/Wire.php (484): WireHooks->runHooks() #22 wire/core/ProcessController.php (362): Wire->__call() #23 wire/core/Wire.php (413): ProcessController->___execute() #24 wire/core/WireHooks.php (1018): Wire->_callMethod() #25 wire/core/Wire.php (484): WireHooks->runHooks() #26 wire/core/admin.php (174): Wire->__call() #27 wire/modules/AdminTheme/AdminThemeUikit/controller.php (15): require('/home/u99035861...') #28 site/templates/admin.php (15): require('/home/u99035861...') #29 wire/core/TemplateFile.php (328): require('/home/u99035861...') #30 wire/core/Wire.php (413): TemplateFile->___render() #31 wire/core/WireHooks.php (1018): Wire->_callMethod() #32 wire/core/Wire.php (484): WireHooks->runHooks() #33 wire/modules/PageRender.module (547): Wire->__call() #34 wire/core/Page.php (3152): PageRender->render() #35 wire/core/Wire.php (416): Page->___renderPage() #36 wire/core/WireHooks.php (1018): Wire->_callMethod() #37 wire/core/Wire.php (484): WireHooks->runHooks() #38 wire/core/Page.php (3097): Wire->__call() #39 wire/core/Wire.php (413): Page->___render() #40 wire/core/WireHooks.php (1018): Wire->_callMethod() #41 wire/core/Wire.php (484): WireHooks->runHooks() #42 wire/modules/Process/ProcessPageView.module (193): Wire->__call() #43 wire/modules/Process/ProcessPageView.module (114): ProcessPageView->renderPage() #44 wire/core/Wire.php (416): ProcessPageView->___execute() #45 wire/core/WireHooks.php (1018): Wire->_callMethod() #46 wire/core/Wire.php (484): WireHooks->runHooks() #47 index.php (56): Wire->__call() #48 {main}
-
I made a little update to 1.3.7, bringing it to the current PHP8+ and PW3+ standards. Changes in 1.3.7 (2026-04-22) Updated for ProcessWire 3.x and PHP 8.x compatibility Declared class properties explicitly to fix PHP 8.2 dynamic property deprecation Fixed nullable type hints on render() signature (?array, ?Page) required since PHP 8.0 Replaced global wire() calls with $this->wire() for correct PW3 multi-instance scoping Replaced new PageArray() with $this->wire(new PageArray()) for proper instance binding Replaced object identity comparisons (===) with ID-based comparisons (->id ===) for $is_current and $is_root Replaced loose null comparisons (!= null) with strict (!== null) throughout Fixed misleading single-line if/else in xtemplates block with proper braces Removed unused $v_unformatted variable in parsePlaceholders() Replaced $this->fields / $this->users magic access with $this->wire('fields') / $this->wire('users') Updated getModuleInfo(): added icon, requires (PHP>=8.0, ProcessWire>=3.0.0), updated href to https Bumped version from 1.3.6 to 1.3.7 I only tested it on a site I'm currently working on, and it still works the same. But if you encounter any issues please let me know.
-
Hi. I've recently moved a long standing site to a new server and now I get wireRenderFile is undefined on home page. So I checked on my localhost server and it is running fine on there. I'm using Markup Regions and the home page is just like all my pages using : <div id="ajax-content" pw-replace> <?=wireRenderFile('_ajax-home.php', array('id' => $page->id))?> </div> I did a clean install of 3.0.255 and it runs ok. Replaced site files and imported my d.b as usual but gives red error screen - The error I'm getting : Hmm… Fatal Error: Uncaught Error: Call to undefined function wireRenderFile() in site/templates/home.php:3 #0 wire/core/TemplateFile.php (328): require() #1 wire/core/Wire.php (413): TemplateFile->___render() #2 wire/core/WireHooks.php (1018): Wire->_callMethod('___render', Array) #3 wire/core/Wire.php (484): WireHooks->runHooks(Object(TemplateFile), 'render', Array) #4 wire/modules/PageRender.module (547): Wire->__call('render', Array) #5 wire/core/Page.php (3152): PageRender->render(Object(HomePage), Array) #6 wire/core/Wire.php (416): Page->___renderPage(Array) #7 wire/core/WireHooks.php (1018): Wire->_callMethod('___renderPage', Array) #8 wire/core/Wire.php (484): WireHooks->runHooks(Object(HomePage), 'renderPage', Array) #9 wire/core/Page.php (3097): Wire->__call('renderPage', Array) #10 wire/core/Wire.php (413): Page->___render() #11 wire/core/WireHooks.php (1018): Wire->_callMethod('___render', Array) #12 wire/core/Wire.php (484): WireHooks->runHooks(Object(HomePage), 'render', Array) #13 wire/modules/Process/ProcessPageView.module (193): Wire->__call('render', Array) #14 wire/modules/Process/ProcessPageView.module (114): ProcessPageView->renderPage(Object(HomePage), Object(PagesRequest)) #15 wire/core/Wire.php (416): ProcessPageView->___execute(true) #16 wire/core/WireHooks.php (1018): Wire->_callMethod('___execute', Array) #17 wire/core/Wire.php (484): WireHooks->runHooks(Object(ProcessPageView), 'execute', Array) #18 index.php (55): Wire->__call('execute', Array) #19 {main} thrown (line 3 of site/templates/home.php) I can login to admin on back end ok. Any attempt to view a page from admin gives the error above as they all use basic-page template with the same markup region. Like I said it all works fine on my localhost on 3.0.229 Any ideas? Thanks .
-
PW 3.0.255 ProcessDatabaseBackups 0.0.6 PHP 8.3.30 On restore I am seeing this error: Passing null to parameter #1 ($num) of type float is deprecated in ***/site/modules/ProcessDatabaseBackups/ProcessDatabaseBackups.module on line 243 The line in question seems to be this one: $this->tdSort($file['numInserts'], number_format($file['numInserts'])), Within: foreach($backupFiles as $id => $file) { $numFiles++; $numTables = $file['numTables']; if($numTables && !count($file['tables'])) $numTables .= " " . $this->_('(all)'); $basename = $file['basename']; $time = $file['time'] ? $file['time'] : $file['mtime']; if($file['description']) $basename .= '*'; $actions = array(); foreach($this->getFileActions($file) as $action) { $actions[] = $this->aTooltip($action['href'], wireIconMarkup($action['icon'], 'fw'), $action['label']); } $checkbox->attr('id', "delete_" . $sanitizer->fieldName($id)); $checkbox->attr('value', $id); $table->row(array( $this->nowrap($basename) => "./info/?id=$id", $this->tdSort(strtotime($time), wireRelativeTimeStr($time, true)), $this->nowrap($numTables), $this->tdSort($file['numInserts'], number_format($file['numInserts'])), $this->tdSort($file['size'], wireBytesStr($file['size'])), $this->nowrap(implode(' ', $actions)), $checkbox->render(), )); } If anyone has an ideas for a fix please do let me know as I use this module a lot!
-
I have it in a Controller file in renderSomething() method called from render(). Function or throw - doesn't matter. I moved this code to init() and even to wireframe.php. The issue is the same. The server returns a 404 code but doesn't redirect and shows an error page. I have tried this on two other sites with Wireframe. And the behaviour is different there. If i put the code in wireframe.php there, it gives me a 500 error. But in controller it works ok and shows the 404 page. Those 2 sites are not multilingual and not so heavily modified... Can you suggest a way I can debug further? I have found the issue. The template of 404 page was changed to a template that caused an infinite loop. Sorry for bothering you @teppo. But natural intellect also needs prompts and you helped me with a one)
-
AgeWire — Age Verification Module for ProcessWire
maximus replied to maximus's topic in Modules/Plugins
AgeWire has been updated — here's what's new since the original release. Multi-framework support The module now supports four CSS frameworks. You pick whichever one your project already uses: Vanilla CSS — fully self-contained, no dependencies, no CDN required Tailwind CSS — the original experience, 13 themes and 4 animations (unchanged) Bootstrap 5 — renders using native Bootstrap card, btn, form-control and alert classes UIkit 3 — renders using native UIkit card, button and grid components Each framework has an independent CDN toggle. If you already load Bootstrap or UIkit in your templates, just uncheck it and the module won't load anything extra. Security fixes CSRF token is now validated on every verification POST Fixed a bug where ProcessWire would render the full page HTML after the JSON response, causing JSON.parse errors in the browser. The AJAX handler now calls ob_clean() before sending JSON to discard any buffered output Strict date parsing with DateTime::createFromFormat — relative strings like "tomorrow" or "-18 years" are rejected Redirect URL validated to http://, https://, or site-relative paths only Custom CSS field sanitized against </style> injection JS console.log output is now gated behind $config->debug — no noise in production Admin UI The config page is now a two-column layout. General Settings and Framework & Theme are open side by side on first load. The rest (Modal Content, Date Picker, Agreement, Exclusions) are collapsed into logical groups — much less scrolling. PHP 8.2+ The module now requires PHP 8.2 and uses typed return types, match expressions, str_contains, str_starts_with, and unnamed catches throughout. GitHub: https://github.com/mxmsmnv/AgeWire Feedback and PRs welcome as always!- 1 reply
-
- 2
-
-
Hi guys, I've been working on a full EditorJS integration for ProcessWire and it's reached a point where I'd like to share it and get some testing feedback before calling it stable. GitHub: https://github.com/mxmsmnv/FieldtypeRapid This is a development preview. The core functionality works, but I'm looking for testers on different server configurations and ProcessWire setups before a stable release. Please report issues on GitHub. Why EditorJS instead of CKEditor or TinyMCE? CKEditor and TinyMCE are document editors — they produce a single blob of HTML. That HTML is hard to restructure, style consistently, or repurpose for different output targets (web, mobile, PDF, API). EditorJS is a block-based editor. Every paragraph, heading, image, and quote is a separate JSON object with a type and structured data. This means: Content is stored as clean JSON, not tangled HTML Each block type can be rendered differently per context Easy to add, reorder, or remove blocks without breaking surrounding content Output is fully controlled server-side via PHP renderers — no frontend JS required It's closer in concept to Notion or Gutenberg than to a traditional WYSIWYG. What Rapid does: 17 block types: paragraph, header (h1–h6 with auto anchor IDs), quote, nested lists, table, code, delimiter, warning, checklist, raw HTML, image (WebP convert + resize), file attachments, YouTube/Vimeo embed, alert (8 color variants), toggle/accordion, link preview with OG metadata Inline tools: bold, italic, underline, inline code, marker, link Template API: echo $page->body; // render all blocks echo $page->body->toText(); // plain text for meta/search echo $page->body->renderWith($renderer); // custom renderer 4 CSS frameworks — Vanilla, Tailwind, Bootstrap 5, UIkit 3 Frontend editing — inline editor on frontend for authorized users No build step needed — pre-built js/dist/editor.js included. Requirements: ProcessWire 3.0.200+, PHP 8.2+ Any feedback on installation, rendering edge cases, or block behaviour is welcome!
- 7 replies
-
- 14
-
-
-
TrackingScripts Module Manage and inject tracking scripts (Google Analytics, Google Ads, Facebook Pixel, custom code) into site pages, with optional PrivacyWire consent integration and robots.txt/llms.txt file management. Features Google Analytics (GA4) — inject gtag.js with Measurement ID Google Ads — inject gtag.js with Ads conversion ID Facebook Pixel — inject Pixel tracking code with noscript fallback Custom code — free-form textareas for any third-party scripts (head and/or body) PrivacyWire integration — when enabled, scripts are injected with data-category attributes and type="text/plain" so they only load after user consent robots.txt & llms.txt — edit and auto-generate both files from the admin; content is written to the site root on save Per-service controls — enable/disable, position (head or body), and consent category for each service independently ID validation — regex validation for GA (G-), Ads (AW-), and Pixel (numeric) IDs before injection Admin-only exclusion — scripts are never injected on admin or form-builder templates Files site/modules/TrackingScripts/ ├── TrackingScripts.info.php ← module metadata ├── TrackingScripts.module.php ← main module (hooks, script injection) ├── TrackingScriptsConfig.php ← module configuration (ModuleConfig) ├── ProcessTrackingScriptsConfig.info.php ← Process module metadata └── ProcessTrackingScriptsConfig.module ← admin UI for non-superusers Installation Copy the TrackingScripts folder into /site/modules/ In the admin go to Modules → Refresh, then install TrackingScripts Optionally install ProcessTrackingScriptsConfig — this adds a Setup → Tracking Scripts page that allows non-superuser roles to edit the configuration. Assign the tracking-scripts-config permission to any role that needs access. Configuration Go to Modules → Configure → TrackingScripts (superuser) or Setup → Tracking Scripts (any user with permission). Google Analytics Field Description Enable Activate/deactivate injection Measurement ID GA4 ID, e.g. G-XXXXXXXXXX Position Inject in <head> or before </body> PrivacyWire Category Consent category (default: Statistics) Google Ads Field Description Enable Activate/deactivate injection Ads ID e.g. AW-XXXXXXXXX Position Inject in <head> or before </body> PrivacyWire Category Consent category (default: Marketing) Facebook Pixel Field Description Enable Activate/deactivate injection Pixel ID Numeric ID, e.g. 123456789012345 Position Inject in <head> or before </body> PrivacyWire Category Consent category (default: Marketing) Custom Tracking Code Two free-form textareas for any additional third-party code: Custom Code — Head: injected before </head> Custom Code — Body: injected before </body> PrivacyWire Integration When enabled, all tracking scripts are rendered with PrivacyWire-compatible attributes: <script type="text/plain" data-type="text/javascript" data-category="statistics" class="require-consent" src="..."></script> This ensures scripts only execute after the user gives consent for the corresponding cookie category. Requires the PrivacyWire module to be installed and active. Robots.txt & LLMs.txt Edit the content of both files directly from the admin. On save, the files are written to (or removed from) the site root: /robots.txt — search engine crawler directives /llms.txt — LLM/AI bot directives If a textarea is left empty, the corresponding file is deleted from the site root. How It Works The module hooks into Page::render (priority 100) to inject scripts via str_replace on </head> and </body>. This means: No template modifications required Works on all front-end pages automatically Runs before PrivacyWire (priority 101), so consent attributes are in place when PrivacyWire processes the page The robots.txt and llms.txt files are written via a hook on Modules::saveConfig, triggered whenever the module configuration is saved from either the module config screen or the Process admin page. ProcessTrackingScriptsConfig (Admin UI) A Process module that mirrors the full TrackingScripts configuration under Setup → Tracking Scripts. Purpose Allows non-superuser roles to manage tracking scripts without access to the Modules admin. Permission The module registers the permission tracking-scripts-config. To grant access: Go to Access → Roles Edit the desired role Check tracking-scripts-config Save How it works Reads and writes the same configuration data as TrackingScripts via $modules->getConfig() / $modules->saveConfig() Changes from either location (Modules → Configure or Setup → Tracking Scripts) are reflected in both Saving triggers the same Modules::saveConfig hook, so robots.txt/llms.txt files are written automatically Requirements ProcessWire 3.0.110+ PHP 7.2+ PrivacyWire (optional, for consent integration) License Licensed under the MIT License.
-
- 5
-
-
Good morning @da² I have wrapped my head around your problem and I have found a possibility to check file uploads against the value of an input field by using a custom validation. In the following example you have 2 form fields: Field 1 is a text input where you have to enter the name of a file (for example "myfile") without the extension Field 2 is the file upload field where you upload a ZIP folder with different files. Validation check In this simple example it will be checked if there is a file with the filename entered in field1 present inside the files uploaded in field 2. In other words: Check if the uploaded ZIP folder contains a file with the filename for example "myfile" in this case. If yes, then the form submission is successful, otherwise an error will be shown at field 1. This is only a simple scenario to demonstrate how a custom validation could be done. You have to write your own validation rules. Now lets write some code for this example: The first one is the custom validation rule. Please add this code to your site/init.php. If this file does not exist, please create it first. <?php namespace ProcessWire; \Valitron\Validator::addRule('checkFilenameInZip', function ($field, $value, array $params) { $fieldName = $params[0]; // fieldname of the upload field $form = $params[1]; // grab the form object // get all the files from the given ZIP folder as an array $zipfiles = $form->getUploadedZipFilesForValidation($fieldName); // now start a validation rule with the value of this input field ($value) and the files inside the ZIP folder if (!is_null($zipfiles)) { $fileNames = []; foreach ($zipfiles as $zipfile) { $fileNames[] = pathinfo($zipfile, PATHINFO_FILENAME); } return in_array($value, $fileNames); } return true; }, 'must be a wrong filename. The ZIP folder does not contain a file with the given name.'); I have named the validation rule "checkFilenameInZip" in this case. The important thing is that you have to add 2 values to the $params variable: The name of the file upload field The form object itself To get all the files that were uploaded via the upload field, you can use one of these methods of the form object. getUploadedZipFilesForValidation (outputs only files of a ZIP file) getUploadedFilesForValidation (outputs all files of a file upload field) Important: To use these 2 new methods you have to update to the latest version of FrontendForms (2.3.14)! All ZIP files will be extracted automatically. Nested ZIP files are supported too. Now lets create the form $form = new \FrontendForms\Form('testform'); $form->setMaxAttempts(0); // set 0 for DEV $form->setMaxTime(0); // set 0 for DEV $form->setMinTime(0); // set 0 for DEV $form->useAjax(false); // set false for DEV // input field containing a filename $filename = new \FrontendForms\InputText('filename'); $filename->setLabel('Name of file'); $filename->setNotes('Please enter the name of the file without extension.'); $filename->setRule('required'); $filename->setRule('checkFilenameInZip', 'fileupload1', $form);// here is the new custom validation rule: Enter the name of the file upload field as the first parameter and the form object as the second parameter $form->add($filename); // the file upload field $file1 = new \FrontendForms\InputFile('fileupload1'); $file1->setRule('required'); $file1->setLabel('Multiple files upload'); $form->add($file1); $button = new \FrontendForms\Button('submit'); $button->setAttribute('value', 'Send'); $form->add($button); if ($form->isValid()) { // do what you want // $extractedFiles = $form->getUploadedFiles(true); // returns all uploaded files including extracted ZIP folder files // $nonExtractedFiles = $form->getUploadedFiles(); // returns all uploaded files but Zip files are not extracted // $values = $form->getValues(); // returns all submitted form values } echo $form->render(); Copy this code and paste it inside a template for testing purposes. Upload a ZIP folder and add the name of a file inside the "filename" input field. If the file exists in your ZIP folder you will succeed. Please note: The extraction of the ZIP files is done only once during the validation process. You can get the extracted files afterwards inside the isValid() method via the getUploadedFiles() method from the temporary upload folder. Use the return of this method to do some more work with the uploaded files. Take this example as a starting point for your own validation rules and let me know if it works for you. I think this is the only possibility to get what you need. Another hint: User browser validation in FrontendForms to make client side validation first by enabling it in the backend.
-
Hello, A small utility I built for my own workflow — export any page directly from the editor as a clean Markdown file. Useful for documentation, content migration, and feeding page content to AI tools. GitHub: https://github.com/mxmsmnv/PageMarkdown What it does: Adds an Export to Markdown button to the page edit form Smart HTML conversion — CKEditor content (tables, lists, headings, links, bold/italic) → standard Markdown Supports ProFields: Table, Combo, Repeater Matrix (with type labels and nested structure) Images and files render as Markdown image/link syntax Page references render as links or titles MapMarker, Email, URL, Color fields all handled Configurable: toggle field labels as headings, ignore lists per field/type, datetime format, empty HTML cleanup Requirements: ProcessWire 3.0+, PHP 8.0+ MIT License.
-
PHP code isn't rendered, but referenced images are being pulled
markus-th replied to Joachim's topic in General Support
@Joachim Exactly, when you use wireRenderFile() (or include), the variable $page remains the current page being viewed in the browser—in this case, the Home page. Since your Repeater fields live on the child pages (the modules), $page->introimg_field is looking at the Home page, finding nothing, and returning an empty object. When you loop through your child pages to render them as components, pass the specific child page into the options array of wireRenderFile. foreach($page->children() as $component) { // We pass the $component page object into the file echo wireRenderFile("components/{$component->template->name}.php", [ "item" => $component ]); } Inside the child (module) template: <section id="<?php echo $item->name; ?>" class="module"> <div class="landing_images"> <?php foreach($item->introimg_field as $intro_image): ?> <a href="<?php echo $intro_image->page_picker->url; ?>"> <h3><?php echo $intro_image->title; ?></h3> <img src="<?php echo $intro_image->image->url; ?>" alt="<?php echo $intro_image->description; ?>"> </a> <?php endforeach; ?> </div> </section> This should give you the correct array. -
If you love the simplicity of ProcessWire's API but want the reactive, SPA-like feel of modern frontend frameworks without writing complex JavaScript, this module bridges that gap. It brings native Server-Side Component state hydration, Out-Of-Band (OOB) swaps, and strict security to your ProcessWire application using HTMX. 🚀 What does it do? This module transforms how you write frontend components in ProcessWire: True Stateful Backend Components: It introduces Component and Ui base classes. Your PHP components automatically rehydrate their state (variables, dependencies, $page assignments) between HTMX AJAX requests! No need to manually parse POST payloads. Auto-Discovery: Just place your components in your site directories. The module automatically discovers and securely namespaces them (Htmx\Component and Htmx\Ui). Zero-Javascript Reactivity: You can handle form submissions, counters, validation, and multi-field updating dependencies directly from PHP using HTMX attributes. Cryptographic Security: The module uses strict HMAC-SHA256 signatures with TTL (Time-To-Live). This guarantees that bad actors cannot modify state payloads or trigger invalid endpoint logic in the browser. WebSockets & SSE Ready: It has built-in helpers to easily hook Server-Sent Events (SSE) and WebSockets onto your templates without exhausting PHP-FPM pools globally. 🛠 How it looks in your code You simply create a PHP component class, define some public properties, and write an endpoint action: <?php namespace Htmx\Component; use Totoglu\Htmx\Component; class ClickCounter extends Component { public int $count = 0; public function increase() { $this->count++; } } Then, you can render it anywhere in your site with a single line: /** @var Htmx $htmx */ echo $htmx->renderComponent(ClickCounter::class); View the documentation and examples on Github Feel free to try it out, run the tests included in the repo, and let me know your thoughts or feedback! htmx.mp4
-
Hello @da² I have added a new method to trigger an error message to a field manually: setErrorMessageToField() Here is a small example on how to use it: $form = new \FrontendForms\Form('testform'); $form->setMaxAttempts(0); $form->setMaxTime(0); $form->setMinTime(0); $firstname = new \FrontendForms\InputText('firstname'); $firstname->setLabel('Firstname'); $firstname->setRule('required')->setCustomFieldName('The first name'); $form->add($firstname); $button = new \FrontendForms\Button('submit'); $button->setAttribute('value', 'Send'); $form->add($button); if ($form->isValid()) { $form->setErrorMessageToField($firstname, 'This is my custom message'); } echo $form->render(); If the "default validation" was successful, then the error message will be displayed under the "firstname" field in this case. You can use it to make another validations inside the isValid() function to check the submitted form values against other conditions. If the conditions fail then you can output this manually triggered error message like written above. To use the new method, you have to replace the file site\modules\FrontendForms\Formelements\Form.php with the one from Github or you simply add the new function to your Form.php. Please let me know if this helps.
-
Hi, everyone! While working on a client project we were looking for a way to let editors apply CSS classes to individual images in rich text fields — quickly, visually, and also in the frontend editor. ProcessWire already has several ways to get CSS classes onto images, so it's worth being precise about what this module does differently: TextformatterFluidImages adds one class to all images automatically — great for img-fluid across the board, but there's no per-image choice. TextformatterImageInterceptor is more powerful: editors tag images in the image field, and the Textformatter applies the corresponding classes at render time. The logic is developer-defined and centralized, which is exactly right when you want consistent, rule-based image treatment. But the class is invisible in the editor, applied only in the frontend output, and editors have to set the tag in a completely separate place from where they're actually working. TinyMCE's built-in styleFormatsCSS is the closest thing to what we wanted. You write CSS, ProcessWire turns it into a Styles dropdown. It works, but the dropdown is generic — it shows all defined styles regardless of what's selected — and there's a known accumulation issue where nothing prevents float-left float-right ending up on the same image. And it doesn't work in the frontend editor. What we needed was simpler: editor clicks an image, picks a style, sees immediately which styles are active, can combine them or remove them individually. No dialogs, no separate fields, no render-time magic — the class goes directly into the <img> tag in the saved HTML, visible and editable right there in the editor. That's what this module does: It registers a context toolbar in TinyMCE that appears as a floating "Image Style" button when an image is selected. For CKEditor the same options show up in the right-click context menu. The class list is defined once in the module settings and works across both editors — no separate configuration per editor type. Each entry shows a checkmark when active, clicking it again removes it, multiple classes can be combined freely. Works in the admin and in the frontend editor. Complete Readme on GitHub: https://github.com/frameless-at/ProcessImageClasses and the module directory. Any thoughts on further improvements welcome! Cheers, Mike
- 1 reply
-
- 12
-
-
[Update] Free Access Display Support (v0.1.7) Hi everyone! Following the latest release of StripePaymentLinksv1.0.23, we've updated this module to match the new free product access — so manually granted product access now shows up properly in all portal views. When a user has products assigned via spl_free_access, those products appear in the portal almost like purchased ones: Grid view: Free-access products render as full active cards instead of grayed-out "not yet purchased" ones. Table view: Free-access rows get a cyan "Free access" badge in the status column. The date column shows a dash (—) since there's no purchase timestamp involved. This makes it immediately clear which products came through Stripe and which were granted manually. Cheers, Mike
-
So I tried the following but I'm not sure if it's good practice or not. Sharing in case anyone finds it useful. This setup requires that the PageImageSource module is installed with srcset image sizes setup in its config. It basically involves creating a page where cross-site images can be uploaded - like a simple image library. I created a short function to make it easier to grab the images where needed. Adding ->render() will output <picture> with srcsets. The media page should be set as hidden. Create a template, give it a name and add a multi-image field In the templates folder create _func.php and add the following: <?php namespace ProcessWire; /* Create a fn. that returns a Pageimage object from media library page you just created. Here i called my function medialib, and $medialibrary is the name of my template.*/ function medialib($filename) { static $medialibrary = null; if (is_null($medialibrary)) { // REPLACE the get directory with the Media Library page url created in admin. Mine was /media-library/ $medialibrary = wire('pages')->get('/media-library/'); } // Returns the image object if found, or null return $medialibrary->id ? $medialibrary->images->get("name=$filename") : null; } Add the include for _func.php in your _init.php in the templates folder: <?php namespace ProcessWire; // This makes the medialib() function available to all templates include_once("./_func.php"); // Render media library images on other pages using format: /*<?= medialib("imagefile.jpg")?->render() ?>*/ Function can now be called in template markup using: // Render media library images on other pages using format: <?= medialib("imagefile.jpg")?->render() ?> //Example <?=medialib("imageNameMustMatchFileName.jpg")->render()?> // Outputs <picture> tag. For CSS styling, target outer div class like so: .class img{.....} Combining this with another module might https://processwire.com/modules/process-media-lister/ might be useful.
- 1 reply
-
- images
- image library
-
(and 3 more)
Tagged with:
-
I've been reading through the forums for best practices on handling images on static pages - that's to say, how best to handle images in my static html markup when I don't have any need for image fields. On my current site, I have a lot of images on the home page and it seems silly creating an image field for all of these. Also, for pages where I do have image fields, I've been using the PageImageSource module to create wepb srcsets automatically (and it works great). I like setting my desired image sizes for the srcsets in one place, so I'd like to use this module for everything everywhere if possible. The problem is that it only works with PageImage objects, so I have to do something with my static images in order for the plugin to work with those. It looks like the media manager modules are not maintained. Could I just somehow put all my non-image field images in one page reference or something and have the PageImageSource module render those wherever they're needed? Could the following work? 1) Create an images/media page and template with images field. Set it to hidden. 2) // In _init.php: $media = $pages->get("template=media", include=hidden); 3) // on a template page markup: $desiredImage = $media->images->get("name=imageName.jpg"); .... <div><=?php $desiredImage->render()?></div> // renders the srcsets and <picture> tags with the PageImageSource module I don't know if this is the recommended way of dealing with <picture> tags for static pages (srcsets are such a pain to do manually!). Maybe there are some downsides to this that I'm not aware of. Any guidance would be very welcome.
- 1 reply
-
- images
- image library
-
(and 3 more)
Tagged with:
-
Looks great. New to PW here and have been scanning the forums for best practices on including images on static pages (no image fields). A media library seems to be the way... I wanted to understand how I might use the PageImageSource module to render webp srcsets on the fly for static images but was disappointed to learn that it can only be used with PageImage images. https://github.com/nbcommunication/PageimageSource I think ProcessWire would be a hard sell to many clients without some a full-featured image library solution, which is a great pity. The UI looks well done.
-
I can guarantee from experience all these work together fine. This could be another logic for your what you are trying to achieve, although I don't see anything wrong in your snippet! Just figuring out that if you are getting started you might find these examples useful: // assuming section is the name of the repeater $projects = $pages->find("template=project, section.images.tags=gallery"); foreach ($projects as $project) { $title = $project->project_header->first()->project_title; // Maybe something like this to get images from any of the repeaters? $images = new WireArray(); $project->section->each(function($images) use(&$images){ $images->import($item->images->findTag("gallery")); }; foreach ($images as $img) { echo "<a href='{$project->url}' title='{$title}'>"; echo $img->render(); echo "</a>"; } }
- 12 replies
-
- beginner portfolio
- masonry
-
(and 3 more)
Tagged with:
-
Hi @elabx Thanks for your post. Your code looks clean but I'd like a little more flexibility ideally for my use case. I'd like the option to choose any image from my projects (not just the first). Also, sometimes I'll need to pick more than one image from the same project for display on the masonry gallery. For this reason, I'm thinking image tags might be the way to go but then the question is, how do I wrap that image in a link to its respective project page.. Page references seem like the way to go, but I'm still trying to wrap my head around how these work tbh. Custom image fields could also work but I'm trying to keep things as simple as possible. I also don't want to screw up alt fields for SEO and I'm looking to use PageimageSource to manage srcset and webp automatically (maybe I'm looking to learn too much at once for my first PW site 😂) https://github.com/nbcommunication/PageimageSource Assuming all image fields are image arrays with tags enabled in admin, could I do this?: // "sections" is the repeater on my projects pages that contain images and content. // "project_header" is a repeater in project template that contains project title and info. // Can I can use find() to get repeaters with template=repeater_repeaterName, like this? $projSections = $pages->find("template=repeater_sections, images.tags=gallery"); foreach ($projSections as $section) { $projectPage = $section->getForPage(); $title = $projectPage->project_header->first()->project_title; foreach ($section->images->findTag("gallery") as $img) { echo "<a href='{$projectPage->url}' title='{$title}'>"; echo $img->render(); echo "</a>"; } } It seems inefficient to me to have to parse through all project content repeaters for tagged images like this but maybe with caching it wouldn't be a problem? In my setup project title and description text is in one repeater, and all images are in another - not sure if that complicates things. I would like to wrap each image in a <a> that links to the respective project page.
- 12 replies
-
- beginner portfolio
- masonry
-
(and 3 more)
Tagged with:
-
So in the Markup Regions output strategy, the docs seem to suggest that we can render markup using a different base to _main.php, for use on specific pages (like rss feed, homepage, etc) if needed. Have I understood that correctly? https://processwire.com/docs/front-end/output/markup-regions/ It says "we can do so by editing the template settings in the ProcessWire admin to use a different file, or no file at all, when appropriate. (This is found in Setup > Templates > [choose a template] > Files [tab])." I'm not sure, however, what setting I should change here. Let's say I want this template to apply Markup Actions to 'base.php' instead of _main.php, where would I set that here? Most of my confusion stems from the "Prepend" and "Append File" wording... I'd really appreciate any help 🙂 Thanks
- 11 replies
-
- markup regions
- template
-
(and 1 more)
Tagged with:
-
@elabx - I don't really know how those apache modules work exactly, but wouldn't they have the same issue as I realized when I thought - why not just hook into Page::render and do a string replace on all <script> tags to add in the nonce at runtime - the problem being of course that this would also add the nonce to any maliciously injected <script> tags, thereby effectively removing all protection?
-
Well that would have been method 'C' aka the preferred method. I did try some of the other ways to render the form but the end result was the same. This is not urgent on my side, but it would have been neat if it worked. If I can help you to debug it just let me know what to try.
- 32 replies
-
- formbuilder
- ajax
-
(and 1 more)
Tagged with:
-
Gave this a quick try using latest PW, Formbuilder and HTMX. The form renders fine, but when I click submit it will just disappear, same happens whether required fields are filled or not. No entries appear either so it's not quite working out of the box. No errors of any kind in PHP or console, just gets a 200 and returns nothing. $htmxForm = $htmxForms->render('contact'); echo $htmxForm->styles; echo $htmxForm->scripts; echo $htmxForm;
- 32 replies
-
- formbuilder
- ajax
-
(and 1 more)
Tagged with:
-
New blog: All about custom page classes in ProcessWire
ryan replied to ryan's topic in News & Announcements
@Ivan Gretsky I'm always a little reluctant to make a blanket statement like "avoid markup in page classes", but I'm referring to what I think works best with the projects I work on. The files in /site/templates/ are the view layer, as nearly all code in there is aimed at generating markup/output. Even if something isn't directly generating markup, it's still finding and preparing things for output. Most markup comes from "partials", which are files that I put in /site/templates/parts/, or if exclusive to a particular template, then /site/templates/[template]/. And then I either include() them, or files()->render() them from the site's template files. I primarily use Markup Regions. The _main.php establishes the base markup: <?php namespace ProcessWire; /** @var Page $page **/ ?><!DOCTYPE html> <html> <head id="html-head"> <?php include('./parts/html-head.php');?> </head> <body id="html-body"> <header id="header"> <?php include('./parts/header.php');?> </header> <h1 id="headline"><?=$page->title?></h1> <main id="content"> <?=$page->body?> </main> <aside id="sidebar" pw-optional> </aside> <footer id="footer"> <?php include('./parts/footer.php');?> </footer> </body> </html> Below is a template file for the /products/ page which lists product pages, supports pagination, and uses URL segments for sorting: <?php namespace ProcessWire; // products.php /** @var ProductsPage|CategoryPage $page */ $products = findProducts($page); $body = input()->pageNum === 1 ? $page->body : ''; $headline = $page->get('headline|title'); ?> <h1 id="headline"><?=$headline?></h1> <main id="content"> <?=$body?> <?php include('./parts/sorts.php'); // no expects ?> <?php include('./parts/products-list.php'); // expects $products ?> </main> <aside id="sidebar"> <?php include('./parts/categories-list.php'); // no expects ?> </aside> The category template file works exactly the same way, except that it lists products for the category rather than listing all products. The same code works either way, so "category.php" just includes "products.php": <?php namespace ProcessWire; // category.php include('./products.php'); There's that findProducts() function above in the products.php template file -- I usually have helper functions in a /site/templates/_func.php, /site/templates/_products.php, or /site/templates/products/func.php (assuming exclusive for "products"). Another place would be for the ProductsPage and CategoryPage to have findProducts() methods, but usually I don't want the page classes getting involved with detecting stuff about the current request (sort, pageNum, etc.) so like these in a simple function library file: <?php namespace ProcessWire; // file site/templates/_func.php included by _init.php function getSorts(): array { return [ 'name' => 'A-Z', '-name' => 'Z-A', 'price' => 'Price (low-high)', '-price' => 'Price (high-low)', 'created' => 'Date added (oldest)', '-created' => 'Date added (newest)' ]; } function getSort(): string { $sorts = getSorts(); $sort = input()->urlSegment('sort-(*)'); if(empty($sort)) $sort = 'name'; if(!isset($sorts[$sort])) wire404('Invalid sort'); return $sort; } function findProducts($page, $limit = 20): PageArray { $sort = getSort(); $find = "template=product, sort=$sort, limit=$limit"; if($page instanceof CategoryPage) $find .= ", categories=$page"; return pages()->find($find); } Here's an example of a ./parts/products-list.php file: <?php namespace ProcessWire; // file: parts/products-list.php /** @var PageArray|ProductPage[] $products */ $subhead = $products->getPaginationStr('Products'); $pagination = files()->render('parts/pagination.php', [ 'items' => $products ]); ?> <h3><?=$subhead?></h3> <?=$pagination?> <ul class="products-list"> <?php foreach($products as $product): ? <?php include('./parts/products-item.php'); // expects $product ?> <?php endforeach; ?> </ul> And the parts/products-item.php, though in reality there would likely be more to it: <?php namespace ProcessWire; // file: parts/products-item.php /** @var ProductPage $product */ ?> <li class="products-item"> <h3><?=$product->title?></h3> <p><?=$product->summary?></p> <p><a href="<?=$product->url?>">View Details</a></p> </li> To complete it, here's the parts/sorts.php file: <?php namespace ProcessWire; // file: parts/sorts.php $currentSort = getSort(); $url = page()->url; $sorts = []; foreach(getSorts() as $sort => $label) { if($sort != $currentSort) $label = "<a href='{$url}sort-$sort/'>$label</a>"; $sorts[] = $label; } echo "<p class='sorts'>Sort by: " . implode(' / ', $sorts) . "</p>"; If I start needing to output products in more places in the site, then I'll usually do fewer include()'s and move the rendering to dedicated functions. That way these things render in their own variable namespace and don't bleed variables or overwrite variables in the main rendering. So this would also go in that _func.php (or _products.php or ./products/func.php) mentioned above, and the include() calls in template fiels would be replaced with render...() calls: function renderProducts(PageArray $products): string { return files()->render('parts/products-list.php', [ 'products' => $products ]); } function renderCategories(): string { return files()->render('parts/categories-list.php'); } function renderPagination(PageArray $items) { return files()->render('parts/pagination.php', [ 'items' => $items ]); } So if using render() functions then the <main> with the include('./parts/products-list.php'); would get replaced with this: <main id="content"> <?=$body?> <?=renderProducts($products)?> </main> Ah yes, I hadn't thought about that in a long time. I can't remember if that was implemented yet or not. I'll find out. If not yet implemented I'll have to implement it, it should be fairly simple.