Jump to content

Leaderboard

Popular Content

Showing content with the highest reputation on 05/14/2024 in all areas

  1. Since ProcessWire v3.0.152 we have been able to use custom Page classes: https://processwire.com/blog/posts/pw-3.0.152/#new-ability-to-specify-custom-page-classes Some PW users have said that they would like to have all their hooks relating to a particular Page class contained within their custom Page class file (in /site/classes/), to keep things tidy and organised. But custom Page classes do not have methods that fire on states like "init" or "ready", and it also wouldn't be ideal to attach hooks within a Page class because the class can be instantiated multiple times and therefore such hooks would be attached multiple times. I understand @bernhard has a feature that addresses this need built into his RockMigrations module, so check that out. And here's another approach, where hooks are attached in the places where you would normally attach them (e.g. in /site/ready.php or /site/init.php, or in the init() or ready() methods of some custom module) but the method that executes in the hook exists within your custom Page classes. Step 1 In /site/classes/DefaultPage.php, add the method shown below: class DefaultPage extends Page { // Call any Page methods with names that match the HookEvent object and method public function callHookMethods(HookEvent $event) { $objectName = $event->object->className; $methodName = $objectName . '_' . $event->method; if(!method_exists($this, $methodName)) return; $this->$methodName($event); } } This method looks for other Page method names that match the HookEvent object and method and if any exist it calls them. Hooks are attached using the format "Class::method" and this format wouldn't be valid for a method name, so an underscore is used instead of the two colons. E.g. "ProcessPageEdit::buildFormContent" would become "ProcessPageEdit_buildFormContent". Step 2 In any of your custom Page class files, add methods named to match the hookable methods you want to target. These custom Page classes should extend DefaultPage. In the example below I'm targeting hookable methods Pages::saveReady and ProcessPageEdit::buildFormContent in the BasicPagePage class. class BasicPagePage extends DefaultPage { public function Pages_saveReady(HookEvent $event) { // Show a message in the PW admin $this->wire()->message("About to save page named: $this->name"); } public function ProcessPageEdit_buildFormContent(HookEvent $event) { /** @var InputfieldWrapper $wrapper */ $wrapper = $event->return; // Add a custom markup field to Page Edit /** @var InputfieldMarkup $f */ $f = $this->wire()->modules->get('InputfieldMarkup'); $f->label = 'My custom markup'; $f->value = 'Hello!'; $wrapper->insertAfter($f, 'title'); } } Step 3 Attach hooks in the places where you normally would. You need to attach a hook for each hookable method you are targeting in your custom Page classes, but the hook code itself is minimal and where multiple Page classes target the same hookable method you only need it attach it once. In the example below I'm attaching hooks in /site/ready.php. $pages->addHookAfter('saveReady', function(HookEvent $event) { /** @var DefaultPage $page */ $page = $event->arguments(0); if($page instanceof DefaultPage) $page->callHookMethods($event); }); $wire->addHookAfter('ProcessPageEdit::buildFormContent', function(HookEvent $event) { /** @var ProcessPageEdit $ppe */ $ppe = $event->object; /** @var DefaultPage $page */ $page = $ppe->getPage(); if($page instanceof DefaultPage) $page->callHookMethods($event); }); Each hook only need to establish a $page object (often this is available as an argument to the hookable method) and if it's an instance of DefaultPage then it simply calls $page->callHookMethods($event), and any relevant hook-targeting methods for that particular Page class will fire. Here's the result of my two hook-targeting methods in BasicPagePage.php when I save a page of this class in Page Edit:
    4 points
  2. Did you try this? $p->save(['noHooks' => true]);
    3 points
  3. Has there been an updated, more formal, non-MagicPage approach to this yet? Is there anything wrong with doing this so at least the hook is "closer" to the page class? <?php namespace ProcessWire; // this is /site/classes/OrderPage.php // hook above class so it's "closer" wire()->addHookAfter("Pages::saved(template=order)", function(HookEvent $event) { $order = $event->arguments('page'); // hook code }); class OrderPage extends Page { // ... } Related GitHub issue since I don't think it's linked in any of the replies above: https://github.com/processwire/processwire-requests/issues/456
    3 points
  4. Hi @Robin S. Please also take a look at my request "automatically call the init method on custom page classes" https://github.com/processwire/processwire-requests/issues/456 and maybe leave a thumbs up. Your method requires changes to /site/ready.php or /site/init.php files. I think self-initiating custom page files are way better, so for example, a blogPage.php custom page class file would automatically create all fields and provide custom methods like `localDate`. This way, you can simply copy the file to a new project, and everything will be set up automatically without needing additional changes to other files.
    2 points
  5. @Jonathan Lahijani Please don't interpret what I wrote in reply to your post as negative - it's not meant to be. That may well be a fine solution for this. I appreciate knowing about the option, and having the link to the github issue you posted is a major bonus - I missed that one until now.
    2 points
  6. @bernhard No, I think that's just a function declaration before a class declaration. If in doubt, you can run phpcs on your code. If you are only looking for side-effect warnings just grep your output for "effects"... phpcs --standard=PSR12 file.php | grep "effects" Run it without the grep for the full PSR12 ruleset... phpcs --standard=PSR12 file.php
    2 points
  7. This does introduce a side-effect to the class file. While it's not "wrong", it is discouraged, and will cause some code checkers to complain about the code. For example, if you are running codesniffer with one of the PSR standards, this would probably be flagged up (not checked.)
    2 points
  8. OK I answered my own question. I need to use noHooks: wire()->addHookAfter("Pages::saved(template=basic-page)", function(HookEvent $event) { $p = $event->arguments('page'); $p->title = time(); $p->save(['noHooks'=>true]); }); You beat me by 5 seconds!
    2 points
  9. Hey ProcessWire RockStars! ? Ever felt like your ProcessWire backend could use a bit more... pizzazz? Or functionality? Well, it's time to roll up your sleeves and dive into the world of RockAdminTweaks! ??️ Creating tweaks is as easy as smashing a power chord on your guitar! If you've got cool features from AOS you'd love to see, why not port them over? We even have a GUI for creating new Tweaks! Let's make the backend rock even harder! Github: https://github.com/baumrock/RockAdminTweaks Modules Directory: https://processwire.com/modules/rock-admin-tweaks/ Docs: https://www.baumrock.com/processwire/module/rockadmintweaks/
    1 point
  10. Forgot to mention; There were a few other new features I forgot to highlight. I have updated the first post. Please see 'frontend' and 'products' sections. Thanks.
    1 point
  11. Looks like there are some CSS issues on the new site! Some people are seeing white text on white background! Sorry for this. Trying to debug.
    1 point
  12. Hi @Juergen I try test your best module with PHP 8.3.18 And I found some mistake. It happened when I wanted to see the details of a blocked address. System env
    1 point
  13. Create (not copy/clone the existing one) a new image field with the exact same settings and try if it still doesn't work. I guess you already tried different images? Just in case. ? Does the file really exist in the file system? Correct mime-type? Just check if there is some weird setting in the body field. Maybe even create a new body field. Are there any textformatters applied? Is there anything in the logs? As you see... just wild guesses but that's how would look for issues and you already tested quite a lot.
    1 point
  14. Hello @TomPich, if I have understood correctly, you are looking for $value. $page is the page on which the sections are created, $value is the section page (@see ___renderValueWrapper() ). The :root properties in css must also be defined in :host for the shadow dom. :root, :host { --color-white: #fff; --color-light: #F5F5F5; --color-gray-light: #EEEEEE; --color-silver: silver; }
    1 point
  15. Interesting idea @Jonathan Lahijani and thx for the input @netcarver! Is something like this also a problem? https://github.com/baumrock/RockFrontend/blob/107af5b51930d0589ecc0a882e46372b640eb6f9/RockFrontend.module.php#L26-L29
    1 point
  16. Renewals and Resend Download Code are ready! Apologies it too longer than expected. Forms should be straightforward. Any issues, please let me know. Might need to hard reload the site for the new CSS to catch. Thanks!
    1 point
  17. Yeah, the subresource key is a hash of the file contents so that will break at some point on @latest. Alternatively, I don't want to introduce external resource versioning since it would mean the module has to be updated to keep up with HTMX. Biggest deal for me is that, as an online privacy advocate, I can't recommend something that I wouldn't use myself. Agreed! Great way to add SPA features without fundamentally changing ProcessWire rendering. I'd argue that it's the way that interactive UI should be built to begin with.
    1 point
  18. I think we have all been there - don't worry ? I tend to put myself under pressure quite often. Only to realize afterwards that it was totally unnecessary. Maybe related to my zodiac sign? It sometimes would be a healthier decision to just take the time I needed and also communicate that.
    1 point
  19. Great stuff ? Can't wait to use it!
    1 point
  20. It's been talked about elsewhere here in the forums, but an alternate idea would be utilizing .env files. It would satisfy both the installation process and long-term management of values. Some benefits: Widely used across applications, frameworks, and languages running in a web server environment and strongly recommended as a standard practice Secure by default. Web servers do not serve .env files Provides the ability to manage local and production configurations separately Frameworks like Laravel provide a .env file as well as a .env.example file containing all variables used in the application. This is kept up to date, used to document application requirements, and is committed into the repository Values that are not security-critical can be safely stored in the example file Provides a convenient location for additional non-ProcessWire site/app specific values such as API keys, or those required for modules Solves the issue of excluding site/config.php from a repository given that it's a required file containing specific syntax and $config object property assignments If .env was available in ProcessWire I would have my Fluency module look for translation API keys there rather than store them in the database. A benefit there is that different environments like local/ staging/production can mean an environment that is yours and an environment that is a client's. Pulling the latest copy of a database from production wouldn't require manually re-configuring modules that require API keys, or module registration keys. It would be good to eliminate storing security-critical values in PHP files entirely. The .env file could be generated and populated during the ProcessWire install process pre-excluded in the .gitignore file. Example: ENVIRONMENT="development" # ProcessWire PW_DB_HOST=localhost PW_DB_NAME="your_database PW_DB_USER=your_database_user PW_DB_PASS="LowuHeju7[BoI3" PW_DB_PORT=3306 PW_DB_ENGINE=InnoDB PW_DEBUG=true PW_CHMOD_DIR=0755 PW_CHMOD_FILE=0644 PW_USE_PAGE_CLASSES=true PW_USE_FUNCTIONS_API=true PW_PREPEND_TEMPLATE_FILE="_init.php" PW_USER_AUTH_SALT=d5e3ac4beda1e382255bbd8744d7e815 PW_LOCALE="en_US.UTF-8" PW_TIMEZONE="America/Los_Angeles" PW_DEFAULT_ADMIN_THEME=AdminThemeUikit PW_INSTALLED=1580675413 PW_HTTP_HOSTS = "domain.com,www.domain.com" # Some API that is used EXTERNAL_SERVICE_API_URL="https://api.someservice.loc/v2" # Development EXTERNAL_SERVICE_API_KEY=0d41fe1b68244f4ea51ae5e4abd24fab # Mailgun MG_KEY=key-192cde47ab73095e747ebe7556577b2d MG_DOMAIN="mg.somedomain.com" # Forecast.io FORECAST_IO_KEY=c28096383d66bcb1e2cb9ec37153f85c To take the idea further, it could be integrated with the ProcessWire API in a way that would prevent conflicts, keep .env variable naming organized, and make any value added by the developer available. Something like: <?php // An accessor method, this is probably the cleanest and resembles other frameworks $config->env('SOME_ENV_VARIABLE'); // Nested under a property acting as a namespace $config->env->SOME_ENV_VARIABLE; // On ProcessWire projects I implement some extras to easily interact with .env values without needing to access them directly. $config->envIs('production'); // boolean I haven't built a ProcessWire site without implementing this for many years and there's a great widely-used great package that provides .env values to PHP. There are also hosting providers that make environment variables/secrets manageable through their admin UI that eliminates the need for an actual file in production entirely. If ProcessWire were to implement environment variables it could look to values that may already exist and fall back to using a file. Implementation aside, if someone is able to access phpinfo() in production it's a critical issue beyond exposing config values given the totality of information dumped by that function.
    1 point
  21. Hi @HarryWilliam and welcome to the forum! E-Commerce is a huge topic and there are many ways how to do it - unfortunately or luckily ... Option 1: Payment Buttons or Links Very simple solutions are payment buttons that Platforms like Paypal offer. You can see an example here: https://www.boukal.at/work/catalogues/catalogue-do-you-also-have-pretty-motifs/ Another option would be payment links that payment providers like stripe or mollie offer: https://www.mollie.com/gb/products/payment-links Pro: Very easy to implement Con: You have to manage different buttons/links for different products on your own, place them in your markup (for example with a textformatter), keep them up do date and you have very limited possibilities for customisations (like different options etc). Option 2: SaaS Shop integration The next option would be to add one of the SaaS solutions to your PW site. One option would be to use Snipcart, which is very simple to add to your site, at least in theory: https://snipcart.com/blog/processwire-ecommerce-tutorial Pro: Easy setup, working shop out of the box, they maintain and develop the product continuously and long term (hopefully) Con: You usually pay for it per purchase and/or you have a monthly fee. Snipcart for example costs at least 20$ per month if your sales are < 1000$. That's 240$ each year. https://snipcart.com/pricing Con: You need to add products to your SaaS shop. That means you can either not manage products directly from within your ProcessWire backend or you need to develop a bridge that keeps products in sync. Con: Using a SaaS Shop means you are locked to the features that this shop provides. In PW we have this module, but I'm not sure whether it's still maintained or will see any updates in the near future. Option 3: A custom PW shop This is maybe the most advanced solution. The benefits are that you get a fully integrated solution. You can manage all your products, all your users, all your orders etc. directly from within your PW backend. You can add hooks wherever you want and you can customise everything to make it work 100% the way you or your client wants. Imagination and your skills are the limit. The con is that it will likely be a more complex setup, as you need to understand the basic workflows of E-Commerce and you need to setup everything the way you want. As far as I know we only have Padloper 2 by @kongondo at the moment. I don't know the price, though, because the shop is down at the moment. I'm developing RockCommerce at the moment and it will hopefully be released during this year. The basic version is already done and you can see it in action on my website baumrock.com where I use it to sell my commercial modules, for example RockForms. As you can see on my website it can already be used to sell single products. It comes with a checkout (live example) but it has no cart functionality at the moment. But it already has a nice Dashboard with filters and charts, at least ? Also, it can already create fully automated and 100% customisable invoices directly from within PW/PHP (using RockPdf) - which is something that not all shopping carts do for you, keep that in mind when evaluating those products! Thanks to the integration into the PW system it can send 100% custom emails to your customers, where you can make sure that you comply to your local legislation (for example in Austria we need to attach terms of service to that email): In this example it's a nice looking mail because I use RockMails, which is another module in the pipeline ? But as it is 100% ProcessWire you can simply send an email with some lines of code as well: $m = new WireMail(); $m->from('office@your-company.com'); $m->to('your@client.at'); $m->subject('Thank you for your oder!'); $m->body('Congrats, you are now a ProcessWire hero!'); $m->send(); So while it is not yet 100% it can already be already a great option! If you want to get notified about the release you can subscribe to my monthly developer newsletter: https://www.baumrock.com/en/rock-monthly/ I'm quite sure there are 100 more options, but I tried to give an overview ?
    1 point
  22. Verified: It was our webhost's (Dreamhost) customized, and overly intrusive mod_security settings.
    1 point
  23. Thank you @ukyo and @elabx this is exactly what I needed. I really tried to read through the API before asking, mainly because I didn't understand the relationship / order between everything. You guys are great!
    1 point
  24. Another really useful tool is RockMigrations.
    1 point
  25. Basically <?php namespace ProcessWire; // create field $field = new Field(); $field->name = 'my_field'; $field->label = 'My Field'; $field->type = 'FieldtypeText'; // save field wire('fields')->save($field); // get field $field = wire('fields')->get('my_field'); // Create fieldgroup $fieldgroup = new Fieldgroup(); // if you want to edit fields in this field group via admin panel, name need to be same with template name $fieldgroup->name = 'my_template'; $fieldgroup->add('title'); $fieldgroup->add('body'); $fieldgroup->add($field); // save fieldgroup wire('fieldgroups')->save($fieldgroup); // get fieldgroup $fieldgroup = wire('fieldgroups')->get('my_template'); // Create template $template = new Template(); $template->name = 'my_template'; $template->label = 'My Template'; $template->fieldgroup = $fieldgroup; // save template wire('templates')->save($template); // get template $template = wire('templates')->get('my_template');
    1 point
  26. 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 ?
    1 point
×
×
  • Create New...