Leaderboard
Popular Content
Showing content with the highest reputation on 03/07/2020 in all areas
-
This week we have some major improvements to our core date/time Inputfield, as well as a new ability to specify your own custom classes for Page objects. Read on for all the details and examples— https://processwire.com/blog/posts/pw-3.0.152/6 points
-
@Zeka Thanks, I think I tracked down the issue (caching issue that occurs only when multiple date fields) and have pushed a fix on the dev branch. @szabesz From your /site/init.php you can set the location with $config->setLocation('classes', 'path/to/classes/'); for example $config->setLocation('classes', 'site/templates/classes/'); You can also use the method mentioned earlier by adding your own directories to the $classLoader. However, the more potential locations there are for something, the more overhead, so I would recommend sticking to the $config->setLocation() and keep all of your classes in one directory. This keeps everything fast and efficient. If that's the case, then you would have to add locations with the $classLoader and accept the overhead of doing so (though it's probably negligible so long as things don't grow too much). But I also think with that kind of structure, things look isolated when it comes to reusability, limiting their usefulness. That may be fine for your use case, but as far as classes go, one of the primary purposes is reuse and extending one another where appropriate, so you ideally want to keep them together. @bernhard ProcessWire preloads a few pages on every request, since they always get used. Preloaded pages are loaded as a group rather than individually, enabling it to boot faster. This happens before the init state for modules, so a module will not have the opportunity to specify a class for these preloaded pages, but a file in /site/classes/ will. See the $config->preloadPageIDs setting for a list of what those pages are. But essentially they are the homepage, admin root page, guest user+role, and superuser role. For your case, it sounds like only the homepage is a consideration here. I would suggest just asking PW to uncache the homepage at your module's init() state. This should force it to reload (when it's next requested) using your custom class. $homePage = $this->pages->cacher()->getCache(1); if($homePage) $this->pages->uncache($homePage); You can also use $config->setLocation() for this, i.e. $config->setLocation('fieldTemplates', 'site/templates/my-custom-directory/'); I think this is fine if you are just doing it once or twice, but if you find you are repeating yourself, typing out "/articles/fields" or similar in multiple places, then you always want to abstract away stuff like that into a single location so that if you ever need to change it, you change it in just one place. PHP functions are ideal for this. This will be simple to do, but when it comes to the directory and file names that you are using, I recommend using the exact same names that you do with your templates and fields, which will make it a simple matter to abstract away repetitive code or locations into functions. "All these directories" are 3 directories, 2 of which are for optional features that are disabled by default — the absolute minimum number of directories necessary to support these things. ProcessWire is focused in what is the most simple, efficient, familiar (as it relates to the admin), and maximizes reusability. It's not opinionated about it outside of those factors. For something like field-templates, a primary purpose of having them is so that you can reuse them across different templates (to avoid repeating yourself); otherwise they aren't that useful. For the structure that you appear to be using, it looks to me like your intention is to isolate them by template, so why have them at all? If the purpose is code/markup isolation (which is still worthwhile) then I think just using an include() or $files->render(); would be preferable, more efficient and flexible for your use case. So that's what I'd recommend there. The same goes for the new page classes—if you are isolating these things in the way you've suggested, it'll be confusing for classes to extend one another or be shared across multiple templates, and it'll take more overhead for PW to find them. If you don't intend to use them like that, maybe they can still be useful, but not nearly as much so. So at that point I would reconsider whether you want/need custom Page classes. Basically, you are using a highly opinionated structure for your site files, and that can be a good thing because you've decided where everything goes and have a plan/system for how it will grow in terms of your file structure. ProcessWire is built to support a lot of different types of websites and applications, and at any scale. But it's not opinionated about this stuff beyond the bare minimum, precisely so that you can be as opinionated as much as you want with your own projects. That's why you are able to build a more complex structure for your files that suits your need, and also why it's also just as suited for others that might prefer a simpler structure in their projects. There's also no need to feel obligated to use things like field templates or custom page classes just because they are there. The point of these features is to reduce redundancy and prevent you from having to repeat yourself. But you may have your own preferred ways of doing that—whether it's with functions, file/directory structure, or anything else, it's all perfectly fine in PW. A template file receiving a $page is the only assumption needed in ProcessWire and everything else is optional. So use these things when they help you do that, and avoid using them when they don't. The reason these features are disabled by default is because not everyone needs them. Personally, the only time I use field templates is for multi-value fields with lots of properties, like repeaters, where I need to reuse the same output/markup across multiple template files. Though custom Page classes I'm already using quite a bit and likely will in most cases.6 points
-
Not without a hook, but this should work: // Hook for site/ready.php that makes $page->render($fieldname) look for // its template in site/templates/$templatename/fields/$fieldname.php. // Specifying alternative files still works. // Pass a path with a leading slash as $file to go directory from the templates dir. $storedFieldTemplates = []; $this->addHookBefore("Page::renderField", function(HookEvent $event) use($storedFieldTemplates) { $page = $event->object; $file = $event->arguments(1); $storedFieldTemplates[] = $this->config->paths->fieldTemplates; $this->config->paths->fieldTemplates = substr($file, 0, 1) == '/' ? $this->config->paths->templates : $this->config->paths->templates . $page->template->name . "/fields/"; }); $this->addHookAfter("Page::renderField", function(HookEvent $event) use($storedFieldTemplates) { $this->config->paths->fieldTemplates = array_pop($storedFieldTemplates); }); Shortens it down to $article->render('article_lead', 'lead')4 points
-
Technically it's already possible — you just need to tell WireClassLoader where it can find your custom Page classes. Just gave it a try, adding this to /site/init.php, and it seemed to work fine: $classLoader->addSuffix('Page', __DIR__ . '/templates/page-classes/'); Now ProcessWire checks for Page classes from /site/templates/page-classes/. This is in addition to the default /site/classes/ directory, which still has precedence if a class is found from there.4 points
-
The replies above by Ryan and BitPoet pretty much covered the questions you raised, so just dropping a quick comment regarding this ? I tend to fuss a lot over things like organising the project, so I'd say that I'm familiar with your concerns. In my opinion a sensible structure is a key design decision, and even more so when you're working with others. So yeah, I can't disagree with anything you've said here. Regardless, after giving this a lot of thought I ended up organising files by type — at least I think that's what you'd call it — in Wireframe. By this I mean that I've indeed bundled controllers in one directory, components in another, views in a third one, etc. For me this just makes more sense: When I add a new template and a view for it (assuming that it isn't already covered by some shared code, which is quite often the case), I'll probably start from the controller. I may copy-paste some parts from other controllers, or perhaps I'll extend another one if I have a very similar need at hand. Some controllers may use other controllers as sort of "subcontrollers", and often there are shared libraries as well. After the "backend" is mostly done, I'll move on to the view in order to actually put those methods to use. In other words I tend to work in layers, going from backend to the frontend. (This is just the way I like to work, and I definitely don't assume everyone to prefer the same approach.) Things like components or partials are made to be shared. If I only need a specific piece of code once — or in one template — then it probably doesn't need to be abstracted away. That'll just make things harder to grasp and add a tiny bit of overhead without actually providing much extra value. Copying files from project to project has been a relatively rare need for me, so when it does come up, it doesn't matter much if I have to copy one directory, or perhaps a couple of files from a couple of directories. That being said: in an old environment this need did come up often (and the structure was pretty complex), so I ended up writing a script to automate it ? It's true that the process of building a new site often involves moving back and forth between backend and frontend, but I still prefer this sort of structure over the alternatives. Also: I find it a tad easier to maintain after the site is already live, since often a specific change will only affect one "layer" of the site. Anyway, this is largely a matter of preference ? Exactly. When I first read about custom Page classes, my first thought was that this stuff is frigging cool... but I probably won't be using it anytime soon. I fully agree that it's a great feature, but in my case I've already solved it in a way that — in my opinion — fits my needs better. I may end up using this for some stuff eventually, but right now I don't have a need for it, and that's fine ?3 points
-
3 points
-
@ryan, I just fiddled with custom Page classes and hit the same issue as @rafaoski. I can access every Page property from methods in my custom class but not createdUser and modifiedUser. <?php namespace ProcessWire; class BasicPagePage extends Page { public function myMethod() { $creator = $this->createdUser; var_dump($creator); // Outputs: // NULL $creator = $this->get('createdUser'); var_dump($creator); // Outputs: // object(ProcessWire\User)#214 (7) { // ["id"]=> // int(41) // ["name"]=> // string(5) "admin" // ["parent"]=> // string(26) "/processwire/access/users/" // ["template"]=> // string(4) "user" // ["email"]=> // string(15) "nobody@invalid" // ["roles"]=> // string(17) "(PageArray) 37|38" // ["data"]=> // array(2) { // ["email"]=> // string(15) "nobody@invalid" // ["roles"]=> // object(ProcessWire\PageArray)#189 (3) { // ... // } } } Calling $page->createdUser->name from outside works, though. Inside the class, $this->createdUser never hits Page::__get().2 points
-
--- Module Directory: https://modules.processwire.com/modules/privacy-wire/ Github: https://github.com/blaueQuelle/privacywire/ Packagist:https://packagist.org/packages/blauequelle/privacywire Module Class Name: PrivacyWire Changelog: https://github.com/blaueQuelle/privacywire/blob/master/Changelog.md --- This module is (yet another) way for implementing a cookie management solution. Of course there are several other possibilities: - https://processwire.com/talk/topic/22920-klaro-cookie-consent-manager/ - https://github.com/webmanufaktur/CookieManagementBanner - https://github.com/johannesdachsel/cookiemonster - https://www.oiljs.org/ - ... and so on ... In this module you can configure which kind of cookie categories you want to manage: You can also enable the support for respecting the Do-Not-Track (DNT) header to don't annoy users, who already decided for all their browsing experience. Currently there are four possible cookie groups: - Necessary (always enabled) - Functional - Statistics - Marketing - External Media All groups can be renamed, so feel free to use other cookie group names. I just haven't found a way to implement a "repeater like" field as configurable module field ... When you want to load specific scripts ( like Google Analytics, Google Maps, ...) only after the user's content to this specific category of cookies, just use the following script syntax: <script type="text/plain" data-type="text/javascript" data-category="statistics" data-src="/path/to/your/statistic/script.js"></script> <script type="text/plain" data-type="text/javascript" data-category="marketing" data-src="/path/to/your/mareketing/script.js"></script> <script type="text/plain" data-type="text/javascript" data-category="external_media" data-src="/path/to/your/external-media/script.js"></script> <script type="text/plain" data-type="text/javascript" data-category="marketing">console.log("Inline scripts are also working!");</script> The data-attributes (data-type and data-category) are required to get recognized by PrivacyWire. the data-attributes are giving hints, how the script shall be loaded, if the data-category is within the cookie consents of the user. These scripts are loaded asynchronously after the user made the decision. If you want to give the users the possibility to change their consent, you can use the following Textformatter: [[privacywire-choose-cookies]] It's planned to add also other Textformatters to opt-out of specific cookie groups or delete the whole consent cookie. You can also add a custom link to output the banner again with a link / button with following class: <a href="#" class="privacywire-show-options">Show Cookie Options</a> <button class="privacywire-show-options">Show Cookie Options</button> I would love to hear your feedback ? CHANGELOG You can find the always up-to-date changelog file here.1 point
-
Here's a small new module that started as a spinoff of a memcache proof-of-concept. Cache your strings and partials in-memory using Redis as a backend. CacheRedis All you need is a running Redis database. The module supports connection through regular TCP, over TLS and via unix domain sockets. Host and port are configurable, and authentication is supported too. Here's a screenshot of the module configuration options: I'll not go into too many details about the usage, you can see everything explained in the README on GitHub, and just highlight the most important points. When the module is active, you'll have a wired $redis variable, which means you can reach the module as $redis, wire("redis") or within Wire classes / modules as $this->redis. CacheRedis is strongly influenced by WireCache, and its usage is (hopefully) straight forward. // Store an entry in the cache under the given key name with the given value: $redis->store("cached_value_number_one", $expire, $value); // Retrieve a value from the cache $value = $redis->fetch($key); // Delete a cache entry $redis->delete("my_other_cached_value"); // Clear the whole cache $redis->flush(); // Retrieve a value from the cache, and if not found, create it through the given function // and store it with a lifetime of 5 minutes (300 seconds) $value = $redis->fetch($key, 300, function() use($page) { return "Page last changed on " . strftime('%m/%d/%Y %H:%M', $page->modified | $page->created); }); // Render a file using wireRenderFile and store it in the cache. // We'll pass a selector as the expiry value so this cache gets // emptied every time a page matching the selector is saved. $news = $redis->renderFile("partials/news.php", 'template=blog-post', ["page" => $page]); The module is still very crude work in progress, but I hope some of you feel daring, try it out and let me know in case anything breaks. Have fun!1 point
-
Hello all, I'm in the process of updating some websites to comply to the new GDPR opt-in regulations. Doing some resarch, I found this open source tool: https://klaro.kiprotect.com/ It looks quite promising and implementation seems quite easy. Have you used this tool? What is your experience so far? I saw that @Jens Martsch - dotnetic made some comments to the klaro GH issues. Did you implement this with PW?1 point
-
Thanks for the update which seems to be a great addition ... Following the blog tutorial I have a some problems. First of all, I'm not sure (I only know the basics of php OOP), but the summary() method should look like this: return wire()->sanitizer->truncate($this->body, 300); instead return $this->sanitizer->truncate($this->body, 300); In general, I mean the byline() method which does not return the created user correctly, Tracy Debugger show me: PHP Notice: Trying to get property 'name' of non-object in ...\regular\site\classes\BlogPostPage.php:7 This is my code site/classes/BlogPostPage.php: <?php namespace ProcessWire; // classes/BlogPostPage.php class BlogPostPage extends Page { public function byline() { $date = wireDate('Y/m/d', $this->published); $name = $this->createdUser->name; return "Posted on $date by $name"; } public function summary() { return wire()->sanitizer->truncate($this->body, 300); } } This is home.php <?php foreach($pages->find('template=blog-post, sort=-published') as $post): ?> <div class='blog-post'> <h2><?=$post->title?></h2> <p class='byline'> <?=$post->byline()?> </p> <p> <?=$post->summary()?> <a href='<?=$post->url?>'>Read more</a> </p> </div> <?php endforeach; ?> All with the latest ProcessWire 3.0.152 dev and Regular UIKIT profile Can anyone confirm the issue or what I am doing wrong?1 point
-
@ryan Thanx for your help and explanation! I understand that from the OOP point of view this is preferable, for example organizing the classes of ProcessWire as a system. However, we are dealing with frontend code in this case, which is not so often requires complex OOP implementation, at least in the case of my projects. Instead, I am seeking "portability", that is – for example – copy/pasting just one "articles" folder to another site in order to use it as the bases for the blog post section of that site. Features implemented in these files are somewhat different – but mostly similar – from project to project anyway, so I do not want to generalize anything in this regard. When generalization is needed, a custom helper module is a good choice, I think. Sure, everyone has her/his own way of organizing, so having options to do things the way one prefers is great. This can be solved by using cache – I guess – which might also be required for other reasons as well.1 point
-
hello @Richard Jedlička, i found the issue. It was not an issue with the module but a problem with an ImageMagick-6 policy update on my Ubuntu machine, which seems to have happen around the same time as i updated my Processwire version: The solution to my problem was to edit /etc/ImageMagick-6/policy.xml than change the following line from: <policy domain="coder" rights="none" pattern="PDF" /> to <policy domain="coder" rights="read|write" pattern="PDF" /> and added the following line <policy domain="coder" rights="read|write" pattern="LABEL" /> By changing the policy you have to make sure that you can trust the users that upload the pdf files.1 point
-
Thanks for the tip! Do you maybe have a similar tip for how to specify a custom folder for field rendering files? Or a tip on how to shorten $article->render('article_lead', '/articles/fields/lead') ? I dislike the idea of scattering related files in all these directories: /site/classes/ /site/templates/ /site/templates/fields/ It is not just me who thinks that the old-school way of organizing files exclusively by type is not the smartest idea of all. I know that lots of developers have long been promoting it by using and recommending folders like "js", "css", "inc", "images", etc. in one parent folder to store them all, which is kinda ok for a small site, but storing logically related files is preferable right at beginning, because – when the need arises – refactoring for no other reason than to reorganize files is just a waste of time. The worst example I've ever encountered was Magento 1 with its frontend-theme's files scattered all over the place, making it hard to remember where to look for. Some related articles to show that I'm not alone: The project/file structure should represent its architecture. Group files by features. Simplifying Projects with a Folder-by-Feature Structure. Quote: "Unfortunately, as your project grows the traditional structure falls apart. Over time code becomes harder to find, the project is harder to maintain, and there is a lot of scrolling around the project to change code for a single feature. This is where the concept of feature folders come in handy."1 point
-
Thank you @ryan this is an awesome update! Really looking forward to replacing lots of hook-chaos by well organized and easy to maintain PageClasses ? ? So far, so ??? BUT: I've put together this quick testing module: <?php namespace ProcessWire; /** * Custom page classes for ProcessWire * * @author Bernhard Baumrock, 07.03.2020 * @license Licensed under MIT * @link https://www.baumrock.com */ class RockPageClasses extends WireData implements Module { public static function getModuleInfo() { return [ 'title' => 'RockPageClasses', 'version' => '0.0.1', 'summary' => 'Custom page classes for ProcessWire', 'autoload' => true, 'singular' => true, 'icon' => 'bolt', 'requires' => [ 'ProcessWire>=3.0.152', // custom page classes update ], 'installs' => [], ]; } public function init() { $this->classLoader->addSuffix('Page', __DIR__ . '/PageClasses/'); $file = $this->classLoader->findClassFile("HomePage"); bd($file); } } Now I deleted the file in /site/classes/HomePage.php and added it to /site/modules/RockPageClasses/PageClasses/HomePage.php and this is the result: Strangely, the file is found but not loaded for the HOME template! Any ideas? I think 95% of my custom classes will live in a folder inside /site/modules ? PS: This is the HomePage class:1 point
-
Hi @szabesz Interesting workflow. Just a sidenote from me: if you want to start right now, you may store the new page class files within your workflow structure and additionally maintain the given site/classes directory by just give it proxy files that include or require the files from your internal structure. Just as a temporary workaround. And if Ryan cannot implement your request, it would be easy to use a script that scans and create / sync the proxy files directory for you in regard to portability. @ryan Well done! ?1 point
-
+1 ? @ryan Thanks for these great new features! However, I am wondering if it is (or will be) possible to define where to store the files of custom classes. Some time ago, I started to organize files belonging to a given parent/children template relationship into their own dedicated directory. I explained it in a bit more detail here: https://processwire.com/talk/topic/23193-mvc-architecture-and-processwire/?do=findComment&comment=198246 For example: I put this in /site/init.php for each template that needs code: $templates->get('article')->filename = $config->paths->templates . 'pages/articles/article.php'; I even put the files of field rendering under /site/templates/pages/articles/fields/ and render them like this (note: it is not seen in the screenshot above...): <?= $article->render('article_lead', '/articles/fields/lead') ?> I am asking for something similar so that I do not have to put class files into /site/classes/ , instead, I would like to store them alongside all the other related files, eg. in /site/templates/pages/articles/ or somewhere else when applicable (as it can bee seen in the screenshot, I also have a "global" folder for files not tied to a given parent/children template relation). I find it particularly useful that I do not have to hunt for files when working on a given parent/children template relationship. By sticking to my simple naming conventions, files are are always just a click away and they are also easily "portable" form project to project.1 point
-
@ryan It looks like there are some issues with InputfieldDatetime. I have to fields 'publish_from' and 'publish_until'. None of them are configurated to prefill today's date if empty, but after the update, if one of the fields has value second one is prefilled with its value. The second issue is that after page save I cannot change the selected value, it shows that the date is changed but after page save it still show the previous date. The third issue is that Date Picker options are not applied to the field. For example if I set Date Picker option to "No date/time picker" on the first field second one will also have no date/time picker. Looks like there is something wrong with js initialization and first field configuration is applied to all other date fields on the page edit screen. Tested on two different sites.1 point
-
And again, this has nothing to do with the Inputfield that is currently being worked on but what I was wishing for a couple times lately was some kind of "strtotime" support for page selectors when used on date fields. This would for example allow for filtering for users that signed up in the last 6 months, and that list is updated automatically, without any custom code involved. $pages->find('template=user,created>"-6 months"'); I know, this is not related to the Inputfield, but I wanted to bring it up in case it's an easy addition while working on dates.1 point
-
Hi guys, I was very excited for this module, but my life took a huge direction change and I no longer have the time to invest in module development. I am gonna leave the files here. You guys can take it and run. Maybe there might be something useful here. Maybe not. I still think it's a good idea to do drag and drop modal building in PW. So hopefully one day something like that can come to light. I love this community and I love ProcessWire. Live long and prosper. - Joshua Designme 2.zip1 point
-
Yes it is good and in use on the site https://www.p-jentschura.com/ I added this directly in the HTML so it has nothing to do with ProcessWire in general, but I am thinking about developing a module for it to set the options. But thats only for the far future and next website project.1 point
-
I'm digging this thread out because I just came across the same problem in my module. I fixed it for now by merging the default config as suggested by @Robin S: $options = $this->modules->getModuleConfigData($this); $defaults = (new TextformatterPageTitleLinksConfig())->getDefaults(); $options = array_merge($defaults, $options); See the complete source code here. However, I agree that this should be handled by ProcessWire. I expected getModuleConfigData to return the final merged configuration, the method doesn't suggest that it only retrieves the config that has been saved to the database. This way the method creates a temporary dead zone between installing a module and saving the module configuration page, where errors can pop up in the meantime. @horst @kongondo By the way, saving the module config during installation may not be sufficient. In my case, I got an error when I upgraded my module to version 3.0.0 which included a new setting. So even though the module was already installed and the options had been saved before, the new version was missing the new configuration option as it wasn't included in the configuration returned by getModuleConfigData. So with this approach one may also need to hook into the upgrade process to make sure the configuration is saved after every update.1 point