I broke in again (sorry)

When I wrote up my (initial) tweaks to the Summary plugin, Rick Cockrum helpfully pointed out in his comment how it would be possible to avoid changing the core code just to change the presentation of the extra textarea. I fully agree that should be avoided whenever possible (and had hoped it would be possible), so I set out to make the proposed change and managed to make it work after a small modification. But now I've done it again, and this time I believe there really is no way around it.

Formatting

When you type in an entry, you don't need to add paragraph tags: these get added automagically when the post is displayed. This magic works quite well in practically all cases, so naturally I wanted that to happen for my (often multi-paragraph) summaries as well. What is more, I would like to implement MarkDown formatting for both the main entry and its summary as well — no doubt a similar process — so I'd better find out how this works. That turned out to be much harder than I expected, due to Habari's own flavor of magic. The first thing I noted was patterns.

Patterns

In the theme.php file of the theme I am currently using (for now), I noted some apparent formatting taking place, like this:

  1. // Apply Format::autop() to post content:
  2. Format::apply( 'autop', 'post_content_out' );
This was sort-of matched by how the post's content was inserted in a template:

  1. <?php echo $post->content_out; ?>
But "content_out" is not something that lives in the database, so it looks like it's dynamically created, maybe by "autop"? The Format class does indeed have a autop() function (as well as an apply() function) but reading those didn't get me closer to understanding the mechanism at work here. I knew that there must be a plugin at work ("autop"? "content_out"?) and that plugin hooks have matching names. So I tried searching for hypothetical hook names. Finding the matching plugin hook proved to be hard in this case, because, as it turned out its name is dynamically generated from string variables — as I saw when I'd finally located the __get() function in post.php:

  1. $out = Plugins::filter( "post_{$name}", $out, $this );
  2. if ( $filter ) {
  3. $out = Plugins::filter( "post_{$name}_{$filter}", $out, $this );
  4. }
So, I read through the __get() function as well, but didn't quite understand it at first. I tried to approach it by analogies.

Analogies

A post's content is represented by:

  1. $post->content
Its formatted output is represented in the templates by:

  1. $post->content_out
The formatting is applied to:

  1. post_content_out
This matches the (dynamic) plugin hook

  1. post_{$name}_{$filter}

Now the summary:

The summary's content is represented by (see the action_publish_post() function):

  1. $post->info->summary
Its formatted output — oops, we have an extra level here! — could then be

  1. $post->info->summary_out
or maybe

  1. $post->info_summary_out
or

  1. $post->info_out
The formatting would then be applied to

  1. post_info_summary_out
or

  1. post_info_out

I tried a lot of things but there were two problems: first of all that extra level so creating analogous patterns was not quite straightforward; and second I could not figure out how to create an extra function in Summary that would mimic what __get() was doing in post.php, and maybe somehow "bridge" that extra level as well. Whatever I tried, I either got an error message, or nothing at all.

During all this trial and error I was also re-reading the various functions involved, and gradually began to explain to myself how the mechanism works.

The mechanism

This is how I understand it now:

  1. When a construct like $post->content_out is encountered, this (somehow) triggers __get() in post.php to be called with content_out as parameter
  2. The function detects that this is not a standard property of $post and parses it into a $name part ('content') and a $filter part ('out'). These are then used to first retrieve the data indicated by the $name part, and then create a plugin hook called 'post_content_out'
  3. But, this is only a hook; nothing will happen at this point unless there is a function (or more than one) that is registered to use it. This is what happens with the Format::apply( 'autop', 'post_content_out' ); statement: the apply() function dynamically creates a function which applies autop() and registers it for the (also dynamically created) post_content_out hook which operates on the data retrieved by __get() just before. So this is where the real magic happens!

The problem

And herein lies the problem: __get() parses its parameter to see what data is requested, retrieves that data, and creates a hook for it; but it uses a switch statement to find which existing or custom object property is requested… while Summary adds its own custom property. And I can see no mechanism for Summary (or any other plugin, for that matter!) to add another property to the list of properties recognized by __get(), or to somehow supplement that with its own parsing-and-hook-creation mechanism.

Once I got to this point I gave up, and broke in: I just added another case to the switch statement in __get():

  1. case 'info_summary': // mk 20081203 added for Summary plugin
  2. $out = $this->get_info()->summary;
  3. break;
where info_summary will find its way into the dynamic hook called post_info_summary_out. Now, in my theme.php, I could add:

  1. // apply output filter for summary - works in combination with post.php extension
  2. Format::apply( 'autop', 'post_info_summary_out' );
to the action_init_theme() function, and in the templates use it as

  1. <?php echo $post->info_summary_out; ?>
And now it started to work, indicating that my explanation to myself at least can't be far from the truth.

Even better: Summary also adds the summary text to the Atom feed, and this can now be formatted as well with a slight modification to Summary's action_atom_add_post() which I now have as follows:

  1. /**
  2.   * Add summary element to Atom feed, if a summary is present.
  3.   *
  4.   * @param SimpleXMLElement $feed_entry
  5.   * @param Post $post The post corresponding to the feed entry
  6.   */
  7. public function action_atom_add_post($feed_entry, $post)
  8. {
  9. if ((boolean) $post->info->summary) {
  10. // apply output filter for summary - works in combination with post.php extension (as in theme) mk 20081203
  11. Format::apply( 'autop', 'post_info_summary_out' );
  12. // add formatted summary to feed
  13. $entry_summary = $feed_entry->addChild( 'summary', $post->info_summary_out );
  14. $entry_summary->addAttribute( 'type', 'html' );
  15. }
  16. }
  17.  
And that works as well. Summary (plus my little break-in) now does exactly what I want it to do.

Solution?

But is this a real solution? I think not. And why shouldn't a plugin that produces its own content be able to use existing formatting functions for that content?

Of course it's entirely possible I still have overlooked a possible mechanism for Summary to accomplish this without breaking into the __get() function in post.php and add a few lines there.

But it seems to me the real problem is the fixed list of property types that the __get() function uses. If this list could be stored as data instead of in code, with a mechanism for plugins to extend that list, there would be no need to break into the code.

Did I miss something? (I'm still very much a Habari noob, after all!) Is it a stupid idea to turn the list of recognized $post properties into (extensible) data, instead of code? Please let me know!


0 Responses to I broke in again (sorry)

  1. There are currently no comments.

Leave a Reply


Some HTML allowed (like a, em, strong, pre). If you want to embed a code fragment, its syntax will be highlighted if you surround it in pseudo-tags like this:
[geshi lang="php"]echo 'highlighted code!';[/geshi] (instead of using pre); specify language in the lang attribute. Do not enclose your code in tags like <?php … ?> as that will make it disappear.