01132: More flexible script & cookbook initialization

Summary: More flexible script & cookbook initialization
Created: 2009-08-04 15:40
Status: InProgress
Category: Feature
Priority: 554
Version: 2.2.x

Description: There's a better way of doing script and cookbook initialisations than the current dependency on including files in the correct order.

Proof of concept: setup.php

For example, I'm putting together a new PmWiki blog package, but I'm running into some rather thorny issues manipulating the $EditFunctions array in a sane manner.

More specifically, at the moment I'm looking to overload the EditDraft function, without having to duplicate the contents of scripts/draft.php and other script files. The simple thing to do would be something like this:

if (IsEnabled($EnableDrafts, 0)) {
 $EnableDrafts = 0;

But that won't play nice, since drafts should come after simuledit + prefs, and hence those too would have to be included before my_draft.php, and those in turn might be influenced by stuff in per-page customisations, etc. So the chance of screwing up and/or interfering with other recipes gets pretty high.

The reason it's so difficult is because much of the initialisation code for scripts as well as cookbook recipes happens right when they're included, which means that the order in which you put things in your config file(s) is important. I think this isn't the best way of doing things.

For a fix, I suggest a new table of initialisation functions that could get called from pmwiki.php at the very end of initialisation, just before HandleDispatch. The format for this table should be something similar to the MarkupTable, which handles the exact same problem, just for a different context (the order in which markup rules get implemented).

Regarding my original issue, this table would let me eg. add a function near its end that would safely replace the appropriate $EditFunctions entry with my own function.

A proof of concept implementation of this SetupTable is attached. To see it in action, include it in a config file and use ?action=setuprules on a wiki page.

I once talked with Pm about such ordering of functions and PageStore objects, he found it far too complex to do it again. Wouldn't it be simpler but also efficient to do something like:

  $EditFunctions = array(
    'EditTemplate'  => 100, 
    'RestorePage'   => 200,
    'ReplaceOnSave' => 300,
  # etc.

This would allow us to insert/disable/replace/reorder edit functions, and later PmWiki will asort() the aray and process it. This way will be implemented in FPLTemplate(), I'd like to do it for the upload functions as well, which would add more flexibility than now. --Petko August 06, 2009, at 04:03 AM

That would indeed help with $EditFunctions, but it's not the only thing I'm looking for with this fix. I would very much like a way to have much more fine-grained control of the order in which stuff happens (or doesn't!) during PmWiki execution. However, my suggested Markup-style table is perhaps overkill, so could we instead have something like this (roughly corresponding to lines 323-344 of pmwiki.php):

$Setup = array(
  'SetCurrentTime' => 100,
  'ResolvePageName' => 200,
  "$FarmD/scripts/stdconfig.php" => 300,
  'ReadInterMapFiles' => 400

Which would get parsed roughly as follows:

foreach($Setup as $k => $v) {
  if (!$k || !$v) continue;
  if (substr($k,-4) == '.php') include_once($k);
  else $k();

Also, regarding your proposed change to $EditFunctions, wouldn't it be better to use array( 100 => 'EditTemplate', ... ) as then it should be relatively well backwards-compatible with the current form, instead of breaking most recipes that do anything with $EditFunctions? —Eemeli Aro August 06, 2009, at 06:58 AM

I would love to see greater control over when config code gets executed. In addition to being able to specify an order for initialization with normal config.php I think there should be some capability for executing code after stdconfig, at the very end of producing a page, etc. --Peter Bowers August 06, 2009, at 03:15 PM

Here are a couple of actual use cases where I'd find it very useful to be able to get a function to run near the end of the setup phase. —Eemeli Aro August 17, 2009, at 05:06 AM

  • EditAttributes adds two entries to $EditFunctions, one of which is meant to run as early as possible as it'll modify the page text and attributes, which other $EditFunctions may depend on (such as the Bloge new post naming). At the moment, Bloge needs to specifically look for EditAttrBeforePost and add itself after it, if it exists.
This could be solved by having an index numbering for $EditFunctions or by being able to set a function to run using $Setup as above.
  • A second difficulty exists with EditAttributes, as it needs to set $EditFields before HandleEdit is run: the added text fields need to be set before the $EditFunctions are executed. This means that easy configuration of the recipe requires the $EditAttrFields to be defined before including editattr.php, which isn't possible is you want to have eg. per-group customizations to edit fields. To fix this, currently you need to execute the following after you've finally set $EditAttrFields:
    $EditFields = array_merge($EditFields, array_keys($EditAttrFields));
This requirement could be removed by having a $Setup array where a function could execute the above line of code.
  • draft.php renames a page when a draft version is published, by removing the -Draft suffix from the name. This is fine, provided that you're not creating a new page or that you know when starting the draft what the page's eventual name will be. This is not necessarily the case with Bloge when creating a new blog post, which has a name that includes the post's publication date. The difficulty here comes from the rather precise positioning of EditDraft in $EditFunctions in relation to at least MergeSimulEdits. Hence overloading EditDraft is very difficult at the moment.
To fix this, either $EditFunctions would need to be changed along with how functions are added to it (to something like SDV($EditFunctions[300],'EditDraft'); in draft.php) or a $Setup array could help replace EditDraft with its own function.

I like your ideas a lot. Does it look reasonable to have something like
$Setup['PageStore'] = array('wiki.d'=>100, 'wikilib.d' => 200, /*etc*/);
$Setup['EditFunctions'] = array(
  'EditTemplate' => 100, 
  'RestorePage'  => 200, /*etc*/);
$Setup['UploadFunctions'] = array( 'UploadVerify'=>100, /*etc*/);
$Setup['FmtTemplateFunctions'] = array( /*etc*/);
$Setup['init'] = array(
  'SetCurrentTime' => 100,
  'ResolvePageName' => 200,
  "$FarmD/scripts/stdconfig.php" => 300, /*etc*/);

The $EditFunctions will be merged with the ones in $Setup so this should be backwords compatible. The stuff in the $Setup['init'] array is launched after all group/page customization is read, so a page could more easily disable or reconfigure a recipe. The snippet should also somehow respect $EnableStdConfig and other similar variables. --Petko August 17, 2009, at 06:28 AM

I think you're talking about a far more in-depth re-working of PmWiki internals than what I was looking for. Not that I'm at all against it, I just think we're having two separate conversations here. So, to address both of them:

I don't think we should restructure $WikiLibDirs or $EditFunctions. Too many recipes and custom configurations use them and they fundamentally are not broken. Yes, I'll agree that using them might be easier if you could numerically determine the order of things, but I think the cost would be too great. Also I'm sceptical of backwards-compatibility between your $Setup['EditFunctions'] and the current $EditFunctions: how would you determine where to add entries from one to the other?

$UploadFunctions (from PITS.00785, yes?) and $FmtTemplateFunctions I'm not familiar enough with to really say anything. However, if/when they are implemented, I think I'd prefer having them as separate global variables like the rest of PmWiki config. If PmWiki is at some point moving to a single $Setup array type of configuration, I think as an intermediate step we'd need to have the members of that array be references to the pre-existing global variables.

But $Setup['init'] or something like it I do want. The way I see it, it could even initially be an empty array that's run through after stdconfig.php—in other words, it should not introduce any regressions with cookbook recipes or configurations. It should also be a powerful enough tool to help fix the shortcomings of eg. $EditFunctions. —Eemeli Aro August 17, 2009, at 07:26 PM

A simple $PostConfig array processed after stdconfig.php was added to subversion, for 2.2.17, unless we discover some problems (speak up). --Petko June 05, 2010, at 07:18 PM

Any possibility it could be moved down a few more lines? If it occurred immediately after this line:


or even just before this line:

if (IsEnabled($EnableActions, 1)) HandleDispatch($pagename, $action);

then functions can call MarkupToHTML() successfully... Also the later it is the less likely we will run into caching issues...

If there's an advantage to having it in its current location then perhaps there could be some mechanism where functions with sort values less than 100 go in the current location but functions with sort values greater than 100 go in the later location?

Or maybe ...

$PostConfig['A']['myfunc'] = 100;   // this will run right after stdconfig is loaded
$PostConfig['B']['mylatefunc'] = 200; // this will run after links are defined

with certain predefined values for the index. ( --Peter Bowers June 21, 2010, at 04:20 AM

I'm not sure that functions should call MarkupToHTML() directly from the config files: at the moment, I can't decide if we should try to support this and to keep fixing it every time someone's code breaks it. :-) If someone requires link variables to be defined, they could define them in the script, however direct calling of MarkupToHTML() might work but is still utterly unsupported. PITS:01214. --Petko September 04, 2010, at 11:50 AM

I don't know if I understood the problem completely, but about this:

if (IsEnabled($EnableDrafts, 0)) {

 $EnableDrafts = 0;


I believe this could be solved by setting a variable called $DraftsScript, that when changed will run the Draft Script you want instead of PmWiki's Draft Script.

Also, to change, remove or substitute an entry in EditFunctions more easily we could create a recipe that does that.

I did a really tiny recipe that does just that as it was a MarkupFrame manipulator, it just doesn't replace or remove, it just ads in front or after an already placed EditFunction and also at the beginig or at the end.

I will change the recipe to do replaces and removes as well.

CarlosAB March 17, 2018, at 01:34 AM