Jump to content

bernhard

Members
  • Posts

    6,631
  • Joined

  • Last visited

  • Days Won

    359

Everything posted by bernhard

  1. Nice!! Yeah, absolutely 🙂
  2. From my research/testing this was necessary to make the SSE stream non-blocking. When using regular url hooks after the session has been started, then you can't open any other tabs while the stream runs. That's one of the caveats that you'll have to fight when developing something from scratch. Thx 🙂 For example?
  3. I've built this over and over in several of my modules (RockDevTools for Livereload, RockCalendar for creating events). Always a lot of work. Always a lot of boilerplate code. Always a lot of issues to fix. I think it's time to bring SSE (Server Sent Events) to everybody. Simple example (Empty Trash): Client-Side: // create stream const stream = ProcessWire.Sse.stream( 'ssedemo-empty-trash', (event) => { const textarea = document.querySelector('textarea[name="empty-trash-status"]'); try { let json = JSON.parse(event.data); stream.prepend(textarea, json.message, 100); } catch (error) { stream.prepend(textarea, event.data); } } ); // update progress bar const progressBar = document.querySelector('#empty-trash-progress'); stream.onProgress((progress) => { progressBar.value = progress.percent; }); // click on start button document.querySelector('#empty-trash').addEventListener( 'click', (e) => { e.preventDefault(); stream.start(); }); // click on stop button document.querySelector('#stop-empty-trash').addEventListener( 'click', (e) => { e.preventDefault(); stream.stop(); }); Server-Side: public function __construct() { parent::__construct(); /** @var Sse $sse */ $sse = wire()->modules->get('Sse'); $sse->addStream('ssedemo-empty-trash', $this, 'emptyTrash'); } public function emptyTrash(Sse $sse, Iterator $iterator) { $user = wire()->user; if (!$user->isSuperuser()) die('no access'); $selector = [ 'parent' => wire()->config->trashPageID, 'include' => 'all', ]; // first run if ($iterator->num === 1) { $iterator->max = wire()->pages->count($selector); } // trash one page at a time $p = wire()->pages->get($selector); if ($p->id) $p->delete(true); else { $sse->send('No more pages to delete'); return $sse->stop(); } // send message and progress info $sse->send( $iterator->num . '/' . $iterator->max . ': deleted ' . $p->name, $iterator ); // no sleep to instantly run next iteration $sse->sleep = 0; } Code + Readme: https://github.com/baumrock/SSE/tree/dev What do you think? Would be nice if you could test it in your environments and let me know if you find any issues!
  4. Congrats on the launch @ryan Would it be possible to get some kind of webhook that I can call and send data to? I have everything automated for all my modules and I really don't want to log into the modules directory to push all my updates manually 😞 It would probably be ok to add all the readmes once, but still all the version numbers and the "last updated" indicator would be outdated as all my paid modules are private git repos. And I put a lot of effort into all my pro modules, so it would be nice if that could be visible somehow in the modules directory. See RockCalendar as an example: https://www.baumrock.com/en/releases/rockcalendar/ Ideally we'd have a github workflow that anybody can add to his/her project that automatically sends new data to the modules directory on every new release. I can build that workflow if you want and all you'd have to do is to add it to the github repo (like this example from RockShell) so that we all can use it in our CI automations and then add an url hook to processwire.com that reads the data from the webhook and updates the modules directory entry.
  5. JavaScript hooks are totally client side. There is no Server involved and also no PHP. Everything happens in JavaScript. The reason why they are extremely useful is because the developer of the JS module can easily make things customisable and we as devs or users can use hooks to change the code execution. Imagine someone builds a modal component for the core. We have a modal.show() and modal.hide() method. Without hooks, that's it. Maybe the developer was wise and added events to it that we can listen to. Maybe something like "modal:shown" or "modal:hidden". UIkit for example has these events: beforeshow Fires before an item is shown. show Fires after an item is shown. shown Fires after the item's show animation has been completed. beforehide Fires before an item is hidden. hide Fires after an item's hide animation has started. hidden Fires after an item is hidden. Now the problem: How would you, as a developer, modify the behaviour of the modal? For example how would you prevent showing the modal under certain circumstances? That's easy when having hooks. All we need is making ___show() and ___hide() hookable and we don't need those events any more, because we can hook into the modal's business logic: ProcessWire.addHookBefore('Modal::show') ProcessWire.addHookAfter('Modal::show') ProcessWire.addHookBefore('Modal::hide') ProcessWire.addHookAfter('Modal::hide') -------------- Another example is RockCommerce. The challenge here is not the backend - we all have the same backend. The challenge is the frontend, as every frontend is different. And every RC customer might need different pricing calculations or different features (like preventing to select items, max amount, custom tax calculations, coupons, etc...). All this can easily be done by providing hooks. RockCommerce does the heavy lifting and provides the base implementation and my customers can add modifications as they need. -------------- Another example could be a DatePicker. It often has some init call like this: const picker = new WhateverDatePicker({ firstWeekday: 0, okLabel: "Apply", ... }); This code will be somewhere in a core .js file. That means it is hardcoded! How would we make the "firstWeekday" setting customisable by the user? So that some can set Monday and some Sunday as first day of the week? And what about the okLabel? etc, etc... With hooks it is easy. The dev just adds a hookable method and all good: Class WhateverDatePicker { function init() { const picker = new WhateverDatePicker(this.getSettings()); } ___getSettings() { return { firstWeekday: 0, okLabel: "Apply", ... } } } Now anybody could easily hook into the setup of the Datepicker: ProcessWire.addHookAfter('WhateverDatePicker::getSettings', (event) => { let settings = event.return; settings.firstWeekday: 1; // change from 0 to 1 settings.okLabel: 'Anwenden'; });
  6. Random errors are the worst! 😉 If you have TracyDebugger installed already you might also want to look into the folder /site/assets/logs/tracy You might find some tracy bluescreen html files there which you could copy to your computer and open in a browser and see something like this: You can click on each item of the trace and you'll see where the problem occurred (top arrow) and also which arguments have been sent to the method (bottom arrow). Good luck 🤞
  7. RockDaemon simplifies running long-running background tasks (daemons) with automatic lifecycle management. Ideal for tasks like PDF generation, email processing, or data synchronization that need to run continuously via cron jobs. Why RockDaemon? Running long-running tasks in PHP can be challenging: Preventing multiple instances from running simultaneously Manual restart capabilities Automatic restart after deployments Signal handling and graceful shutdown Debug output control Command-line argument parsing Hosting environment compatibility RockDaemon solves these problems with a simple, cron-based approach. Example <?php // pdf-daemon.php namespace ProcessWire; use RockDaemon\Daemon; require_once __DIR__ . '/public/index.php'; $rockdaemon = wire()->modules->get('RockDaemon'); $daemon = $rockdaemon->new('pdf-daemon'); $daemon ->run(function (Daemon $daemon) { // get a newspaper page that has the "createPdf" checkbox enabled $p = wire()->pages->get([ 'template' => 'newspaper', 'createPdf' => 1, ]); if (!$p->id) return $daemon->echo('Nothing to do'); $timer = Debug::startTimer(); $p->createPdf(); $ms = Debug::stopTimer($timer) * 1000; $daemon->log( message: "created PDF for $p in {$ms}ms", logname: "pdf-create", pruneDays: 30, ); $daemon->run(); }); Docs + Download: baumrock.com/RockDaemon Showcase in PW Weekly: https://weekly.pw/issue/590/
  8. Needed the exact same thing today and came up with this solution that allows using regular page selectors: public function getSheetToCreatePdf(): SheetPage|NullPage { // make sure to only find pages that have been modified since creation wire()->addHookAfter("PageFinder::getQuery", function (HookEvent $event) { $query = $event->return; $query->where("pages.modified > pages.created"); $event->removeHook(null); }); // find the oldest page that has no PDF and make sure to flush the cache return wire()->pages->getFresh([ 'template' => 'sheet', 'pdf.count' => 0, 'has_parent' => '/papers', 'sort' => 'id', ]); } This is called from an endless reactPHP loop that automatically creates PDFs when new pages are modified 🙂
  9. Hy @poljpocket thx for your questions I'm not using the functions API myself either. I never use pages() in my module, for example, I'm always using wire()->pages->... which will work with $config->useFunctionsAPI = false; Sure. For me this is not an issue and I thought it would not be an issue for anybody else, because I didn't consider a staging site being visible to others on the same network being critical in any way. But you are right, it might not be the most secure approach, so I have added a config setting for this which is disabled by default. So the default will be to only allow access for the session and allow the IP only when the checkbox is checked. I added this because when I tested my site on staging with my mobile phone and got a signup confirmation via mail and clicked the activation link my phone opened that link in some other browser and therefore I got "access denied". Sure it would be possible to copy the link and open it in the same browser that is already authenticated, but explain that to clients that you sent the staging link for testing... 😉
  10. What I often do in such situations is to add this via hook: $wire->addHookMethod('Block::shouldRender', function($event) { $event->return = true; }); Then you have a shouldRender() method for all blocks that returns true by default and you can implement custom logic for each block that needs it. But I'm wondering... Maybe it would be a better idea to implement a DefaultBlock class that everybody can implement that RPB picks up and uses to extend every block on? You could then place your shouldRender() there and you could place anything else there as well. I'm a bit concerned about adding shouldRender() as it's really simple for your situation but if in the core I'm quite sure I'll get support requests why shouldRender() does not account for helper methods like getBlockNum() or isEvenType() etc... What do you think? I think having a DefaultBlock class would be nice and should help in your situation as well?
  11. @FireWire not sure what you want to say or ask?
  12. @adrian unfortunately I'm lost with this and need some more help 😞 Where would I put this mapping and what would I place as patterns? It feels complicated to me and I'm a bit afraid of doing this for each and every project 😮 I have a wrong return value in Editor.php on line 49 which throws a bluescreen like this: As you can see the path to that file shows /example.com/site/modules/.../Editor.php The tooltip shows the correct path in the DDEV web container: /public/site/modules... When I click the link it tries to open that folder in Cursor, but without the needed /public and therefore it does not find the file: I'm totally lost and I hope you have an idea?
  13. Hey @FireWire thx for the idea/suggestion! Sounds good to me! The only thing to consider would be to also account for hidden blocks. Blocks already have an _mxhidden flag/attribute which is used both for rendering/not rendering the block but it is also used in the helper methods like getBlockNum() or getBlockIndex(). The latter is the base implementation where it accounts for that flag. So maybe all we need to do is to move this: foreach ($items as $item) { if ($item->_mxhidden) continue; if ($item->_temp) continue; if ($item->id === $this->id) return $i; $i++; } Into a dedicated method as you suggest?
  14. Hey @FireWire thx sounds good! I can't remember any reason for having it in __construct I have just added this to the dev branch so we have a month of testing before it's merged to main 🙂
  15. I think the latte docs here should answer your question: https://latte.nette.org/en/template-inheritance
  16. @Sanyaissues and I have been working intensively on a new version of RockMigrations Deployments, which will be moved to RockShell and will support Github Environments. So we thought about having an .env file per environment instead of prefixing every variable: // STAGING.env dbHost=localhost dbName=db_staging dbUser=whatever dbPass=1234 vs STAGING_dbHost=localhost STAGING_dbName=db_staging STAGING_dbUser=whatever STAGING_dbPass=1234 PRODUCTION_dbHost=localhost PRODUCTION_dbName=db_production PRODUCTION_dbUser=whatever PRODUCTION_dbPass=1234 But at the moment I decided to go with PHP files, so there is no need to change anything in your library 🙂 Thx for the explanation!
  17. v7.0.0 removes the hideFromGuests() feature and moves it to the dedicated RockGatekeeper module:
  18. RockGatekeeper A lightweight ProcessWire module that provides simple password protection for your website using a configurable gatekeeper password. Overview RockGatekeeper adds a basic authentication layer to your ProcessWire site. When enabled, it blocks access to all pages for guest users unless they provide the correct password via URL parameter. Features Minimal footprint: Only runs when gatekeeper is configured Session-based: Once authenticated, users stay logged in for the session IP tracking: Remembers the IP address of authenticated users Clean URLs: Automatically removes the password parameter from URLs after authentication CLI safe: Won't interfere with command-line operations Installation Copy the RockGatekeeper folder to your site/modules/ directory Install the module in ProcessWire admin Configure the gatekeeper password in your site config Configuration Add the gatekeeper password to your site configuration: // In site/config.php $config->gatekeeper = 'your-secret-password'; Usage Basic Authentication To access the protected site, users need to append the password as a URL parameter: https://yoursite.com/?gatekeeper=your-secret-password After successful authentication, users will be redirected to the same page without the password parameter, and they'll have access to the entire site for the duration of their session. Download & Docs: baumrock.com/RockGatekeeper
  19. Hey @FireWire this is a really cool class! @Sanyaissues recommended it, so I had a closer look. I added it to RockShell (experimental) here: https://github.com/baumrock/RockShell/tree/env And I have added this little improvement: https://github.com/baumrock/RockShell/blob/7605dcaad6f0b009e01039405936aad825dc47c5/Env/Env.php#L37-L39 Then I added this test command: https://github.com/baumrock/RockShell/blob/env/App/Commands/EnvTest.php What I do not understand: It creates the file RockShell/Env/cache/env.php to cache the results. Does that mean that your class only supports reading from one single .env file system wide? What if I loaded another .env file from another folder? Would that be cached somewhere else or would that overwrite my existing .env file? Or is it maybe the point to always ever only have one .env file for the project? Never used .env before, sorry 🙂
  20. // get current mail body $html = $mail->get('bodyHTML'); if (!$html) $html = nl2br($mail->get('body')); // <--------- add this line if (!$html) return;
  21. I'd just add the button as regular html that you then place in ##content## in your mjml file. That way you don't have to recreate the .html from the .mjml - but it depends. If you need complex content like a responsive grid then mjml might be the better choice.
  22. @snck yeah that's likely the change detection in RPB not recognising the change. Sounds like it would need a fix in RPB not in fluency. Do you have the latest version of RPB? There have been some changes in change detection, one of them coming from @FireWire himself 🙂
  23. Glad it was helpful 🙂 Believe it or not - I can't reproduce it at the moment 😅 I'll keep an eye on it!
  24. Hey @adrian thx for your thoughts on this. With htmx you can add hx-boost to and dom element and htmx will convert all links in that element to ajax powered links that fetch the new page's data and then replace the content of the dom element instead of reloading the page. You could, for example, do this: <body hx-boost> and it would replace the whole body for you after clicking on a link. This would cause the tracy debug bar to disappear. That's why I did this: <body> <div hx-boost> ... </div> </body> Tracy injects the debug bar in the body, which means it can survive different swaps of the hx-boost content and shows new requests properly as ajax requests. This cannot be the issue. I have no tabs and I have a single d($page) call. If I find time I'll ask the folks at nette about it. Thx four your assessment!
×
×
  • Create New...