-
Posts
1,501 -
Joined
-
Last visited
-
Days Won
44
Everything posted by gebeer
-
Migration for a Block Type - is there an API to create Block Types
gebeer replied to gebeer's topic in RockPageBuilder
I ran the migrations again through modules refresh on the server after deploying. But the template was not created. That was in the instance when I did the deployment for the first time. I haven't tried to reconstruct the situation by reverting to the previous DB stage and run migration again. Don't have the budget for that in that client project, unfortunately. Sorry, if this is not comforting you. But sometimes we have to act under budget/time constrains. Though if this occurs again, I will further investigate. -
Migration for a Block Type - is there an API to create Block Types
gebeer replied to gebeer's topic in RockPageBuilder
Thanks for all the info. I guess then that it was a hickup on the server. Nothing unusual in the migrations for that block. 'templates' => [ $this->getTplName() => [ 'fields' => [ 'title' => [ 'label' => 'Headline', ], 'questions' => [], // page ref field ], ], And this was the first time that this happened. -
Hi @bernhard, I created a new block type `Questions` through the GUI. All files and template `rockpagebuilderblock-questions` were created in that process. When I deployed that to our staging server, the template for that block was not created. So I had to add that template `rockpagebuilderblock-questions` to my RockMigrations to make the block work on staging. Is this normal behavior? Is there an API method that I could use instead of the template migration? Couldn't find it in the docs. Fields for that block are managed through the migrate method in site/RockPageBuilder/blocks/Questions.php. So the template migration is somewhat redundant.
-
Different templates-folder for different users/roles?
gebeer replied to pideluxe's topic in Wishlist & Roadmap
Reviving this for a slightly different use case. I need to serve a different templates folder based on a certain URL segment. My code goes in site/config.php. In there $input is not available yet. So the logic uses $_SERVER['REQUEST_URI'] /** * Switch ProcessWire templates directory based on the second URL segment. * Type the second URL segment as key and the name of the new templates folder as value. * Example: '/imaging/' maps to 'templates-magazine' folder. * We use the second segment because the first segment is always the language (en, de, etc.) */ $config->templateSegments = array( 'imaging' => 'templates-magazine', // first url segment => templates folder name // Add other segment => folder mappings here ); // Check the second URL segment directly from REQUEST_URI $requestUri = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '/'; $path = parse_url($requestUri, PHP_URL_PATH); $segments = explode('/', trim($path ?: '', '/')); $firstSegment = $segments[1] ?? null; if ($firstSegment && isset($config->templateSegments[$firstSegment])) { $folder = $config->templateSegments[$firstSegment]; $config->urls->templates = "/site/" . $folder . "/"; $config->paths->templates = $config->paths->site . $folder . "/"; } Works well. in site/init.php I have a ProcessPageView::execute hook so that PW can find the template file and offer the "View" link in the page edit screen in admin. $wire->addHookBefore("ProcessPageView::execute", function (HookEvent $event) { // set templates dir for magazine pages in admin /** @var Pages $pages */ $pages = wire()->pages; /** @var PagesRequest $request */ $request = $pages->request(); /** @var Page $page */ $page = $request->getPage(); $editPage = $pages->get(input()->get('id')); if($page->template == 'admin' && $editPage->template == 'magazine-page') { /** @var Config $config */ $config = wire()->config; $folder = 'templates-magazine'; $config->urls->templates = "/site/" . $folder . "/"; $config->paths->templates = $config->paths->site . $folder . "/"; } }); Not the most elegant implementation, but works. Could have put this in site/templates/admin.php but decided against for separation of concerns reasons. -
This looks very promising. I will adapt that format with frontmatter usage. Only, I want AI to rephrase only when I tell it to. For smaller things there is really no need for rephrasing imo. Would you mind sharing those rules? Grok 3 is really good in my experience. I use it mostly in the web app for DeepResearch tasks.
-
Hello @horst, I am running PageImageManipulator02 for over 10 years now :-). We recently switched to PHP 8.3 and get following deprecation warnings: I am only using the module to get a low quality version for lazy loaded images (pixelation and smoothing): $imgLow = $img->pim2Load('lowq', true) ->setOptions(['quality' => 20]) ->pixelate(3) ->smooth(255) ->pimSave(); Could I replace this functionality with native PW image manipulation API?
-
Sure: https://chromewebstore.google.com/detail/markdownload-markdown-web/pcmpcfapbekmbjjkdalcgopdkipoggdi?hl=en-GB
-
Yeah, Yifan has some great videos on the subject. And context is all that matters. Since LLMs nowadays support larger context windows, we can take advantage of that and pass quite a lot of docs for in-context learning. I have a docs folder in each project, mostly with markdown files that contain detailed requirements for a feature, documentation for obscure libraries etc. I then pass those to the assistant via the @files feature when needed. This makes a big difference in code quality. Haven't used PW-specific examples but will definitely try that out. So thanks for sharing this idea! I am using a Browser extension for downloading webpage content as markdown. It is quite fast and much cheaper than passing this in via @web to windsurf, which eats up lots of credits.
-
Thanks for sharing this. It is much more comprehensive then mine: # Project Guidelines ## Expert Knowledge - ProcessWire CMS PHP development - Tailwind / Flowbite markup and classes ## Core Technical Requirements - PHP 8.3+ - ProcessWire CMS - Tailwind CSS - ES6+ JavaScript - Webpack NPM/NPX ## Development Principles - PSR-12 and SOLID compliance - Type-safe PHP implementation - Defensive programming with early returns - DRY code organization - Exception handling with WireException - Logging via ProcessWire log() API ## ProcessWire Best Practices - API-first development approach - Markup regions for output processing - Strategic hook usage in site/ready.php and site/modules/Site.module.php - Input sanitization through PW API - CSRF protection implementation - Field/template migrations with RockMigrations - use of functions API preferred. (`pages()` instead of `$pages` etc.) ## Frontend Standards - Mobile-first Tailwind / Flowbite design - WCAG 2.1 accessibility compliance - Progressive enhancement strategy - Modern browser support (last 2 versions) - CSS custom properties Will definitely take some inspirations from yours. Judging from parts of your rules, it almost seems like you might have read https://neoexogenesis.com/posts/rust-windsurf-transformation/ :-)
-
Actually, my rule is quite different from aider architect mode where the architect agent plans and lays out the implementation steps on a higher level and then passes that to the coding agent. Love aider and am getting really great results with it, too. My prompt simply rephrases the original user prompt in more technical terms and most of the time more on the spot for the request at hand. Please let me know how it is working for you. Since the prompt is just a few days old, there might be room for improvement.
-
What is this about? When working with AI enhanced IDEs like Cursor or Windsurf, we often tend to give somewhat ambiguous requests like these exaggerated examples: or Much better wording for these examples would be AI tends to deliver much better results when we give it concise, technical instructions that fit the context. My approach I have tried to "automate" this in some ways with simple AI rules. Both Cursor and windsurf have a feature called AI rules where you can set global and project specific rules that the assistant will follow. This snippet is in my global rules: ## User Prompt Rephrasing Every time you encounter the exact keyword "rephrase" in a user prompt, do the following: 1. rephrase the user prompt in concise technical terms focusing on: - specific technical task scope - affected components/files - required functionality changes 2. preserve the users intent in the rephrased prompt 3. output the rephrased prompt and ask for confirmation with exact phrase "Act on the rephrased prompt? [y/n]" 4. IMPORTANT: after asking for confirmation, STOP and wait for explicit user response 5. proceed ONLY after receiving "y" confirmation, otherwise ask for clarification 6. when proceeding, act only on the rephrased prompt How it works Now, whenever I add "rephrase" to my prompt, the assistant will act accordingly. Example: Benefits The rephrased version offers several benefits over the original version: Component Clarity - Original was vague, rephrased version explicitly lists required components (GUI framework, drag-drop handler, PDF converter) Scope Definition - Clearly separates existing functionality (PDF conversion) from new requirements (GUI wrapper) Implementation Direction - Suggests specific technical approaches (tkinter/PyQt) while maintaining flexibility The AI assistant will perform better with the rephrased prompt because: More precise input leads to more precise output - technical specifications eliminate ambiguity about what to implement Breaking down into components helps the AI reason systematically about the solution architecture Explicit requirements (e.g., "single file processing") prevent the AI from making incorrect assumptions about scope Clear instructions to AI yield clear results. Or as the old saying goes: garbage in, garbage out :-) A bit of theory behind the concept The idea for a rule like that came to me when I heard about the concept of "Latent Space Activation" in Large Language Models. Very brief explanation from Claude: My rephrasing prompt supposedly has some impact on latent space activation. Claude again: Does it really work? I've been experimenting with this for several days now and my subjective impression is that I really get better results with this approach. Better, working code often on the first shot. Try it yourself Have a play and let me know if you get better results, too.
-
Hi there, I'm experiencing unexpected behavior with markup regions where multiple pw-append actions are being processed in reverse order. I have been using markup regions for several years in multiple projects and never came across this. There are no project specific potential causes for my problem that I could identify. Involved HTML markup syntax is correct. Setup Delayed output strategy with site/templates/_main.php (simplified): <html> <head> </head> <body> <main id="maincontent" class="content"> </main> </body> </html> site/templates/products.php: <div class="bg productsintrobg" pw-append="maincontent"> <!-- Products Intro Section --> <div class="container"> <!-- Products Intro Content --> </div> </div> <div class="bg productcatsbg" pw-append="maincontent"> <!-- Product Categories Section --> <div class="container"> <section class="productCats teasers row"> <!-- Product categories content --> </section> </div> </div> Expected Behavior Since products.php executes before _main.php and contains region actions (pw-append) that should be processed in order, the final output should show: Products Intro Section Product Categories Section Actual Behavior The sections are rendered in reverse order: Product Categories Section Products Intro Section Technical Context According to the Markup Regions documentation: Since products.php is processed before _main.php (using $config->appendTemplateFile = '_main.php'), the region actions in products.php are technically output "before" the <html> tag and region definitions in _main.php. Therefore, the implementation follows the documentation correctly, but the output order is reversed. Current Workaround Changed first section from pw-append to pw-prepend to achieve desired order: <div class="bg productsintrobg" pw-prepend="maincontent"> <!-- Products Intro Section --> </div> <div class="bg productcatsbg" pw-append="maincontent"> <!-- Product Categories Section --> </div> MarkupRegions Debug Output (shows wrong order of processing): • APPEND #maincontent with <div class="bg productcatsbg" ... +24 bytes><div class="container"> <section class="productCats teasers row"> … 14986 bytes</div> • APPEND #maincontent with <div class="bg productsintrobg... +26 bytes><div class="container"> <article class='intro row'> … 1264 bytes</div> • 0.0014 seconds Environment ProcessWire version: .3.0.243 PHP version: 8.1 (same behavior with 8.2) Has anyone experienced this before? Couldn't find related forum posts or github issues
-
I just managed to get the Blade Formatter extension (https://open-vsx.org/extension/shufo/vscode-blade-formatter) working with Latte files :-) I needed to use the Blade Formatter from the actions menu: Although I have it defined as a default formatter for latte files in settings.json, the default formatting command always asks for a formatter. "[latte]": { "editor.defaultFormatter": "shufo.vscode-blade-formatter" }, But pressing Shift+P and enter to trigger the Blade formatter from the list is still much better than manual formatting :-)
-
Hi @bernhard, in the docs-old folder of the module there is a nice section about prices. It says that we can switch to gross prices with a config setting. I was not so sure whether that still applies and how it works with the cart calculations. So I just wanted to share my custom implementation, so that editors can enter gross price and net price will be calculated. I added a field (type RockMoney) "price_gross" in addition to the "rockcommerce_net" field to my product template. And in my ProductPage class, I have a saveReady hook that calculates the net price from the gross price and the vat for that product. /** * Calculates the taxrate of the product * based on the taxrate field of the product * if no custom taxrate is set, the global taxrate is used * * @return float */ public function getTaxrate(): float { return (float) $this->rockcommerce_taxrate ? $this->rockcommerce_taxrate/100 : $this->taxrate(); } /** * Executes when the page is ready to be saved. * * Only executes if price_gross is given and field rockcommerce_net is present. * Calculates the net price and VAT based on the gross price * and tax rate, if a gross price is provided. * It saves the net price to rockcommerce_net. * * @return void */ public function onSaveReady(): void { // only execute if we have price_gross if ($this->price_gross && $this->template->hasField('rockcommerce_net')) { // take price_gross and calculate vat and net price from it using taxrate $taxrate = $this->getTaxrate(); $vat = rockmoney()->parse($this->price_gross)->times($taxrate / (1 + $taxrate)); /** @var \RockMoney\Money */ $priceNet = rockmoney()->parse($this->price_gross)->minus($vat); $this->rockcommerce_net = $priceNet->getFloat(); } } I also use a custom getTaxrate() method from my page class, in case a custom rate is set for that product. Page edit screen
-
Thank you so much for the quick reply @bernhard and for adding stuff to the docs so quickly! Now the docs are coming together very nicely. I really appreciate that. That is totally fine. If we know these limitations as developers, we can maybe implement custom solutions if the existing cart hooks are sufficient and allow us to do those calculations and display them in the cart. I have a rather simple shop atm with single tax rate. Just wanted to know how to handle things when tax rates are needed per product. My experence with other shops shows that this will eventually come up at some point in time :-)
-
To expand on this, imo these are the minimum points that should be covered by the docs: defining tax rates setting default tax rate apply tax rate different from default to product explain how prices are calculated: - does RC always calculate with default rate if no other rate is set for a product? - do we need to implement those calculations ourselves? If you can add those, that would be awesome. Thank you.
-
Hi @bernhard, I'm just getting started with setting up my first project using your RockCommerce module. Installation on a new site went fine. Also basic product setup. But now I'm stuck on setting up and applying tax rates. I figured out that I can add child pages And I can set a default tax rate on the "Taxrates" page. But how are non-default rates handled per product? Do I use the field rockcommerce_defaulttaxrate on my product template? When I add the field rockcommerce_vat to my product template, I get a non-editable field When I add rockcommerce_taxrate, I get an input for the tax rate When I enter a value here, will this be picked up automatically for the price calculations? And I think it would be better to have a page select field where we can choose from the tax rates that we added as children of the "Taxrates" page. Is this the correct way to set this up? Unfortunately I couldn't find anything in the docs. Would be much appreciated if you added a section about tax handling there. The quickstart docs are great, but I think tax handling is a very essential thing in every shop. So this should definitely be covered by the docs. EDIT (1h later): I just realized that in the readme.md inside the docs-old/taxes folder of the module there is a section about taxes that answers some of my questions. I was relying on the documentation at https://www.baumrock.com/en/processwire/modules/rockcommerce/docs/ The information in the readme.md is not contained there and I am not sure if it still applies since it is in the docs-old folder? Imo it would be great to have everything in one place.
-
I can kind of understand that. But imagine some html structure with more than 3 levels of depth. Then you remove some wrapper deep in the structure and now you have to manually mark the relevant section and move it back 1 tab. That is just a waste of time imo. I prefer to just hit my short key for Format Document and done. It really is a shame that there are no formatters out there for latte. Guess it is too much of a niche template engine. I even tried to use the blade formatter. But didn't work for me. Honestly, little things like that have kept me from using Latte at all in the past. Now I took another try and hitting the wall again. Guess, I will just revert to pure PHP and be happy with that :-)
-
Having the same issue here. VsCode (Windsurf) always asks for a formatter. I tried in .prettierrc: Still no luck. I also have this in my user settings.json: "[latte]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, Doesn't help either. So for the moment I'm stuck. A solution would be much appreciated.
-
Hi there, why the heck would you want to migrate your migrations? Since v6 of RockMigrations there's a new feature called "config migrations" which you can read more about here. In a nutshell: instead of having an often lengthy migration function like $rm->migrate([ 'fields' => [...], 'templates' => [...] ]) with hundreds of lines in either site/migrations.php or site/modules/Site.module.php or other places, we can neatly organize our migration definitions into single files inside site/RockMigrations/fields and site/RockMigrations/templates (...roles, ...permissions). Where each file has the name of the field/template/role/permission and returns an array of settings. If you have module specific migrations, they go under /site/modules/MyModule/RockMigrations/(fields/templates/etc) This is absolutely wonderful for several reasons. Here's just a few that come to mind: clean structure smaller migration files easy to discover in explorer panel faster migrations because only migrations for changed files will fire portability across projects Because I immediately fell in love with this concept, I am currently updating migrations for all projects to the new structure. This can be a very tedious task. We could write a script (and maybe this is the ultimate solution). BUT since I'm lazy, I just commanded my favorite AI assistant to do the job for me. I did this in windsurf editor Write mode (Cascade), but you can also use cursor's composer for this. Let me share how I approached it. I created the directories "fields" and "templates" under site/RockMigrations. And one migration file each for a field and a template to have examples to pass to the AI. Then I copied the 'fields' array from above mentioned $rm->migrate(['fields => []]) (that was originally in my Site.module.php) and pasted it into the windsurf Cascade (or cursor composer) chat. Along with these instructions: For each item in this array do the following operations: 1. create file site/RockMigrations/fields/{array_key}.php 2. add namespace ProcessWire and return the array found under this {array_key} Mirror structure of @seo_fieldset.php Where @seo_fieldset.php is passing the contents of site/RockMigrations/fields/seo_fieldset.php as context to the AI. And here's what happened: windsurf Cascade decided to do the operation in batches. It would let me check the results after every 4 files or so and asked me if I wanted to continue with the next batch. After about 10 minutes all my 31 field migrations where refactored into the new structure and I was very happy following along while sipping away at my coffee :-) After that I did the same for the 'templates' array. Some considerations on why I chose this exact approach: I could have given the whole Site.module.php file as context and asked the AI to read the fields array from there. But that file is pretty large and the fields array alone was about 400 lines of code. And that might have exceeded the context window or at least confused the AI. I used the phrase "Mirror structure of ..." because I found that to be a very effective prompt whenever you want the assistant to write code based on an example. Actually I did not find that out by myself (I'm not that great of a prompt engineering genius). Got it from IndyDevDan over at YT. Highly recommended channel btw. To wrap things up, I can highly recommend switching to the new config migrations structure. So much cleaner. So much better. A big praise to @bernhard for coming up with this. Also praises to the developers over at cursor and codeium (windsurf) for these amazing tools. And, not to forget: Hail Claude!
- 1 reply
-
- 2
-
-
-
We were experiencing a similar issues because the POST request now goes to / root instead of the current page loaded like before with ./ In our case the console result frame would load the homepage of the frontend instead of the actual results. I solved it for our case (a hook to PageView::execute on /) by returning if it is a tracy request $wire->addHookBefore("ProcessPageView::execute", function (HookEvent $event) { // do not redirect if tracy request if(isset($_POST['tracyConsole']) && $_POST['tracyConsole'] == 1) return; // alternative // if (isset($_SERVER['HTTP_X_TRACY_AJAX']) && $_SERVER['HTTP_X_TRACY_AJAX']) return; if ($_SERVER['REQUEST_URI'] === '/') { ... session()->redirect("{$lang}{$marketName}"); } We need that redirect to determine language and market based on a request to GeoApify API. I think that others might potentially run into this as well. So if the change of xmlhttp.open("POST", "./", true); to root / is not strictly necessary for Tracy to work, it might be better to revert that? @adrian
-
https://www.perplexity.ai/search/php-constants-naming-conventio-g4mLG8sbQyu34r4mGXmx6w Although the source for this is not php.net directly. I wanted to read up on that over there but php.net currently throws a 502 error. Anyways, that was just a side note. That explains why you never saw that error. But imo you should not assume that everybody uses underscores so I think, the renaming logic in my PR at https://github.com/baumrock/RockMigrations/pull/70/commits/a06c6062c61b5264932a8044cf25bac0c689f8cf should be implemented. Otherwise people who use hyphens in their template names would have to rename all their templates just to be able to use the RockMigrationsConstants magic.
-
Ah, thank you. That make sense. Will prioritize my Site module migration so that the backup is created before the config migrations kick in.