Jump to content


Popular Content

Showing content with the highest reputation since 05/05/2020 in all areas

  1. 39 points
    ProcessWire 3.0.157 on the development branch continues the trend of core refactoring that’s been happening quite a bit in 2020. Rather than doing a rewrite every few years (like some CMS projects) we instead refactor parts as we go, constantly improving and optimizing the core. This works because the core design/architecture is right where it needs to be, even 10 years in. But there’s always still bits of legacy code, and code that can be improved. So in the context of ProcessWire, refactoring means incrementally rewriting code on the inside, without changing its behavior on the outside (other than making it faster and/or more secure). This has been happening regularly over the last 10 years, and will likewise continue happening over the next 10 years and likely beyond. This week the code behind ProcessWire’s core Database and PageFinder classes got a major refactoring. This is some of the most used code in PW, as it handles everything involved in taking a selector and converting it to a database query. But it’s always been a little bit of a pain point for me because it had to build queries in a way that I thought wasn’t ideal, in order to make it possible for lots of different modular parts (mostly Fieldtype modules) to contribute to the query and for PageFinder to put it all together. It was fast and secure, but still one of those parts that felt like a little too much duct tape to me. But considering how crucial the code is, I’ve always been reluctant to make major changes, since it all worked just fine. Spending lots of years thinking about it (on and off), a desire to work out any pain points, and having better tools available (like Phpstorm and Tracy) made it possible to finally massage out this pain point. Some work still remains to be done, but it’s mostly there and I’m feeling good about it. Stuff like this is key for the maintenance and longevity of the core, and involved a lot of time and effort, but makes very little difference to users, even if it makes a lot of difference to me in maintaining the core. It would make a boring blog post for sure—lots of work and changes, but no new toys to show for it. Nevertheless, it has that feeling of a good house cleaning, even if you can't see it from the outside. The scope of changes here means that there may actually be new bugs to work out, so to be on the safe side, consider 3.0.157 to be a little more “beta” than the dev branches usually are. Though I’m running it here on processwire.com and it’s working well. Beyond the fairly major updates to the Database classes, there are also a few new Sanitizer convenience methods that are primarily variations on existing ones, but useful ones for sure. Thanks for reading and have a great weekend!
  2. 30 points
    I've seen a couple of questions regarding namespaces and autoloading floating around the forum recently, so I decided to write a little tutorial. In general, I often see people getting confused when they try to wrap their head around namespaces, autoloading, Composer and the mapping of namespaces to directory structures all at once. In fact, those are very much independent, distinct concept, and it is much easier to explain and understand them separately. So this guide is structured as follows: How namespaces work in PHP. How autoloading works in PHP. Conventions for mapping namespaces to directory structures: PSR-4. How autoloading works in Composer and ProcessWire's class loader. How to use the class loader in a ProcessWire module. Feel free to skip the sections you're already familiar with. Namespaces in PHP The purpose of namespaces in PHP is to avoid naming conflicts between classes, functions and constants, especially when you're using external libraries and frameworks. Nothing more. It's important to understand that this has nothing at all to do with autoloading, directory structures or file names. You can put namespaced stuff everywhere you want. You can even have multiple namespaces inside a single file (don't try this at home). Namespaces only exist to be able to use a generic name – for example, ProcessWire's Config class – multiple times in different contexts without getting a naming conflict. Without namespaces, I couldn't use any library that includes a Config class of it's own, because that name is already taken. With namespaces, you can have a distinction between the classes ProcessWire\Config and MoritzLost\Config. You can also use sub-namespaces to further segregate your code into logical groups. For example, I can have two classes MoritzLost\Frontend\Config and MoritzLost\Backend\Config– a class name only needs to be unique within it's namespace. You can declare the namespace for a PHP file using the namespace statement at the top: // file-one.php <?php namespace ProcessWire; // file-two.php <?php namespace MoritzLost\Frontend; This way, all classes, methods and constants defined inside this file are placed in that namespace. All ProcessWire classes live in the ProcessWire namespace. Now to use one of those classes – for example, to instantiate it – you have a couple of options. You can either use it's fully qualified class name or import it into the current namespace. Also, if you are inside a namespaced file, any reference to a class is relative to that namespace. Unless it starts with a backward slash, in this case it's relative to the global namespace. So all of those examples are equivalent: // example-one.php <?php namespace ProcessWire; $page = new Page(); // example-two.php <?php use ProcessWire\Page; $page = new Page(); // example-three.php <?php $page = new ProcessWire\Page(); // example-four.php <?php namespace MoritzLost\Somewhere\Over\The\Rainbow; $page = new \ProcessWire\Page(); The use statement in the second example can be read like this: “Inside this file, all references to Page refer to the class \ProcessWire\Page” How autoloading works Every PHP program starts with one entry file – for ProcessWire, that's usually it's index.php. But you don't want to keep all your code in one file, that would get out of hand quickly. Once you start to split your code into several individual files however, you have to take care of manually including them with require or include calls. That becomes very tedious as well. The purpose of autoloading is to be able to add new code in new files without having to import them manually. This, again, has nothing to do with namespaces, not even something with file locations. Autoloading is a pretty simple concept: If you try to use a class that hasn't been loaded yet, PHP calls upon it's registered autoloaders as a last-ditch attempt to load them before throwing an exception. Let's look at a simple example: // classes.php <?php class A { /** class stuff */ } class B { /** class stuff */ } // index.php <?php spl_autoload_register(function ($class) { include_once 'classes.php'; }); new A(); new B(); This is a complete and functional autoloader. If you don't believe me, go ahead and save those two files (classes.php and index.php) and run the index.php with php -f index.php. Then comment out the include_once call and run it again, then you'll get an error that class A was not found. Now here's what happens when index.php is executed (with the autoloader active): Our anonymous function is added to the autoload queue through spl_autoload_register. PHP tries to instantiate class A, but can't because it's not loaded yet. If there was no autoloader registered, the program would die with a fatal error at this point. But since there is an autoloader ... The autoloader is called. Our autoloader includes classes.php with the class definition. That was a close one! Since the class has been loaded, execution goes back to the index.php which can now proceed to instantiate A and B. If the class was still not loaded at this point, PHP would go back to the original plan and die. One thing to note is that the autoloader will only be called once in this example. That's because both A and B are in the same file and that file is included during the first call to the autoloader. Autoloading works on files, not on classes! The important takeaway is that PHP doesn't know if the autoloader knows where to find the class it asks for or, if there are multiple autoloader, which one can load it. PHP just calls each registered autoloader in turn and checks if the class has been loaded after each one. If the class still isn't loaded after the last autoloader is done, it's error time. What the autoloader actually does is pretty much wild wild west as well. It takes the name of the class PHP is trying to load as an argument, but it doesn't have to do anything with it. Our autoloader ignores it entirely. Instead, it just includes classes.php and says to itself “My job here is done”. If class A was in another file, it wouldn't have worked. This process has two main advantages: Since autoloaders are only called on-demand to load classes just in time, we only include the files we actually need. If in the example above class A and B are not used in some scenarios, the classes.php will not be included, which will result in better performance for larger projects (though this isn't as cut and dry, since autoloading has it's own overhead, so if you load most classes anyway during a single request, it will actually be less efficient). If the autoloader is smart enough to somehow map class names to the files they're located in, we can just let the autoloader handle including the classes we need, without having to worry about jamming include statements everywhere. That brings us to ... PSR-4, namespaces and directory structures As you see, namespaces and autoloading are both pretty limited concepts. And they aren't inherently linked to each other. You can namespace your classes without ever adding an autoloader, and you can autoload classes that are all in the same namespace. But they become useful when you put them together. At the core of all that autoloading talk is a simple idea: By putting classes in files named after their class names, and putting those files in directory hierarchies based on the namespace hierarchy, the autoloader can efficiently find and load those files based on the namespace. All it needs is a list of root namespaces with their corresponding directories. The exact way class names and namespaces are mapped to directory structures and file names is purely conventional. The accepted convention for this is PSR-4. This is a super simple standard which basically just sums up the ideas above: A base namespace is mapped to a specific directory in the file system. When the autoloader is asked to load a class in that namespace (or a sub-namespace of it), it starts looking in that folder. This "base" namespace may include multiple parts – for example, I could use MoritzLost\MyAwesomeLibrary as a base and map that to my source directory. PSR-4 calls this a "namespace prefix". Each sub-namespace corresponds to a sub-directory. So by looking at the namespace, you can follow subdirectories to the location where you expect to find the class file. Finally, the class name is mapped directly to the file name. So MyCoolClass needs to be put inside MyCoolClass.php. This all sounds simple and straightforward - and it absolutely is! It's only once you mash everything together, mix up language features, accepted conventions and proprietary implementations like Composer on top that it becomes hard to grasp in one go. Composer and ProcessWire's class loader Now all that's left is to talk about how Composer and ProcessWire provide autoloading. Composer, of course, is primarily a tool for dependency management. But because most libraries use namespaces and most developers want to have the libraries they're using autoloaded, those topics become a prerequisite to understanding what Composer does in this regard. Composer can use different autoloading mechanisms; for example, you can just give it a static list of files to include for every request, or use the older PSR-0 standard. But most modern libraries use PSR-4 to autoload classes. So all Composer needs to function is a mapping of namespace prefixes to directories. Each library maintains this mapping for it's PSR-4-structured classes through the autoload information in their composer.json. You can do this for your own site to: Just include the autoload information as shown in the documentation and point it to the directory of your class files. Composer collects all that information and uses it to generate a custom file at vendor/autoload.php — that's the one you need to include somewhere whenever you set up Composer in one of your projects. Bells and whistles aside, this file just registers an autoloader function that will use all the information collected from your own and your included libraries' composer.json to locate and include class files on demand. You can read more about how to optimize Composer's autoloader for production usage here. If you want to read up on how to set up Composer for your own sites, read my ProcessWire + Composer integration guide instead. And finally, what does ProcessWire do to handle all this? Turns out, ProcessWire has it's own autoloader implementation that is more or less PSR-4 compliant. You can access it as an API variable ($classLoader or wire('classLoader'), depending on context). Instead of using a static configuration file like Composer, the namespace -> directory mapping is added during the runtime by calling $classLoader->addNamespace. As you would expect, this function accepts a namespace and a directory path. You can use this to register your own custom namespaces. Alternatively, if you have site-specific classes within the ProcessWire namespace, you can just add their location to the class loader using the same method: $classLoader->addNamespace('ProcessWire', '/path/to/your/classes/'). Utilizing custom namespaces and autoloading in ProcessWire modules Now as a final remark, I wanted to give an example of how to use custom namespaces and the class loader in your own modules. I'll use my TrelloWire module as an example: Decide what namespace you're going to use. The main module file should live in the ProcessWire namespace, but if you have other classes in your module, they can and should use a custom namespace to avoid collisions with other modules. TrelloWire uses ProcessWire\TrelloWire, but you can also use something outside the ProcessWire namespace. You need to make sure to add the namespace to the class loader as early as possible. If either you or a user of your module tries to instantiate one of your custom classes before that, it will fail. Good places to start are the constructor of your main module file, or their init or ready methods. Here's a complete example. The module uses only one custom namespaced class: ProcessWire\TrelloWire\TrelloWireApi, located in the src/ directory of the module. But with this setup, I can add more classes whenever I need without having to modify anything else. /** * The constructor registers the TrelloWire namespace used by this module. */ public function __construct() { $namespace = 'ProcessWire\\TrelloWire'; $classLoader = $this->wire('classLoader'); if (!$classLoader->hasNamespace($namespace)) { $srcPath = $this->wire('config')->paths->get($this) . 'src/'; $classLoader->addNamespace($namespace, $srcPath); } } Source Thanks for making it through to the very end! I gotta learn to keep those things short. Anyway, I hope this clears up some questions about namespaces and autoloading. Let me know if I got something wrong, and feel free to add your own tips and tricks!
  3. 27 points
    This week I was back to focusing on the core and got quite a lot done. A lot of GitHub issue reports were resolved, plus several minor tweaks and additions were made in 3.0.156 as well. But the biggest update was the addition of the $pages->parents() API, which is something that I think you’ll likely have zero use for (and why I’m not putting it into a blog post) but something that the core itself will use quite a bit, and is a really nice improvement for the system and its scalability. So if you don’t mind some technical reading, read on. Whenever you call a $page->find() method ($page, not $pages) or use a “has_parent=“ in a selector, ProcessWire joins in a special table for the purpose called pages_parents. It uses this table to keep track of family relationships that aren’t otherwise apparent. For instance, let’s say we have page “g” that lives at path /a/b/c/d/e/f/g/. Page “g” only knows that it has page “f” as its parent. It doesn’t know that page “e” is its grandparent unless or until page “f” is loaded. Once “f” is loaded, then “f” can reveal its parent “e”. It works the same for every relationship down to the root parent “a”. So the pages are like a linked list or blockchain of sorts, where only 1 relationship forward or backward is known per page. The “pages_parents” table fills in this gap, enabling PW to quickly identify these relationships without having to load all the pages in the family. This is particularly useful in performing find() operations that you want to limit to a branch started by a particular parent. It’s the reason why we have both $pages->find() that searches the entire site, and $page->find() that limits the search within the branch started by $page. I haven’t paid much attention to the code behind this pages_parents table because it generally just worked, needing little attention. But I came across a couple of cases where the data in the table wasn’t fully accurate with the page tree, without a clear reason why. Then I became aware of one large scale case from a PW user where it was a huge bottleneck. It involved a large site (250k+ pages) and a recursive clone operation that appeared to involve hundreds of pages. But that operation was taking an unreasonable 10 minutes, so something wasn’t right. It came down to something going on with the pages_parents table. Once I dove into trying to figure out what was going on, I realized that if I was to have any chance of keeping track of it, we needed a dedicated API for managing these relationships and the table that keeps track of them. So that’s what got a lot of attention this week. While still testing here, it does appear initially that the 10 minute clone time has gone down to a few seconds, and everything about this relationship management is now rewritten, optimized and significantly improved. It was a lot of work, but absolutely worth it for PW. Rebuilding the entire table from scratch now takes between 2-3 seconds on a site with 250k pages and 150k relationships. The new API can be accessed from $pages->parents(). This API is really useful to the work that I do here (maintaining the core) but I’ll be honest, it’s probably not useful to most others, so I won’t go into the details here, other than to say I’m happy with it. But if you are interested, there are methods finding all the parents in a site, or a particular branch, and methods for rebuilding the pages_parents table, among others. Maybe more will be added to it later that actually would be useful in the public API, but for now I’ll likely leave it out of our public API docs. The $sanitizer->selectorValue() method also got a full rewrite this week (actually, one the last few weeks). It’s now quite a bit more comprehensive and configurable (see the new method options). The previous version was just fine, and actually still remains — you can use it by specifying [ ‘version’ => 1 ] in the $options argument to the selectorValue() method. But the new version is coded better and covers more edge cases, plus provides a lot more configurability for the times when you need it.
  4. 21 points
    There were lots of core updates this week, very much in the same theme as last week — major long term quality improvements for the core, but no shiny toys to play with… Just lots of good and solid foundational core improvements, and a few fixes too. It's mostly kind of technical stuff that's probably not that interesting to read about, so I'll keep this short, but for those interested here's the commit log. Thanks for reading and have a great weekend!
  5. 20 points
    I've only recently stumbled across Processwire. Thanks to ADHD and avoiding doing other work. I'm blown away at the power of Processwire. Ryan and all those who've built modules have done an incredible job of making a system that is fully customizable with incredible dev speed. Wordpress has so much bloat, it isn't very user friendly when all you want to do is quick a blog post or change. In fact unless you're a geek, It's actually quite daunting. Hell I'm still daunted by it all the time. I've never got my head around the interface layout and placement of menus and settings. On top of that, most plugins are trying to cater for anyone everyone and they have more junk to 'impress' than anything else. I love that I can just build my own functionality with PW easily or find a way to do it. What I'm particularly excited about is the end clients point of view. I could show a client in 5 minutes what they need to do to maintain their site. Changing look and theme as looks and trends move on would also be a cinch because no mark-up data is stored within the fields, unlike all the WP editor themes out there. Change templates, new site and look. All legacy data looks as good. So thank you to Ryan and the community for Processwire. I've only recently restarted developing after 15 years and frankly really didn't want to but PW has excited about web development again. I'm not one for blowing air up peoples arses but when something excites me, it excites me. 😁 Now I have to decide on whether I'm going to import my existing website's data or start an entirely new one 🙂 G
  6. 19 points
    Developing custom fields for ProcessWire is great! And it's easier than many might think once you get the basic concepts. But it's hard to learn those concepts by reading the code and doing some trial and error... That's why I think we need a good tutorial about that topic. It took me quite long, but now I feel knowledgeable enough to write such a tutorial. I also have the idea (or the need) for some new fields that might be helpful to the community. What I don't have is time 😄 So I thought to share the workload and share my knowledge while the development of the module (and testing, writing docs, etc) could be done by someone else under my supervision (hope that does not sound scary 😄 ). What do you think? I'm happy to hear your opinions - we are in the PUB 😉 Have a great week and happy coding!
  7. 18 points
    Last week I told you guys about how I was working on development of a client’s site and deep diving into the details. Well this week was the same, except perhaps more so. Yesterday we finally launched phase one. There’s still more to do, and still working out some small bugs and such. This is a site I’ve been developing since back in the early versions of ProcessWire 2.x, which I think was nearly a decade ago. In fact, this was one of the first sites running ProcessWire 2.x, if I recall correctly. We’ve been keeping it up-to-date over the years, but this is the first time we’ve done a full front-end redo of the site, essentially starting from scratch on that, and spending a few months on it. But the admin side (fields, templates and page structure) remains roughly the same from where it was 10 years ago, and that’s what we’re going to redo in phase 2 (this year). There’s a lot of fields in this site, so I’m looking forward to really simplifying it with ProFields, repeaters and more — none of these existed when the original site was built. One thing really great about this iteration of the site is that it’s a ProcessWire community collaboration. Pete (our awesome forum administrator) did the design, as well as most of the front-end markup/css for the site. Jan (our awesome system administrator) setup the servers that it runs on, with AWS load balancer setup and everything related to that (he’s also one of the owners of the site). I did the other development stuff (the ProcessWire API side of things), making all the content fill all the markup in the right places, structure and organization, markup regions, search engines, optimization, etc., basically the stuff needed to get it all working in PW. This is the first time that I’ve developed a site where we can run new and old site side-by-side off the same ProcessWire installation. During development, the client could click a checkbox in their user profile and then they’d be using the new site. Behind the scenes, it does this using a fairly new feature in ProcessWire which is: $config->setLocation(‘templates’, ‘site/tpl’); So we had site/tpl/ having the new site templates, while site/templates/ had the old version. So one limitation for this phase 1 is that the old and new sites had to deliver the same exact content, even if the resulting output was very different. This site also uses custom page classes (another new feature), Markup Regions, and ProcessWire’s Functions API, and the Uikit 3 front-end framework. Pete also wrote a nice custom PW module for this site to handle localized weather forecasts — I ran out of time to get it in place yesterday, but that should be there next week hopefully. Yesterday we launched the site and we were finally able to start running it through its paces with live traffic. I thought I was going to be working on the PW core today, but you know that as soon as you launch a new site you always find things that need adjustment and can’t wait, so that was all of today. There’s more optimization work to do, and then there’s phase 2, where we start using ProFields and Repeaters to better isolate a lot of data and be able to implement the rest of Pete’s design in the parts that we weren’t able to in phase 1. This is where things like the current pricing tables (one example of a weak point) start to look really sharp. But I’m also looking forward to taking a little breather and catch up on some serious PW core work, issue reports and module updates over the next few weeks before diving into phase 2 of this site. I didn’t want to share the site quite yet because there’s still some details to take care of and such. But here it is Friday and I’ve got no other ProcessWire news to report, so was feeling a little guilty that I didn’t get more ProcessWire core work done over the last week, due to being focused on developing this site. But this was one of the first sites running ProcessWire, so it's an important one to me. And here it is about 10 years later, still the same installation (templates, fields, page tree) but with a brand new design and running the latest PW, and lots more to come. Tripsite.com
  8. 16 points
    This module allows you to integrate hCaptcha bot / spam protection into ProcessWire forms. hCaptcha is a great alternative to Google ReCaptcha, especially if you are in the EU and need to comply with privacy regulations. The development of this module is sponsored by schwarzdesign. The module is built as an Inputfield, allowing you to integrate it into any ProcessWire form you want. It's primarily intended for frontend forms and can be added to Form Builder forms for automatic spam protection. There's a step-by-step guide for adding the hCaptcha widget to Form Builder forms in the README, as well as instructions for API usage. Features Inputfield that displays an hCaptcha widget in ProcessWire forms. The inputfield verifies the hCaptcha response upon submission, and adds a field error if it is invalid. All hCaptcha configuration options for the widget (theme, display size etc) can be changed through the inputfield configuration, as well as programmatically. hCaptcha script options can be changed through a hook. Error messages can be translated through ProcessWire's site translations. hCaptcha secret keys and site-keys can be set for each individual inputfield or globally in your config.php. Error codes and failures are logged to help you find configuration errors. Please check the README for setup instructions. Links Github Repository and documentation InputfieldHCaptcha in the module directory (pending approval) Screenshots (configuration) Screenshots (hCaptcha widget)
  9. 14 points
    We recently relaunched the website of IBIS Backwaren, a brand for international pastries and bakery products. Concept, design and implementation by schwarzdesign, built with ProcessWire. As always, we aimed to deliver a clean website design with a focus on content and fast loading times. Here's a list of modules used on this site, followed by a couple of interesting features included. The site is bilingual, though currently only the German version is available. The English version will be released at a later date. ProFields FormBuilder WireMailSMTP Cache Control MarkupSitemap Hanna Code Automatically link page titles Unique image variations Product database and product collections The website features a product database with all the products IBIS distributes. The product template is rather extensive, including fields for product categories and attributes, nutrition facts, certifications etc. We make heavy use of page reference fields, e.g. for product categories and attributes such as vegan or gluten-free products. Nutrition facts are stored in a Textareas field. There are multiple ways for visitors to explore products. One is a classic product finder with filters for product category and attributes. But there are also individual product collections (Produktwelten) which can be regularly updated by the client. Product collections include a list of related products. This allows the marketing department to quickly set up targeted landing pages. Recipes and custom forms Another approach to marketing and activating fans is the recipe section. Each recipe uses at least one IBIS product to give visitors some ideas. Of course, recipes and products are cross-linked to allow for exploration and discovery. There's a recipe submission form which allows you visitors to send in their own recipes, as well as a couple of other forms. All forms on the site are built with the Form Builder module. We made use of the form submissions to pages feature, which automatically creates a new (unpublished) page for new recipe submissions. Those can be reviewed by the staff and published directly. We also use a hook to automatically transform the plaintext fields for ingredients and instructions into HTML lists. Multi-brand IBIS uses different brands for different sets of products. Some landing pages as well as the gluten-free selection are branded differently. To accomodate this, we create an additional "brand" template with an override for the default logo. Each page has a brand selection field to allow switching the logo / branding for that page. Since brands are represented by normal pages, then client can create additional brands on their own.
  10. 13 points
    Change Default Language to be None-English | Walk Trough When you start a new (single) language site and the default language shouldn't be English, you can change it this way: Go to the modules core section: Select the Language ones by the filter function: We have four language related modules here, but for a single language site in none english, we only need the base module, named "Languages Support". So go on and install it. After that, you can leave it, ... ... and switch to the newly created Language section under SETUP: Select the default language Enter your new language name or its Shortcut and save the page. I will use DE for a single language site in german here as example: Now I go to the ProcessWire online modules directory, down to the subsection for language packs and select and download my desired (german) one: After downloading a lang pack as ZIP, I go back into my SETUP > LANGUAGES > default language page in admin, select the downloaded lang pack ZIP and install it: After the ZIP is uploaded, the files are extracted and installed, most of my screen is already in the new default language. To get all fully switched, we save and leave that page, ... ... and completely logout from the admin. Now, of course, we directly login back, ... ... and see, that now also the cached parts of the admin have switched to the new default language. 🙂 That was it for a single language site in none english. If you want to have a multi language site, just add more languages to the SETUP > LANGUAGES section. When using a multi language site, I think you also want to use multi language input fields, and maybe different page names for your language page pendents. If so, you need to go into MODULES > CORE > filter LANGUAGE and install what you need or want to use of it, (if not already done). Thanks for reading and happy coding, 🙂
  11. 13 points
    Combine the power of ProcessWire selectors and SQL Differences to previous RockFinder modules RockFinder3 comes with extensive docs 😉 RF3 supports chaining: $RockFinder3->find("template=foo")->addColumns(['foo']). RF3 fully supports multi-language. RF3 makes it super-easy to add custom columnTypes. RF3 makes it easier to use custom SQL statements. No bloat! The module does just do one thing: Finding data. This module is built to stay, so I'm quite sure there will be no need for RockFinder4. The concepts are proven and used in many of my modules. RockFinder3 has seen some major refactoring and reduces LOC compared to RF2 by more than 50% from 1220 to 560 😎 I love the new API and if there where needs for changes the API should stay untouched meaning no need for a breaking version update in the future. DOCS & DOWNLOAD: https://github.com/baumrock/rockfinder3 I'll do some more testing in the near future and add it to the modules directory once it proves as stable as I expect it to be. Thank you ...for using RockFinder3. If you find RockFinder3 helpful consider giving it a star on github or saying thank you. I'm also always happy to get feedback in the PW forum! Happy finding 🙂
  12. 13 points
    Hi huseyin, 1. Use the appropriate Sanitizer methods to test user input from post and get. Depending on the circumstance, you'll either want to validate (reject it completely if there's something wrong with it), or filter/sanitize it (accept it but strip out unwanted characters). If doing both filter and validate, do your validation AFTER your filtering. 2. Even more important than step #1 is to use escaping on your output. This means using htmlspecialchars() or htmlentities() or $sanitizer->entities() when you output any field from the database or user input to the page (if you have htmlentities setup on your field's output formatting, then you can skip this step for those fields). Even if you mess up on the filter/validation from #1, as long as you've escaped all of the html, you should be ok. 3. When using user input (get or post variables) inside ProcessWire selector strings, use the Sanitizer::selectorValue() method on the value first. Even better, just use Selector Arrays since selectorValue can sometimes strip out characters (quotes and commas) that you actually want to search for. 4. If you're using any SQL directly, you must use prepared statements to bind any user input, which automatically escapes the input for SQL. 5. For protection against Cross Site Request Forgery (CSRF), use ProcessWire's SessionCSRF class when building custom forms. See https://processwire.com/api/ref/session-c-s-r-f/ for details on how to use this. 6. Don't use GET for secret data (passwords, security codes, etc). That data can get picked up by browser extensions or appear in server logs that might get compromised. 7. Use SSL/https on your whole site.
  13. 13 points
    For a gallery of images you wouldn't want to insert the images separately into the CKEditor field. You'd want to loop over all the images in the field and output markup for each of them. A typical approach would be to show thumbnail images linked to larger images that display in a JS lightbox (I like to use Fresco), so your template code might look like this (assumes your images field is named "images"): <?php if($page->images->count): ?> <div class="gallery"> <?php foreach($page->images as $image): ?> <a href="<?= $image->maxSize(1000,1000)->url ?>" class="fresco" data-fresco-caption="<?= $image->description ?>"> <img src="<?= $image->size(300,300)->url ?>" alt="<?= $image->description ?>"> </a> <?php endforeach; ?> </div> <?php endif; ?> So the simplest scenario is if your blog text and gallery are separate from each other - e.g. the gallery appears before or after the text. This way you just output the gallery and text separately in your template file. Chances are that's what you want to do, but here are a couple of other scenarios and possible solutions... 1. You want to optionally insert one gallery somewhere within the blog text. In other words, you want some text, followed by the gallery, followed by some more text. One way to do this would be to have two CKEditor fields in your template, labelled "Before gallery" and "After gallery". You divide your text between these two fields. The two CKEditor fields and the images field are all output directly by code in your template file. Another way is to have a single CKEditor field use the Hanna Code module to insert the gallery somewhere in the text. You would create a PHP Hanna tag named "gallery" and use the gallery code shown above for the tag. Then you'd insert [[gallery]] in your CKEditor field wherever you want the gallery to appear. 2. You want to insert multiple galleries somewhere within the blog text. If you stump up some $$ to buy the excellent ProFields module you could use the included Repeater Matrix module to create separate matrix types for Text and Gallery. Then you just add alternating Text and Gallery items to the page as needed to build up the blog post content. Another approach for this that is a bit more advanced but that doesn't cost anything is to use a standard Repeater field for the galleries. You would add an Images field and the Title field to the Repeater field and create a Repeater item for each gallery on the page. Then you'd use Hanna Code to insert the galleries within a single CKEditor field. And to make it easier to select a gallery in the Hanna tag you can use the Hanna Code Dialog module. The "gallery" Hanna tag would have a "title" attribute and the code would look like this: <?php $repeater_item = $page->galleries->findOne("title=$title"); ?> <?php if($repeater_item->id): ?> <div class="gallery"> <?php foreach($repeater_item->images as $image): ?> <a href="<?= $image->maxSize(1000,1000)->url ?>" class="fresco" data-fresco-caption="<?= $image->description ?>"> <img src="<?= $image->size(300,300)->url ?>" alt="<?= $image->description ?>"> </a> <?php endforeach; ?> </div> <?php endif; ?> And the Hanna Code Dialog hook in /site/ready.php to build the dialog form would look like this: $wire->addHookAfter('HannaCodeDialog::buildForm', function(HookEvent $event) { // The Hanna tag that is being opened in the dialog $tag_name = $event->arguments(0); // The page open in Page Edit /* @var Page $edited_page */ $edited_page = $event->arguments(1); // The form rendered in the dialog /* @var InputfieldForm $form */ $form = $event->return; if($tag_name === 'gallery') { $modules = $event->wire('modules'); $gallery_titles = $edited_page->galleries->explode('title'); /* @var InputfieldSelect $f */ $f = $modules->InputfieldSelect; $f->name = 'title'; $f->id = 'title'; $f->label = 'Gallery title'; $f->addOptions($gallery_titles, false); $form->add($f); } }); And this would give you an interface in Page Edit that looks like this (when the Hanna Code dialog is open):
  14. 12 points
    I've been using Fathom Analytics for a while now and on a growing number of sites, so thought it was about time there was a PW module for it. WayFathomAnalytics WayFathomAnalytics is a group of modules which will allow you to view your Fathom Analytics dashboard in the PW admin panel and (optionally) automatically add and configure the tracking code on front-end pages. Links GitHub Readme & documentation Download Zip Modules directory Module settings screenshot What is Fathom Analytics? Fathom Analytics is a simple, privacy-focused website analytics tool for bloggers and businesses. Stop scrolling through pages of reports and collecting gobs of personal data about your visitors, both of which you probably don't need. Fathom is a simple and private website analytics platform that lets you focus on what's important: your business. Privacy focused Fast-loading dashboards, all data is on a single screen Easy to get what you need, no training required Unlimited email reports Private or public dashboard sharing Cookie notices not required (it doesn't use cookies or collect personal data) Displays: top content, top referrers, top goals and more
  15. 12 points
    We've just launched our new site, rebranded AND become a B Corp. Phew! (only took us 2 years to do all that...) https://humanafterall.studio/ We're a design agency in London working with tech and purpose organisations. We've used PW on many client projects and on all our own previous websites. For this one, we built with: Processwire 3.0.155 ProCache for speeding everything up Custom Inputfield Dependencies for keeping the back-end in check (thanks @Robin S) Markup regions for templates Bulma as a frontend framework PJAX for page loading Service Worker module for adding a service worker ImageOptim (online) for optimisation of all images (using this PW module) KeyCDN for serving static assets Lazysizes for lazyloading of all images/video Lots of what I worked on was optimising for speed — which can be a challenge for an agency that wants a lot of big imagery. (it's also a PWA so you can install it on your desktop in Chrome and add to homescreen on iOS for fullscreen standalone app view) Anyway it's a culmination of working and developing with PW for a long time, hope you enjoy!
  16. 12 points
    Hi everyone! This is Antti from Avoine. Our website business is growing and we are looking for some extra talent to help us. We are looking for web developer(s) with great visual skills who can design and develop websites that look great and work fast on desktop and mobile. We are looking for a long-term partnership, not a single project gig. Agencies and freelancers can both apply. All the projects will come to our hosting and maintenance, so we expect you to build websites in a same way that we build all our sites. We will of course teach and help to get in track with this. Our current process includes: source code hosted at github.com gulp based workflow composer based processwire installation default site profile that all our projects use tailwind css wireframe used for output strategy We hope that you are familiar with tools like git and composer. Not too much, so don't be afraid about these. Please contact me with email to antti.peisa@avoine.fi and please include: Links to at least 2 reference projects online (both design and front end development needs done by you or your company) Some kind of estimate about how many hours reference project development took Your hour price in euros or usd Looking forward for your application. This is great opportunity to work with legendary creatures like @teppo @Fokke and unfortunately me too.
  17. 11 points
    I have added a new field type to the FieldtypeColor package. It is still in beta, but is already working quite well. The module is an extension of the Core FieldtypeOptions module and offers colors as predefined selectable options via 4 different input field types (Select, SelectMultiple, Checkboxes and Radios). Please try it out and if you like it, recommend it in the modules directory 👍 2 Screenshots
  18. 10 points
    I received the link here in different newsletters last week. So it seems to be a good source if you consider that the newsletter curators are people like Chris Coiyer or Rachel Andrew: https://moderncss.dev/
  19. 10 points
    Most of us know and use site/config-dev.php file. If present, it is used instead of site/config.php, so it is easy to set database connection and debug mode for local development, not touching the production config. It is also very useful when working with git. You can simply ignore it in the .gitignore file, so local settings won’t end up in the repo. But sometimes you need to add code to site/ready.php or site/init.php just for the dev environment. For example, to add ryan’s super cool on demand images mirrorer. I can’t live without it when working with big sites, which have more assets then I want to download to my desktop. It would be great if there was something like site/ready-dev.php for this. Not out-of-the-box, but it’s pretty easy to achieve. Unlike site/config-dev.php, site/ready.php is not hardcoded. It’s name is set with a special config setting: // wire/config.php $config->statusFiles = array( 'boot' => '', 'initBefore' => '', 'init' => 'init.php', 'readyBefore' => '', 'ready' => 'ready.php', 'readySite' => '', 'readyAdmin' => '', 'render' => '', 'download' => '', 'finished' => 'finished.php', 'failed' => '', ); As you can see, we can not only define, which files are loaded on init, ready and finished runtime states, but probably even add more if we need to. So we override this setting in site/config-dev.php like this: // site/config-dev.php // Change ready.php to ready-dev.php $temp = $config->statusFiles; $temp['ready'] = 'ready-dev.php'; $config->statusFiles = $temp; For some reason we can’t just do $config->statusFiles['ready'] = 'ready-dev.php'; and have to override the whole array. Maybe you PHP gurus can explain this in the comments. Now we can create the site/ready-dev.php file and place all the dev-only code there. Important thing is to include the main site/ready.php. // site/ready-dev.php include 'ready.php'; // DEV HOOK TO MIRROR ASSETS ON DEMAND $wire->addHookAfter('Pagefile::url, Pagefile::filename', function($event) { $config = $event->wire('config'); $file = $event->return; if($event->method == 'url') { // convert url to disk path $file = $config->paths->root . substr($file, strlen($config->urls->root)); } if(!file_exists($file)) { // download file from source if it doesn't exist here $src = 'https://mysite.com/site/assets/files/'; $url = str_replace($config->paths->files, $src, $file); $http = new WireHttp(); try { $http->download($url, $file); } catch (\Exception $e) { bd($file, "Missing file"); } } }); Do not forget to replace "mysite.com" if you’re copypasting this)) Now, add the newly created file to the `.gitignore` and we’re done. # .gitignore # Ignore dev files site/config-dev.php site/ready-dev.php Thanks for reading!
  20. 10 points
    "Markup Regions pw vs data-pw different behavior" I have not yet experienced such a behavior. Maybe you are mixing up "Boolean action attributes (inner HTML)" with "Action attributes with value (outer HTML)"? Regarding the differences between these two, see my explanation below. +1 Here is my simplified docs for markup regions, we can also call it "cheat sheet". I wrote it some time ago: Defining markup regions Basic region Wrapping tags do appear in the final markup. Children tags are preserved if not explicitly replaced. <div id="hello"> OR <div data-pw-id="hello"> data-pw-... They are removed from the final output and thus not visible to front-end markup, while id="..." is not removed! Placeholder region Only the inner HTML will be used and the wrapping tags won't appear in the final markup. <pw-region id="hello">...</pw-region> Good for groupping tags in the <head> eg.: <pw-region id="site_scripts"> Optional region It will be automatically removed from the document markup if nothing populates it, so it is a region which should be empty by default. <div id='hello' data-pw-optional></div> Examples: an <ul>, which [according to HTML5 specs] is required to have one or more <li> elements within it, otherwise it's invalid HTML. a sidebar which is only needed on pages populated with widgets /* -------------------------------------------------------------------------------------------------- */ Populating markup regions Available action attributes: data-pw-replace: replaces a region’s markup data-pw-append: appends markup to a region data-pw-prepend: prepends markup to a region data-pw-before: inserts markup before a region data-pw-after: inserts markup after a region Boolean action attributes (inner HTML) Only applies the inner HTML to the region. TARGET CODE: <div id='hello'> <h2> Hello World </h2> </div> <p data-pw-id="hello" data-pw-append> This text will APPEND to div#hello </p> <p data-pw-id="hello" data-pw-prepend> This text will PREPEND to div#hello </p> <p data-pw-id="hello" data-pw-before> This will insert this text BEFORE div#hello </p> <p data-pw-id="hello" data-pw-after> This will insert this text AFTER div#hello. </p> RESULTS: This will insert this text BEFORE div#hello <div id='hello'> This text will PREPEND to div#hello <h2> Hello World </h2> This text will APPEND to div#hello </div> This will insert this text AFTER div#hello Action attributes with value (outer HTML) All of the markup that you specify (the outer HTML) becomes part of the final document markup (except for the pw-* attributes): TARGET CODE: <div id='hello'> <h2> Hello World </h2> </div> <p data-pw-append="hello"> This paragraph will APPEND to div#hello </p> <p data-pw-prepend="hello"> This paragraph will PREPEND to div#hello </p> <p data-pw-before="hello"> This will insert this paragraph BEFORE div#hello </p> <p data-pw-after="hello" class="world"> This will insert this paragraph with class "world" AFTER div#hello. </p> RESULTS: <p> This will insert this paragraph BEFORE div#hello </p> <div id='hello'> <p> This paragraph will PREPEND to div#hello </p> <h2> Hello World </h2> <p> This paragraph will APPEND to div#hello </p> </div> <p class="world"> This will insert this paragraph with class "world" AFTER div#hello. </p> /* ------------------------------------------------ ------------------------------------------------ */ Adding HTML attributes Any HTML attribute you add to the action tag that does not begin with pw- or data-pw- will be added to the originally defined region tag. TARGET CODE: <ul id="foo" class="bar"> <li> First item </li> </ul> ACTION CODE: <ul data-pw-append="foo" title="Hello"> <li> Second item </li> </ul> RESULTS: <ul id="foo" class="bar" title="Hello"> <li> First item </li> <li> Second item </li> </ul> Adding and removing classes Classes from the action tag and the region tag are merged by default. To remove a class by the region action: prepend a minus sign to the class to be removed, eg: class="-foo bar" will result in class="bar"
  21. 9 points
    Thanks Ryan - I can't claim all credit on the design by a long shot as there's still a tonne of bits you had to figure out on the complex results filtering, plus whilst we're working with older content in new templates I still want to tweak some of the templates around the site. There's some seriously impressive regexp going on behind the scenes by the way everyone - the old tour pages had maybe 3 or 4 tabs and the new ones have more but it's mostly all in one CKEditor field so Ryan had to do some magic pulling the right bits out and even displaying icons next to some parts of it. Will be much saner to manage all that after the fields get an overhaul in phase 2. Carousels - there's a bit of variation in how they were implemented. Some have dotnav (which isn't the clearest), we're probably going to implement arrows overlaid either side on some of them where it makes sense during phase 2 (customer request I've not got around to yet), but if you don't autoplay who's going to bother to look for the play button? 🙂 I don't think ANY of the information in the carousels is essential so in this case doesn't matter so much, but given the audience is looking for trips and will be drawn in by the nice pics carousels work well for this site. Plus it's somewhat consistent with the old site and also what the customer wants as has been said. I don't really want to be drawn into an argument about carousels (too late) but I think they're perfectly fine in some scenarios.
  22. 9 points
    Beluga, I'm not much into carousels either, but also wouldn't claim there's no place for them. There's 1 small carousel on this entire site, and there was also one on the previous iteration of the site—the client has always liked it, and the customers react well to it. I really like this client for a lot of reasons, but one is that they are much more involved than most, know their technology, their product and their audience better than anyone I've worked with. The carousel is not my idea, but I trust and am certain the client knows their customers better than any self proclaimed experts online. I got a kick out of that linked anti-carousel site because it's a bit of a self own by whoever made it—it uses a carousel to make points that we likely would not have bothered to read if they weren't in a carousel. 🙂
  23. 8 points
    I would probably prefer to put my users under the Members page directly. That way you would still end up with your suggested page tree, but those pages would actually be the users and there would be no need to maintain a relationship between users and their pages. Have a look at this blog post for a tutorial: https://processwire.com/blog/posts/processwire-core-updates-2.5.14/#multiple-templates-or-parents-for-users
  24. 8 points
    We recently launched the website “Resilienz-Kongress”, which is kind of an online summit / online conference about resilience (only in German language). The users can register as a participant and will receive daily emails during the conference timeframe with a conference room link for each day. Within this virtual conference room the participants can watch several interviews with experts and exchange ideas via comments. This concept is especially during the COVID-19 lockdown an interesting possibility for idea exchanges. Some technical details: • ProcessWire 3.0.155+ • ProCache • ProFields (RepeaterMatrix) • PrivacyWire • Seo Maestro • WireMail SendGrid • CodyFrame UI Framework • CopeCart (as the E-Commerce API) • Git, Node, Yarn, Gulp, PostCSS, Autoprefixer for a modern workflow combined with our individual local <-> stage <-> live environment and sync helper build with Python We heavily used the ability to specify custom Page classes (introduced in PW 3.0.152) and a lot of custom helper classes for cleaner code. Every frontend user registration with double opt-in is linked with the SendGrid API. The login process for frontend users is quite individual as we wanted to integrate a password-free login method similar to “Magic Links”. To achieve this we developed a “FrontendUserManagement” Class, which takes care of all the magic in the background. I’ll write another article about this topic as soon as I find the time, as I like the idea to implement this solution also as an open-source ProcessWire module. For the after-selling area we used CopeCart as our cart system with their IPN. With this combination we don’t have to handle the billing process at all. Also a clean cross-device affiliate tracking system between CopeCart, ProcessWire and SendGrid is integrated even though we’re currently not using the affiliate system at all. As frontend framework we used CodyFrame with an intense usage of CSS Custom Properties (for modern browsers with a classic fallback CSS for old browsers). https://www.resilienz-kongress.de/
  25. 8 points
    Cacheable Placeholders This module allows you to have pieces of dynamic content inside cached output. This aims to solve the common problem of having a mostly cacheable site, but with pieces of dynamic output here and there. Consider this simple example, where you want to output a custom greeting to the current user: <h1>Good morning, <?= ucfirst($user->name) ?></h1> This snippet means you can't use the template cache (at least for logged-in users), because each user has a different name. Even if 99% of your output is static, you can only cache the pieces that you know won't include this personal greeting. A more common example would be CSRF tokens for HTML forms - those need to be unique by definition, so you can't cache the form wholesale. This module solves this problem by introducing cacheable placeholders - small placeholder tokens that get replaced during every request. The replacement is done inside a Page::render hook so it runs during every request, even if the response is served from the template cache. So you can use something like this: <h1>Good morning, {{{greeting}}}</h1> Replacement tokens are defined with a callback function that produces the appropriate output and added to the module through a simple hook: // site/ready.php wire()->addHookAfter('CachePlaceholders::getTokens', function (HookEvent $e) { $tokens = $e->return; $tokens['greeting'] = [ 'callback' => function (array $tokenData) { return ucfirst(wire('user')->name); } ]; $e->return = $tokens; }); Tokens can also include parameters that are parsed and passed to the callback function. There are more fully annotated examples and step-by-step instructions in the README on Github! Features A simple and fast token parser that calls the appropriate callback and runs automatically. Tokens may include multiple named or positional parameters, as well as multi-value parameters. A manual mode that allows you to replace tokens in custom pieces of cached content (useful if you're using the $cache API). Some built-in tokens for common use-cases: CSRF-Tokens, replacing values from superglobals and producing random hexadecimal strings. The token format is completely customizable, all delimiters can be changed to avoid collisions with existing tag parsers or template languages. Links Github Repository & documentation Module directory (pending approval) If you are interested in learning more, the README is very extensive, with more usage examples, code samples and usage instructions!
  26. 8 points
    Something like this should get you going. This is stolen from a recent import I did which worked well. This assumes you have a field called "images" that you want the images uploaded to. I have also done more complex versions of this when the source HTML image tags have width and height tags - you can use those to resize the images using the PW API and embed that version back into the HTML. $dom = new \DOMDocument(); @$dom->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8')); foreach($dom->getElementsByTagName('img') as $img) { // grab image from the external URL and add to images field try { $np->images->add('http://olddomain.com/' . $img->getAttribute('src')); if($img->getAttribute('alt') != '') { $np->images->last()->description = $img->getAttribute('alt'); } $img->setAttribute('src', $np->images->last()->url()); } catch(\Exception $e) { // in case remote image can't be downloaded } } return preg_replace('/^<!DOCTYPE.+?>/', '', str_replace( array('<html>', '</html>', '<body>', '</body>', '<p>&amp;n<p>', '<p><p>', '</p></p>'), array('', '', '', '', '<p>', '<p>', '</p>'), $dom->saveHTML()));
  27. 7 points
    @bernhard https://all-inkl.com/ https://kasapi.kasserver.com/dokumentation/ I wrote a PHP class (SOAP API client wrapper) which is used by the ProcessWire module KasProcessEmail to connect with the API.
  28. 7 points
    This little module is for verifying a vat number (UID) through a SOAP webservice (http://ec.europa.eu/taxation_customs/vies/checkVatService.wsdl). After installing this module you get a additional option in details-tab to check the field, if it is a real UID. https://modules.processwire.com/modules/inputfield-uid/ https://github.com/hintraeger/PW-InputfieldUID
  29. 7 points
    You asked about this once before... 🙂 But "pages.id==1234" is a pretty naff syntax, so here's a hook that lets you match pages just by typing an ID into the admin search: $wire->addHookAfter('ProcessPageSearch::findReady', function(HookEvent $event) { $selector = $event->return; $q = $event->wire('input')->get('q'); // If the admin search query is a number if(is_numeric($q)) { // Get the individual pieces of the selector $selector_pieces = explode(', ', $selector); // Modify the first piece so that it includes an ID clause in an OR-group $selector_pieces[0] = "({$selector_pieces[0]}), (id=$q)"; // Replace the original selector $event->return = implode(', ', $selector_pieces); } });
  30. 6 points
    You can disable autocomplete on input elements with the autocomplete attribute. You can modify built-in forms through hooks by looking up which Process module is creating the form and then hooking after it's buildForm method. In this case, it's ProcessPageAdd. This hook sets autocomplete="off" on both the title and the name input fields: // site/init.php wire()->addHookAfter('ProcessPageAdd::buildForm', function (HookEvent $e) { $form = $e->return; // disable autocomplete for the title field $title = $form->getChildByName('title'); $title->attr('autocomplete', 'off'); // disable autocomplete for the name field $pwPageName = $form->getChildByName('_pw_page_name'); $pwPageName->attr('autocomplete', 'off'); });
  31. 6 points
    Thx for the interest everybody! I'm in contact with @elabx and @Sephiroth and we will try to build something useful and document the process so that everybody can learn from it. Similar to what ryan planned with the events fieldtype, but step by step, so it's easier to follow 🙂
  32. 6 points
    Hi Ivan, very nice tip. I haven't known about this. May come in handy. Thank you for sharing. 🙂 Regarding your question on overwriting single items of config arrays, you can do it this way: $config->statusFiles('ready', 'ready-dev.php'); Also, if I want to overwrite more then one item, I prefer this way: $config->statusFiles = array_merge($config->statusFiles, [ 'init' => 'init-dev.php', //'readyBefore' => '', 'ready' => 'ready-dev.php', ]);
  33. 6 points
    I think the carousel implementation is perfectly done. Well designed, and perfect auto scroll speed. As for the whole site, great job people. As for the link you provided @Beluga, I would respectfully disagree. Running 100s of sites, especially ecommerce, carousels contribute to a massive spike in product sales. Dont read what works for others, implement what works for you.
  34. 6 points
    Summary: When you start a new site and the default not should be english: 1) enable languages support 2) set Title / Label of the default language to your desired none english native language, (e.g. 'Deutsch' (German)) 3) drop in the none english language pack (for admin backend) into the default language, (e.g. german langpack) Now you are ready to start with a single language site of your choice. If you want to use a multi language site, you now can add as many additional languages you want. If one of them should be english, you add it, but do not need to apply a language pack. 4) add a new language to it and drop in a language pack for any none english language or simply don't drop in a language pack to get the english version (but not as the default one!) For single language sites you only need to enable languages support, NOT language inputfields!, NOT language pagenames!, only basic language support, - to be able to change the default languages name and its lang pack! Of course, if you plan a multilanguage site, you also will need language pagenames and language inputfields, but not if you only need a none english backend. 🙂 So, if you know at the beginning of a new site / project what has to be the default language, it is done with a snip. Only disadvantage is, if you need to switch the default language later on, when already content was filled into a site. These seems to be very rare cases in real life. And for that I think have read a complete step by step guide from @BitPoet within the last two or three month somewhere. (Is this right @BitPoet ? and sorry for ping you here) You also can read it here:
  35. 5 points
    It's not the same situation, but maybe interesting in this context. I work for a professional association with around 2000 members. Until recently the association had rented a root server to host web content, member management and email accounts for the members. Now we have switched to shared hosting including all the e-mail accounts. The members now can independently activate and deactivate their e-mail accounts via a ProcessWire interface (API to hoster) and further administrate via the webmailer provided by the hoster. The member management was transferred to an external cloud based service. Result: lower costs. less work (administration and support). Outsourcing responsibility for security and administration. The members are satisfied.
  36. 5 points
    I don't know why the limit is hardcoded, but I can offer a workaround for this. If you want to modify a core module without having to worry about updates, just copy the entire module folder / file from the core to your site's module directory (/site/modules/). Then execute Modules -> Refresh through the CMS. This will create a notice about the duplicate module asking you to select which module file you use. There you can select the copied module. Once whatever is not working is fixed in the core, just change that setting back and delete the duplicated module. You still have to check for changes inside the core module when you update the core, in case your older copied version is not compatible anymore. In this case, just copy the module again and reapply your changes.
  37. 5 points
    Welcome to the forum @Greg Lumley! Another great aspect of Processwire vs any other is this forum itself. You have access to many great developers that have been using PW for many years and their depth of knowledge will benefit you with whatever you want to create. So jump right in with any questions!
  38. 5 points
    have you tried to double click one trash icon? Should do the trick. (since PW version 2.2?)
  39. 4 points
    Hm, that's interesting - I didn't think about that 🙂 I think it's not too late for that change and will make using RockFinder3 easier, so I just committed that change, thx!
  40. 4 points
    v1.0.3 adds two new features: addPath() each() Callbacks RockFinder3 supports row callbacks that are executed on each row of the result. Usage is simple: each() $RockFinder3 ->find("template=cat") ->addColumns(['title', 'weight']) ->each(function($row) { $row->myTitle = "{$row->title} ({$row->weight} kg)"; }) ->dump(); These callbacks can be a great option, but keep in mind that they can also be very resource intensive! That applies even more when you request page objects from within your callback (meaning there will be no benefit at all in using RockFinder compared to a regular $pages->find() call). addPath() A special implementation of the each() method is the addPath() method that will add a path column to your result showing the path of every page. This will not load all pages into memory though, because it uses the $pages->getPath() method internally. $RockFinder3 ->find("template=cat") ->addColumns(['title', 'weight']) ->addPath("de") ->dump(); If you need the path for linking/redirecting from your data to the pages it might be better to build a custom redirect page that works with the page id, so you don't need the overhead of getting all page paths: <a href='/your/redirect/url/?id=123'>Open Page 123</a> If you really need to access page objects you can get them via the $finder parameter of the callback: $finder->each(function($row, $finder) { $row->foo = $finder->pages->get($row->id)->foo; }
  41. 4 points
    Thank you! 🙂 Yes, sure, I can give some examples. The workflow is getting refactored right now, so I can't show any specific details, but I'll describe the process. A quite simple example for a tiny helper class is this Headline Class for rendering headlines without risking to use more than one <h1> tag per page call: I like to use this on content heavy pages (e.g. within RepeaterMatrix Elements) where I want to be sure to not have more than one <h1>. By default only the first call of this helper method returns the <h1> tag, all the others an <h2> (unless otherwise specified with $sizeTag). To output a headline with this, simply use <?php echo Headline::render($page->headline); ?> I also have some helpers for rendering responsive (lazy loading) images or buttons and in this project most of the business logic of the "magic link" login is within one of these helper functions. Currently I require these classes manuelly within the ready.php include_once($_SERVER['DOCUMENT_ROOT'] . "/site/templates/helpers/Headline.php"); include_once($_SERVER['DOCUMENT_ROOT'] . "/site/templates/helpers/Image.php"); include_once($_SERVER['DOCUMENT_ROOT'] . "/site/templates/helpers/Link.php"); include_once($_SERVER['DOCUMENT_ROOT'] . "/site/templates/helpers/FrontendUserManagement.php"); include_once($_SERVER['DOCUMENT_ROOT'] . "/site/templates/helpers/CopeCartHandler.php"); But I definitely want to dig deeper into autoloading them. I want to refactor especially my Link and Image helper soon, perhaps I'll explain them afterwards further... As example for the custom Page Class here is an extract from my SpeakerPage: My Workflow Local <-> Stage <-> Live Our webserver uses Plesk Onyx as the hosting control panel. For each web project we create a separate "subscription" where the stage and live domain will live. For the dev / stage domain we use the plesk integrated git repo as dev branch. The live domain gets connected to the master branch of the same repo. As we exclude a lot of files in git (e.g. the whole /wire/ directory or the assets and module directories) we had to find a solution to copy them between stages. I'll come to that point later again. In my local environment I'm using MAMP Pro for the server environment. After pulling everything from the dev branch, we need also pull the database and exluded files / directories. To do so, the python script connects itself via SSH to the server and dumps the database, creates the same database locally and imports its contents. The files / directories will be pulled as zip files and extracted locally. Our first try here was to use rsync, but with asset-heavy pages this can take quite a while to copy... During development we usually do all the database-related stuff in the staging environment and just pull the database with our Python CLI script. When we need to pull also modules or the wire dir, our CLI script can handle that as well. Our content-editors mostly work in the staging environment ( on new web projects) or directly in the live environment (on existing projects). I'll definitely write an in-depth article about this, as soon as we have refactored it completely! Then I can also show some code snippets or even publish it, we'll see.
  42. 4 points
    Great job! With the time you start appreciating such things as robust and maintainable core as much if not more than some shiny new features) And doing it little by little is surely better than a death by rewrite) Way to go, @ryan! And I'm sure you'll fix a lot of bugs by the way in addition to make a few new ones)
  43. 4 points
    Basically just refactoring and getting rid of all the bloat that is in RockFinder2 that is not needed (process module for defining finders, the sandbox, etc). The API and usage will not change drastically though. I'm quite confident that RockFinder3 will be the final version that will be there to stay 🙂 And it will also come with proper docs 😅
  44. 4 points
    Here's an example of a basic module to store each download as a row on a separate table. I hope it can be useful somehow 🙂 And here's how to call it from your template, form hook etc.: $download_counter = wire('modules')->get("ExternalDownloadCounter"); $download_counter->countDownload($page_name, $resource_file, $selected_language);
  45. 4 points
    You need to enclose the api vars with curly brackets if there is more than one arrow in it. echo "<img src='$front_img_800->url'>"; // good echo "<h2>$item->front_productpage->title</h2>"; // bad echo "<h2>{$item->front_productpage->title}</h2>"; // good Read more: https://processwire.com/talk/topic/4439-when-do-we-need-curly-brackets/ https://www.php.net/manual/de/language.types.string.php#language.types.string.parsing.complex
  46. 4 points
    @bernhard I am up for it if you can share the knowledge, I can write an article around it as a tutorial since I tend to write tutorials. Ironically this was part of a series I am creating for Processwire, just video editing skills delaying but writing this as a tutorial shouldn't be a problem. Just let me know what you need me to do, I will create time for this. Take care
  47. 4 points
    With respect, it's replies like these when people post their sites in the showcase that somewhat annoy me and I think would either be better handled through a private message or just left alone. With a site as complex as the one Ryan posted, how could you possibly know what's best? Sites like these take thousands of hours and go through many iterations. There's a lot of group-think with any "best practice" but in my belief and experience, especially with ecommerce sites, visitors usually don't care. Here's 2 examples: A friend of mine started a Shopify website, which is probably the biggest Shopify website in the world (not an exaggeration). For the first 3 years, his website was nothing "special" and could probably be designed by anyone here in their sleep. A basic logo, simple lines, decent pictures of products. Knee-jerk snarky-developer reaction would be "omg, your website sucks lol bye". But, customers don't care. They want a good deal and he provided that and had great timing. Needless to say, he's probably a billionaire at this point. With one of my own ecommerce websites, I'm not using whatever the latest cutting edge web development practices are (React / all that JS stuff / AJAX everywhere / crazy system infrastructure / etc.). Customers don't care (customers in this case being Fortune 100 companies, even Fortune 1 companies... 😉 ). They are looking for something specific and will find what they need. Snarky developer reaction: lol bro you should be using technology 'x', a read about it in a blog this week. Do the end customers care? No, they are people looking to buy a product on our website, not judging the behind-the-scenes code. It's easy to fall into this trap of taking whatever latest article you read and thinking you must apply it or you are falling behind / going to lose customers / are a bad developer / whatever. I went through years of this until the above to examples opened my eyes a lot. I would recommend focusing on your customers and what's working for you... not what trends are.
  48. 4 points
    That, for example, may not work for people who don't like much reading. Can be that those people just don't see or get what you want them to, also if you explicitly have done it for that reason. I think it depends a lot, or only, on the use case, the target audience and its implementation, ( and sometimes maybe plus what your customer want to have ) 😉 Haha, yep. Now its one of my favorites, I never would have read all quotes when they where listed top down. But with the carousel, I was curious about every next quote. @ryan Very well done redesign. I know this site for some time now and have a feeling that your customers do know their target audience very well, and do and like to do their own jobs in more depth than many people may do. Seems to me that this is one of their essentiell points. - I am a bit envious of the way you work together and would like to work with (more) such clients too. But they really seem to be very rare. 😄 Ah, and I sometimes like carousels.
  49. 4 points
    Maybe @dotnetic can elaborate why this is a bad practice?
  50. 4 points
    I would say go with another solution for now. I respect Kongondo for not rushing this out to market, it makes me more more excited to see it. Software development should never have timescales unless you are dealing with a client, which I know you are, but lets not rush good work. I am guessing this shouldn't even really be called Padloper anymore, so I wouldn't expect a free of charge upgrade, and in fact I hope there is no license fee reduction just because you have the previous version. I would like to reward Kongondo for his work as I do Ryan by purchasing his Pro modules, even if I don't use them.
  • Create New...