Jump to content


Popular Content

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

  1. 12 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. 5 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!
  3. 5 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.
  4. 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.
  5. 4 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/
  6. 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 😅
  7. 2 points
    It's not possible to set the following attributes via setOptionAttributes() function: value, checked, selected. https://github.com/processwire/processwire/blob/51629cdd5f381d3881133baf83e1bd2d9306f867/wire/modules/Inputfield/InputfieldSelect.module#L612 If you want to dynamically change a page field value: $page->selectfield = array(1030,1031);
  8. 2 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!
  9. 2 points
    If you don't make (enough) money by providing hosting I'd not take on that burden. Providing hosting can be business, but it depends on the bottom line as well as your enjoyment out of doing that job. If neither or both are missing it's most likely not worthwhile.
  10. 2 points
    A sanitizer is a good idea, but not $sanitizer->selectorValue() because as you say you are not supplying a selector to the method but are supplying a path (you can also supply an ID which is different again). So you'd want $sanitizer->pagePathName(). Yes, in fact you can see in the error message that PW builds a selector using "path=" when you supply a path to $pages->get(). See here: https://github.com/processwire/processwire/blob/88e04129c72c1702926543c8bef555a9f0d1043f/wire/core/PagesLoader.php#L131-L133 else if($selector[0] === '/') { // if selector begins with a slash, it is referring to a path $selector = "path=$selector";
  11. 2 points
    @bernhard Just a little rough experimentation - I've added a new echo() method to the TD class which outputs exactly what is sent to it. With this addition, I managed to do this: Obviously in this case I am not passing any data to the tabulator() method, but you can make this happen easily by modifying your new tabulator() function. In a process module I have a couple of things to make this all work: 1) Load the tabulator js and css files in the init() method: $this->wire('config')->scripts->add("https://unpkg.com/tabulator-tables@4.6.3/dist/js/tabulator.min.js"); $this->wire('config')->styles->add("https://unpkg.com/tabulator-tables@4.6.3/dist/css/tabulator.min.css"); 2) Define a new tabulator function - note that in this case this is a global function outside the process module class, but you could also make it a class function and call $rf->tabulator() function tabulator($str, $title = NULL) { \TD::echo('<div id="tabletest"></div> <script> var table = new Tabulator("#tabletest", { height:205, data:[ {id:1, name:"Oli Bob", age:"12", col:"red", dob:""}, {id:2, name:"Mary May", age:"1", col:"blue", dob:"14/05/1982"}, {id:3, name:"Christine Lobowski", age:"42", col:"green", dob:"22/05/1982"}, {id:4, name:"Brendon Philips", age:"125", col:"orange", dob:"01/08/1980"}, {id:5, name:"Margret Marmajuke", age:"16", col:"yellow", dob:"31/01/1999"}, ], layout:"fitColumns", //fit columns to width of table (optional) columns:[ //Define Table Columns {title:"Name", field:"name", width:150}, {title:"Age", field:"age", hozAlign:"left", formatter:"progress"}, {title:"Favourite Color", field:"col"}, {title:"Date Of Birth", field:"dob", sorter:"date", hozAlign:"center"}, ], rowClick:function(e, row){ //trigger an alert message when the row is clicked alert("Row " + row.getData().id + " Clicked!!!!"); }, }); </script>', $title); } You can try it out by replacing the attached TD.php for your Tracy install (in the includes subfolder). Note that you can supply two arguments to the \TD::echo method: $str and $title, just like with d() and bd() Does that work for you ok? TD.php
  12. 2 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
  13. 1 point
    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, 🙂
  14. 1 point
    Hi all, Work is ramping up for Nifty Solutions so I'm looking for a seasoned PW dev/team I can pass work to on an ad-hoc basis when I'm too busy to take it on myself - fingers crossed this could be as soon as next month with the level of interest our recent marketing efforts have generated. The three most likely scenarios at the moment for websites are: Complete design and build - I'm working on a base site profile that will be the standard way sites start and would really prefer Bootstrap 4 for the design framework as it's what I'm most used to Alteration of an existing theme and new installation from an existing site profile (we have one or two customers in a Group of companies, so re-using the same theme/structure and minor alterations to colours, templates etc on those for example) Ad-hoc template changes to existing websites Since I imagine it will vary quite a bit from job to job and workload each month could be anywhere from zero to lots, hourly rate is probably sensible for the more piecemeal work as well. For new, complete websites though we are usually working off a fixed price so maybe a rough cost/time estimate of how long it might take to design and build a complete website if I were to point you in the direction of an example? I'd love to set up something with someone who's strong on the design side of things ideally and happy with both design and dev, definitely happy using hooks and maybe occasionally writing small modules for backend interfaces. Would love to set something up where I can offload work to more than one developer if possible as I realise the ad-hoc nature may mean relying on one person doesn't line up with everyone's own workload. I've not had to outsource before for development so this is all quite new to me. Would prefer someone within a few hours +- GMT as well if possible but not necessarily essential - there's some merit to having work happen overnight and magically appear in my inbox the next morning as long as there's not lots of calls going on at really odd hours too often 🙂 All work goes through a project management system on our side so it's easy to track tasks, have discussions and log hours etc. PM me if interested, happy to answer questions via PM and I'll reply early next week.
  15. 1 point
    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
  16. 1 point
    It’s been a while since I developed a new project with PW, but I have an upcoming client/project that could certainly use SearchEngine. I have a question about search results: Is SearchEngine able to return results into groups? For instance, the client is a home builder/seller. If a user performs a search, it would be nice to return the results as sets. In their case, single-floor homes; two-story homes; etc. That’s just an example. The bottom line is that their homes are divided into categories, so it would be nice if users could see results grouped as such. Is this possible?
  17. 1 point
    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.
  18. 1 point
    The localStorage key for this is "tracyConsole"
  19. 1 point
    Look at the LocalStorage section in your browser dev console:
  20. 1 point
    Wow, this is brilliant 🙂 That worked perfectly, thanks! 🙂 Same here 😉 I think we'd need to collect some example use cases and then decide what could make sense. Pushed everything to RockFinder2 master branch 🙂 So I can get rid of lots of bloat in RockFinder3 😎
  21. 1 point
    Sounds interesting. Which hoster are you using? Links to the api please 🙂
  22. 1 point
    Hi, @mn-martin, @snobjorn, @zoeck we've build some kind of fork of this module: https://modules.processwire.com/modules/elasticsearch-feeder/
  23. 1 point
    I split projects in the use of xml based databases and mysql databases. With xml everything flies even on cheap crap shared hosting. I use Processwire for serious projects with decent hosting.
  24. 1 point
    Hi adrian, this is great, thank you!! 🙂 I got it working here 🥳 You can try it using RockFinder2 "tracy" branch: https://github.com/BernhardBaumrock/RockFinder2/commit/4b78566ecc87d2af0e5b33b721cbad27912ddb8f So you see that I've created a very simple process module that loads tabulator and does a css override to make the console fullscreen. Simple and perfect solution. Only thing missing is a feature to open the console panel automatically when the page loads. Somehow I didn't get that working using jQuery's load event and triggering a click on the panel item. How could that be done? PS: I wonder if that tabulator feature could be useful not only for RockFinder but in many other cases, like dumping the result of an SQL query for example. Using RockFinder this is already possible (because RockFinder can take an sql query as source), but I wonder if it could make sense to ship such a tabulator feature with tracy by default? Just not sure how to check if tabulator is already loaded (eg via my module)?
  25. 1 point
    I have a few clients that I work for as a freelancer besides my full time job. So I cannot compare to 500. But I have a preferred shared hosting provider that I use and recommend, based on a few arguments such as it having a great pricing model, being stable and my knowledge of it. Although I have clients with different shared hosting providers if they want that. I think cost matters for both my business and my clients. If I should run a server, learn enough about server security and stability beside web development, the cost for my clients would be more than they would be willing to pay today. If I had more clients, maybe it would be otherwise. I mostly handle support inquiries between my client and their shared hosting provider as well.
  26. 1 point
    We've been doing basically the same thing but like this: $selector .= ", has_started=(start_date=''), has_started=(start_date<=today)"; $selector .= ", not_ended=(end_date=''), not_ended=(end_date>today)"; It seems to work ok. I don’t know of an easier way.
  27. 1 point
    I actually found that quite buggy still and IIRC it doesn't handle RTE embedded images (ie it doesn't rewrite their paths to the new location) like Migrator does. Not saying Migrator is perfect by any means, but the core page export/import was pretty broken in my experience.
  28. 1 point
    Just had another idea that might be even more powerful and simpler to implement: What if we could define custom dumping functions? For example: $rf = new RockFinder2(); [...] tabulator($rf); // modify tabulator and dump another one tabulator($rf); instead of a regular dump() this would render the markup of a tabulator, eg: <div id="1905terf8dvuid"></div> <script> var table = new Tabulator("#1905terf8dvuid", { height:205, data:[ {id:1, name:"Oli Bob", age:"12", col:"red", dob:""}, {id:2, name:"Mary May", age:"1", col:"blue", dob:"14/05/1982"}, {id:3, name:"Christine Lobowski", age:"42", col:"green", dob:"22/05/1982"}, {id:4, name:"Brendon Philips", age:"125", col:"orange", dob:"01/08/1980"}, {id:5, name:"Margret Marmajuke", age:"16", col:"yellow", dob:"31/01/1999"}, ], layout:"fitColumns", //fit columns to width of table (optional) columns:[ //Define Table Columns {title:"Name", field:"name", width:150}, {title:"Age", field:"age", hozAlign:"left", formatter:"progress"}, {title:"Favourite Color", field:"col"}, {title:"Date Of Birth", field:"dob", sorter:"date", hozAlign:"center"}, ], rowClick:function(e, row){ //trigger an alert message when the row is clicked alert("Row " + row.getData().id + " Clicked!!!!"); }, }); </script> So basically we'd only need a way to define custom functions like "tabulator()" that define the returned markup and that's it 🙂
  29. 1 point
    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!
  30. 1 point
    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.
  31. 1 point
    "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"
  32. 1 point
    @ryan and @Pete Congratulations on the site upgrade! It is impressive to see all the elements of ProcessWire coming together in a unified solution. Best of all, the website seems to be blazingly fast. Thanks for sharing.
  33. 1 point
    Hi all, I've been practicing with Processwire. I was wondering at the moment what were the best modules to realize my project. I would like to ask your advice for the following problems: multi-language (for now i use: Languages Support, Languages Support - Fields, Languages Support - Page Names, Languages Support - Tabs) contact form (Form Builder?) form inside a product sheet (Form Builder?) seo tool for page (title, description, opengraph, json ld, ecc) meta data management for images (e.g.: image's tag "title" in the gallery developed with a Repeater) associations (intended as the best module to create associations for example between product and categories or tags) cache + css-js minify tool (ProCache ?) ps: I checked some posts in the forum but I found very old posts so I wonder if there are any new ones introduced over time. Thank you in advance, have a nice day!
  34. 1 point
    Exactly what @flydev 👊🏻 said, but for 5.) you alternatively may use core image fields with the lately added functionality of adding custom fields to them. So it depends on how you want to structure that part. And additionally, also if you haven't asked for it yet, but maybe the question arises later: 9) Search = https://modules.processwire.com/modules/search-engine/ 10) Privacy & Cookies = https://modules.processwire.com/modules/privacy-wire/ 🙂
  35. 1 point
    Interesting, on my side for every project, it's : TracyDebugger because coding without it it's like masochism AutocompleteModuleClassName super-man backend module BreadcrumbDropdowns super-man backend module VersionControl for versioning page's content Migrations to keep control between dev and prod sites Duplicator for backups and migrations @JeevanisM you should give a try to the last one 👆🏼🙊
  36. 1 point
    Hi @SeriousUser OK, the core ship what's needed FormBuilder again FormBuilder SEOMaestro MediaManager RepeaterMatrix ProCache Backup it with Duplicator 😅 Thanks, you too 🤙🏼
  37. 1 point
    Hi all, Apologies for the very loud silence! I hope to elaborate more on this a bit later. However, rather than keep people guessing, I'll write something short. I have been working my fingers to the bone to release a beta by spring 2020. I suppose it hasn't gone unnoticed that I rarely post in the forums at large these days. This is because I am dedicating nearly all my time to Padloper. The plan was to start early beta testing in mid-April 2020. This was largely on track. Like many of us, maybe most of us in the forums, we have all been affected in one way or another by the current situation in the world. This has thrown a monkey wrench in the works. I have had to readjust how I work, albeit my productivity taking a hit. I wish I could properly 'guesstimate' how much delay this is going to cause but it will just be futile. On the other hand, I appreciate that you have been waiting for a relatively long time for this release. I want to reassure you that I am not just kicking the can down the road. Maybe I should have been showing you more screenshots of progress but currently, that would just eat further into my limited time. Thanks for reading, and hopefully, your patience. Cheers.
  38. 1 point
    v0.2.0 released, which adds support for File fields so that file URLs can be added the same as with Image fields. Thanks to @gmclelland for the idea. I've also submitted the module to the directory now seeing as it seems to be working well without any significant issues reported.
  39. 1 point
    v0.1.2 released - adds compatibility with PW < 3.0.66
  40. 1 point
    That reminds me of when I first came to ProcessWire. My impression was that PW was really powerful so I expected it to be difficult to use and so I thought "I'll only use PW for really complex sites that need it". But as soon as I had built my first site I knew I was never going to use anything but PW from that point on, even for basic brochure sites. Once you have understood the basics then development with PW is super fast. For developing custom sites (i.e. excluding off-the-shelf themes tied to a specific CMS, and who wants to work on those anyway?) I think PW is the fastest, most intuitive and most elegant framework out there.
  • Create New...