<?php if (!defined('PmWiki')) exit(); /************************************************************************/ /*! PkTemplate allows for quickly creating multiple pages from a * user-supplied template. * * Copyright Peter Kay 2017 * pkay42@gmail.com * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * USE: * Creating a template: * Create a page you'd like to use as a template. The page may * contain non-template information; the template part starts with * the markup: * (:PkTemplate:) * Anything after that is part of the template. * * Create fields that a user can populate with data using the * markup (:pktvar variablename A description of the variable:) * * pktvar = PK Template Variable * * don't use symbols in names * * limited markup for descriptions is ok, but nothing with :) * If you use the same variable more than once in the page, you may * omit the description: (:pktvar variablename:) (It will be ignored * if you do give additional descriptions.) * * Create blocks of text a user can populate with the Markup: * (:pkttext textblockname Description of what multi-line text goes here:) * * Save the page and then you will be presented with a form to create * new pages based on your template. * * Filling out the form: * New page name: New page names may contain spaces * //creates Main.NewPageNamesMayContainSpaces * May Use Group . Name For Convenience * //ignores Group field for convenience * //creates MayUseGroup.NameForConvenience * If "Add link to created page" is checked, a link to the newly created * page will be added to the top of the template page * This is useful to not lose pages. * (Admin may change whether this is checked by default by specifying * $PkTemplate['addLinkByDefault']=false; in config.php) * * Note: * There is no real difference between (:pktvar ...:) and (:pkttext ...:). * Both accept Markup and both may be multiline if a user is determined enough. * Don't (:include TemplatePages:) - the behavior is undefined. If you really * need this, let me know, I might be able to provide an update * * Installation: * As usual, * Add PkTemplate.php to your farm/page's cookbook/ directory * Add the following line to config.php: * include_once("$FarmD/cookbook/PkTemplate.php"); * ************************************************************************/ $RecipeInfo['PkTemplate']=array( 'Version'=>'20170418', 'Author'=>'Peter Kay', 'contact'=>'pkay42@gmail.com', ); if (!isset($PkTemplate)) $PkTemplate=array(); /* Admins may easily change: $PkTemplate['addLinkByDefault']=false; SDV($HandleAuth['pkdotemplate'], 'admin'); // change permissions to admin(read/etc) include_once("$FarmD/cookbook/PkTemplate.php"); */ SDVA($PkTemplate, [ // User editable: // Set form default to add a new link to the new page on template page: 'addLinkByDefault' => true, // Change these only if you must: 'templateMarkup' => '(:PkTemplate:)', # case negotiable 'templateExpression' => '/\(:PkTemplate:\)(.*)/is', 'templateExpression2'=> '/(.*?)\(:PkTemplate:\)(.*)/is', #variable Expression: v vs t varName description 'variableExpression' => '/\(:pkt(var|text) (\w*) ?(.*?):\)/i' ]); // Need markup to run before any Includes Markup("PkTemplate", '<_begin', $PkTemplate['templateExpression'], 'PkTemplateMakeForm'); SDV($HandleActions['pkdotemplate'], 'PkImplementTemplate'); SDV($HandleAuth['pkdotemplate'], 'edit'); // creating new page is an edit /* Replace the rest of the page ($m[1]) with form for creating new items from template: * Because we require variable names to be alphanumeric, they should be valid "name" * attributes for a form. So we use the var names to create a form, ask up description * and return the whole thing to the default script page */ function PkTemplateMakeForm($m) { global $PkTemplate, $pagename; $pagename=ResolvePageName($pagename); $pageText=$m[1]; if ($PkTemplate['alreadyRun']) { return "\n----\n%red%$[Error: two templates in the page]%%\n----\n" .$pageText; } $PkTemplate['alreadyRun']=true; // Pull variable requests from page, then put them into a hash (allow multiple instances of each // var in the page; only use description of 1st entry) $tmpVarArray=array(); #'variableExpression' => '/\(:pkt(var|text) (\w*) ?(.*?):\)/' $numVars=preg_match_all($PkTemplate['variableExpression'], $pageText, $tmpVarArray); $templateVars=array(); // Fill hash with {varName} => {variable description} or // {textName} => {text area description} for ($i=0; $i<$numVars; $i++) { $key=$tmpVarArray[1][$i].$tmpVarArray[2][$i]; if (!isset($templateVars[$key])) if ($tmpVarArray[3][$i]) $templateVars[$key]=$tmpVarArray[3][$i]; else $templateVars[$key]=$tmpVarArray[2][$i]; // use variable name if no description } // Messages and instructions $out="\n<:vspace>\n(:messages:)\n" . "'''\$[Create a new page based on the template here {*\$FullName}]:'''\n\\\\\n". '[-$[If you want to edit the template, use the "edit" link above.]-]' . "\n<:vspace>\n"; // Form start $out .= '(:input form "{$PageUrl}":)(:input hidden action pkdotemplate:)'. "(:input hidden PkTemplate $pagename:)(:input hidden n $pagename:)\n"; // Group, new Pagename, and Author // (only table directives allow valign=top as of Jan 10 2017; otherwise I'd use || || || notation) $out .= "(:table:)\n(:cellnr:)\$[Group]:\n(:cell:)(:input text pkgroupname {\$Group} size=80:)\n"; $out .= "(:cellnr:)\$[New page name]:\n(:cell:)(:input text pknewpagename \"\" size=80 focus:)\n(:cellnr:)\$[Author]:\n(:cell:)(:input text author {\$Author} size=60:)\n(:cellnr colspan=2:) \n"; // Input line for each var // order should be as in the page? foreach ($templateVars as $varName => $varDescription) { if ($varName[0] == "t") // textName $out .= "(:cellnr:)$varDescription\n(:cell:)(:input textarea pkt$varName \"\" cols=80 rows=4:)\n"; else // varName $out .= "(:cellnr:)\n$varDescription\n(:cell:)(:input text pkt$varName \"\" size=80:)\n"; } $addLinkByDefault=""; if ($PkTemplate['addLinkByDefault']) $addLinkByDefault="checked=\"checked\""; // Submit and end form $out .= "(:cellnr:)(:input submit value=\"$[Create New Page]\":)\n(:cell:)(:input checkbox pkaddlink pkaddlink $addLinkByDefault \"$[Add link to created page?]\" :)\n(:tableend:)\n(:input end:)\n<:vspace>\n"; // Show the template: // First run page text through ReplaceOnEdit so the template looks like what a user editing the page would see $pageText=PPRA((array)$GLOBALS['ROEPatterns'], $pageText); $out .= "$[Template text:]\n----\n" . Keep("<pre>$pageText</pre>"); return $out; } function PkImplementTemplate($pagename, $auth='edit') { global $PkTemplate, $MessagesFmt, $Now; $pagename=ResolvePageName($pagename); // Get template name: $templatePageName=MakePageName($pagename, $_POST['PkTemplate']); if (!$templatePageName) Abort("$[Error: got lost trying to follow template path]: ".htmlspecialchars($_POST['PkTemplate'])); // Get new page name: $newPageName; if (preg_match('/[.\\/]/', $_POST['pknewpagename'])) { // if name of form GroupX.NewPage $newPageName=MakePageName($pagename, $_POST['pknewpagename']); } else { $newPageName=MakePageName($pagename, "{$_POST['pkgroupname']}.{$_POST['pknewpagename']}"); } if (!$newPageName) { Abort("$[Error: bad page name supplied.] " . htmlspecialchars($_POST['pkgroupname'])." / ".htmlspecialchars($_POST['pknewpagename'])); } // Are we going to add a link to the template page? $addLink=$_POST['pkaddlink']; Lock(2); // only one file writing at a time // Would be nice TODO: have javascript check if the pagename exists before posting if (PageExists($newPageName)) Abort("$[Error: that page already exists. No changes have been made.] $newPageName"); $emptyPage=RetrieveAuthPage($newPageName, $auth, true); if (!$emptyPage) Abort("$[Error: cannot open page for writing]"); $new=$emptyPage; $new['csum'] = $new["csum:$Now"] = "Created from template $templatePageName"; // Prepare template: $templatePage=RetrieveAuthPage($templatePageName, 'read', true, READPAGE_CURRENT); if (!$templatePage['text']) Abort('$[Error: could not load template]'); // Get everything after the (:PkTemplate:) call $new['text']=preg_replace($PkTemplate['templateExpression2'], '$2', $templatePage['text']); // Now swap out all variables! $new['text']=preg_replace_callback($PkTemplate['variableExpression'], # '/\(:pkt(var|text) (\w*) ?(.*?):\)/' function ($matches) { // Looking for pktvarname1, pkttextname1, pktvarname2, etc return $_POST["pkt{$matches[1]}{$matches[2]}"]; }, $new['text']); // Save page if (!UpdatePage($newPageName, $emptyPage, $new)) { Abort("\$[Error: write from template failed]"); } if ($addLink) { $templatePage=RetrieveAuthPage($templatePageName, $auth, false, 0); if ($templatePage) { $newTP=$templatePage; // Just slip the link in right before (:PkTemplate:): * [[Main.MyPage | My Page]] $newTP['text']=preg_replace($PkTemplate['templateExpression2'], "\$1* [[$newPageName | {$_POST['pknewpagename']}]]\n{$PkTemplate['templateMarkup']}\$2", $templatePage['text']); $newTP['csum']=$newTP["csum:$Now"]="Created Page $newPageName from template"; if (!UpdatePage($templatePageName, $templatePage, $newTP)) { // Two super rare failure cases; not going to bother translating $MessagesFmt[]="<div class='wikimessage'>WARNING: Your new page $newPageName has been created! But a link could not be added to the template page.</div>"; } } else { # no template page?! $MessagesFmt[]="<div class='wikimessage'>WARNING: Your new page $newPageName has been created! But the template page could not be updated with a link.</div>"; } } #if addLink unset($GLOBALS['PageExistsCache'][$newPageName]); // See http://www.pmwiki.org/wiki/PITS/01401 // Put a message at the top of the page - the (:message:) markup added earlier will pick it up // for the record, $PageStartFmt is not the way to go $MessagesFmt[]=MarkupToHTML($templatePage, "!\$[New page successfully created]: [[$newPageName]]"); Lock(0); $PkTemplate['alreadyRun']=false; // UpdatePage does some MarkupToHTML processing HandleBrowse($templatePageName); }