Jump to content

MoritzLost

Members
  • Posts

    364
  • Joined

  • Last visited

  • Days Won

    18

Everything posted by MoritzLost

  1. @a-ok I didn't know that method existed, look like it does pretty much the same as my example code above, so absolutely yes, use it! I would only change the default headers: Since your file is password-protected, you want to set 'Cache-Control: no-store' (or private) so it can't be cached on the network / CDN layer. The method also sets 'Pragma: public' by default, so this should be changed to 'Pragma: no-store' as well (or better yet, removed entirely).
  2. I have now integrated the ProCache branch into the module and released it as version 1.0.0! I have tested the module on multiple sites and it's pretty stable now. But if you do run into bugs, let me know! The ProCache integration is now included, so you can clear out the ProCache page render cache alongside all other caches. Hopefully we can resolve the issue with minified assets, see the previous post. The documentation is now updated with information on all the new methods added in the previous release. Let me know if something isn't working for you!
  3. I have built little forms like this manually a couple of times! It's definitely fun, though building complex forms is a lot more work than one might think, but for small forms you'll be fine. No problem at all - you just need to handle the things that FormBuilder will do. In your example, most notably: Write the HTML for the form. You can try to dynamically generate this, but building exhaustive options for form fields is much work, so for a simple form static HTML will be fine. Handle the result. I'd do that within the template file of the template where your form is output. For a normal form you'd need some form of Spam protection, CSRF protection and other stuff but since in your case you require a password that won't be necessary, since you can just throw out submission that don't match the password. Store the submission, more on that below. Submit the file. For a simple solution, you can just redirect to the URL of the file. I have built something where I wanted to make sure the file can ONLY be accessed through the form. For this, you can put your file in a safe location (outside your webroot) and stream it through PHP once you have checked if the password is correct. Make sure to output the correct header to instruct the browser to start a download. Quick and dirty, based on the readfile documentation: if ($input->post('password_field') === $page->my_secret_password) { $file = '/path/to/file.pdf'; header('Content-Type: application/octet-stream'); header('Content-Disposition: attachment; filename="'.basename($file).'"'); header('Content-Length: ' . filesize($file)); header('Cache-Control: private'); readfile($file); exit; } Honestly, don't overthink this bit. Unless you are expecting thousands of request an hour, you don't need to optimise it. Depending on what you plan to do with this data I might go with a ProFields Table field or a regular template. Those are nice because all content is stored in a single, simple table. This gives you a bit more room to optimise if and when you need to. But if you are really getting so many requests that the server can't handle them, you will probably yield more improvements by building a seperate endpoint that doesn't load ProcessWire at all and instead writes to the database directly. This will be a bit easier with a ProFields Table field, though it really doesn't matter that much. If you are using a ProFields Table, make sure you are not loading the field on your form page, because that will load ALL it's data, that might in fact get out of hand quickly if you have lots of submissions.
  4. Thanks @David Karich! Hm, right now the module is just calling $procache->clearAll() which is supposed to clear everything according to the ProCache store page. Is there another method to clear the minified assets as well? I checked all documentation I could find, a couple of ideas: I don't know how ProCache works with minified assets, but according to this blog post any changes to the CSS & JS files should be picked up automatically, so maybe it's not necessary to clear those? Though of course that is not ideal for a cache clear button ? Judging by the last screenshot on this blog post, ProCache stores minified assets inside /site/assets/pwpc, or is that out of date? Currently, ProcessCacheControl only supports clearing out folders inside /site/assets/cache (so it can't accidentally nuke the files directory because of a misconfigured setting ...). Maybe I could extend that to also allow clearing /site/assets/pwpc. Is that directory path static or can it be changed? Anyway, I'll put out a stable release with updated documentation & the ProCache integration soon! Hopefully the minification issue can also be resolved then.
  5. So I just opened an issue and closed it immediately because I realized that my proposed solution already exists in the form of wirePopulateStringTags ? It works perfectly for my use case, only requiring to use {title} with curly braces instead of title for the first example, which in a way is even more predictable. Those functions should really have better visibility in the documentation ?
  6. Thanks @Robin S, that was my conclusion as well. I would say that the behaviour in my third case is somewhat unspecified in the documentation – because the string is neither a field name nor a replacement pattern. I guess what I don't like about the implementation is that the distinction between the two cases is done by checking if the string contains curly braces. For my taste, it would be more explicit to check if the passed value is a field name and fall back to the curly brace implementation if it isn't. Though admittedly there are a lot of edge cases with subfields or alternate fields ... Maybe it would be best to add an optional parameter to those methods that forces one specific behaviour. I have considered that, but that will produce an 'incorrect' result (for my use case) in another case. Namely, if the passed value is a field name but the field happens to be empty on the current page, the field name will be output – even though an empty string would be the expected result in that case. So in order to get the expected result for every input, I'll have to: Return the output of $page->getMarkup if it is not empty. If it is empty, check if the original string is a fieldname (or sub-fieldname or similar). If it isn't, output the original string. If it is, output an empty string. I would prefer if that logic was done inside getMarkup. I guess I'll open a feature request for that.
  7. A couple of times I've stumbled over a problem with $page->getMarkup / $page->getText. I'm often using this to parse module settings where the user can either enter a field name or a longer format with field replacements in curly braces. The problem is that entering a static text (not a field name) WITHOUT curly braces always returns an empty string. This seems counterintuitive, and it also presents a problem for those settings. More specific, for a module configuration where the user can enter a string that will be output somewhere. Something like this: echo $page->getMarkup($modules->get('MyModule')->MyTextFormatSetting); This works great for two out of three inputs: title -> Returns the page's title, great. The current title is {title} -> Returns the custom string with the page's title replaced in the end, great. My custom text without replacements -> Returns an empty string, not so great. It's arguable whether getMarkup should just return the original text unmodified in the third case (imho it should). But how do I work around this when working on a module? I can think of two approaches: Add another setting where the user can switch between a static text and a replacement pattern for getMarkup. This is not ideal because it's another setting I have to code and the user has to think about, which should be unnecessary. Fallback to the original string if getMarkup returns an empty string. The problem here is if the original input is in fact a field name and the field is just empty on that field, I'm now outputting a field name instead of an empty string, which is incorrect in that case. I could work around that by checking if the original input is the name of an existing field, but that once again adds more complexity. Is there a better way to do this? Some function I haven't considered? How do you handle this in your modules? Any suggestions would be appreciated!
  8. PageArray inherits from PaginatedArray which inherits from WireArray. WireArray checks for duplicates by default. You can change this behaviour through setDuplicateChecking!
  9. @horst Hm, I hadn't considered that, that makes it more difficult indeed. Come to think of it, most of my sites would have this issue as well - for example, I use a non-standard pagination approach with URL segments, those wouldn't be caught by just using the public URL of the base page; only the first page would be reached. I guess webcrawling would be a way around that, though that is a real can of worms ... Maybe a good compromise would be making the process hookable, like you said. Maybe the method can call a hookable method that returns an array of URLs for each page; by default it returns only the normal URLs, and additional URLs with additional parameters, url segments et c. can be added through hooks. I'll give this a go when I have the time!
  10. @horst Thanks for the feedback. Does warming up ProCache require any special consideration? I liked the approach of HTTP requests because of it's simplicity – i.e. it's virtually identical with a regular page visits, so the normal caching mechanisms can just do their thing. Or will that not work with ProCache for some reason?
  11. @horst Awesome, thank you very much! I'll merge the branch into master soon then. I just need to update the documentation with all the new methods I added in the previous release, then I'll update the module to the first stable release. I'm still thinking about other useful cache actions. I considered a "warm up" method that will just iterate over all the pages and perform anonymous HTTP requests to warm up the template render cache. Does something like this already exist? Would it be useful to anyone? For smaller sites it would be trivial, but for larger sites the module might need some form of rate limiting, batch processing ... not sure if it's worth the effort?
  12. I just updated the module to version 0.5.0! I have now installed it on a couple of sites and it's pretty stable by now. But before I tag a stable 1.0.0 release I would really like to get the ProCache integration working. ProCache integration - Testers wanted If somebody has access to the ProCache module and would like to help me, I just need someone to test the ProCache integration! To get started, download the procache branch of the module on Github and install it . There should be a new "ProCache" option in the module configuration. This field should also tell you whether you have ProCache installed. Check this option. Then go to the Process page through Setup -> Cache Control and click the "Clear all" button. The log output should clear the ProCache entirely. I'd like to know if you get any errors during any of those steps. Also, please check if the ProCache has been actually cleared. Thanks! Version 0.5.0 The new version provides some additional helper methods to check if users can access particular actions and improves the status messages displayed on the process page. In particular, there's a new static method to check if the user can access the module. This is important, because instantiating the module ($modules->get('ProcessCacheControl')) will throw an error if the user does not have the required permission. So this can now be checked with the static method ProcessCacheControl::canUseModule($user). The complete Changelog can be found in the CHANGELOG.md in the repository.
  13. You don't even need to adjust your template, as long as the rows have one common parent you can use the CSS nth-selector: .row { display: flex; flex-flow: row nowrap; justify-content: space-between; align-items: center; } .row:nth-child(2n) { flex-direction: row-reverse; }
  14. @Flashmaster82 That's the same thing. {$page->parent} evaluates to the parent page's ID. The difference is that you're now using "team_page" instead of "sports_team". The query needs to match the name of the field you're using to select the sports team on the profile_page, so make sure to use the correct field names.
  15. I don't completely understand your structure, but you are passing in only a name (not a complete path), so ProcessWire has no way of knowing where in your page tree the $relative page is located. If you use the ID instead, it should work: $relative = $page->parent->id; $event->return = $event->pages->find("template=profile_page, sports_team=$relative");
  16. You need to iterate through all languages and set the name in all of them. After all, they might also have different titles, so you want to use the local title as well. Something like this: function customPageName(HookEvent $event) { $page = $event->arguments(0); if ($page->template->name == 'about-events-single') { $eventDate = date("dmY", $page->global_date_end); $page->of(false); foreach (wire('languages') as $lang) { $localTitle = $page->getLanguageValue($lang, 'title'); $localPageName = wire()->sanitizer->pageName($localTitle . '-' . $eventDate, true); $page->setLanguageValue($lang, 'name', $localPageName); } } } wire()->addHookAfter('Pages::saveReady', null, 'customPageName'); Quick and untested, might need some adjustments, but you get the idea ?
  17. @baronmunchowsen Hm, that is true. I don't often use the callback argument, so I haven't encountered that problem yet. The hooked method passes on the argument, but if it's used the WireCache class will still use the normal save() method to save the return value, as you said. I suppose you could replicate the functionality of the protected renderCacheValue method inside the hook method, and execute the fallback function manually instead of passing it on to $cache->get. But for my taste that would be too much code duplication. So yes, I'd say it's best to not use the fallback function with the saveRaw method.
  18. @baronmunchowsen A little trick I use when I need to store JSON is to add two methods $cache->saveRaw and $cache->getRaw through hooks, which just add the prefix to bypass the JSON encoding. See my reply here, which is a slight modification of LostKobrokai's code. Same result as @BitPoet's code above, just feels a bit cleaner than manually prefixing the JSON wherever you need it.
  19. @Gadgetto That doesn't work in all cases though. My module allows you to call it's main method with custom arguments, overwriting any configuration option for this method call only. If I were accessing the module config through the magic __get properties, that would mean I'd have to check for overwrites anywhere I need to access an option, making the code overly complicated. Right now I'm merging the passed options with the module configuration and then merging the defaults in manually. In my opinion, the second step should be done by getModuleConfigData automatically.
  20. You can allow UTF8 characters in page names, or extend the default to allow for uppercase letters through the configuration. You need to update two config settings to tell ProcessWire to allow UTF8 page names and whitelist the characters you want to use, as well as update your .htaccess file to route those URLs to ProcessWire. This blog post has a detailed tutorial. I haven't tested this, but if you only want to allow uppercase letters (and no UTF8 characters), it should be enough to add the uppercase letters to $config->pageNameWhitelist. The default .htaccess routing rule already includes A-Z characters, and uppercase letters are ASCII anyway ...
  21. Use the function wireMail() or the $mail->new() to instantiate an E-Mail object, then ProcessWire will automatically look for other WireMail modules and instantiate one of them instead of the default WireMail object. You can also request a specific WireMail module by specifing it as an option. For example, for the Mailgun module, this should work: $mailgun = $mail->new(['module' => 'WireMailMailgun']); $mailgun instanceof WireMailMailgun; // true See the documentation for $mail->new().
  22. You can check $page->prev() and $page->next() to find siblings matching a selector. You need of course some criteria to identify the "Completed" page – if using an ID is not an option, you could use the name or title of the page. Here are some code examples (those assume $page is the "In Progress" page): // will be true if the "Completed" page is somewhere before the current $page $CompletedIsBefore = $page->prev('title="Completed"')->id !== 0; // will be true if the "Completed" page is somewhere after the current $page $CompletedIsAfter = $page->next('title="Completed"')->id !== 0; // will be true if the "Completed" page comes directly before the current $page $CompletedIsPreviousPage = $page->prev()->matches('title="Completed"'); See $page->prev() and $page->next() in the API reference.
  23. Oh I see, that's easier of course ^^ In that case, two suggestions: Have you tried setting the default value for the Selector string to something more permissive so that it allows all pages? Not sure why the overwrite is not working, but this could be a workaround. Can you use separate fields for each context? I'd usually only re-use fields if they have the same function within different contexts, though that is of course only personal preference. @dragan You can allow overrides for pretty much any field setting if you use advanced mode. Not all overrides are really stable though, I guess this is one of those cases.
  24. How are you overriding the selectable pages with contextual options? I have recently used a similar approach to have a different set of categories selectable depending on a page's position in the page tree. The error you mentioned will definitely occur if you aren't hooking getSelectablePages during the request that saves the page. For reference, here is my code that is working for me (this is for a multisite, so $homepage is the current multisite instance): $wire->addHookAfter('InputfieldPage::getSelectablePages', function (HookEvent $event) { if ($event->object->hasField->name === 'category') { $currentPage = $event->arguments('page'); $homepage = $currentPage->rootParent; $categories = $homepage->get('template=category-index, include=hidden')->children('template=category'); $event->return = $categories; } });
  25. Another perspective: The API acts as the model, with the $page / Page class giving you access to page data through a generic interface. By default, the PHP template file for the current content type / template acts as Controller and View, though you can easily seperate Controller and View by using, for example, a frontend templating library like twig. That's close to my usual setup: View: Twig acts as a View layer, since it gets pretty privileged access to API variables, you can get anything done you'd usually do in a PHP View class. Controller: The PHP template file acts as the Controller, for example processing input and rendering the correct Twig template. Usually, I just keep the template logic procedural, because ProcessWire already does a lot of work in determining the correct template file and setting up API variables. Though you could also use a custom Controller class and just use the template file to instantiate it. Model: As mentioned above, the $page API variable is already a kind of generic model for your data, and for sites that are mostly brochure sites / presentational in nature, this is really all you need. But if you want to go further, you can create custom page classes by extending the Page class and set your template to use that, this way you can make your model as smart as it need to be. I have written two tutorials on how to integrate Twig if you're interested ? Part 1: Extendible template structures Part 2: Custom functionality and integrations
×
×
  • Create New...