This post covers a few of the bigger updates in ProcessWire 3.0.154 and 3.0.155 on the dev branch. This includes a new function for live replacement of text in core and modules, a new method for creating canonical URLs, and some major upgrades to our $input->urlSegment() method that I think you’ll like!
This continues from last week’s post in the ProcessWire support forum about 3.0.154 core updates and includes several new details.
New wireLangReplacements() function
A question that seems to come up just about every week is: “how to change the text for something in the core or a module?” The answer is often to use the built-in language translation functions, which can do this quite easily. But often times, someone is not using multi-language support, or they only want to change the text for a particular case (like an InputfieldPassword field, but just on the front-end), rather than all cases. So when faced with these, the answer often ends up being to hook something or another, and populate the modified text for the case where it is desired. Now, there's a much simpler solution that makes replacement of text in the core or any module much simpler: the wireLanguageReplacements() function.
This new function enables you to replace text sent to translation calls like __('text');
and $this->_('text');
with your own replacement text. And the beauty of it is that this function works whether ProcessWire multi-language support is installed or not, making it quite simple and useful for replacing text/phrases in the core or any modules.
This applies globally to all translations that match, regardless of language. As a result, you would typically surround this in an if()
statement to make sure you are in the desired state before you apply the replacements.
The function affects behavior of any future __('text')
and _x('text', 'context')
calls, as well as their object-oriented equivalents. It can be called from a /site/init.php file (before PW has booted) to ensure that your replacements will be available to any translation calls. However, it can be called from anywhere you’d like, so long as it is before the translation calls that you are looking to replace.
The following example replaces the labels of all the Tabs in the Page editor (and anywhere else the labels are used):
wireLangReplacements([
'Content' => 'Data',
'Children' => 'Family',
'Settings' => 'Details',
'Delete' => 'Trash',
'View' => 'See',
]);
If you wanted to be sure the above replacements applied only to the Page editor, then you would place it in /site/ready.php or /site/templates/admin.php and surround with an if()
statement:
if($page->process == 'ProcessPageEdit') {
wireLangReplacements([
'Content' => 'Data', // and so on
]);
}
To make the replacement apply only for a specific _x()
context, specify the translated value in an array with text first and context second, like the following example that replaces 'URL' with 'Path' when the context call specifed 'relative-url' as context, i.e. _x('URL', 'relative-url');
wireLangReplacements([
'URL' => [ 'Path', 'relative-url' ],
]);
This can be used to replace any translatable text anywhere in the ProcessWire core, modules or in 3rd party modules (whether multi-language is installed or not).
Upgrades to $input->urlSegment() method
In all ProcessWire versions this method accepts a 1-based index and returns the corresponding URL segment, where 1 is first URL segment, 2 is second, etc. But in 3.0.155 this method adds several useful new options:
If given a negative number, it will retrieve from the end of the URL segments. For example, if given -1 it will return the last URL segment, -2 will return second to last, and so on.
If given a full URL segment (i.e. “foo”) it will return the 1-based index at which that segment exists, or 0 if not present.
If given URL segment followed by equals sign, i.e. “foo=” it will return the next URL segment that comes after it. If equals sign comes before URL segment, i.e. “=bar”, it will return the URL segment that came before it. This lets you create “key=value” type relationships with URL segments. For example, an argument of “foo=” would return the segment “bar” when applied to URL /path/to/page/foo/bar/.
If given a wildcard string, it will return the first matching URL segment. For example, the wildcard string foo-*
would match the first URL segment to begin with “foo-”, so any of these segments would match & be returned: foo-bar
, foo-12345
, foo-baz123
. A wildcard string of *bar
would match anything ending with “bar”, i.e. it would match and return foo-bar
, foobar
, baz_123bar
, etc.
If given a wildcard string with parenthesis in it, then only the portion in parenthesis is returned for the first matching URL segment. For example, foo-(*)
would match the URL segment foo-baz123
and would return just the baz123
portion.
If given a regular expression (PCRE regex), the behavior is the same as with wildcards, except that your regex is used to perform the match. If there are capturing parenthesis in the regex then the first captured text is returned rather than the whole URL segment. To specify a regex, choose one of the following characters as your opening and closing delimiters: /
, !
, %
, #
, @
.
- If you want to focus any of the above options upon a URL segment at a specific index, then you can append the index number to the method name. For example, if you want it to just focus on URL segment #1, then call
$input->urlSegment1(…)
, or for URL segment #2 you would call $input->urlSegment2(…)
, and so on.
Not specific to 3.0.155, but please also note the following about using URL segments:
- URL segments must be enabled in the template settings (for template used by the page).
- When using index numbers, note that it is 1-based. There is no 0 index for URL segments.
- If no arguments are provided, it assumes you ar asking for the first (1) URL segment.
- The maximum segments allowed can be adjusted in your
$config->maxUrlSegments
setting. - URL segments are populated by ProcessWire automatically on each request.
- URL segments are already sanitized as page names.
- Strongly recommended: throw a 404 when encountering URL segments you do not recognize.
- URL segments can be preferable to query strings for many cases because the entire output can be cached by ProCache or template cache. Not to mention, they just look nicer.
$input->urlSegment() method examples
Now let’s take a look at some of these new features in action. The following examples assume the current URL is /path/to/page/foo/bar and that /foo/bar is the URL segments portion of the URL.
// Check if URL segment “foo” is present
if($input->urlSegment('foo')) {
// “foo” is present as a URL segment
}
// Get index of matching URL segment
if($input->urlSegment('foo') === 1) {
// “foo” is first URL segment
}
// Get last URL segment
if($input->urlSegment(-1) === 'bar') {
// “bar” is last URL segment
}
// Get next URL segment
$next = $input->urlSegment('foo='); // returns 'bar'
// Get previous URL segment
$prev = $input->urlSegment('=bar'); // returns 'foo'
Now let's take a look at some pattern matching examples, but getting into something more practical than foo/bar. The following examples assume current URL is /products/sort-date/
and /sort-date/ is the URL segment portion of the URL.
// Match URL segment using wildcard
$sort = $input->urlSegment('sort-*');
if($sort === 'sort-title') {
// sort by title
} else if($sort === 'sort-date') {
// sort by date
} else if(strlen($sort)) {
// unknown sort value, throw 404 or fallback to default
} else {
// no sort specified, use default
}
// Match using wildcard and parenthesis
$sort = $input->urlSegment('sort-(*)');
if($sort === 'title') {
// sort by title
} else if($sort === 'date') {
// sort by date
} else if(strlen($sort)) {
// unknown sort value, throw 404?
} else {
// no sort specified, use default
}
// Match using regular expression
$sort = $input->urlSegment('/^sort-(.+)$/');
if($sort === 'title') {
// same if statement as example 5...
}
Continuing this "sort" example, let's say that instead of "sort-[field]" in a single URL segment, we want to instead use 2 URL segments to represent it, like /products/sort/date/ or /products/sort/title/, etc. We can do that by using the equals "=" feature from earlier examples. Calling $input->urlSegment("sort=")
makes it return the URL segment that follows the "sort" segment, which should give us "date", "title", etc.
$sort = $input->urlSegment('sort=');
if($sort === 'title') {
// sort by title: /products/sort/title/
} else if($sort === 'date') {
// sort by date: /products/sort/date/
} else if($sort === '-date') {
// reverse sort by date: /products/sort/-date/
} else {
// no sort specified, use default
}
New $input->canonicalUrl() method
Earlier in this post we talked about URL segments, and when it comes to using them, depending on the case, you’ll likely want to be using a canonical link tag in your document <head>
or a canonical HTTP header. (Using a link tag is preferable to a header if you are using any kind of full-document caching). If you aren’t familiar with canonical link tags/headers, this article at moz.com has a pretty good overview of them, as does this one from Google.
If you read the earlier section, we were using URL segments to dictate different sorting for items on the same URL. This is a case where we’d likely want to make the various URL segment variations (as it relates to sorts) canonical to the default version without any sort segments. But that’s just one example of dozens that you may come across in development of any reasonably complex site. There may be other URL segments that represent unique URLs that we do not want canonical to the main page URL.
When it comes to what is the canonical URL, it’s not just URL segments that you might consider, but any variation of a given page (or other pages) that might be presenting the same content. URL segments, page numbers, languages and query strings (GET vars), scheme and HTTP host are all considerations, and supported by this method.
The new $input->canonicalUrl()
method has similarities to the existing $input->httpUrl()
methods, but is much more useful when it comes to rendering the URL that you would use in the href attribute of a link canonical tag (or HTTP header). It lets you dictate exactly which parts of the URL are the canonical parts and which are not.
<link rel="canonical" href="<?=$input->canonicalUrl($options)?>" />
For more details the $options
supported by this method, see this page in our API reference.
Updated CKEditor version to 4.14.0
This is a copy/paste from my 3.0.154 core updates post in the forum last week, but I wanted to repeat it here as well. The CKEditor version has been upgraded from 4.12.1 to 4.14.0. To see what’s changed, see the release notes for CKEditor 4.13.0, 4.13.1 and 4.14.0 here. There’s actually quite a lot here in terms of additions and fixes. Note especially the two security related fixes in 4.14.0 (though I think only the first may be applicable to us). While it looks like a rather unlikely combination of factors needs to come together, and with just the right person, it’s good to be aware of nevertheless. Of note is that these particular cases are entirely client side, before PW ever gets to see or filter the content, so this is something that only a CKEditor upgrade can fix.
Thanks for reading! This post covers just a few of the updates in 3.0.154 and 3.0.155, so for more details and all the latest ProcessWire news, be sure to read ProcessWire Weekly (editions #309 and #310 have several more details from 3.0.154).