<?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:)&nbsp;\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);
}