Textpattern CMS support forum

You are not logged in. Register | Login | Help

#1 2017-09-14 11:22:31

etc
Developer
Registered: 2010-11-11
Posts: 2,248
Website

Making your plugins 4.7-aware

We all remember the plugin breakage panic provoked by some changes (mainly registration and null date defaults) in txp 4.6. Fortunately, the most popular plugins are patched now, so it’s time to make them 4.7-ready.

The good news is that a vast majority of 4.6-compatible (public-side) plugins should work in 4.7 without any problem. But, to be sure, or if you want your favorite plugin to enjoy new possibilities, please read what follows.

An important new feature of the upcoming txp 4.7 are global attributes. Roughly speaking, any plugin can register one or more attributes that will be valid for any (core or plugin) tag and applied to its output.

Let us consider a basic example. Suppose that we need a plugin that replaces all the occurrences of from string to to string. In 4.6 we would do it this way:

// TXP 4.6 tag registration
if (class_exists('\Textpattern\Tag\Registry')) {
	Txp::get('\Textpattern\Tag\Registry')->register('abc_replace');
}

function abc_replace($atts, $thing = null)
{
	extract(lAtts(array(
		'from' => 'foo',
		'to' => 'bar'
	), $atts, false));

	return str_replace($from, $to, $thing);
}

Now, the new abc_replace tag can be used as container:

<txp:abc_replace from="wordpress" to="textpattern">
	<txp:body />
</txp:abc_replace>

In 4.7 we get new powers: implement our plugin as attribute:

// TXP 4.7 tag registration
if (class_exists('\Textpattern\Tag\Registry')) {
	Txp::get('\Textpattern\Tag\Registry')->register('abc_replace'); //container mode
	if(method_exists('\Textpattern\Tag\Registry', 'registerAttr'))    //attribute mode
		Txp::get('\Textpattern\Tag\Registry')->registerAttr('abc_replace', 'from')->registerAttr('abc_replace', 'to');
}

function abc_replace($atts, $thing = null) { ... }

And voilà, now every tag also accepts from and to attributes:

<txp:body from="wordpress" to="textpattern" />

Some of the commonly used attributes (wraptag, class, etc) have been registered by core, so plugins don’t have to implement them anymore. But if you wish, you can override the core behavior and treat them yourself. For this, it is important to call lAtts() function somewhere inside your plugin:

  • bad way: $wraptag = $atts['wraptag'];
  • good way: extract(lAtts(array('wraptag' => ''), $atts));

This will inform the core that the plugin has its own wraptag attribute, so the global one will not be applied. Fortunately, most plugins adhere to this rule, but if yours starts to wrap its output twice, you know what to do now.

To resume, a public-side plugin:

  • does not need to implement wraptag, class, html_id, atts, label, labeltag and escape attributes if it treats them in the usual way;
  • otherwise, ensure that they are passed through lAtts() filter;
  • if you register a global attribute, don’t give it a txp- prefixed name, they are reserved for core;
  • additionally, parse(EvalElse($thing, $condition)) is deprecated, replace it with parse($thing, $condition).

I leave the eventual admin-side plugins patching with Stef and Phil. Happy coding!


etc_[ query | search | pagination | date | tree | cache ]

Offline

#2 2017-09-14 11:39:33

Bloke
Developer
From: Leeds, UK
Registered: 2006-01-29
Posts: 7,682
Website

Re: Making your plugins 4.7-aware

Thanks for the detailed info on this important topic, Oleg.

For completeness, can you please explain what happens if two plugins try to register the same attribute name, or two plugins attempt to override a core global attribute. i.e. which one wins:

  1. When plugins are loaded from the database (earliest load order? last load order?)
  2. When plugins are loaded from the cache directory (earliest alphabetical plugin implementation wins?)

Will the second one issue a warning that the attribute is already defined by abc_some_plugin or silently overwrite its implementation? Or are they chained? Presumably, applying the developer prefix to attributes is recommended to avoid this in most cases.

Last edited by Bloke (2017-09-14 11:41:20)


The smd plugin menagerie — for when you need one more gribble of power from Textpattern. Bleeding-edge code available on GitHub.

Txp Builders – finely-crafted code, design and Txp

Offline

#3 2017-09-14 12:16:08

etc
Developer
Registered: 2010-11-11
Posts: 2,248
Website

Re: Making your plugins 4.7-aware

The last loaded plugin will win without any warning, exactly as with the tags. They can not be chained (think of wraptag), but we could add a warning. Actually, we already have this problem with tags: suppose that in some plugin, <txp:child /> depends on <txp:parent />. Now, if another plugin registers <txp:parent /> tag, <txp:child /> will be still alive, but orphan. So yes,

Bloke wrote #306992:

applying the developer prefix to attributes is recommended to avoid this in most cases.


etc_[ query | search | pagination | date | tree | cache ]

Offline

#4 2017-09-14 12:54:00

Bloke
Developer
From: Leeds, UK
Registered: 2006-01-29
Posts: 7,682
Website

Re: Making your plugins 4.7-aware

Thanks for the clarification. I’m not bothered about a warning, unless you think it’ll be handy for debugging. Otherwise, anyone overriding global atts like wraptag will get a warning, when it’s actually a feature.


The smd plugin menagerie — for when you need one more gribble of power from Textpattern. Bleeding-edge code available on GitHub.

Txp Builders – finely-crafted code, design and Txp

Offline

#5 2017-09-14 12:56:59

etc
Developer
Registered: 2010-11-11
Posts: 2,248
Website

Re: Making your plugins 4.7-aware

Actually, some auxiliary global attributes (like class) can be shared if they are mapped to an empty value:

Txp::get('\Textpattern\Tag\Registry')->registerAttr(false, 'class');

It just suppresses the “unknown attribute” warning which otherwise would be issued if a tag has no own class attribute.

Another subtlety: if in a tag the default value of some global attribute is null, the global will apply. This allows for overridable defaults, like in

function file_download_description($atts)
{
    global $thisfile;

    assert_file();

    extract(lAtts(array(
        'escape'  => null
    ), $atts));

    if ($thisfile['description']) {
        return ($escape === null)
            ? txpspecialchars($thisfile['description'])
            : $thisfile['description'];
    }
}

etc_[ query | search | pagination | date | tree | cache ]

Offline

#6 2017-09-14 17:45:11

ruud
Developer emeritus
From: a galaxy far far away
Registered: 2006-06-04
Posts: 5,054
Website

Re: Making your plugins 4.7-aware

etc wrote #306991:

if you register a global attribute, don’t give it a txp- prefixed name, they are reserved for core;

Should we, similar to plugin tag names, use our 3-char prefix for global attributes we introduce?
If so, you may want to update the examples to reflect this.

Some attributes don’t seem usable on their own, like from and to in the example. Is there a way to generate an error if a user forgets or misspells one of the required attributes in a set?

Offline

#7 2017-09-14 20:07:01

etc
Developer
Registered: 2010-11-11
Posts: 2,248
Website

Re: Making your plugins 4.7-aware

ruud wrote #307004:

Should we, similar to plugin tag names, use our 3-char prefix for global attributes we introduce?
If so, you may want to update the examples to reflect this.

Some attributes (e.g. wraptag) are unambiguous enough, so I wouldn’t impose a prefix. Actually, a plugin can already substitute itself to a (unprefixed) core tag, hence there is no reason to forbid it for attributes.

Some attributes don’t seem usable on their own, like from and to in the example. Is there a way to generate an error if a user forgets or misspells one of the required attributes in a set?

Dunno. The problem is the same for the tags: what

<txp:abc_replace to="textpattern">
	<txp:body />
</txp:abc_replace>

means? The plugin (be it tag or attribute) will just use the default values in this case or throw an error, but catching it on processing is too expensive in loops, and the syntax parser has no idea of mandatory attributes.

We could require that plugins register their attributes with dependencies, which would be great for parser and tagbuilder. But these dependencies can be very complex, so this seems unreal. We should rather allow the plugins to modify txp_parsed tree, switching their node off if something is wrong on the first pass. Why not, any side-effects?


etc_[ query | search | pagination | date | tree | cache ]

Offline

#8 Today 08:49:03

etc
Developer
Registered: 2010-11-11
Posts: 2,248
Website

Re: Making your plugins 4.7-aware

Another novelty are valueless attributes, internally converted to true. By checking $attr === true condition, you can give them some special (natural) meaning. For example,

<txp:category_list exclude parent="animal" />

will be interpreted (in this case) like “output the descendants of animal category excluding animal itself”, i.e. a shortcut for

<txp:category_list exclude="animal" parent="animal" />

etc_[ query | search | pagination | date | tree | cache ]

Offline

Board footer

Powered by FluxBB