-
Posts
6,314 -
Joined
-
Last visited
-
Days Won
318
Everything posted by bernhard
-
Are you sure you want to use Pages::save() ? This does NOT mean that the page has been saved. For this we have the Pages::saved() hook! You are hooking into saving a single page field value, eg $page->save('myfield', 'myvalue');
-
I already showed an example here https://processwire.com/talk/topic/21212-rockmigrations-easy-migrations-from-devstaging-to-live-server/?do=findComment&comment=212496 and here https://processwire.com/talk/topic/25342-custom-classes-for-page-objects-the-discussion/?do=findComment&comment=212563 of course you can use $tpl = "template=yourtemplate" instead of using the class constant.
-
Please see my post here That's correct behaviour. You are attaching the hook in your custom page class, but the hook you are attaching belongs to "Pages" and not to the custom page class. If you attach a Pages::saveReady hook, it will always fire on Pages::saveReady having the saved page as arguments(0) and therefore you need to check if that page is your custom pageclass or not. BTW I'd recommend doing this instead of $this != $p $page = $event->arguments(0); if(!$page instanceof self) return; I've had problems using $page != $this when some properties of my page have changed and therefore the check returned false even though it was an instance of my custom pageclass. "instanceof" should always work as expected. You could also just check for $page->template if you prefer...
-
Custom Classes for Page objects - The Discussion
bernhard replied to szabesz's topic in API & Templates
In Page.php we have the hookable loaded method: /** * For hooks to listen to, triggered when page is loaded and ready * * #pw-hooker * */ public function ___loaded() { } It is called on these spots in PW: So this means that loaded() is triggerd on a page find operation: I'm not 100% sure if that means that it is called on every page find or if PW's internal cache somehow plays a role, but as long as the page is not loaded, the loaded hook will not trigger. If you look at the docs of modules you see that init() is called immediately when the module is loaded and ready() is called when the api is ready. This can make an important difference sometimes where hooks work in init() but not in ready() and vice versa. Page objects don't have these methods though. These are all the hookable methods of the Page baseclass: Additionally you have hookable methods in PagePermissions.module: And several others ? But no init() and ready(). Because init() and ready() are a concept of PW modules: My point is that loaded() of a page might only be triggered in very special cases. Imagine a module that loads a page only on uninstall() of the module. Then the loaded() event of your pageClass would happen very late in the request. But if you use custom page classes in an object oriented way then they become much more like a PW module and then I think it makes sense to stick to the PW module naming convention and that means that init() is triggered on module/pageclass init - just like you had the code in init.php. That IMHO does also mean, that this code does get executed on EVERY request. It does NOT mean, that the code is executed only ONCE - that's a matter of the "singular" setting of a module. See examples below. Code in ready() on the other hand is just like code in ready.php - it fires once, but later in the process, when the API is ready and all other init() methods of all modules and pageclasses have been triggered. Try this /site/modules/Test.module.php <?php namespace ProcessWire; class Test extends WireData implements Module { public static function getModuleInfo() { return [ 'title' => 'Test', 'version' => '0.0.1', 'summary' => 'Your module description', 'autoload' => false, 'singular' => false, 'icon' => 'smile-o', 'requires' => [], 'installs' => [], ]; } public function init() { bd('test init'); } public function ready() { bd('test ready'); } } Install the module and then call it from the tracy console: It only fires init() but not ready() - because when loading the module PW's ready event has already been triggered. Now make that module autoload, do a modules refresh and fire the console code again: You see the difference? ? Now just for fun we make the module "singular": If I happen to need something to execute on every load of the pageClass I use the class constructor: This means that my custom pageclass always has the correct template and does always live under the correct parent in the pagetree. As you can see I define all my fields as class constants. This makes the code so much more readable and also makes typos nearly impossible. It might feel like overhead, but actually it makes me a lot more efficient as well: So I'm actually using most if not all of my page classes as something like a singular autoload module. Overhead? Maybe a little. Problem? I think/hope no ? Hope that helps. Suggestions for improving this further always welcome ? -
Custom Classes for Page objects - The Discussion
bernhard replied to szabesz's topic in API & Templates
I'm all in on pageClasses and it has totally changed the way I'm building apps with processwire ? I'm even rewriting old projects step by step to make my code better readable and maintainable. The great thing is that this does not break any existing code - you can move hooks step by step into the new pageClasses as dedicated methods of that pagetype. I have showcased my way of doing what you are asking for in 2 posts starting here: https://processwire.com/talk/topic/21212-rockmigrations-easy-migrations-from-devstaging-to-live-server/?do=findComment&comment=212491 I'd recommend triggering a method called "ready" only on the PW "ready" event, not on "loaded" - that's something different and if you create a method called "ready" that is actually triggered on "loaded" you'll likely produce code that might at some day produce hard to find bugs... Same goes for "init". I'd be interested what Ryan says to this topic. The reason why ready() and init() of custom page classes are not triggered is obviously because custom page classes are not autoload modules. They are requested on demand and you can therefore not tell when or if they get loaded. I guess that's to minimize the footprint of every request as much as possible. In my modules I simply use RockMigrations class loader to load all my custom pageClasses: https://github.com/BernhardBaumrock/RockMigrations/blob/2d85e460ce5aa394480906ad5b41cd1a0e86d0fe/RockMigrations.module.php#L1991-L2003 That might produce a little overhead, but I don't think that this get's noticable any time soon so the benefits outweigh by far. -
RockFinder3 - Combine the power of ProcessWire selectors and SQL
bernhard replied to bernhard's topic in Modules/Plugins
RF3 got a very nice little update today ? Aggregations Often we need to calculate sums or averages of table data quickly and efficiently. RockFinder3 makes that easy as well: $avg = $rockfinder->find("template=cat") ->addColumn('weight') ->getObject("SELECT AVG(weight)"); db($avg); $cats = $rockfinder ->find("template=cat") ->addColumns(['title', 'weight']); $cats->dump(); // dump finder data to tracy $obj = $cats->getObject("SELECT SUM(`weight`) AS `total`, COUNT(`id`) AS `cats`, SUM(`weight`)/COUNT(`id`) AS `avg`"); db($obj); // dump result of aggregation What happens behind the scenes is that RockFinder3 gets the current SQL query of the finder and adds that as FROM (...sql...) AS tmp to the query that you provide for aggregation. This is the resulting SQL query of the example above: SELECT SUM(`weight`) AS `total`, COUNT(`id`) AS `cats`, SUM(`weight`)/COUNT(`id`) AS `avg` FROM ( SELECT `pages`.`id` AS `id`, `_field_title_605cab16f38ce`.`data` AS `title`, `_field_weight_605cab16f3993`.`data` AS `weight` FROM `pages` LEFT JOIN `field_title` AS `_field_title_605cab16f38ce` ON `_field_title_605cab16f38ce`.`pages_id` = `pages`.`id` LEFT JOIN `field_weight` AS `_field_weight_605cab16f3993` ON `_field_weight_605cab16f3993`.`pages_id` = `pages`.`id` WHERE (pages.templates_id=44) AND (pages.status<1024) GROUP BY pages.id ) AS tmp You can even provide a suffix for your query to do things like GROUP BY etc: $rf = $rockfinder->find("template=cat|dog"); $rf->addColumns(['created']); $rf->dump(); db($rf->getObjects( "SELECT COUNT(id) AS `count`, DATE_FORMAT(created, '%Y-%m-%d') AS `date`", "GROUP BY DATE_FORMAT(created, '%Y-%m-%d')" )); -
RockMigrations1 - Easy migrations from dev/staging to live server
bernhard replied to bernhard's topic in Modules/Plugins
I think I don't understand your question.. I don't have any functions in init.php nowadays. I have everything in init() of a module or in init() of a pageclass. If there is something in init() of a pageclass I have to trigger that manually, which is the reason for the loadClasses method of RM which triggers init() automatically https://github.com/BernhardBaumrock/RockMigrations/blob/2d85e460ce5aa394480906ad5b41cd1a0e86d0fe/RockMigrations.module.php#L1991-L2003 This is a simple pageClass I'm using in my CRM for managing contacts: <?php namespace RockCRM; use ProcessWire\HookEvent; use ProcessWire\Page; class Contact extends Page { const tags = RockCRM::tags; const tpl = RockCRM::prefix."contact"; const prefix = RockCRM::prefix."contact_"; const field_skills = self::prefix."skills"; public function __construct() { parent::__construct(); $this->parent = $this->wire->rockcrm->contacts(); $this->template = $this->wire->templates->get(self::tpl); } public function init() { $tpl = "template=".self::tpl; $this->wire->addHookAfter("ProcessPageEdit::buildForm", $this, "buildForm"); $this->wire->addHookAfter("Pages::saveReady($tpl,id=0)", $this, "onCreate"); } /** * Page edit screen */ public function buildForm(HookEvent $event) { $form = $event->return; $page = $event->process->getPage(); if(!$page instanceof self) return; $crm = $this->wire->rockcrm; /** @var RockCRM $crm */ $rm = $crm->rm(); // modify page edit screen of a contact if($f = $form->get('title')) { $f->label = 'Name of the Contact'; $f->notes = 'Enter a great Name'; } // or load scripts with the new update $rm->addScript(__DIR__."/Contact.js"); } /** * Migrate this pageclass */ public function migrate() { $crm = $this->wire->rockcrm; /** @var RockCRM $crm */ $rm = $crm->rm(); $rm->migrate([ 'fields' => [ self::field_skills => [ 'type' => 'text', 'tags' => self::tags, ], ], 'templates' => [ self::tpl => [ 'tags' => self::tags, 'icon' => 'user-o', 'noSettings' => 1, 'pageClass' => '\RockCRM\Contact', 'fields' => [ 'title', self::field_skills, ], ], ], ]); } /** * Things to do when a contact is created * @return void */ public function onCreate(HookEvent $event) { $page = $event->arguments(0); $page->status = 1; // published, non-temp $event->pages->names()->uniqueRandomPageName(); // unique name } } This setup is extremely powerful, extremely clear and extremely nice to work with ? Was that what you were asking for? ? -
RockMigrations1 - Easy migrations from dev/staging to live server
bernhard replied to bernhard's topic in Modules/Plugins
The module gets better and better and I love working with it every day ? v0.0.42 has a nice little update to include scripts or styles in the PW backend easily. Why is that great? Because you can organize your files better and have everything in place while still having the ease of use (and you don't need to install any other modules for such simple tasks): Usage: I'm using custom page classes for EVERY page I have in my PW installs. One might think that this is overkill - but I'm wasting so much time on old projects looking for code snippets that are all over the project. Some in ready.php, some in init.php, another story are JavaScript snippets for small GUI tweaks... Not any more ? I have a custom PageClass "Foo" and there I have a method that modifies the page edit form (ProcessPageEdit::buildForm) - this is great for changing field properties like columnWidth or labels etc. on the fly without ever having to fire a migration. If I want to change something for my Foo pages, I head over to my IDE, type "Foo" and get directly to the Foo PageClass. But what about JavaScript? Today I needed a little snippet that populates some fields based on other fields. As easy as that: // in the buildForm hook $rm->addScript(__DIR__."/Foo.js"); Everything is well organized, because I have all my classes in a custom folder (inside a module, but the same would apply for the /site/classes folder): .../myModule/classes |-- Bar.php |-- Foo.js |-- Foo.php '-- Whatsoever.php And the JS file will only be loaded on the page edit screen to prevent overhead. Why not just use $config->scripts->add() you might ask? Good question ? $rm->addScript() will additionally add a cache busting timestamp to the file and it will only include the file if it exists on the system. It will also work on Windows systems, which you might forget to take into account if quickly adding it via $config->scripts->add() and last but not least it will also work on PW instances that are running in a subfolder ? if(!is_file($path)) return; $path = Paths::normalizeSeparators($path); $config = $this->wire->config; $url = str_replace($config->paths->root, $config->urls->root, $path); $m = $timestamp ? "?m=".filemtime($path) : ''; $this->wire->config->scripts->add($url.$m); Happy migrating ? -
Exactly - THX ? I should maybe use this panel more often, it looks very useful ?
-
@adrian I think you mentioned that you are using some kind of sql query log often, but I can't remember where you were talking about this... any hints? ?
-
[solved] could a modules refresh also refresh the admin menu cache?
bernhard replied to bernhard's topic in Tracy Debugger
thx, that works ? I've just created a PR to fix an issue with my field code panel ? -
Hi @adrian I know there are other cache related modules, but I didn't need any of those features over the last months/years. One thing that gets more and more annoying when developing backend apps is that the menu cache does not refresh when I refresh the modules cache. So for example I create a new module or do some changes, then I do a modules refresh via tracy and then I have to logout and login to see the changes in the pw backend navbar. Could tracys refresh trigger the menu cache to reset as well? Thx
-
German language pack (de_DE) with formal salutation
bernhard replied to dotnetic's topic in ProcessWire Language Packs
done -
https://github.com/processwire/processwire-issues/issues/1356
-
To be honest I've never really understood the maxage setting ? I tried to understand how it works again... but didn't. But I've found this working solution: $td = $files->tempDir('hello-world'); $td->setRemove(false); $td->removeExpiredDirs(dirname($td), 10); // remove dirs older than 10 seconds file_put_contents($td.'some-file.txt', 'Hello world'); Fire that from the tracy console several times and you'll see that on each request a new dir is created and also old dirs are removed. That's because you can't access files in a hidden folder! You'd need to write your own custom logic to include() the content of this file and show that on a custom url (maybe using the new url hooks). But the problem is that you have to keep track of the exact file/folder name of your temporary file. And another problem is, that you can end up with security problems, because the file content of a file can change over time whereas the link to that file could still be the one from an earlier version of another file. eg create temp file --> .../0/yourfile.txt create another --> .../1/yourfile.txt create another --> .../0/yourfile.txt (because the old file in folder 0 does not exist any more because it was too old and was removed) Now you got 2 links: yoursite.com/tempfile0 and yoursite.com/tempfile1 and tempfile0 will show different files depending on WHEN the link is clicked. I guess that's not a good idea ? I'll open an issue regarding the tempDir though. https://github.com/processwire/processwire-issues/issues/1356
-
Wow, looks like you have put a lot of work into that ? I think every step towards better PW migrations is important and very welcome ? https://github.com/BernhardBaumrock/RockMigrations/blob/bb43552f55ef7ff57533083f4d886c3aa00a8e41/RockMigrations.module.php#L1956-L2000 $rm->migrate([ 'fields' => [...], 'templates' => [...], 'pages' => [...], ]); Maybe I'm missing something, but I thought I had that problem too when developing the migrate() method and it turned out to be quite easy: I create fields, then templates, then I setup the fields and templates again (now that fields and templates exist in the system the references can properly be set) and finally I create all pages. In my scenarios this has worked perfectly for several months (years?) now ? I have to think about that sentence ? Maybe you could elaborate a little more on that? I try to understand your workflows better. I always thought that if somebody does not want to learn how to use code-based migrations he/she could simply use PW's import/export tools?! Do you think you could create a quickstart-screencast using some free tool like https://screencast-o-matic.com/home to show the workflow when using your migrations?
-
German language pack (de_DE) with formal salutation
bernhard replied to dotnetic's topic in ProcessWire Language Packs
Hey Jens, could you please fix mistake (vor dem -Z-uschneiden) --> groß! Thx ? -
RockMigrations1 - Easy migrations from dev/staging to live server
bernhard replied to bernhard's topic in Modules/Plugins
I'm quite sure it is possible, but I don't have an example. I'm not using RepeaterMatrix any more and on my old projects I did not use it in combination with RockMigrations, sorry ? -
width or height exceeds limit in OpenPixelCache/3911
bernhard replied to bernhard's topic in General Support
Thx BitPoet for joining ? I have no idea what you are talking about - it's totally possible that I'm missing something obvious ? -
I'm getting this on every modules refresh and it gets a little annoying... Do you also experience this? Any ideas what the reason could be? That's the moduleinfo of the module...
-
When uploading a large image (16667x5000) I get this error: This happens after the 7MB image is uploaded. I'm using ImageSizerImagick and have 512MB memory setting. I have tried to set client side resizing to max 1920x1920 but then it gets stuck before upload. Has anybody of you ever experienced something similar? Any ideas how to solve that? Manually resizing the Image before upload is of course a solution, but not the best one... Maybe @horst ?
-
Are you sure you need to check? If you place a custom "loaded" method in your custom page class only pages using your custom page class will execute that code anyway... Or am I missing anything? But if I'm missing anything checking for the current pageclass is as easy as this: if($page instanceof self) { ... }
-
RockMigrations1 - Easy migrations from dev/staging to live server
bernhard replied to bernhard's topic in Modules/Plugins
That sounds interesting ? Though I'm not sure if that approach can actually work well ? I mean... It can work for somebody of course, but I'm not sure how big the difference to existing solutions (field/pages import/export) would be? Maybe you could clarify that a bit? But I don't want to discourage you from trying to find a way that makes migrations more easy to use. I know that this is a big hurdle at the beginning - it took me years to understand why Benjamin built his migrations module ? On the other hand migrations are a totally different concept compared to building websites via the PW backend... you need to take care of logical restrictions that sometimes arise (for example field settings depending on the existance of other fields or pages etc). That's why migrations SHOULD be code and that's why it is so hard to build some kind of UI or recording/diff tool for it... Whenever I don't know the code for a migration I simply create the field and have a look at Tracys field code panel: Then I copy the parts that I need to my migration and that's it. My IDE then tells me about the changes, I can commit them and everybody is happy ? But I'm really happy to see different approaches coming up and I'm looking forward to seeing any drafts ? -
RockMigrations1 - Easy migrations from dev/staging to live server
bernhard replied to bernhard's topic in Modules/Plugins
Hi @MarkE That question ist not that simple ? I've had problems with non-lowercase fieldnames from time to time. I did not have time to dig into that in detail, so the quick solution was to force rockmigrations to use lowercase fieldnames. I know that RockFinder had problems with non-lowercase fieldnames, because the DB table name is always lowercase whereas the field name can also contain uppercase letters. But that problem was solved recently: So if you could provide simple and reproducable test cases and maybe a PR I'm happy to remove that restriction and allow non-lowercase fieldnames ?