Jump to content

Weekly update – 1 October 2021


ryan
 Share

Recommended Posts

All the updates I mentioned last week are committed on the dev branch today, and it's quite a lot of new code and revised code. There's enough here to justify a couple of version bumps, but I'm leaving it at 3.0.185 temporarily because the ProcessWireUpgrade module will see the version change and some people might upgrade automatically. Because there's so much that's been rewritten, refactored, added and changed here, I'd like it to marinate for a week on the dev branch before it triggers any upgrade notifications. Our dev branch tends to be very stable, and this is one week where there's enough new code that I don't want to call it as-stable-as-usual just yet (though it might very well be). In addition, it's also not totally finished as there's still some redundant code to be removed and stuff that needs to be moved around a bit, though everything should be fully functional as-is. I am running it here on the processwire.com site without any issues, so chances are that all is good, but I always like to be cautious.

Below is a summary of what's new and changed. It's a bit technical, and there's nothing here that's all that important to know about, but whether you read on or not, just know that the core is getting even better and faster with updates like these.

The core PagePaths module has been refactored and has multi-language support. Previously it only worked on single-language sites. Now multi-language sites can take advantage of potentially significant performance improvements offered by this module. This is one module that is not installed by default, but it's definitely worth installing: Modules > Core > PagePaths. 

The core LanguageSupportPageNames module has been refactored. Assuming I did it well, you shouldn't notice any difference other than that it's faster. For instance, one of the changes is that it drops its old indexes on the pages table and re-creates them in a different way. I discovered some situations where finding pages by multi-language path was resulting in full pages table scans. Using MySQL “explain” statements I found I could prevent that by reversing the order of the columns in the language-specific indexes, preventing the full table scan and making it many times faster. This could make a big difference on large multi-language sites, but is less likely to be noticed on smaller sites. 

The LanguageSupportPageNames module also gained a new configuration setting that lets you specify what should happen when a page is accessed at a language it's not available in: throw a 404 or redirect to the default language.

The core PagePathHistory module has been refactored. It has a powerful new getPathInfo() method, though it's primarily of interest for internal core use.

A new PagesRequest class has been added to the core and it can be accessed from $pages->request(). It primarily focuses on one hookable method: $pages->request()->getPage(). The method accepts no arguments but it analyzes the current request, identifies the page to render, and returns it. Previously this logic was in the ProcessPageView module. The benefit of this method is that you can now determine the page to render much earlier, like during the boot process. Previously the current page wasn't identified until after PW booted and the ProcessPageView module loaded. This will enable [for example] a module such as SessionAllow to decide whether or not to allow sessions based on what page was requested. In addition, this method is hookable, so you could have a module that adds its own logic to identify or change the page that should be rendered. 

A new PagesPathFinder class has been added to the core and can be accessed from $pages->pathFinder(). Like the PagesRequest class, this new class primarily focuses on one method: $pages->pathFinder()->get($path). The given $path can contain any page path, optionally containing language prefix, URL segments, pagination numbers, or even a previously named version of the path. It will find and validate that path and return an array of information about it. PagesPathFinder does its job very quickly. In fact, it's about 10 times faster (in testing/timing locally) than PW could previously identify this information using existing methods. PagesPathFinder will also take advantage of the PagePaths and/or PagePathHistory modules when appropriate, if they are installed. 

Below is an example of what the return value would be for path /es/hello/bar/baz/page3 where /es/ is the language prefix, /hello/ is the actual page, /bar/baz/ are the URL segments and /page3 is the pagination number segment. It analyzes everything out and returns a 200 "ok" response if the path is good, or in this case, it's returning a redirect because it detected a Spanish URL with English page name and pagination prefix, and it's suggesting a redirect:

$info = $pages->pathFinder()->get('/es/hello/bar/baz/page3'); 
[
  'request' => '/es/hello/bar/baz/page3', // path that was requested
  'response' => 301, // one of 200 (ok), 301 (permRedirect), 302 (tempRedirect), 404 (pageNotFound), 414 (pathTooLong)
  'type' => 'permRedirect', // ok, permRedirect, tempRedirect, pagePathError, pageNotFound or pathTooLong
  'errors' => [], // array of error messages indexed by error name
  'redirect' => '/es/hola/bar/baz/pagina3', // suggested path to redirect to, blank otherwise
  'page' => [ // info about page that was matched
    'id' => 1237, 
    'parent_id' => 1232,
    'templates_id' => 12,
    'status' => 1, 
    'name' => 'hello'
  ],
  'language' => [
    'name' => 'spanish', // name of language detected
    'segment' => 'es', // segment prefix in path (if any)
    'status' => 1 // language status where 1 is on, 0 is off
  ],
  'parts' => [ // all the parts of the path identified
    [
      'type' => 'language', 
      'value' => 'es',
      'language' => 'spanish' 
    ],
    [
      'type' => 'pageName',
      'value' => 'hello',
      'language' => 'default'
    ],
    [
      'type' => 'urlSegment',
      'value' => 'bar',
      'language' => ''
    ],
    [
      'type' => 'urlSegment',
      'value' => 'baz',
      'language' => ''
    ],
    [
      'type' => 'pageNum',
      'value' => 'page3',
      'language' => 'default'
    ]
  ],
  'urlSegments' => [ // URL segments identified in order
    'bar',
    'baz'
  ],
  'urlSegmentStr' => 'bar/baz', // URL segment string
  'pageNum' => 3, // requested pagination number
  'pageNumPrefix' => 'page', // prefix used in pagination number
  'scheme' => 'https', // blank if no scheme required, https or http if one of those is required
  'method' => 'pagesRow', // method(s) that were used to find the page
]

While you might never need to use this method yourself, ProcessWire now uses it on every request. And now that this logic is isolated from ProcessPageView and has a dedicated $pages API, it's much more open and we can, for example, more easily apply tests to this logic. I've covered a few updates here but there's more if you feel like digging in the commit log https://github.com/processwire/processwire/commits/dev. Thanks for reading and have a great weekend!  

  • Like 19
  • Thanks 7
Link to comment
Share on other sites

10 hours ago, ryan said:

A new PagesRequest class has been added to the core and it can be accessed from $pages->request(). It primarily focuses on one hookable method: $pages->request()->getPage(). The method accepts no arguments but it analyzes the current request, identifies the page to render, and returns it. Previously this logic was in the ProcessPageView module. The benefit of this method is that you can now determine the page to render much earlier, like during the boot process. Previously the current page wasn't identified until after PW booted and the ProcessPageView module loaded.

Hi @ryan, does this have an impact on init.php and ready.php? For instance, will init.php still not be aware of the current page? Can $pages->request()->getPage() inside a template file be a replacement for ready.php?

Thanks for the awesome updates!

  • Like 4
Link to comment
Share on other sites

Great job @ryan!

I am really interested in this:

18 hours ago, ryan said:

The LanguageSupportPageNames module also gained a new configuration setting that lets you specify what should happen when a page is accessed at a language it's not available in: throw a 404 or redirect to the default language.

While you're at it, could we make this behavior hookable, so it is possible to choose to redirect not to the default, but rather to the other language available?  Right now the languages getDefault() method is not hookable, so we would have to replace the whole ___pageNotAvailableInLanguage(). I guess making getDefault() hookable would solve this (and many similar demands in other places). What do you think?

P.S. Making publishing page in default language not mandatory would be even better, but this is a larger request)

  • Like 2
Link to comment
Share on other sites

@kongondo

Quote

Hi @ryan, does this have an impact on init.php and ready.php? For instance, will init.php still not be aware of the current page? Can $pages->request()->getPage() inside a template file be a replacement for ready.php?

It shouldn't have any impact on those files and the same rules apply for when the $page API variable is available. The difference now is that you could potentially call $pages->request()->getPage(); on your own to find out what page it's going to be before the rest of PW knows. I think this probably isn't a common need, but the best example I've been able to think of is that SessionAllow module, which would like to know what the current $page is going to be before it decides whether or not to start a user session.

That getPage() method is also hookable, so you could also dictate what the $page API var is going to be if you wanted to. Or you could call $pages->request->setPage($page); before PW calls getPage(). 

@Ivan Gretsky We couldn't make the getDefault() hookable because usually when code calls that, it's because it literally needs the default language and not one that's been substituted for it. So I think we'd have to focus further in on what you are wanting to do, or what front-end logic you are wanting to change, to see if that could be made hookable. That pageNotAvailableInLanguage() method was added to enable hooking into the logic of what the method describes, but perhaps other types of logic can be made hookable too. 

 

  • Like 3
Link to comment
Share on other sites

13 hours ago, ryan said:

We couldn't make the getDefault() hookable because usually when code calls that, it's because it literally needs the default language and not one that's been substituted for it. So I think we'd have to focus further in on what you are wanting to do, or what front-end logic you are wanting to change, to see if that could be made hookable. That pageNotAvailableInLanguage() method was added to enable hooking into the logic of what the method describes, but perhaps other types of logic can be made hookable too.

Understood. To make dealing with this particular case easier, do you think we add parameters to ___pageNotAvailableInLanguage() method which defines what page the redirect will lead to and what http code will it be. Or do you suggest to replace the whole method in this case?

  • Like 2
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...