Leaderboard
Popular Content
Showing content with the highest reputation on 05/17/2020 in all areas
-
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!4 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', ]);4 points
-
I am not a guru, but: When you access $object->property, a magic __get() method is called that returns a copy of the value of this property. If the value is an array and you try to add an item, only the copy is affected, not the original property value. For this reason, an error message is displayed that your attempt to change the value has no effect. The magic __set() method must be used to change the (whole) value of the property. Both ways (yours and @horst 's) are fine to set the value.3 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.2 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 concepts, 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!2 points
-
The first selectors would not return unpublished pages. You have to use $pages->find('your-selector, include=unpublished') or include=all instead. The $pages->get() method already includes hidden and unpublished results, as you are explictly requesting pages. Regarding your problem: You want to hook the ProcessPageView::pageNotFound method and modify the output. I made a little proof-of-work module for you. It is a combination of the hooks for pageNotFound and Page::viewable. Maybe there could be a better and easier solution. Install the attached module to test it out. previewOption.module.php2 points
-
I think the module I found below does the job. The main limitation is that it only allows you choose one Branch to restrict a user to. That works for me now, but ideally it should allow you to choose multiple branches to restrict a user to. http://modules.processwire.com/modules/admin-restrict-branch/1 point
-
I usually hide some pages because the user has no business snooping around them. Put in ready.php: if(!$this->user->hasRole('superuser')){ /* hide pages on their ID: 1026 FormBuilder 1000 Search */ $wire->addHookAfter('Page(id=1026|1000)::listable', function($event) { $event->return = false; }); } You have ro refine the condition fo the user though... In case you have more users or have to do this dynamically you are better off with permissions or a module to do this.1 point
-
Have a look at this: Or if you'd like to buy me coffee, have a look at this ?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"1 point
-
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-InputfieldUID1 point
-
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):1 point
-
This module is great! It would be nice if there was an option to stay on the edit view after saving a page. Currently the module redirects to the browse view. I had some clients complaining about the redirect as they have to edit long pages and save a lot and than have to constantly click edit again..1 point
-
Hi, Are we talking about ProcessWire 3.x? Then it might be the namespace issue. The easiest way to fix it is to start all your template files, _func.php, _init.php, etc... with <?php namespace ProcessWire; More on this: https://processwire.com/blog/posts/processwire-3.0.14-updates-file-compiler-fields-and-more/1 point