Jump to content

Search the Community

Showing results for 'runtime'.

  • Search By Tags

    Type tags separated by commas.
  • Search By Author

Content Type


Forums

  • Welcome to ProcessWire
    • News & Announcements
    • Showcase
    • Wishlist & Roadmap
  • Community Support
    • Getting Started
    • Tutorials
    • FAQs
    • General Support
    • API & Templates
    • Modules/Plugins
    • Themes and Profiles
    • Multi-Language Support
    • Security
    • Jobs
  • Off Topic
    • Pub
    • Dev Talk

Product Groups

  • Form Builder
  • ProFields
  • ProCache
  • ProMailer
  • Login Register Pro
  • ProDrafts
  • ListerPro
  • ProDevTools
  • Likes
  • Custom Development

Find results in...

Find results that contain...


Date Created

  • Start

    End


Last Updated

  • Start

    End


Filter by number of...

Joined

  • Start

    End


Group


AIM


MSN


Website URL


ICQ


Yahoo


Jabber


Skype


Location


Interests

  1. er314, I think your intentions are in the right place and I appreciate your determination. You are still asking for an established method with a specific purpose to have a different purpose and an access controlled front-end context, when that is not the purpose or full context of the method. So I hope you can understand why it's not realistic to just change the definition and implementation of an established method. But I get where you are coming from and think you've identified a potential helpful addition in the API, so I go ahead and add a "one" method or change the "findOne" method (in PW3) to behave as a "find just one" method with regard to access control, hidden/unpublished visibility settings, etc. While I don't personally think I will ever use this method, I think that you and Teppo have identified a couple of situations where some might find it handy. Even if one doesn't need it, perhaps just the presence of it in the API will further clarify the intentions of the methods as a whole, for folks that may have thought that the existing find() and get() were the same. I still worry a little bit about people forming an impression that PW's API is doing access control for them, when our entire API is based around providing methods for the developer to control access the way they see fit. The viewable() method is the basis of that. But we've already dipped into that territory with the find() method, and by adding the proposed method, we're not removing any control, just adding more options. So I think it's an okay. What I was stating is that I'm not aware of an instance where anyone has introduced security problems into their site as a result of using the get() method. Though you stated above that you did just that, so now I'm aware of an instance. You are correct that if a developer uses a $pages->get() method to retrieve a page that came from user input, and neglects to validate that the page is one you allow, then that could be a problem. But the same problem could surface anytime a developer neglects to properly sanitize or validate something–anything–that originated from user input. And this is not something you can skip regardless of what method you are using to retrieve a page or group of pages. Simply validating that a page is viewable does not mean it's valid for whatever operation you may be performing upon it. You would need to validate that it uses the expected template, comes from the expected parent, etc. So while we are adding a PW3 method to support your request, be careful not to get the impression that you no longer need to validate a page that originated from user input. Also keep in mind that a $pages->find() or proposed $pages->findOne() method that filters results is based on database-filtering, not runtime filtering. Part of PW's access control model supports runtime hooks to Page::viewable. If your situation includes any runtime access control options and pages you are loading as a result of user input, you shouldn't skip a $page->viewable() call regardless of what method you used to retrieve the page.
  2. I don't understand what you're saying here or what screenshot you're referring to. But my general point is that fields like Page Reference or Repeater are ultimately about storing a relationship between pages (Repeater items are pages). Rather than use a field to store the relationship, you can use the parent-child relationship. In PW, until pagination support is eventually rolled out to Page-type fields only the parent-child relationship can scale to thousands or millions of pages. But for your case this should be no big problem because the parent-child relationship can achieve what you are doing with the Repeater field. You get a nice sortable paginated list of child pages on the Children tab, and you can customise the page labels in a similar way to the Repeater item labels (see "List of fields to display in the admin Page List" in the Advanced tab of the child template). I would just stick with the Children tab, but if you wanted try more things you could look at Batch Child Editor, or listing child pages using a runtime-only inputfield with modules like this or this, or making use of Lister / Lister Pro to view and filter the child pages.
  3. In MySQL `NULL` is always less then any non-null value. Therefore you either need to sort at runtime, somehow make the sql query issued sort by `ISNULL(col), col` with different directions or use my paginator module for paginating though multiple different selectors: https://github.com/LostKobrakai/Paginator
  4. I suspected the problem was something like that. Re: solution 1, the list I'm rendering consists of 700 pages, so load time is the primary driver of why I'm looking for a pagination solution. It looks like this option will have the same load time issues as the single page setup I've currently got going. Re: solution 2, if I understand you correctly a single sort_weight variable won't do the trick, because the sort does depend on dynamic input. Different catalog pages will have a different collection of child pages rendered, and their positions can change. I'll explain what the sorting is intended to do: The template running this code produces a list of books. Those books always have a title, but they may or may not have an author and/or subject. When sorting, we want books to be listed by subject, but if there is no subject, then primarily by author, but if there is neither subject nor author, then by title. When subject/author is identical between books we want items to then be properly sorted within that subset by the next highest priority field. So for example a set of books might be sorted as below: 1. [American Fiction] Frost, Robert. Poems. 2. [American Fiction] Twain, Mark. Huckleberry Fin. 3. Christie, Agatha. And then there were None. 4. Report on the Mississippi River Valley. If you are wondering why the data is as convoluted as it seems to be (why not give everything a subject?), the answer is the traditions of the field I'm in are centuries older than modern computing and have no concerns over the misery of the poor person trying to make sense out of it. Previously I had a sort that looked like this: foreach($items as $tempsort){ $tempsort->set('sortval', $tempsort->subject ?: $tempsort->cleanauthor ?: $tempsort->cleantitle); // Runtime field } $items->sort("sortval, date"); This is imperfect because after something is sorted by subject, the items that share a subject are not subsequently sorted by author since it's only sorting once, by a single assigned value. So I created a tier-based sort so that every book would be sorted by primary, secondary, and tertiary terms. What I'm thinking is the most effective way is to make permanent fields for srt1, srt2, and sr3 that are generated on saving the page. I've never used hooks before and am still a little apprehensive about it, but if pages did have those fields baked into the database then would the below work as expected? $items = $pages->find("template=book, catalog={$page->title}, sort=srt1, srt2, srt3, limit=50");
  5. teppo

    Wireframe

    Hey @bernhard! I believe this is the same concept that was discussed earlier, i.e. having direct access to public component class methods from the component view? If so, I'll be sure to dedicate this some thought soon, and if it does indeed seem like a good idea, I'll try to get it implemented for the next release (after 0.13.0, which went live a few minutes ago) ? I'm leaning towards adding this feature, but the added complexity still worries me. Just providing access to public methods of the class is one thing, but if I go on and add that, I pretty much have to add at least the (runtime) caching support currently provided by the controller to make sure that devs don't accidentally step into a really nasty performance trap. And if I add this, for the sake of consistency I have to consider also adding support for persistent caching, method aliases, disabled methods, and a few other things. Anyway, this is just me thinking out loud. I'll see what I can do about this in the near future ? I've added your idea about catching errors and logging them to my todo list. Not entirely sure about this one yet, but it's definitely worth thinking through.
  6. Static site generators will probably be 'the next big thing'. You get way better performance and security by default than you could have with any CMS by definition. Which doesn't mean that you can't have dynamic content like comments, e-commerce etc. But instead of bundling that functionality, you're using external services, APIs, serverless functions and build automation to persist data and trigger builds whenever something changes. I'd recommend reading the Jamstack book (created by the Netlify people), you can get the ebook for free and it explains all the concepts related to SSG. By the way, you don't need NodeJS / NPM to have a static site generator. Though many SSG are built with JavaScript, they use NodeJS only as a runtime for the build step, not as an actual server. But there are SSG written in all kinds of languages - check out this site if you want to find one you like. One could argue, by the way, that the ProCache module for ProcessWire is also kind of a SSG generator, even though it works a bit differently than most tools on that list. My current recommendation is eleventy, I'm currently rebuilding a couple of my own sites as well as a ProcessWire-related project that I might release soon with it ?
  7. Page Link Abstractor module for ProcessWire 2 Plugin module that lets you move pages in your site without worry of ever breaking static links on other pages. Download at: https://github.com/ryancramerdesign/PW2-PageLinkAbstractor What it does Converts links in textarea/rich-text fields to an abstract format for storage, and converts them back at runtime. This means that if you move a page and another page is linking to it, the link won't be broken. It also means you can move your site from subdirectory to root (or the opposite) and not break links you may have created in your textarea fields. This applies to any kind of links: pages, files, images, etc. This module will also notify you when you edit a page that has a link to a page that doesn't exist or is in the trash. This module has been tested with ProcessWire 2.1 but should also work with 2.0. Since this module has not yet had a lot of testing, it should be considered "beta" and use is at your own risk. Please let me know of any issues or bugs you run into. How to install 1. Download the PageLinkAbstractor.module file from https://github.com/ryancramerdesign/PW2-PageLinkAbstractor and place it in: /site/modules/ 2. In the admin control panel, go to Modules. At the bottom of the screen, click the "Check for New Modules" button. 3. Now scroll to the PageLinkAbstractor module and click "Install". 4. Edit any of your textarea fields in Setup > Fields. You'll see a new configuration option to enable this module for that field. How it works This is a technical explanation for how this module works for those that are interested. Reading this is not required in order to use this module, but it may help you to use it more effectively. When you save a page that has a textarea field with this module enabled, it will look for URLs in HTML attributes by looking for an equals sign followed by a URL. It replaces instances of your site's root URL with a special tag: {~root_url} Next it checks to see if any of the URLs it found can be loaded as pages in your site. If so, it replaces those URLs with this special tag: {~page_123_url} where "123" is the Page's ID. When the page is loaded, ProcessWire does the opposite and converts those special tags back to their URLs. Because the URLs were abstracted to tags that are generated at runtime, when a page (or a site) is moved, no links are broken. Note that this module only converts URLs to tags when you save a page, so it only affects pages saved after the module is installed. Where to use it This would be most useful on your main 'body' field that uses a rich text editor (like TinyMCE). Where not to use it There is some overhead in using this module that will be insignificant if you use it carefully. Here are a few instances to avoid using it: Avoid use on fields that have the 'autojoin' option on, unless your site doesn't load lots of pages in a given request. Don't use on textarea fields that can contain anonymous (guest) user input. Avoid use on fields that aren't likely to contain links to local site pages in HTML markup. No need to have this module parsing things unnecessarily. Avoid use on fields where you think you might disable it later. Once disabled, the abstract tags representing the URLs will still be in place. If the module is disabled, those tags will no longer be converted to URLs are runtime. You would have to correct them manually by editing the page. Side benefit The tags that this module abstracts to are intentionally fulltext indexable, so you can perform searches for these tags. This means that you can find all pages linking to another by searching for it (minus the brackets and "~"). For example: $links = $pages->find("body~=page_123_url"); That would return all [viewable and visible] pages linking to page ID 123. Please note In order to convert URLs for pages, this module needs to load those pages in order to obtain their URL. If you are linking to a hundred pages in your 'body' field, you should expect that it may slow down the page 'load' and page 'save' time for pages containing lots of links. This module doesn't yet abstract local URLs that have a schema/protocol and domain in it. It just works with path-type links like /path/to/page/ and not http://domain.com/path/to/page/. This module hasn't yet been tested with migrating a site from subdirectory to root, but I will be testing this soon.
  8. You could use "addHookProperty" to add runtime "variables" instead of a method. These can be used in runtime selectors and are more or less created the same way as normal hooks, but are called without the function parenthesis and therefore cannot have arguments. // runtime selector (no db involved) $pageArray->find("myRuntimeProperty=something"); // db selector (db involved; more features like or groups and so on) $pages->find($sel); // internally replaced by $pages->find("has_parent=$page, …"); $page->find($sel); If you'd also need the greater efficiency from calling less pages from the db you'd need to cache those values in real fields, so they are available to the database queries as well.
  9. I just made a textformatter module that allows you to insert dummy content (lorem ipsum style) in text fields via shortcodes. Usage is simple - just type for example [dc3] into a textarea with this textformatter applied (plain textarea or CKEditor) and it will be replaced at runtime by 3 paragraphs of dummy content. It can also be used to populate text fields (for headings etc) using e.g. [dc4w]. This will produce 4 words (rather than paragraphs) at runtime. The actual content comes from an included 'dummytext.txt' file containing 50 paragraphs of 'Lorem ipsum' from lipsum.com. The 50 paragraphs is arbitrary - it could be 10 or 100 or anything in between, and the contents of that file can be changed for your own filler text if you wish. The slightly clever bit is that for any given page, the same content will be returned for the same tag, but the more paragraphs available in 'dummytext.txt', the less likely it is that two pages will get the same content (very roughly speaking - it's actually based on the page ID) so content selection is determinate rather than random, even though only the tags are saved to the db. Update Tags now work like this - [dc3] - Show 3 paragraphs ([dc:3], [dc3p] & [dc:3p] all do the same). [dc3-6] - Show 3 to 6 paragraphs randomly per page load ([dc:3-6], [dc3-6p] & [dc:3-6p] all do the same). [dc3w] - Show 3 words ([dc:3w] does the same). [dc3-6w] - Show 3 to 6 words randomly per page load ([dc:3-6w] does the same). <End update on tags.> If you think it might be useful, you can download it from GitHub and give it a try.
  10. I draw the line at what is necessary for the module to begin functioning and what is produced during the normal functioning of the module. I put the former in the config while the latter is generated during runtime.
  11. ? Yet another runtime field. I can also provide one: slim and full PW-API provided. In addition, you can change everything in another field (Database) via hook into ProcessInput. https://github.com/kixe/FieldtypeMarkup
  12. Progress update: Have managed to throw in a few hours on v2. The frontend is being rebuilt in Svelte, because I simply don't use jQuery/Datatables anymore. By not using any of these, I'm free to code this thing the way I want, without having to use a bunch of dependencies that are simply frustrating to build things with. Datatables in particular is hell to work with. (As an aside, I primarily work with Vue, but Svelte is a little more suited here. No runtime, much smaller bundle.) Anyways, here's a preview of the redesigned jumplinks list. Newly built styles, consistent in both the default and UIKit themes (should be fine in custom themes too) and scoped to the Svelte container. Have carried over the behaviours, however instead of fetching all jumplinks, they're now fetched as a paginated collection. I'm sure there are a few sites out there with many jumplinks, and fetching them all is just too much. As mentioned in a previous preview post (such a long time ago!), you'll be able to turn off the colour helpers if you don't like/need them.
  13. Hi, the output of a CKEditor field is postprocessed by the MarkupHTMLPurifier module, which has no configuration settings in the backend. I want to tweak the html output, so I'm trying to change the MarkupHTMLPurifier settings. The easiest method would be, to change one line in MarkupHTMLPurifier.module, but I want to be save when updating ProcessWire. So I had the idea to change the module settings on runtime. To achieve that, I wrote following module: class configModifer extends WireData implements Module { public static function getModuleInfo() { return array( 'title' => 'Config Modifier Test', 'version' => 1, 'summary' => 'This module tries to change the settings of another module on runtime.', 'singular' => true, 'autoload' => true, ); } public function init() { $this->addHookBefore('Inputfield::render', $this, 'beforeInputFieldRender'); } public function beforeInputFieldRender (HookEvent $event) { $purifier = wire()->modules->get('MarkupHTMLPurifier'); $purifier->set('HTML.Doctype', 'HTML 4.01 Transitional'); } } Unfortunately this doesn't work. The Hook is called, but it doesn't change the settings of MarkupHTMLPurifier. How can I change the module settings of MarkupHTMLPurifier on runtime? Or is there another way to achieve that? Thanks in advance.
  14. To be fair ProCache isn't really what one commonly understands by "static page". You cannot easily take what ProCache generates and e.g. publish it to GitHub pages or netlify. The cache is also generated on actual requests to a certain page. There's no command to "generate all pages". So you'll likely still need to run the full CMS on a webhosting doing php and having a mysql db. You just won't hit the php runtime/db for requests, which hit the cache. Therefore you can take a bunch more traffic on the same machine. For actual static pages (html/js/css as output) I really like eleventy. It's really flexible and if you like processwire for the authoring experience you could e.g. render a json document out of processwire, which you feed into eleventy as data source.
  15. Yes. It is used extensively elsewhere in Availability.php and works fine. Config ($config->admin_site outputs ProcessWire 3.0.153 dev #1) No the host page is in site-web, which I think you are calling website_instance. No. It is in admin_instance, where the runtime markup field with Availability.php is.
  16. Sorry - I forgot to say that I had tried various other things, including your suggestion. I've now done those things again (with $mailOwner rather than $mail - I'd forgotten that was predefined) and recorded the error in each case. These are reproduced below as a pair of lines - code followed by error: $mailOwner = $config->admin_site->wire('mail'); Could not get the right WireMail-Module (WireMailSmtp); found: WireMailTools [My debugging line] $mailOwner = $config->admin_site->mail->new(); require_once(./WireMailSmtpAdaptor.php): failed to open stream: No such file or directory in M:\laragon\www\BawdHall\site\assets\cache\FileCompiler\site\modules\WireMailSmtp\WireMailSmtp.module on line 290 The second of these seems to be calling the right module (WireMailSmtp) but can't find the file because $config is always for instance_A. This seems similar to the issue we had with Runtime Markup.
  17. @MarkE, it is not necessary for me to know what and which forms you are using and how you have organized this. ? I only want to know where and how you call the WireMail object. Is it in the runtime markup field? Where and how do you call it?
  18. In your terminology, lets say instance_B is "site" and instance_A is "site-web". Both sites sit under the same root, sharing the same wire/ (i.e. a multi-site installation). in _init.php (part of the template rendering) in instance_A: $config->admin_site = new ProcessWire($config->paths->root . 'site/'); I use $config->admin_site so that it can be accessed anywhere (assuming the current context is instance_A). Ah, that's a bit (!) more complicated. Instance_A accesses a runtime markup field in instance_B thus: $adminPage = $config->admin_site->pages->get("template=Availability, name=bawd-hall-availability"); $availabilityTable = $adminPage->runtime_markup_availability; This field renders php in the file Availability.php, which lives in instance_B ("site") Availability.php renders a form thus: $out .= $config->admin_site->files->render($currentPath . 'booking_form.php'); where $currentPath is $files->currentPath(); - i.e. booking_form is a sibling of Availability. (BTW, Availability.php checks the host name in the headers to determine whether it is being called as a second instance or not). booking_form's action is $_SERVER['REQUEST_URI'] (i.e. the original page, thus running Availability.php again). Availability.php tests (isset($_GET["submit"]) ) and if true processes the POST variables from the form. After validation, the mail is constructed from these variables with $mail = wireMail(); etc. For more background (if you really want ? ) you can see here, where @kongondo was extremely helpful getting the RuntimeMarkup Fields module working in a multi-instance environment.
  19. That might be possible, if that were to be the only method. However, methods 1 and 2 require the data to be processed in the admin app as the front-end for those methods is not PW (and both these methods work well). In any case, I have been experimenting a bit with your suggestion, by putting my RM code on site 2 (since that is where the module is looking for it) and have been running into problems with selectors not working properly - will continue to investigate this. Excerpts of code are given below. The page is rendered using the markup regions strategy. _init.php is loaded first, the template is in Prices.php and the rendering is via _main.php. In _init.php $admin = new ProcessWire($config->paths->root . 'site/'); In _main.php <div id="wrapper"> <!-- content replaced by template --> </div> In Prices.php <div id="wrapper"> ....... <div id="availability"> <?php $adminPage = $admin->pages->get("template=Availability, name=bawd-hall-availability"); $availabilityTable = $adminPage->runtime_markup_availability; bd($adminPage, 'admin page'); bd($adminPage->availabilityColumns, 'admin page columns'); bd($availabilityTable, 'admin page runtime'); ?> <?= $adminPage->title ?> <?= $adminPage->runtime_markup_test ?> <?= $availabilityTable ?> </div> ---- </div> The bardumps illustrate that the other fields appear OK (as do $adminPage->title and $adminPage->runtime_markup_test {your test field - only works with the PHP placed in site-2/templates}). $availabilityTable (the original RM field) does not work even after placing a copy of the php in site-2/templates and modifying the $pages-> to invoke the site-1 instance, owing to the selector issue mentioned above, which I am investigating. EDIT: The selector issue is my code, I think. I got the field to render eventually in site 2 (minus a lot of css) with replicating all the PHP for the field in site-2/templates and modifying $pages etc. to pick up the right instances. Part of the issue seemed to be the need to explicitly set the $user in site 1, otherwise the field could not pick up the required data. I was assuming that since the API was all in PW that superuser in site-2 would allow API access in site-1; it doesn't! So, if we can get the RM module to pick up the code from the correct place, it may all work nicely.
  20. I've now implemented a multi-site and multi-instance setup and it mostly works. For some reason, the 2nd site cannot access select options fields on pages on the 1st site - I changed these to page reference fields and that works. The only major problem so far is with runtime markup fields, which are pretty crucial to my app (great module btw @kongondo ? ). These don't seem to be accessible by the 2nd site at all - either directly or via $page->render(). Curiously, I had no problem accessing them from a non-PW site (on the same server).
  21. 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!
  22. 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!
  23. Wanze

    SeoMaestro

    Hi @csaggo.com It seems like there is no url field for opengraph tags. At the moment, the og url is generated dynamically based on the page's httpUrl, so you cannot change it at runtime. If the module would use the canonical url at render time, it should work. Can you open an issue on GitHub? ? Cheers
  24. @psy, in theory you don't need any sort of special module to put runtime markup into a field. Instead just add a Markup field to the form and then hook before the form is rendered to put whatever markup you want into it. Having said that, there are some oddities with Markup fields in FormBuilder. I raised some questions that Ryan answered here: But I still had some other problems (I can't remember exactly what they were now) that I ended up resolving by making a clone of the core InputfieldMarkup module with some minor changes. I've attached the resulting InputfieldCustomMarkup module. If you install this module and enable it for use in FormBuilder then you can use it the same as a Markup field but without the snags. InputfieldCustomMarkup.zip
  25. This one is a little bit special... I want to set an Inputfield setting at runtime via code. This works great for changing Inputfield labels or description and having everything under GIT. I'm using ProcessPageEdit::buildForm to modify the Inputfield before it gets rendered. The problem is that this technique does not work in several situations when AJAX is involved. For example I can not set InputfieldFile settings like max filesize or another example is the noLang = 1 setting that hides the multilanguage field descriptions of the Inputfield and shows only one inputfield. When using the buildForm hook, both settings modifications have no effect on newly uploaded files. That means the max filesize will never get checked and the setting noLang = 1 does also only work for existing files. When I upload a file it shows both language inputfields for the description and shows one just after saving the page (because then the buildForm hook knows about the file where to set noLang = 1). Any ideas where I could hook into to modify the settings stored in the database as early as possible? Do you understand my problem / question? ?
×
×
  • Create New...