-
Posts
6,317 -
Joined
-
Last visited
-
Days Won
319
Everything posted by bernhard
-
RockMigrations1 - Easy migrations from dev/staging to live server
bernhard replied to bernhard's topic in Modules/Plugins
v0.0.19 adds support for RepeaterFields @zoeck $rm->migrate([ 'fields' => [ 'my_repeater_field' => [ 'type' => 'repeater', 'label' => 'This is my great repeater field', 'repeaterFields' => ['title', 'body', 'images'], ], ], ]); -
Hi @Nicole Bardales I don't have a solution for you yet, but a recommendation that might help: Install TracyDebugger and you'll get a better output of your error and also some great tools for debugging your scenario. For example you can add bd($var) calls in your code and get a nice visual dump of your variable and can then track the issue down. It is a great tool for learning PW and PHP in general because it makes a lot of things visible that are usually hidden somewhare in the memory ? Hope that helps a little ?
-
Hi @spercy16 welcome to the forum! Could you please tell us a little bit more what you already tried (beside using google)? Also it would be great to hear a little about your experience with ProcessWire and WebDev in general so that we can provide better answers for you.
-
Almost all of my modules I build nowadays do exactly that. That's why my RockMigrations module takes a different route than Lostkobrakai's -> you can ship migrations with your modules and your module basically just needs to implement one migrate() method: class MyModule extends WireData implements Module { ... public function migrate($rm) { $rm->migrate([ 'fields' => ... 'templates' => ... ]); $rm->createPage(...); } } It looks like overkill on first sight, but it is of so much help once you need to revert back, fix bugs, change things, push to a live system while the live system can't be shut off for development or finally: Reuse your work to not build everything again on the next project... At the moment I fire those migrations on every modules::refresh - that works but is not ideal. Having migrations for every version (001.php, 002.php, etc) on the other hand makes things less easy to understand. You end up with template/field setups split up in many different files whereas if you have everything in one migrate function you see the setup on one glance. Maybe I should just add a RockMigrations::fire method that can be hooked instead of the Modules::refresh...
-
Ajax repeater - wrong language field active - ? or just me?
bernhard replied to ICF Church's topic in Multi-Language Support
I have a similar issue! @ICF Church did you find a solution? -
I hope so, but I think I hit some walls there... Seems that PW relies on several page reloads for that to work properly. $modules->refresh() after installing each module was not enough. And I also had problems breaking my site, that's why the restore method exists... If you find a good way to flush pw completely after some changes have been made that might be a good addition to RockMigrations! Thx and good luck ?
-
Tried, but failed: https://github.com/BernhardBaumrock/RockMigrations/blob/b15fc176e3fa3736275a943f18882cdd87b19bf3/RockMigrations.module.php#L1701-L1758 Would also love to get a solid solution for that! If you are doing more automation stuff, I'd also love to get help for RockMigrations (thx for your PR yesterday) ?
-
Thx @Juergen that also helped me today. This is what I came up with (for future reference) when I needed to add a page clone button: /** * Add clone button to form */ public function addCloneButton() { // add button to form $this->addHookAfter("ProcessPageEdit::buildForm", function($event) { $form = $event->arguments(0); $form->add([ 'type' => 'submit', 'name' => 'btn_clone_plan', 'value' => __('Clone'), 'icon' => 'clone', ]); $form->get('btn_clone_plan')->addClass('ui-priority-secondary'); }); // process input $this->addHookAfter("ProcessPageEdit::processInput", function($event) { $form = $event->arguments(0); if($form != "InputfieldForm") return; // dont fire on inputfields if($this->input->post('btn_clone_plan')) { // tell PW to save the page $this->input->post->submit_save = 1; } if(count($form->getErrors())) { // clone only if there are no errors, otherwise do a regular save unset($this->input->post->btn_clone_plan); } }); // do the clone $this->addHookAfter("Pages::saved", function($event) { if(!$this->input->post->btn_clone_plan) return; unset($this->input->post->btn_clone_plan); // prevent endless loop! $clone = $this->pages->clone($event->arguments(0)); $this->session->redirect($clone->editUrl); }); } PS: This approach might be easier, but I wanted an extra button:
-
Hi Teppo, thx for your thoughts! ? Interesting question. I'll try to brainstorm some answers/questions and refer to the two solutions as packages- and modules-route: Modules could only support one markup (how would you ship markup for uikit/bootstrap/tailwind/... ? Or you would need to define renderFormUikit(), renderFormBootstrap(). Modules would have markup code in module code (ugly string concats etc) or would need to implement $files->render(...). Related to the previous point: Render files in module-route could have any name, live in any folder, etc - there's no standard, wheras using the packages route a module could have /partials or /views folder and everybody familiar with Wireframe would get what's going on Everybody not familiar would not get what's going on or could maybe even not use it ? Maybe it would add more overhead then necessary?! Your modules syntax seems very clear to me! I guess the request came with some background in my mind: I wanted to have packages with PHP (logic + view) code, LESS for style and JS for frontend. For example the searchForm might need some JS to work that you need to add to your theme. But that would also be easy to add to the layout with one single include, so that would also be no real argument for the packages route ? Packages would have the same context as all Wireframe view files (including other partials etc; not sure if that makes sense though, because packages should work as-is (standalone) without any theme related partials...). I think I have to just try both approaches on my current project and report back my findings ? I really want to avoid copying files over to an extra place that breaks updates! ?
-
Hi @teppo First I have to say: WOW! I took another look at your module because I got again frustrated with my setup and I wanted to build something on my own. Luckily I remembered your module and came back to the docs before developing something on my own ? Thx for the docs, they are great! Some suggestions: It would be great to have < prev | next > links on each page at the bottom. I almost missed all the other great pages when reaching the bottom of https://wireframe-framework.com/docs/ (the menu is not visible in the sidebar on such long pages) It would be great to have a simple hello-world walkthrough for setting up a custom wireframe template (instead of providing a full-blown site profile). It's always easier to understand something if you start from scratch than finding your way around several files not knowing which pieces came before and after another. And then something more advanced and more important: I think it would be great to get a little more (or different) control over where Wireframe does look for files. I read about the config settings, but IMHO they are a little limiting. As far as I understood one can define a path for every type that Wireframe is based on (views, controllers, components, etc). And as far as I understood it is possible for components to define custom view files via Wireframe::component('foo')::setView('bar'); Is that correct? The problem with that approach is that it is not possible to load components from outside of the wireframe folder structure. That's a quite big deal, because if that were possible, we could ship custom components/views/partials (I'm planning on working on styles using RockLESS) directly within our modules and that would just be awesome! Take this example: module RockSearch lives in /site/modules/RockSearch |- Wireframe | |- js | | '- search.js | |- less | | '- searchform.less | '- partials | '- searchform.php '- RockSearch.module.php What if we had a new Wireframe type called "package"? This could be included in any Wireframe layout like this: <?php $css = RockLESS::css([ $packages->RockSearch->less->searchform.less, $packages->MyGreatModule->less->style1.less, ]); ?> <html> <head> <link rel="stylesheet" href="<?= $css ?>"> <?= $packages->RockSEO->partials->meta() ?> </head> <body> <section><?= $partials->header() ?></section> <section><?= $packages->RockSearch->partials->searchform() ?></section> ... </body> </html> Finally we'd only need to tell Wireframe about the packages: $config->wireframe = [ ... 'packages' => [ 'RockSearch' => $config->paths->siteModules."RockSearch", 'RockSEO' => $config->paths->siteModules."RockSEO", ], ]; // or something like this Wireframe::addPackage($config->paths->siteModules."RockSearch"); This would finally bring some standards to the ProcessWire frontend which would make a huge difference regarding reusability! If we found a bug in RockSearch, we could directly fix it in the modules partials which would make it instantly available to all projects using it after a simple modules update! Thx for your great work again! What do you think? PS: Defining different views/partials for RockSearch should also be possible making it easy to support different frameworks or framework versions, eg $packages->RockSearch->partials->uikit3() or ->bootstrap4() etc. PPS: I didn't quite get where the differences between using controllers+views or components+views/partials are?!
-
How to get a list of all changes on page save?
bernhard replied to bernhard's topic in General Support
Unfortunately not... I'm changing fields tmp1+2 from X to XX and Y to YY: This is what I tried in site/ready.php $wire->addHookBefore('Pages::saveReady', function($event) { $page = $event->arguments(0); $page->setTrackChanges(Wire::trackChangesValues); }); $wire->addHookAfter('Pages::saveReady', function($event) { $page = $event->arguments(0); bd($page->getChanges(true), 'getChanges'); }); $wire->addHookAfter('Pages::saved', function($event) { bd($event->arguments(1), 'changes'); bd($event->arguments(2), 'values'); }); $wire->addHookAfter('Page::changed', function(HookEvent $event) { bd($event->arguments(0), 'changed(0)'); bd($event->arguments(1), 'changed(1)'); }); -
This is what I have so far: $wire->addHookAfter('Page(template=contact|sample)::changed', function(HookEvent $event) { $page = $event->object; $title = $page->title; $change = $event->arguments(0); $old = $event->arguments(1); // old value $new = $event->arguments(2); // new value $text = '...'; $mail = new WireMail(); $mail->to(['...']); $mail->from('...'); $mail->subject("Changed page: $title"); $mail->bodyHTML($text); $mail->send(); }); The problem is that this will send me an e-mail for every changed field, eg "Changed page X, field A" and "Changed page X, field B". But I want one single E-Mail that says "Changed page X, field A and B". getChanges() on the other hand (https://processwire.com/api/ref/page/get-changes/) does get me an array of changes, but without their old and new values! It should be possible using https://processwire.com/api/ref/wire/set-track-changes/ but where/how would I set this? Thx
-
I'm using SessionHandlerDB: $s = $modules->get('SessionHandlerDB'); foreach($s->getSessions() as $session) { $s->destroy($session['id']); } If not using SessionHandlerDB it should be enough to clear /site/assets/sessions
- 1 reply
-
- 4
-
[SOLVED] Hook not working inside module
bernhard replied to jploch's topic in Module/Plugin Development
If the hook works in ready.php but not in your module it will most likely not get called. Did you try this? public function ready() { bd('fired!'); // or die('fired'); if you are not using tracy } In your module? I guess it will NOT fire and I guess the reason is that your module does simply not get loaded in your request. An Inputfield does for example never get loaded when visiting any frontend page or for example when viewing the page tree in the backend. It does only get loaded in ProcessPageEdit! So the first step is to find out when your ready() method in your module gets fired and when not. ready.php loads on every request. Inputfields do also have the renderReady() method that get called even when the Inputfield is loaded via AJAX. Is your Inputfield collapsed somehow? -
I have to add some important informations ? This is what I get in the console when doing "git pull" me@vs1:/home/users/xxx/www/test# git pull remote: Enumerating objects: 5, done. remote: Counting objects: 100% (5/5), done. remote: Compressing objects: 100% (2/2), done. remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0 Unpacking objects: 100% (3/3), done. From github.com:baumrock/test 624fe5e..e227704 master -> origin/master Updating 624fe5e..e227704 Fast-forward README.md | 1 + 1 file changed, 1 insertion(+) This is the output for git pull 2>&1 // cron.php echo shell_exec("git pull 2>&1"); // output me@vs1:/home/users/xxx/www/test# php cron.php From github.com:baumrock/test e227704..7b1b6f4 master -> origin/master Updating e227704..7b1b6f4 Fast-forward README.md | 1 + 1 file changed, 1 insertion(+) So, this is only a part of the original output!! And this when using git pull // cron.php echo shell_exec("git pull"); // output me@vs1:/home/users/xxx/www/test# php cron.php remote: Enumerating objects: 5, done. remote: Counting objects: 100% (5/5), done. remote: Compressing objects: 100% (2/2), done. remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0 Unpacking objects: 100% (3/3), done. From github.com:baumrock/test 7b1b6f4..f106792 master -> origin/master Updating 7b1b6f4..f106792 Fast-forward README.md | 1 + 1 file changed, 1 insertion(+) That's the same output as using git pull, so shell_exec("git pull") should be what I need... But using this setup: $out = shell_exec("git pull"); file_put_contents("cron.log", $out, FILE_APPEND); I get this in the log: Updating f106792..897d27c Fast-forward README.md | 1 + 1 file changed, 1 insertion(+) And this in the console: me@vs1:/home/users/xxx/www/test# php cron.php remote: Enumerating objects: 5, done. remote: Counting objects: 100% (5/5), done. remote: Compressing objects: 100% (2/2), done. remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0 Unpacking objects: 100% (3/3), done. From github.com:baumrock/test f106792..897d27c master -> origin/master I get the same result using this cron.php ob_start(); echo shell_exec("git pull"); file_put_contents("cron.log", ob_get_clean(), FILE_APPEND); I guess this post explains the problem better ? Why does the output get split? There's one part in the logfile and one part as direct output! Any ideas?
-
Hello Community, I'm building a module that runs a master cronjob every minute. This cron calls a PHP script that fires a hookable PW method, so I can use this cron from all of my modules to execute custom code: <?php namespace ProcessWire; chdir(dirname(__FILE__)); // file path chdir("../../../"); // pw root folder include("index.php"); /** @var RockTrigger $rt */ $rt = $modules->get('RockTrigger'); if(!$rt) return; $rt->log("# ----- ".date("Y-m-d H:i:s")." -----", true); $rt->log("# Running RockTrigger Master Cronjob"); $rt->masterCron(); In my other module I can hook the masterCron method: $this->addHookAfter("RockTrigger::masterCron", function($event) { $rt = $event->object; $rt->log("# RockGit"); $rt->log(shell_exec("git stash")); $rt->log(shell_exec("git submodule foreach 'git stash'")); $rt->log(shell_exec("git pull")); $rt->log(shell_exec("git submodule update --init --recursive")); } This works great. Almost ? The cronjob is setup to send me an email on error. My panel treats every output as ERROR and every cronjob without output as SUCCESS. The problem is, that I get this output in my error notification email: From github.com:baumrock/xyz d36..a30 master -> origin/master Fetching submodule site/modules/xxx From github.com:baumrock/xxx 06d..641 master -> origin/master The logfile looks something like this: # ----- 2020-07-06 21:00:01 ----- # Running RockTrigger Master Cronjob # RockCI - 1 webhooks found No local changes to save No local changes to save 1 file changed, 134 insertions(+), 23 deletions(-) So it seems there is a part of the output in the logfile, but a part of the output is sent to STDOUT. The log method looks like this: /** * Log data to logfiles * @return void */ public function log($data, $newtask = false) { $last = $this->cronLogpath.self::lastlog; $day = $this->cronLogpath.date("\dN").".log"; if($newtask) file_put_contents($last, ""); $log = ''; $log = $this->logString($data); // log to last-logfile file_put_contents($last, $log, FILE_APPEND); // log to day-logfile $fresh = $this->resetDayLog($day); if($newtask AND !$fresh) { // add some space $log = "\n\n$log"; } file_put_contents($day, $log, FILE_APPEND); } So the output is written (or should be written) to two logfiles: _last.log and d1.log (on monday). That's why I think something like git pull >> /foo/_last.log 2>&1 would not work (or is not what I want). Any idas how I can reliably catch all the output of the commands via PHP and then write them to my logfiles as I want? Thx in advance!
-
Hi @adrian could you please add a $session->redirect after the email-sent-flag has been reset? I've had it several times the last days that I had a tab oben and got an error "file could not be removed" on reload which was caused by a ?tracy-clear-email-sent-flag-whatsoever=1 that was still left in the url. Thx ?
-
Sounds very interesting! Thx for the updates Ryan ? I understand "we" -> "i" but don't understand the pointed 2.. are they wrong or is my english too bad? ?
-
Hi @gornycreative this could work, yes. But I guess there would be 1000 edge cases that could make that setup fragile. I've used Kickstart on almost all installations for the last couple of months years ? and had no problems, but for example on my new live server I get a 500 (though the installation works afterwards, but I have to clean some files manually... likely a permissions issue). What I'd really like to have is some kind of simple and solid kickstart that installs RockMigrations and can then do whatever you want. Yeah, you always get the latest versions of PW and all the modules and you stay flexible in the setup (just comment out unneeded modules).
-
RockFinder3 - Combine the power of ProcessWire selectors and SQL
bernhard replied to bernhard's topic in Modules/Plugins
v1.0.8 improves the readme (had no section about getRows() ? ) and adds a nice shortcut $finder->groupBy(): https://github.com/baumrock/rockfinder3#getting-data Getting data When using a regular $pages->find() you get a PageArray as result. When working with RockFinder we don't want to get the PageArray to be more efficient. We usually want plain PHP arrays that we can then use in our PHP code or that we can send to other libraries as data source (for example as rows for a table library). getRows() This returns an array of the result having the id as key for every array item: $finder = $rockfinder ->find("template=cat") ->addColumns(['title', 'owner']); $rows = $finder->getRows(); db($rows); Having the id as item key can be very handy and efficient to get one single array item via its id, eg db($rows[1071]): getRowArray() Sometimes having custom ids as array item keys is a drawback, though. For example tabulator needs a plain PHP array with auto-increment keys. In such cases you can use getRowArray(): https://github.com/baumrock/rockfinder3#predefined-methods Predefined Methods At the moment there is one shortcut using the string modification technique for grouping a result by one column: $finder = $rockfinder ->find("template=cat") ->addColumns(['title', 'owner']); $cats_by_owner = $finder->groupBy('owner', [ 'GROUP_CONCAT(title) as title', ]); db($cats_by_owner); Another example could be getting averages: $finder = $rockfinder ->find("template=cat") ->addColumns(['title', 'owner', 'weight']); $cat_weight_by_owner = $finder->groupBy('owner', [ 'AVG(weight) as weight', ]); db($cat_weight_by_owner); Of course you can combine both: $finder = $rockfinder ->find("template=cat") ->addColumns(['title', 'owner', 'weight']); $combined = $finder->groupBy('owner', [ 'GROUP_CONCAT(title) as title', 'AVG(weight) as weight', ]); db($combined); -
Very interesting ? Have you thought about using https://www.chartjs.org/ or https://apexcharts.com/ ? (not meant to be a recommendation, it's really just a question why you chose SVG and if you are happy with that and why...) ?
-
See the settings:
-
Great looking site as always! What tool is this? ?